From 044d628a47f063bcbbd9adba7860542156a0c66e Mon Sep 17 00:00:00 2001 From: Clawd Date: Sun, 1 Mar 2026 10:15:14 -0800 Subject: feat(mcp): add tool discovery and per-tool permissions - Add MCP protocol client for tool discovery (initialize + tools/list) - Show discovered tools in settings UI with enable/disable checkboxes - Build explicit allowedTools list from enabled MCP tools - Remove bypassPermissions hack - now uses proper tool allowlisting - Format: mcp__servername__toolname for SDK allowedTools --- src/main/claude/index.ts | 59 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 12 deletions(-) (limited to 'src/main/claude') diff --git a/src/main/claude/index.ts b/src/main/claude/index.ts index ca54164..8cf512c 100644 --- a/src/main/claude/index.ts +++ b/src/main/claude/index.ts @@ -53,7 +53,38 @@ export async function sendMessage({ // Load MCP servers config (JSON string → object, or undefined if not set) const mcpServersJson = getSetting("mcpServers"); - const mcpServers = mcpServersJson ? JSON.parse(mcpServersJson) : undefined; + const mcpServersConfig = mcpServersJson ? JSON.parse(mcpServersJson) : undefined; + + // Build allowedTools list from enabled MCP tools + // Format: mcp__servername__toolname + const mcpAllowedTools: string[] = []; + if (mcpServersConfig) { + for (const [serverName, config] of Object.entries(mcpServersConfig)) { + const serverConfig = config as { + enabledTools?: string[]; + discoveredTools?: Array<{ name: string }>; + }; + // Use enabledTools if available, otherwise allow all discovered tools + const enabledTools = serverConfig.enabledTools || + serverConfig.discoveredTools?.map((t) => t.name) || + []; + for (const toolName of enabledTools) { + mcpAllowedTools.push(`mcp__${serverName}__${toolName}`); + } + } + } + + // Strip tool management fields from config before passing to SDK + // SDK only needs: type, command, args, env, url, headers + let mcpServers: Record | undefined; + if (mcpServersConfig && Object.keys(mcpServersConfig).length > 0) { + mcpServers = Object.fromEntries( + Object.entries(mcpServersConfig).map(([name, config]) => { + const { discoveredTools, enabledTools, ...sdkConfig } = config as Record; + return [name, sdkConfig]; + }) + ); + } const phaseConfig = getPhaseConfig( session.phase as Phase, @@ -62,28 +93,32 @@ export async function sendMessage({ customSystemPrompt ); + // Build allowedTools for this phase + const allowedTools: string[] = []; + if (session.phase === "implement") { + // Allow git inspection in implement phase + allowedTools.push("Bash(git status*)", "Bash(git log*)", "Bash(git diff*)"); + } + if (mcpAllowedTools.length > 0) { + // Add enabled MCP tools + allowedTools.push(...mcpAllowedTools); + } + const q = query({ prompt: message, options: { cwd: project.path, model: configuredModel, - mcpServers, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + mcpServers: mcpServers as any, resume: session.claude_session_id ?? undefined, tools: phaseConfig.tools, permissionMode: phaseConfig.permissionMode, // Required companion flag when bypassPermissions is active allowDangerouslySkipPermissions: phaseConfig.permissionMode === "bypassPermissions", systemPrompt: phaseConfig.systemPrompt, - // Allow Claude to inspect git state during implementation without prompts. - // git add/commit intentionally omitted — the app handles those. - ...(session.phase === "implement" && { - allowedTools: ["Bash(git status*)", "Bash(git log*)", "Bash(git diff*)"], - }), - // When MCPs are configured in research phase, bypass permissions to allow MCP tools - ...(session.phase === "research" && mcpServers && { - permissionMode: "bypassPermissions" as const, - allowDangerouslySkipPermissions: true, - }), + // Pre-approve specific tools to avoid permission prompts + ...(allowedTools.length > 0 && { allowedTools }), }, }); -- cgit v1.2.3