diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index a0fe39e7e1..cf2b3d1c62 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -717,6 +717,8 @@ npm run build 5. Install the MCP Server by adding the MCP server configuration to the settings file located at '${await mcpHub.getMcpSettingsFilePath()}'. The settings file may have other MCP servers already configured, so you would read it first and then add your new server to the existing \`mcpServers\` object. +IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false and alwaysAllow=[]. + \`\`\`json { "mcpServers": { diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8828c3e6a4..18fcd34ddf 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 "toggleMcpServer": { + try { + await this.mcpHub?.toggleServerDisabled(message.serverName!, message.disabled!) + } catch (error) { + console.error(`Failed to toggle MCP server ${message.serverName}:`, error) + } + break + } case "toggleToolAlwaysAllow": { try { await this.mcpHub?.toggleToolAlwaysAllow(message.serverName!, message.toolName!, message.alwaysAllow!) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index bca3bfa958..f3f5420192 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -33,6 +33,7 @@ const StdioConfigSchema = z.object({ args: z.array(z.string()).optional(), env: z.record(z.string()).optional(), alwaysAllow: AlwaysAllowSchema.optional(), + disabled: z.boolean().optional(), }) const McpSettingsSchema = z.object({ @@ -54,7 +55,8 @@ export class McpHub { } getServers(): McpServer[] { - return this.connections.map((conn) => conn.server) + // Only return enabled servers + return this.connections.filter((conn) => !conn.server.disabled).map((conn) => conn.server) } async getMcpServersPath(): Promise { @@ -192,11 +194,13 @@ export class McpHub { } // valid schema + const parsedConfig = StdioConfigSchema.parse(config) const connection: McpConnection = { server: { name, config: JSON.stringify(config), status: "connecting", + disabled: parsedConfig.disabled, }, client, transport, @@ -458,11 +462,91 @@ export class McpHub { // Using server + // Public methods for server management + + public async toggleServerDisabled(serverName: string, disabled: boolean): Promise { + let settingsPath: string + try { + settingsPath = await this.getMcpSettingsFilePath() + + // Ensure the settings file exists and is accessible + try { + await fs.access(settingsPath) + } catch (error) { + console.error("Settings file not accessible:", error) + throw new Error("Settings file not accessible") + } + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + + // Validate the config structure + if (!config || typeof config !== "object") { + throw new Error("Invalid config structure") + } + + if (!config.mcpServers || typeof config.mcpServers !== "object") { + config.mcpServers = {} + } + + if (config.mcpServers[serverName]) { + // Create a new server config object to ensure clean structure + const serverConfig = { + ...config.mcpServers[serverName], + disabled, + } + + // Ensure required fields exist + if (!serverConfig.alwaysAllow) { + serverConfig.alwaysAllow = [] + } + + config.mcpServers[serverName] = serverConfig + + // Write the entire config back + const updatedConfig = { + mcpServers: config.mcpServers, + } + + await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2)) + + const connection = this.connections.find((conn) => conn.server.name === serverName) + if (connection) { + try { + connection.server.disabled = disabled + + // Only refresh capabilities if connected + if (connection.server.status === "connected") { + connection.server.tools = await this.fetchToolsList(serverName) + connection.server.resources = await this.fetchResourcesList(serverName) + connection.server.resourceTemplates = await this.fetchResourceTemplatesList(serverName) + } + } catch (error) { + console.error(`Failed to refresh capabilities for ${serverName}:`, error) + } + } + + await this.notifyWebviewOfServerChanges() + } + } catch (error) { + console.error("Failed to update server disabled state:", error) + if (error instanceof Error) { + console.error("Error details:", error.message, error.stack) + } + vscode.window.showErrorMessage( + `Failed to update server state: ${error instanceof Error ? error.message : String(error)}`, + ) + throw error + } + } + async readResource(serverName: string, uri: string): Promise { const connection = this.connections.find((conn) => conn.server.name === serverName) if (!connection) { throw new Error(`No connection found for server: ${serverName}`) } + if (connection.server.disabled) { + throw new Error(`Server "${serverName}" is disabled`) + } return await connection.client.request( { method: "resources/read", @@ -481,6 +565,11 @@ export class McpHub { `No connection found for server: ${serverName}. Please make sure to use MCP servers available under 'Connected MCP Servers'.`, ) } + + if (connection.server.disabled) { + throw new Error(`Server "${serverName}" is disabled and cannot be used`) + } + return await connection.client.request( { method: "tools/call", diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index a408f134a3..5eb54169d5 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -36,8 +36,10 @@ export interface WebviewMessage { | "openAdvisorModelSettings" | "requestVsCodeLmModels" | "toggleToolAlwaysAllow" + | "toggleMcpServer" // | "relaunchChromeDebugMode" text?: string + disabled?: boolean askResponse?: ClineAskResponse apiConfiguration?: ApiConfiguration images?: string[] diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index a00b34328b..7df1415cf4 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -6,6 +6,7 @@ export type McpServer = { tools?: McpTool[] resources?: McpResource[] resourceTemplates?: McpResourceTemplate[] + disabled?: boolean } export type McpTool = { diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index 532282914c..993d060954 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -190,12 +190,62 @@ const ServerRow = ({ server }: { server: McpServer }) => { background: "var(--vscode-textCodeBlock-background)", cursor: server.error ? "default" : "pointer", borderRadius: isExpanded || server.error ? "4px 4px 0 0" : "4px", + opacity: server.disabled ? 0.6 : 1, }} onClick={handleRowClick}> {!server.error && ( )} {server.name} +
e.stopPropagation()}> +
{ + vscode.postMessage({ + type: "toggleMcpServer", + serverName: server.name, + disabled: !server.disabled, + }) + }} + onKeyDown={(e) => { + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + vscode.postMessage({ + type: "toggleMcpServer", + serverName: server.name, + disabled: !server.disabled, + }) + } + }}> +
+
+