Skip to content

Commit d5fac4c

Browse files
committed
Unify Runtime MCP bindings and qualify workspace capabilities
1 parent 4b97c11 commit d5fac4c

42 files changed

Lines changed: 608 additions & 265 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎CONTRIBUTING.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ copy, when a contract changes.
2323
| Environment ownership and capability preparation (Skills, Plugins, MCP, `packages.system`) | [Environments](contracts/agents-api/environments.md) |
2424
| Adding a Harness (steps) | [Harness onboarding](contracts/agents-api/harness-onboarding.md), `apps/parsar-daemon/internal/agent/harness.go` and `internal/harnessconfig/harness.go` |
2525
| Built-in Harness identifiers, configuration/profile bindings and display names | `internal/harnessconfig/builtin/catalog.json` and its [generated reference](contracts/agents-api/harness-catalog.md) |
26+
| Effective MCP bindings and credential authority | [Environment MCP](contracts/agents-api/environments.md#skills-plugins-and-environment-mcp) and `apps/parsar-daemon/internal/agent/mcp_binding.go` |
2627
| Harness qualification and acceptance | [Harness integration](contracts/agents-api/harnesses.md) |
2728
| Harness selection and Agent defaults | [Harness selection](contracts/agents-api/harness-selection.md) |
2829
| Adding a Sandbox Provider | [Sandbox Provider guide](docs/sandbox-provider.md) and `services/agents-api/internal/sandbox/sandbox_provider.go` |

‎README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ the [architecture guide](docs/architecture.md).
7272
| Build an application on the API | [Quickstart](docs/getting-started/quickstart.md), then the [Agents API guide](docs/api/public-agent-api.md) |
7373
| See a complete application | [Examples](docs/examples.md) |
7474
| Run agents on my own machine | [Self-hosted execution](docs/getting-started/self-hosted.md) |
75+
| Check verified Runtime capabilities and limits | [Capability qualification](contracts/agents-api/environment-capabilities-qualification.md) |
7576
| Understand the design | [Architecture](docs/architecture.md) |
7677
| Add a sandbox, harness or other component | [Developer guide](docs/development.md) |
7778

‎README.zh-CN.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Core 对外提供两组 API:
6868
| 基于 API 开发应用 | [快速开始](docs/getting-started/quickstart.md),然后看 [Agents API 指南](docs/api/public-agent-api.md) |
6969
| 看一个完整的应用 | [示例](docs/examples.md) |
7070
| 在自己的机器上运行 Agent | [自托管执行](docs/getting-started/self-hosted.md) |
71+
| 查看 Runtime 能力和验收范围 | [能力验收记录](contracts/agents-api/environment-capabilities-qualification.md) |
7172
| 了解设计 | [架构说明](docs/architecture.md) |
7273
| 接入新的沙箱、Harness 或其他组件 | [开发指南](docs/development.md) |
7374

‎apps/docs/content/docs/environments-and-files.mdx‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -641,17 +641,32 @@ initialized values override the selected declaration's variables. There is no
641641
Python sandbox launcher, mount policy, shell prefix or separate credential sandbox.
642642
Process groups and Windows Jobs own cancellation and descendant cleanup only.
643643
Claude composes MCP identity and observation with the same workspace profile;
644-
MiniMax keeps its existing supported transport qualification. A public capability
644+
MiniMax uses the same effective bindings for stdio and HTTP. A public capability
645645
advertisement must still match the installed engine's actual supported transport.
646646

647+
Runtime resolves public HTTP declarations and installed Plugin MCP through
648+
`agent.ResolveMCPBindings` before adapter projection. Each transient binding
649+
retains its connection origin, transport, nullable tool allowlist, required flag,
650+
credential authority and installed stdio identity. Bindings are never persisted
651+
or logged. Duplicate identities and unavailable selected credentials reject.
652+
A service-origin request cannot silently become an Environment-origin connection;
653+
the existing public HTTP MCP profile remains service-origin `environment:none`.
654+
This internal consolidation does not qualify public `connection_origin=environment`.
655+
647656
Environment-origin literal HTTP headers remain rejected for the pinned Claude
648-
client because its interpolation and cross-origin forwarding change their meaning.
649-
MiniMax accepts environment stdio only. Its adapter reads the existing Session-private
650-
native runtime-name registry for exact first-frame identities and cross-checks
651-
completed native results. Reuse existing observation and cancellation settlement;
652-
never fabricate a delayed start event, guess normalized identities or add a registry
653-
of our own. Its unqualified HTTP transport remains rejected.
654-
These mechanisms alone do not establish public MCP support or full compatibility.
657+
and MiniMax clients because their cross-origin forwarding cannot preserve header
658+
authority. MiniMax supports installed stdio and HTTP servers with anonymous or
659+
explicit user-selected HTTPS bearer authentication. ACP HTTP declarations remain
660+
Session-local native memory; tokens do not enter native configuration files or
661+
process arguments. Required initialization and tool allowlists are not exposed
662+
through the Plugin manifest, and public MiniMax MCP remains unqualified.
663+
MiniMax reads the existing Session-private native runtime-name registry for exact
664+
first-frame identities and cross-checks completed native results for both transports.
665+
Reuse existing observation and cancellation settlement; never fabricate a delayed
666+
start event, guess normalized identities or add a registry of our own.
667+
668+
See [capability qualification](https://github.com/MiniMax-AI/parsar-core/blob/f6d258735fc601c521dd990e6f9e1ed261f4ef2d/contracts/agents-api/environment-capabilities-qualification.md) for
669+
real model evidence and the remaining public-origin and image gaps.
655670

656671
## Contract inventory
657672

‎apps/docs/content/guide-sources.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"contracts/agents-api/execution-tools.md": "e0b61f6c0c236c362186c5f6d0ad69a16dd8a8d9afabe1329a1fe22e5f4a71e9",
1414
"docs/api/public-agent-api.md": "00979732412a971013e8f01b4c74820ff51aafdded6b0f105d25a78af86a6627",
1515
"docs/examples.md": "0e1bdaeff51c9c36779f817be31ea9816b7d8cb2290cf8350d3c4801f436f4f9",
16-
"contracts/agents-api/environments.md": "d5fc16c904f751bd13e2af35c9e43c92ca39c6b86401f85cf7f9ee4093c156c9",
16+
"contracts/agents-api/environments.md": "a000ca89f5d7611f52c963467dd72f2ab5195d2e3dc48a0aeefb89fd9bcfc7a6",
1717
"docs/getting-started/nodes.md": "c1ad18c445990ea696625011cc3cfa0725779beefd379064d278656801d5578e",
1818
"docs/getting-started/self-hosted.md": "5ade597e09cbfa2e321f341693b47730b3696fe7bd4d6834eafbe6b279a55e6b",
1919
"docs/self-hosted-native.md": "b3cf736f88792e6925c50b81a4c33eba6b9ee86e195f9ed4e2187db7bed71d88",
@@ -44,7 +44,7 @@
4444
"content/docs/agents-and-tools.mdx": "b8a85fe92e19a71b7ad7c9357ce7ad0595d50c9190edb748ca834df615b056cb",
4545
"content/docs/sessions.mdx": "bb63d799ed90038836d652f3f866d0309822425114c6b2aa36654b8a991947f6",
4646
"content/docs/examples.mdx": "587061e65ab2e841d14980539ba94e2216d4e8135c948a852b9a8bb0359c85d7",
47-
"content/docs/environments-and-files.mdx": "352ca1f2b0ab1b0853b890be694629bb47399ba6bc88905f68feaa3bfe401949",
47+
"content/docs/environments-and-files.mdx": "a35d35841740e6f9092506e019bf23675a0946b1e9f1ff61dc561cd11b64d152",
4848
"content/docs/hosted-providers.mdx": "85696d88b76f1233db3d8db3266bb0ab4d297d795a44ede13cf34e5169524c68",
4949
"content/docs/self-hosted-execution.mdx": "eeed4c6b3646927ccc3c7ac2d20b2c0f9c8a65e42d734310fe3959e016324e57",
5050
"content/docs/self-hosted-native.mdx": "671451580dfb4f5c134221991f68292a4f992008174d23190b1da3742046571f",

‎apps/parsar-daemon/internal/agent/claudesdk/mcp_environment.go‎

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package claudesdk
33
import (
44
"fmt"
55

6+
"github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/agent"
67
"github.com/MiniMax-AI-Dev/parsar/apps/parsar-daemon/internal/localworkspace"
78
"github.com/MiniMax-AI-Dev/parsar/internal/agentdaemon/proto"
89
)
@@ -15,45 +16,34 @@ type environmentMCPServer struct {
1516
Args []string `json:"args,omitempty"`
1617
}
1718

18-
func prepareEnvironmentMCP(environment *proto.LocalEnvironment) ([]environmentMCPServer, []string, error) {
19-
if environment == nil || len(environment.MCP) == 0 {
20-
return nil, nil, nil
21-
}
22-
if environment.NetworkAccess != "enabled" {
23-
return nil, nil, fmt.Errorf("claudesdk: environment MCP requires enabled network")
19+
func prepareRuntimeMCP(req proto.PromptRequestPayload) ([]environmentMCPServer, []string, error) {
20+
bindings, err := agent.ResolveMCPBindings(req)
21+
if err != nil {
22+
return nil, nil, err
2423
}
2524
var servers []environmentMCPServer
2625
var env []string
27-
labels := map[string]bool{}
28-
for _, item := range environment.MCP {
29-
declaration := item.Server
30-
if !mcpLabel.MatchString(declaration.Name) || declaration.Name == "functions" || labels[declaration.Name] {
31-
return nil, nil, fmt.Errorf("claudesdk: unsupported environment MCP identity")
26+
for _, binding := range bindings {
27+
if !mcpLabel.MatchString(binding.ServerLabel) || binding.ServerLabel == "functions" {
28+
return nil, nil, fmt.Errorf("claudesdk: unsupported MCP identity")
29+
}
30+
if binding.Stdio != nil {
31+
command, args := localworkspace.MCPStdioCommand(*binding.Stdio)
32+
servers = append(servers, environmentMCPServer{mcpHTTPServer: mcpHTTPServer{ServerLabel: binding.ServerLabel}, Command: command, Args: args})
33+
continue
34+
}
35+
// Native header interpolation and redirect behavior cannot preserve literal
36+
// custom-header authority. Reject this unqualified combination explicitly.
37+
if len(binding.HTTPHeaders) != 0 {
38+
return nil, nil, fmt.Errorf("claudesdk: literal MCP HTTP headers are not supported")
3239
}
33-
labels[declaration.Name] = true
34-
switch declaration.Type {
35-
case "stdio":
36-
command, args := localworkspace.MCPStdioCommand(item)
37-
servers = append(servers, environmentMCPServer{mcpHTTPServer: mcpHTTPServer{ServerLabel: declaration.Name}, Command: command, Args: args})
38-
case "http":
39-
// The pinned native client expands literal headers again and forwards
40-
// custom headers across origins. Do not reinterpret their public meaning.
41-
if len(declaration.HTTPHeaders) != 0 {
42-
return nil, nil, fmt.Errorf("claudesdk: environment MCP literal HTTP headers are not supported")
43-
}
44-
if declaration.BearerTokenEnvVar != "" && item.BearerToken == nil {
45-
return nil, nil, fmt.Errorf("claudesdk: environment MCP credential unavailable")
46-
}
47-
http := []proto.MCPHTTPServer{{ServerLabel: declaration.Name, ServerURL: declaration.URL, BearerToken: item.BearerToken}}
48-
if err := validateMCPServers(http); err != nil {
49-
return nil, nil, err
50-
}
51-
projected, credentials := prepareMCPHTTP(&http)
52-
servers = append(servers, environmentMCPServer{mcpHTTPServer: (*projected)[0]})
53-
env = append(env, credentials...)
54-
default:
55-
return nil, nil, fmt.Errorf("claudesdk: unsupported environment MCP transport")
40+
declarations := []proto.MCPHTTPServer{{ServerLabel: binding.ServerLabel, ServerURL: binding.ServerURL, AllowedTools: binding.AllowedTools, Required: binding.Required, BearerToken: binding.BearerToken}}
41+
if err := validateMCPServers(declarations); err != nil {
42+
return nil, nil, err
5643
}
44+
projected, credentials := prepareMCPHTTP(&declarations)
45+
servers = append(servers, environmentMCPServer{mcpHTTPServer: (*projected)[0]})
46+
env = append(env, credentials...)
5747
}
5848
return servers, env, nil
5949
}

‎apps/parsar-daemon/internal/agent/claudesdk/mcp_environment_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func TestEnvironmentMCPRejectsUnqualifiedCombinations(t *testing.T) {
6666
} {
6767
environment := &proto.LocalEnvironment{NetworkAccess: "enabled", MCP: []proto.EnvironmentMCP{{InstallationRoot: "/private/runtime/capabilities", WorkspaceRoot: "/private/runtime/workspace", PackageRoot: "plugins/remote", Server: agentplugin.MCPServer{Name: "remote", Type: "http", URL: "https://example.invalid/mcp"}}}}
6868
mutate(environment)
69-
if _, _, err := prepareEnvironmentMCP(environment); err == nil {
69+
if _, _, err := prepareRuntimeMCP(proto.PromptRequestPayload{LocalEnvironment: environment}); err == nil {
7070
t.Fatal("unsupported declaration accepted")
7171
}
7272
}

‎apps/parsar-daemon/internal/agent/claudesdk/options.go‎

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
7171
return startRequest{}, nil, err
7272
}
7373
if req.ToolSearch {
74-
if config.Workspace != nil || req.LocalEnvironment != nil || req.MCPHTTPServers != nil || !req.DisableSubagents || (req.ExecutionControls != nil && req.ExecutionControls.OutputFormat != nil) {
74+
if (req.LocalEnvironment != nil && (len(req.LocalEnvironment.Skills) != 0 || len(req.LocalEnvironment.MCP) != 0)) || req.MCPHTTPServers != nil || !req.DisableSubagents || (req.ExecutionControls != nil && req.ExecutionControls.OutputFormat != nil) {
7575
return fail("tool discovery requires the single-agent text/function profile")
7676
}
7777
start.ToolSearch = true
@@ -187,8 +187,17 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
187187
}
188188
env := withProvider(append(append([]string{}, os.Environ()...), config.Env...), provider)
189189
env = append(env, "CLAUDE_CONFIG_DIR="+config.StateDir, "TMPDIR="+filepath.Join(config.StateDir, "tmp"), "DISABLE_TELEMETRY=1", "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1")
190-
var mcpEnv []string
191-
start.MCPHTTPServers, mcpEnv = prepareMCPHTTP(req.MCPHTTPServers)
190+
projectedMCP, mcpEnv, err := prepareRuntimeMCP(req)
191+
if err != nil {
192+
return startRequest{}, nil, err
193+
}
194+
if req.MCPHTTPServers != nil {
195+
servers := make([]mcpHTTPServer, 0, len(projectedMCP))
196+
for _, server := range projectedMCP {
197+
servers = append(servers, server.mcpHTTPServer)
198+
}
199+
start.MCPHTTPServers = &servers
200+
}
192201
env = append(env, mcpEnv...)
193202
return start, env, nil
194203
}

‎apps/parsar-daemon/internal/agent/claudesdk/readiness.go‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ func (info RuntimeInfo) SupportsToolSearch() bool {
3737
return slices.Contains(info.Features, "tool_search")
3838
}
3939

40+
func (info RuntimeInfo) SupportsWorkspaceToolSearch() bool {
41+
return info.SupportsWorkspaceFunctions() && info.SupportsToolSearch() && slices.Contains(info.Features, "workspace_tool_search")
42+
}
43+
4044
func (info RuntimeInfo) SupportsStructuredOutput() bool {
4145
return slices.Contains(info.Features, "structured_output")
4246
}

‎apps/parsar-daemon/internal/agent/claudesdk/readiness_test.go‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,3 +149,17 @@ func runReadinessHelper() {
149149
os.Exit(3)
150150
}
151151
}
152+
153+
func TestWorkspaceToolSearchRequiresNativeWorkspaceFeature(t *testing.T) {
154+
features := []string{"tool_search", "workspace_tool_search", "workspace_functions", "workspace_tools", "workspace_prepare", "workspace_command_observations", "local_runtime_v2"}
155+
if !(RuntimeInfo{Features: features}).SupportsWorkspaceToolSearch() {
156+
t.Fatal("complete workspace discovery contract rejected")
157+
}
158+
for omitted := range features {
159+
candidate := append([]string{}, features[:omitted]...)
160+
candidate = append(candidate, features[omitted+1:]...)
161+
if (RuntimeInfo{Features: candidate}).SupportsWorkspaceToolSearch() {
162+
t.Fatal("incomplete native discovery contract advertised")
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)