Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/daemon/internal/agent/claudesdk/mcp_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ type environmentMCPServer struct {
Declared bool `json:"declared,omitempty"`
}

// cwd is the directory the native process starts in; native starts stdio servers there.
func prepareRuntimeMCP(req proto.PromptRequestPayload, cwd string) ([]environmentMCPServer, []string, error) {
func prepareRuntimeMCP(req proto.PromptRequestPayload) ([]environmentMCPServer, []string, error) {
bindings, err := agent.ResolveMCPBindings(req)
if err != nil {
return nil, nil, err
Expand All @@ -33,7 +32,9 @@ func prepareRuntimeMCP(req proto.PromptRequestPayload, cwd string) ([]environmen
return nil, nil, fmt.Errorf("claudesdk: unsupported MCP identity")
}
if process := binding.Process; process != nil {
if filepath.Clean(process.Cwd) != cwd {
// Native starts stdio servers in its workspace, which the declared
// directory names directly or through the hosted /workspace alias.
if req.LocalEnvironment == nil || filepath.Clean(process.Cwd) != req.LocalEnvironment.WorkspaceDirectory {
return nil, nil, fmt.Errorf("claudesdk: MCP stdio cwd must be the workspace directory")
}
args := append([]string{}, process.Args...)
Expand Down
13 changes: 7 additions & 6 deletions apps/daemon/internal/agent/claudesdk/mcp_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestEnvironmentMCPRejectsUnqualifiedCombinations(t *testing.T) {
} {
environment := &proto.LocalEnvironment{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"}}}}
mutate(environment)
if _, _, err := prepareRuntimeMCP(proto.PromptRequestPayload{LocalEnvironment: environment}, ""); err == nil {
if _, _, err := prepareRuntimeMCP(proto.PromptRequestPayload{LocalEnvironment: environment}); err == nil {
t.Fatal("unsupported declaration accepted")
}
}
Expand All @@ -91,17 +91,18 @@ func TestEnvironmentMCPObservationsUseInstalledDeclarations(t *testing.T) {
}

// Native starts a stdio server in the directory of its own process, so the
// declared cwd must be that workspace directory.
// declared cwd must be the declared workspace directory.
func TestPublicMCPStdioStartsInTheWorkspaceDirectory(t *testing.T) {
servers := []proto.MCPHTTPServer{{ConnectionOrigin: "environment", ServerLabel: "local",
Stdio: &proto.MCPStdio{Command: "run", Cwd: "/work", EnvVars: []string{"TOKEN"}}}}
req := proto.PromptRequestPayload{LocalEnvironment: &proto.LocalEnvironment{}, MCPHTTPServers: &servers}
projected, _, err := prepareRuntimeMCP(req, "/work")
Stdio: &proto.MCPStdio{Command: "run", Cwd: "/workspace", EnvVars: []string{"TOKEN"}}}}
req := proto.PromptRequestPayload{LocalEnvironment: &proto.LocalEnvironment{WorkspaceDirectory: "/workspace", WorkspaceRoot: "/environment/workspace"}, MCPHTTPServers: &servers}
projected, _, err := prepareRuntimeMCP(req)
raw, _ := json.Marshal(projected)
if err != nil || string(raw) != `[{"server_label":"local","allowed_tools":null,"command":"run","args":[],"declared":true}]` {
t.Fatal("public stdio server was not projected", string(raw), err)
}
if _, _, err := prepareRuntimeMCP(req, "/elsewhere"); err == nil {
req.LocalEnvironment.WorkspaceDirectory = "/elsewhere"
if _, _, err := prepareRuntimeMCP(req); err == nil {
t.Fatal("stdio cwd outside the workspace directory was accepted")
}
}
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ func prepareConfiguration(config Config, req proto.PromptRequestPayload) (startR
}
env := withProvider(append(append([]string{}, os.Environ()...), config.Env...), provider)
env = append(env, "CLAUDE_CONFIG_DIR="+config.StateDir, "TMPDIR="+filepath.Join(config.StateDir, "tmp"))
projectedMCP, mcpEnv, err := prepareRuntimeMCP(req, "")
projectedMCP, mcpEnv, err := prepareRuntimeMCP(req)
if err != nil {
return startRequest{}, nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion apps/daemon/internal/agent/claudesdk/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func prepareWorkspace(config Config, req proto.PromptRequestPayload) (*workspace
profile.Skills = req.LocalEnvironment.Skills
profile.CapabilityRoot = req.LocalEnvironment.CapabilityRoot
}
servers, credentials, err := prepareRuntimeMCP(req, workspaceCwd(config.Workspace))
servers, credentials, err := prepareRuntimeMCP(req)
if err != nil {
return nil, nil, err
}
Expand Down
Loading