From b92fdc7b6ff3c07d5b72300a5333c04b937cfe9b Mon Sep 17 00:00:00 2001 From: Jeremy Date: Mon, 13 Apr 2026 06:26:07 -0500 Subject: [PATCH] fix(claude-code): pre-authorize workflow MCP tools so interactive acceptEdits mode stops blocking GSD commands Since 2.72.0 the interactive permission default is acceptEdits, which auto-approves built-in Edit/Write/Bash but leaves the SDK permission gate up for MCP tools. Without a canUseTool handler, every mcp__gsd-workflow__* call surfaces as "This command requires approval" and blocks GSD actions (#4099). Add allowedTools entries (mcp____*) for each registered workflow MCP server in buildSdkOptions so they run unattended while the rest of the acceptEdits safety gate stays intact. Env-overridden server names are handled by deriving the glob list from the built mcpServers keys. Fixes #4099 --- .../extensions/claude-code-cli/stream-adapter.ts | 8 ++++++++ .../claude-code-cli/tests/stream-adapter.test.ts | 2 ++ 2 files changed, 10 insertions(+) diff --git a/src/resources/extensions/claude-code-cli/stream-adapter.ts b/src/resources/extensions/claude-code-cli/stream-adapter.ts index bd9bcf853..70c30e641 100644 --- a/src/resources/extensions/claude-code-cli/stream-adapter.ts +++ b/src/resources/extensions/claude-code-cli/stream-adapter.ts @@ -612,6 +612,13 @@ export function buildSdkOptions( const mcpServers = buildWorkflowMcpServers(); const permissionMode = overrides?.permissionMode ?? "bypassPermissions"; const disallowedTools = ["AskUserQuestion"]; + // Pre-authorize every registered workflow MCP server's tools. Without this, + // `acceptEdits` mode (the interactive default) auto-approves built-in + // Edit/Write/Bash but still gates MCP calls like `mcp__gsd-workflow__*`, + // surfacing "This command requires approval" on every GSD action (#4099). + const allowedTools = mcpServers + ? Object.keys(mcpServers).map((serverName) => `mcp__${serverName}__*`) + : []; return { pathToClaudeCodeExecutable: getClaudePath(), model: modelId, @@ -623,6 +630,7 @@ export function buildSdkOptions( settingSources: ["project"], systemPrompt: { type: "preset", preset: "claude_code" }, disallowedTools, + ...(allowedTools.length > 0 ? { allowedTools } : {}), ...(mcpServers ? { mcpServers } : {}), betas: modelId.includes("sonnet") ? ["context-1m-2025-08-07"] : [], ...extraOptions, diff --git a/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts b/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts index 056a15e01..3a9ce8934 100644 --- a/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +++ b/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts @@ -369,6 +369,7 @@ describe("stream-adapter — session persistence (#2859)", () => { assert.equal(srv.env.GSD_PERSIST_WRITE_GATE_STATE, "1"); assert.equal(srv.env.GSD_WORKFLOW_PROJECT_ROOT, "/tmp/project"); assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]); + assert.deepEqual(options.allowedTools, ["mcp__gsd-workflow__*"]); } finally { process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND; process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME; @@ -397,6 +398,7 @@ describe("stream-adapter — session persistence (#2859)", () => { const mcpServers = options.mcpServers as Record; assert.ok(mcpServers?.["custom-workflow"], "expected custom workflow server config"); assert.deepEqual(options.disallowedTools, ["AskUserQuestion"]); + assert.deepEqual(options.allowedTools, ["mcp__custom-workflow__*"]); } finally { process.env.GSD_WORKFLOW_MCP_COMMAND = prev.GSD_WORKFLOW_MCP_COMMAND; process.env.GSD_WORKFLOW_MCP_NAME = prev.GSD_WORKFLOW_MCP_NAME;