From d9e1031f8597b7fc5268a672a178cfe63be817f2 Mon Sep 17 00:00:00 2001 From: Saoud Rizwan <7799382+saoudrizwan@users.noreply.github.com> Date: Sun, 19 Jan 2025 13:43:58 -0800 Subject: [PATCH] Add auto approve settings for mcp tools --- src/core/Cline.ts | 9 +++- src/core/webview/ClineProvider.ts | 8 +++ src/services/mcp/McpHub.ts | 57 +++++++++++++++++++- src/shared/WebviewMessage.ts | 6 +++ src/shared/mcp.ts | 1 + webview-ui/src/components/chat/ChatRow.tsx | 20 ++++--- webview-ui/src/components/mcp/McpToolRow.tsx | 34 ++++++++++-- webview-ui/src/components/mcp/McpView.tsx | 2 +- 8 files changed, 123 insertions(+), 14 deletions(-) diff --git a/src/core/Cline.ts b/src/core/Cline.ts index 721e95799e..ff016906e3 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2489,7 +2489,14 @@ export class Cline { arguments: mcp_arguments, } satisfies ClineAskUseMcpServer) - if (this.shouldAutoApproveTool(block.name)) { + const isToolAlwaysAllowed = this.providerRef + .deref() + ?.mcpHub?.connections?.find((conn) => conn.server.name === server_name) + ?.server.tools?.find((tool) => tool.name === tool_name)?.alwaysAllow + + // console.log("isToolAlwaysAllowed", server_name, tool_name, isToolAlwaysAllowed) + + if (this.shouldAutoApproveTool(block.name) && isToolAlwaysAllowed) { this.removeLastPartialMessageIfExistsWithType("ask", "use_mcp_server") await this.say("use_mcp_server", completeMessage, undefined, false) this.consecutiveAutoApprovedRequestsCount++ diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 616c06e4de..8828c3e6a4 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -608,6 +608,14 @@ export class ClineProvider implements vscode.WebviewViewProvider { } break } + case "toggleToolAlwaysAllow": { + try { + await this.mcpHub?.toggleToolAlwaysAllow(message.serverName!, message.toolName!, message.alwaysAllow!) + } catch (error) { + console.error(`Failed to toggle auto-approve for tool ${message.toolName}:`, error) + } + break + } case "restartMcpServer": { try { await this.mcpHub?.restartConnection(message.text!) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 59dd3bf38b..bca3bfa958 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -25,11 +25,14 @@ export type McpConnection = { transport: StdioClientTransport } +const AlwaysAllowSchema = z.array(z.string()).default([]) + // StdioServerParameters const StdioConfigSchema = z.object({ command: z.string(), args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), + alwaysAllow: AlwaysAllowSchema.optional(), }) const McpSettingsSchema = z.object({ @@ -275,7 +278,21 @@ export class McpHub { const response = await this.connections .find((conn) => conn.server.name === serverName) ?.client.request({ method: "tools/list" }, ListToolsResultSchema) - return response?.tools || [] + + // Get always allow settings + const settingsPath = await this.getMcpSettingsFilePath() + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + const alwaysAllowConfig = config.mcpServers[serverName]?.alwaysAllow || [] + + // Mark tools as always allowed based on settings + const tools = (response?.tools || []).map((tool) => ({ + ...tool, + alwaysAllow: alwaysAllowConfig.includes(tool.name), + })) + + // console.log(`[MCP] Fetched tools for ${serverName}:`, tools) + return tools } catch (error) { // console.error(`Failed to fetch tools for ${serverName}:`, error) return [] @@ -476,6 +493,44 @@ export class McpHub { ) } + async toggleToolAlwaysAllow(serverName: string, toolName: string, shouldAllow: boolean): Promise { + try { + const settingsPath = await this.getMcpSettingsFilePath() + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + + // Initialize alwaysAllow if it doesn't exist + if (!config.mcpServers[serverName].alwaysAllow) { + config.mcpServers[serverName].alwaysAllow = [] + } + + const alwaysAllow = config.mcpServers[serverName].alwaysAllow + const toolIndex = alwaysAllow.indexOf(toolName) + + if (shouldAllow && toolIndex === -1) { + // Add tool to always allow list + alwaysAllow.push(toolName) + } else if (!shouldAllow && toolIndex !== -1) { + // Remove tool from always allow list + alwaysAllow.splice(toolIndex, 1) + } + + // Write updated config back to file + await fs.writeFile(settingsPath, JSON.stringify(config, null, 2)) + + // Update the tools list to reflect the change + const connection = this.connections.find((conn) => conn.server.name === serverName) + if (connection) { + connection.server.tools = await this.fetchToolsList(serverName) + await this.notifyWebviewOfServerChanges() + } + } catch (error) { + console.error("Failed to update always allow settings:", error) + vscode.window.showErrorMessage("Failed to update always allow settings") + throw error // Re-throw to ensure the error is properly handled + } + } + async dispose(): Promise { this.removeAllFileWatchers() for (const connection of this.connections) { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 897dabbb86..a408f134a3 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -35,6 +35,7 @@ export interface WebviewMessage { | "taskCompletionViewChanges" | "openAdvisorModelSettings" | "requestVsCodeLmModels" + | "toggleToolAlwaysAllow" // | "relaunchChromeDebugMode" text?: string askResponse?: ClineAskResponse @@ -45,6 +46,11 @@ export interface WebviewMessage { autoApprovalSettings?: AutoApprovalSettings browserSettings?: BrowserSettings chatSettings?: ChatSettings + + // For toggleToolAutoApprove + serverName?: string + toolName?: string + alwaysAllow?: boolean } export type ClineAskResponse = "yesButtonClicked" | "noButtonClicked" | "messageResponse" diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index 82efae2f72..a00b34328b 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -12,6 +12,7 @@ export type McpTool = { name: string description?: string inputSchema?: object + alwaysAllow?: boolean } export type McpResource = { diff --git a/webview-ui/src/components/chat/ChatRow.tsx b/webview-ui/src/components/chat/ChatRow.tsx index a063905da4..8034142459 100644 --- a/webview-ui/src/components/chat/ChatRow.tsx +++ b/webview-ui/src/components/chat/ChatRow.tsx @@ -712,13 +712,19 @@ export const ChatRowContent = ({ message, isExpanded, onToggleExpand, lastModifi {useMcpServer.type === "use_mcp_tool" && ( <> - tool.name === useMcpServer.toolName)?.description || "", - }} - /> +
e.stopPropagation()}> + tool.name === useMcpServer.toolName)?.description || "", + alwaysAllow: + server?.tools?.find((tool) => tool.name === useMcpServer.toolName)?.alwaysAllow || + false, + }} + serverName={useMcpServer.serverName} + /> +
{useMcpServer.arguments && useMcpServer.arguments !== "{}" && (
{ +const McpToolRow = ({ tool, serverName }: McpToolRowProps) => { + const { autoApprovalSettings } = useExtensionState() + + const handleAlwaysAllowChange = () => { + if (!serverName) return + + vscode.postMessage({ + type: "toggleToolAlwaysAllow", + serverName, + toolName: tool.name, + alwaysAllow: !tool.alwaysAllow, + }) + } return (
-
- - {tool.name} +
e.stopPropagation()}> +
+ + {tool.name} +
+ {serverName && autoApprovalSettings.enabled && autoApprovalSettings.actions.useMcp && ( + + Always allow + + )}
{tool.description && (
{ width: "100%", }}> {server.tools.map((tool) => ( - + ))}
) : (