From f4b0cf3a0233aa44cf1b338c6596e903016aa1d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A0=D0=BE=D0=B4=D0=B8=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Tue, 13 May 2025 15:08:47 +0300 Subject: [PATCH 01/18] add toggle for disable mcp server tool from prompt --- .changeset/slimy-years-smell.md | 5 + src/__mocks__/fs/promises.ts | 1 + .../prompts/instructions/create-mcp-server.ts | 3 +- src/core/prompts/sections/mcp-servers.ts | 1 + src/core/webview/webviewMessageHandler.ts | 17 +++ src/services/mcp/McpHub.ts | 88 +++++++++++++-- src/services/mcp/__tests__/McpHub.spec.ts | 102 ++++++++++++++++++ src/shared/WebviewMessage.ts | 2 + src/shared/mcp.ts | 1 + webview-ui/src/components/mcp/McpToolRow.tsx | 45 +++++++- webview-ui/src/i18n/locales/en/mcp.json | 3 +- 11 files changed, 254 insertions(+), 14 deletions(-) create mode 100644 .changeset/slimy-years-smell.md diff --git a/.changeset/slimy-years-smell.md b/.changeset/slimy-years-smell.md new file mode 100644 index 0000000000..834933c368 --- /dev/null +++ b/.changeset/slimy-years-smell.md @@ -0,0 +1,5 @@ +--- +"roo-cline": minor +--- + +new feature allowing users to toggle whether an individual MCP (Model Context Protocol) tool is included in the context provided to the AI model diff --git a/src/__mocks__/fs/promises.ts b/src/__mocks__/fs/promises.ts index 91e686fb70..63d7a42007 100644 --- a/src/__mocks__/fs/promises.ts +++ b/src/__mocks__/fs/promises.ts @@ -168,6 +168,7 @@ const mockFs = { args: ["test.js"], disabled: false, alwaysAllow: ["existing-tool"], + disabledForPromptTools: [], }, }, }), diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 3d1d2a20cf..062c847355 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -50,6 +50,7 @@ Common configuration options for both types: - \`disabled\`: (optional) Set to true to temporarily disable the server - \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) - \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation +- \`disabledForPromptTools\`: (optional) Array of tool names that don't includes to system prompt and won't be used ### Example Local MCP Server @@ -276,7 +277,7 @@ 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=[]. +IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false, alwaysAllow=[] and disabledForPromptTools=[]. \`\`\`json { diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts index 2a9b6d148a..0af850cbb1 100644 --- a/src/core/prompts/sections/mcp-servers.ts +++ b/src/core/prompts/sections/mcp-servers.ts @@ -17,6 +17,7 @@ export async function getMcpServersSection( .filter((server) => server.status === "connected") .map((server) => { const tools = server.tools + ?.filter((tool) => tool.enabledForPrompt !== false) ?.map((tool) => { const schemaStr = tool.inputSchema ? ` Input Schema: diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index c6ef3c839c..3e988a0c79 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -596,6 +596,23 @@ export const webviewMessageHandler = async ( } break } + case "toggleToolEnabledForPrompt": { + try { + await provider + .getMcpHub() + ?.toggleToolEnabledForPrompt( + message.serverName!, + message.source as "global" | "project", + message.toolName!, + Boolean(message.isEnabled), + ) + } catch (error) { + provider.log( + `Failed to toggle enabled for prompt for tool ${message.toolName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, + ) + } + break + } case "toggleMcpServer": { try { await provider diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 0b6af7747d..c9e771f228 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -45,6 +45,7 @@ const BaseConfigSchema = z.object({ timeout: z.number().min(1).max(3600).optional().default(60), alwaysAllow: z.array(z.string()).default([]), watchPaths: z.array(z.string()).optional(), // paths to watch for changes and restart server + disabledForPromptTools: z.array(z.string()).default([]), }) // Custom error messages for better user feedback @@ -835,34 +836,39 @@ export class McpHub { const actualSource = connection.server.source || "global" let configPath: string let alwaysAllowConfig: string[] = [] + let disabledForPromptToolsList: string[] = [] // Read from the appropriate config file based on the actual source try { + let serverConfigData: any if (actualSource === "project") { // Get project MCP config path const projectMcpPath = await this.getProjectMcpPath() if (projectMcpPath) { configPath = projectMcpPath const content = await fs.readFile(configPath, "utf-8") - const config = JSON.parse(content) - alwaysAllowConfig = config.mcpServers?.[serverName]?.alwaysAllow || [] + serverConfigData = JSON.parse(content) } } else { // Get global MCP settings path configPath = await this.getMcpSettingsFilePath() const content = await fs.readFile(configPath, "utf-8") - const config = JSON.parse(content) - alwaysAllowConfig = config.mcpServers?.[serverName]?.alwaysAllow || [] + serverConfigData = JSON.parse(content) + } + if (serverConfigData) { + alwaysAllowConfig = serverConfigData.mcpServers?.[serverName]?.alwaysAllow || [] + disabledForPromptToolsList = serverConfigData.mcpServers?.[serverName]?.disabledForPromptTools || [] } } catch (error) { - console.error(`Failed to read alwaysAllow config for ${serverName}:`, error) - // Continue with empty alwaysAllowConfig + console.error(`Failed to read tool configuration for ${serverName}:`, error) + // Continue with empty configs } - // Mark tools as always allowed based on settings + // Mark tools as always allowed and enabled for prompt based on settings const tools = (response?.tools || []).map((tool) => ({ ...tool, alwaysAllow: alwaysAllowConfig.includes(tool.name), + enabledForPrompt: !disabledForPromptToolsList.includes(tool.name), })) return tools @@ -1566,6 +1572,74 @@ export class McpHub { connection.server.tools = await this.fetchToolsList(serverName, source) await this.notifyWebviewOfServerChanges() } + } catch (error) { + this.showErrorMessage( + `Failed to toggle always allow for tool "${toolName}" on server "${serverName}" with source "${source}"`, + error, + ) + throw error + } + } + + async toggleToolEnabledForPrompt( + serverName: string, + source: "global" | "project", + toolName: string, + isEnabled: boolean, + ): Promise { + try { + const connection = this.findConnection(serverName, source) + if (!connection) { + throw new Error(`Server ${serverName} with source ${source} not found`) + } + + let configPath: string + if (source === "project") { + const projectMcpPath = await this.getProjectMcpPath() + if (!projectMcpPath) { + throw new Error("Project MCP configuration file not found") + } + configPath = projectMcpPath + } else { + configPath = await this.getMcpSettingsFilePath() + } + + const normalizedPath = process.platform === "win32" ? configPath.replace(/\\/g, "/") : configPath + const content = await fs.readFile(normalizedPath, "utf-8") + const config = JSON.parse(content) + + if (!config.mcpServers) { + config.mcpServers = {} + } + if (!config.mcpServers[serverName]) { + // Initialize with minimal valid config if server entry doesn't exist + config.mcpServers[serverName] = { + type: "stdio", + command: "echo", + args: ["MCP server not fully configured"], + } + } + if (!config.mcpServers[serverName].disabledForPromptTools) { + config.mcpServers[serverName].disabledForPromptTools = [] + } + + const disabledList = config.mcpServers[serverName].disabledForPromptTools + const toolIndex = disabledList.indexOf(toolName) + + if (!isEnabled && toolIndex === -1) { + // If tool should be disabled (not included in prompt) and is not in disabled list + disabledList.push(toolName) + } else if (isEnabled && toolIndex !== -1) { + // If tool should be enabled (included in prompt) and is in disabled list + disabledList.splice(toolIndex, 1) + } + + await fs.writeFile(normalizedPath, JSON.stringify(config, null, 2)) + + if (connection) { + connection.server.tools = await this.fetchToolsList(serverName, source) + await this.notifyWebviewOfServerChanges() + } } catch (error) { this.showErrorMessage(`Failed to update always allow settings for tool ${toolName}`, error) throw error // Re-throw to ensure the error is properly handled diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts index eaa9af908c..6bef662c7e 100644 --- a/src/services/mcp/__tests__/McpHub.spec.ts +++ b/src/services/mcp/__tests__/McpHub.spec.ts @@ -111,6 +111,7 @@ describe("McpHub", () => { command: "node", args: ["test.js"], alwaysAllow: ["allowed-tool"], + disabledForPromptTools: ["disabled-tool"], }, }, }), @@ -272,6 +273,107 @@ describe("McpHub", () => { }) }) + describe("toggleToolEnabledForPrompt", () => { + it("should add tool to disabledForPromptTools list when enabling", async () => { + const mockConfig = { + mcpServers: { + "test-server": { + type: "stdio", + command: "node", + args: ["test.js"], + disabledForPromptTools: [], + }, + }, + } + + // Mock reading initial config + ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) + + await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "new-tool", false) + + // Verify the config was updated correctly + const writeCalls = (fs.writeFile as jest.Mock).mock.calls + expect(writeCalls.length).toBeGreaterThan(0) + + // Find the write call + const callToUse = writeCalls[writeCalls.length - 1] + expect(callToUse).toBeTruthy() + + // The path might be normalized differently on different platforms, + // so we'll just check that we have a call with valid content + const writtenConfig = JSON.parse(callToUse[1]) + expect(writtenConfig.mcpServers).toBeDefined() + expect(writtenConfig.mcpServers["test-server"]).toBeDefined() + expect(Array.isArray(writtenConfig.mcpServers["test-server"].enabledForPrompt)).toBe(false) + expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).toContain("new-tool") + }) + + it("should remove tool from disabledForPromptTools list when disabling", async () => { + const mockConfig = { + mcpServers: { + "test-server": { + type: "stdio", + command: "node", + args: ["test.js"], + disabledForPromptTools: ["existing-tool"], + }, + }, + } + + // Mock reading initial config + ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) + + await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "existing-tool", true) + + // Verify the config was updated correctly + const writeCalls = (fs.writeFile as jest.Mock).mock.calls + expect(writeCalls.length).toBeGreaterThan(0) + + // Find the write call + const callToUse = writeCalls[writeCalls.length - 1] + expect(callToUse).toBeTruthy() + + // The path might be normalized differently on different platforms, + // so we'll just check that we have a call with valid content + const writtenConfig = JSON.parse(callToUse[1]) + expect(writtenConfig.mcpServers).toBeDefined() + expect(writtenConfig.mcpServers["test-server"]).toBeDefined() + expect(Array.isArray(writtenConfig.mcpServers["test-server"].enabledForPrompt)).toBe(false) + expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).not.toContain("existing-tool") + }) + + it("should initialize disabledForPromptTools if it does not exist", async () => { + const mockConfig = { + mcpServers: { + "test-server": { + type: "stdio", + command: "node", + args: ["test.js"], + }, + }, + } + + // Mock reading initial config + ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) + + // Call with false because of "true" is default value + await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "new-tool", false) + + // Verify the config was updated with initialized disabledForPromptTools + // Find the write call with the normalized path + const normalizedSettingsPath = "/mock/settings/path/cline_mcp_settings.json" + const writeCalls = (fs.writeFile as jest.Mock).mock.calls + + // Find the write call with the normalized path + const writeCall = writeCalls.find((call) => call[0] === normalizedSettingsPath) + const callToUse = writeCall || writeCalls[0] + + const writtenConfig = JSON.parse(callToUse[1]) + expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).toBeDefined() + expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).toContain("new-tool") + }) + }) + describe("server disabled state", () => { it("should toggle server disabled state", async () => { const mockConfig = { diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 9ea0e192eb..0f2342cbd0 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -91,6 +91,7 @@ export interface WebviewMessage { | "restartMcpServer" | "refreshAllMcpServers" | "toggleToolAlwaysAllow" + | "toggleToolEnabledForPrompt" | "toggleMcpServer" | "updateMcpTimeout" | "fuzzyMatchThreshold" @@ -183,6 +184,7 @@ export interface WebviewMessage { serverName?: string toolName?: string alwaysAllow?: boolean + isEnabled?: boolean mode?: Mode promptMode?: PromptMode customPrompt?: PromptComponent diff --git a/src/shared/mcp.ts b/src/shared/mcp.ts index f6c4fe8cc1..ef1d51bad3 100644 --- a/src/shared/mcp.ts +++ b/src/shared/mcp.ts @@ -25,6 +25,7 @@ export type McpTool = { description?: string inputSchema?: object alwaysAllow?: boolean + enabledForPrompt?: boolean } export type McpResource = { diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 507933ddf1..0cd11bb65f 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -25,6 +25,17 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR }) } + const handleEnabledForPromptChange = () => { + if (!serverName) return + vscode.postMessage({ + type: "toggleToolEnabledForPrompt", + serverName, + source: serverSource || "global", + toolName: tool.name, + isEnabled: !tool.enabledForPrompt, + }) + } + return (
{tool.name}
- {serverName && alwaysAllowMcp && ( - - {t("mcp:tool.alwaysAllow")} - - )} +
+ {" "} + {/* Wrapper for checkboxes */} + {serverName && ( +
+ +
+ )} + {serverName && alwaysAllowMcp && ( + + {t("mcp:tool.alwaysAllow")} + + )} +
{tool.description && (
Date: Tue, 13 May 2025 16:02:14 +0300 Subject: [PATCH 02/18] languages --- webview-ui/src/i18n/locales/ca/mcp.json | 3 ++- webview-ui/src/i18n/locales/de/mcp.json | 3 ++- webview-ui/src/i18n/locales/es/mcp.json | 3 ++- webview-ui/src/i18n/locales/fr/mcp.json | 3 ++- webview-ui/src/i18n/locales/hi/mcp.json | 3 ++- webview-ui/src/i18n/locales/it/mcp.json | 3 ++- webview-ui/src/i18n/locales/ja/mcp.json | 5 +++-- webview-ui/src/i18n/locales/ko/mcp.json | 5 +++-- webview-ui/src/i18n/locales/nl/mcp.json | 4 +++- webview-ui/src/i18n/locales/pl/mcp.json | 3 ++- webview-ui/src/i18n/locales/pt-BR/mcp.json | 3 ++- webview-ui/src/i18n/locales/ru/mcp.json | 3 ++- webview-ui/src/i18n/locales/tr/mcp.json | 3 ++- webview-ui/src/i18n/locales/vi/mcp.json | 3 ++- webview-ui/src/i18n/locales/zh-CN/mcp.json | 3 ++- webview-ui/src/i18n/locales/zh-TW/mcp.json | 3 ++- 16 files changed, 35 insertions(+), 18 deletions(-) diff --git a/webview-ui/src/i18n/locales/ca/mcp.json b/webview-ui/src/i18n/locales/ca/mcp.json index ab3173cfad..16129b1dff 100644 --- a/webview-ui/src/i18n/locales/ca/mcp.json +++ b/webview-ui/src/i18n/locales/ca/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Permet sempre", "parameters": "Paràmetres", - "noDescription": "Sense descripció" + "noDescription": "Sense descripció", + "togglePromptInclusion": "Canviar inclusió al prompt" }, "tabs": { "tools": "Eines", diff --git a/webview-ui/src/i18n/locales/de/mcp.json b/webview-ui/src/i18n/locales/de/mcp.json index 3fbc9ea1de..d01b2858fe 100644 --- a/webview-ui/src/i18n/locales/de/mcp.json +++ b/webview-ui/src/i18n/locales/de/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Immer erlauben", "parameters": "Parameter", - "noDescription": "Keine Beschreibung" + "noDescription": "Keine Beschreibung", + "togglePromptInclusion": "Einbeziehung in Prompt umschalten" }, "tabs": { "tools": "Tools", diff --git a/webview-ui/src/i18n/locales/es/mcp.json b/webview-ui/src/i18n/locales/es/mcp.json index 27af50ddc0..5f2bd84dbf 100644 --- a/webview-ui/src/i18n/locales/es/mcp.json +++ b/webview-ui/src/i18n/locales/es/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Permitir siempre", "parameters": "Parámetros", - "noDescription": "Sin descripción" + "noDescription": "Sin descripción", + "togglePromptInclusion": "Alternar inclusión en el prompt" }, "tabs": { "tools": "Herramientas", diff --git a/webview-ui/src/i18n/locales/fr/mcp.json b/webview-ui/src/i18n/locales/fr/mcp.json index 1cde3bad23..7443c25c96 100644 --- a/webview-ui/src/i18n/locales/fr/mcp.json +++ b/webview-ui/src/i18n/locales/fr/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Toujours autoriser", "parameters": "Paramètres", - "noDescription": "Aucune description" + "noDescription": "Aucune description", + "togglePromptInclusion": "Basculer l'inclusion dans le prompt" }, "tabs": { "tools": "Outils", diff --git a/webview-ui/src/i18n/locales/hi/mcp.json b/webview-ui/src/i18n/locales/hi/mcp.json index d0339a2bde..5971affd9a 100644 --- a/webview-ui/src/i18n/locales/hi/mcp.json +++ b/webview-ui/src/i18n/locales/hi/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "हमेशा अनुमति दें", "parameters": "पैरामीटर", - "noDescription": "कोई विवरण नहीं" + "noDescription": "कोई विवरण नहीं", + "togglePromptInclusion": "प्रॉम्प्ट में शामिल करना टॉगल करें" }, "tabs": { "tools": "टूल्स", diff --git a/webview-ui/src/i18n/locales/it/mcp.json b/webview-ui/src/i18n/locales/it/mcp.json index e60c1c1ef9..32d307465e 100644 --- a/webview-ui/src/i18n/locales/it/mcp.json +++ b/webview-ui/src/i18n/locales/it/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Consenti sempre", "parameters": "Parametri", - "noDescription": "Nessuna descrizione" + "noDescription": "Nessuna descrizione", + "togglePromptInclusion": "Attiva/disattiva inclusione nel prompt" }, "tabs": { "tools": "Strumenti", diff --git a/webview-ui/src/i18n/locales/ja/mcp.json b/webview-ui/src/i18n/locales/ja/mcp.json index 204bdca1e4..af7814181e 100644 --- a/webview-ui/src/i18n/locales/ja/mcp.json +++ b/webview-ui/src/i18n/locales/ja/mcp.json @@ -17,8 +17,9 @@ "learnMoreEditingSettings": "MCP設定ファイルの編集方法を詳しく見る", "tool": { "alwaysAllow": "常に許可", - "parameters": "パラメーター", - "noDescription": "説明なし" + "parameters": "パラメータ", + "noDescription": "説明なし", + "togglePromptInclusion": "プロンプトへの含有を切り替える" }, "tabs": { "tools": "ツール", diff --git a/webview-ui/src/i18n/locales/ko/mcp.json b/webview-ui/src/i18n/locales/ko/mcp.json index 957f4ee69a..83f0c5adf4 100644 --- a/webview-ui/src/i18n/locales/ko/mcp.json +++ b/webview-ui/src/i18n/locales/ko/mcp.json @@ -17,8 +17,9 @@ "learnMoreEditingSettings": "MCP 설정 파일 편집 방법 더 알아보기", "tool": { "alwaysAllow": "항상 허용", - "parameters": "파라미터", - "noDescription": "설명 없음" + "parameters": "매개변수", + "noDescription": "설명 없음", + "togglePromptInclusion": "프롬프트 포함 여부 전환" }, "tabs": { "tools": "도구", diff --git a/webview-ui/src/i18n/locales/nl/mcp.json b/webview-ui/src/i18n/locales/nl/mcp.json index 571dc36e9e..7751e55e41 100644 --- a/webview-ui/src/i18n/locales/nl/mcp.json +++ b/webview-ui/src/i18n/locales/nl/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Altijd toestaan", "parameters": "Parameters", - "noDescription": "Geen beschrijving" + "noDescription": "Geen beschrijving", + "togglePromptInclusion": "Inclusie in prompt in-/uitschakelen" }, "tabs": { "tools": "Tools", @@ -28,6 +29,7 @@ "emptyState": { "noTools": "Geen tools gevonden", "noResources": "Geen bronnen gevonden", + "noLogs": "Geen logboeken gevonden", "noErrors": "Geen fouten gevonden" }, "networkTimeout": { diff --git a/webview-ui/src/i18n/locales/pl/mcp.json b/webview-ui/src/i18n/locales/pl/mcp.json index 4a694aac4d..ab39dee200 100644 --- a/webview-ui/src/i18n/locales/pl/mcp.json +++ b/webview-ui/src/i18n/locales/pl/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Zawsze pozwalaj", "parameters": "Parametry", - "noDescription": "Brak opisu" + "noDescription": "Brak opisu", + "togglePromptInclusion": "Przełącz uwzględnianie w podpowiedzi" }, "tabs": { "tools": "Narzędzia", diff --git a/webview-ui/src/i18n/locales/pt-BR/mcp.json b/webview-ui/src/i18n/locales/pt-BR/mcp.json index 7b1cbbd19d..3ed314e16c 100644 --- a/webview-ui/src/i18n/locales/pt-BR/mcp.json +++ b/webview-ui/src/i18n/locales/pt-BR/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Sempre permitir", "parameters": "Parâmetros", - "noDescription": "Sem descrição" + "noDescription": "Sem descrição", + "togglePromptInclusion": "Alternar inclusão no prompt" }, "tabs": { "tools": "Ferramentas", diff --git a/webview-ui/src/i18n/locales/ru/mcp.json b/webview-ui/src/i18n/locales/ru/mcp.json index 8f064d168c..26480b1b5f 100644 --- a/webview-ui/src/i18n/locales/ru/mcp.json +++ b/webview-ui/src/i18n/locales/ru/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Всегда разрешать", "parameters": "Параметры", - "noDescription": "Нет описания" + "noDescription": "Нет описания", + "togglePromptInclusion": "Переключить включение в промпт" }, "tabs": { "tools": "Инструменты", diff --git a/webview-ui/src/i18n/locales/tr/mcp.json b/webview-ui/src/i18n/locales/tr/mcp.json index e8b6b5b4fe..58e4b934e3 100644 --- a/webview-ui/src/i18n/locales/tr/mcp.json +++ b/webview-ui/src/i18n/locales/tr/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Her zaman izin ver", "parameters": "Parametreler", - "noDescription": "Açıklama yok" + "noDescription": "Açıklama yok", + "togglePromptInclusion": "Komut isteminde dahil etmeyi aç/kapat" }, "tabs": { "tools": "Araçlar", diff --git a/webview-ui/src/i18n/locales/vi/mcp.json b/webview-ui/src/i18n/locales/vi/mcp.json index 11c18003a2..8a0679414f 100644 --- a/webview-ui/src/i18n/locales/vi/mcp.json +++ b/webview-ui/src/i18n/locales/vi/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "Luôn cho phép", "parameters": "Tham số", - "noDescription": "Không có mô tả" + "noDescription": "Không có mô tả", + "togglePromptInclusion": "Bật/tắt bao gồm trong lời nhắc" }, "tabs": { "tools": "Công cụ", diff --git a/webview-ui/src/i18n/locales/zh-CN/mcp.json b/webview-ui/src/i18n/locales/zh-CN/mcp.json index 67d47ea75e..d381a3721f 100644 --- a/webview-ui/src/i18n/locales/zh-CN/mcp.json +++ b/webview-ui/src/i18n/locales/zh-CN/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "始终允许", "parameters": "参数", - "noDescription": "无描述" + "noDescription": "无描述", + "togglePromptInclusion": "切换在提示中的包含" }, "tabs": { "tools": "工具", diff --git a/webview-ui/src/i18n/locales/zh-TW/mcp.json b/webview-ui/src/i18n/locales/zh-TW/mcp.json index 9406433ae9..1493f35650 100644 --- a/webview-ui/src/i18n/locales/zh-TW/mcp.json +++ b/webview-ui/src/i18n/locales/zh-TW/mcp.json @@ -18,7 +18,8 @@ "tool": { "alwaysAllow": "總是允許", "parameters": "參數", - "noDescription": "無說明" + "noDescription": "無說明", + "togglePromptInclusion": "切換在提示中的包含" }, "tabs": { "tools": "工具", From f48aa40842a8e075450aae1a7f6f2f7b525a370a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A0=D0=BE=D0=B4=D0=B8=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Tue, 13 May 2025 16:39:47 +0300 Subject: [PATCH 03/18] fix error message --- src/services/mcp/McpHub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index c9e771f228..965d4174de 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -1641,7 +1641,7 @@ export class McpHub { await this.notifyWebviewOfServerChanges() } } catch (error) { - this.showErrorMessage(`Failed to update always allow settings for tool ${toolName}`, error) + this.showErrorMessage(`Failed to update disabledForPromptTool ssettings for tool ${toolName}`, error) throw error // Re-throw to ensure the error is properly handled } } From cf66a6dc43b8ba1436649695584f4aee195ed58a Mon Sep 17 00:00:00 2001 From: Ksandr Date: Tue, 13 May 2025 17:41:23 +0300 Subject: [PATCH 04/18] Update src/core/prompts/instructions/create-mcp-server.ts Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com> --- src/core/prompts/instructions/create-mcp-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 062c847355..11f261befa 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -50,7 +50,7 @@ Common configuration options for both types: - \`disabled\`: (optional) Set to true to temporarily disable the server - \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) - \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation -- \`disabledForPromptTools\`: (optional) Array of tool names that don't includes to system prompt and won't be used +- `disabledForPromptTools`: (optional) Array of tool names that are not included in the system prompt and won't be used ### Example Local MCP Server From 4cdb1e89476ca44db05a8e4d4cffe28558a236b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A0=D0=BE=D0=B4=D0=B8=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Tue, 13 May 2025 18:43:35 +0300 Subject: [PATCH 05/18] fix build --- src/core/prompts/instructions/create-mcp-server.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index 11f261befa..f2b4b2e61e 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -50,7 +50,7 @@ Common configuration options for both types: - \`disabled\`: (optional) Set to true to temporarily disable the server - \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) - \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation -- `disabledForPromptTools`: (optional) Array of tool names that are not included in the system prompt and won't be used +- \`disabledForPromptTools\`: (optional) Array of tool names that are not included in the system prompt and won't be used ### Example Local MCP Server From 2b729f52e9d394df6c41b42db2fc43b703aaaead Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 28 May 2025 08:46:35 -0500 Subject: [PATCH 06/18] typo --- src/services/mcp/McpHub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 965d4174de..ea7c605163 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -1641,7 +1641,7 @@ export class McpHub { await this.notifyWebviewOfServerChanges() } } catch (error) { - this.showErrorMessage(`Failed to update disabledForPromptTool ssettings for tool ${toolName}`, error) + this.showErrorMessage(`Failed to update disabledForPromptTool settings for tool ${toolName}`, error) throw error // Re-throw to ensure the error is properly handled } } From a30087ff4c1e4b5c05261611f935cfdf1144ab81 Mon Sep 17 00:00:00 2001 From: Daniel <57051444+daniel-lxs@users.noreply.github.com> Date: Wed, 28 May 2025 09:17:53 -0500 Subject: [PATCH 07/18] refactor: make the switch style consistent --- webview-ui/src/components/mcp/McpToolRow.tsx | 37 ++++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 0cd11bb65f..93b15c9550 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -57,16 +57,39 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
{ + if (e.key === "Enter" || e.key === " ") { + e.preventDefault() + handleEnabledForPromptChange() + } + }} data-tool-prompt-toggle={tool.name} title={t("mcp:tool.togglePromptInclusion")}> -
)} From a66b5300ce0d80a5552140d0283fee0e519c5f8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=A0=D0=BE=D0=B4=D0=B8=D0=BE=D0=BD=D0=BE=D0=B2?= Date: Sat, 31 May 2025 23:12:52 +0300 Subject: [PATCH 08/18] respect review --- src/__mocks__/fs/promises.ts | 2 +- .../prompts/instructions/create-mcp-server.ts | 4 +- src/services/mcp/McpHub.ts | 191 +++++++----------- src/services/mcp/__tests__/McpHub.spec.ts | 22 +- 4 files changed, 91 insertions(+), 128 deletions(-) diff --git a/src/__mocks__/fs/promises.ts b/src/__mocks__/fs/promises.ts index 63d7a42007..daed479d4a 100644 --- a/src/__mocks__/fs/promises.ts +++ b/src/__mocks__/fs/promises.ts @@ -168,7 +168,7 @@ const mockFs = { args: ["test.js"], disabled: false, alwaysAllow: ["existing-tool"], - disabledForPromptTools: [], + disabledTools: [], }, }, }), diff --git a/src/core/prompts/instructions/create-mcp-server.ts b/src/core/prompts/instructions/create-mcp-server.ts index f2b4b2e61e..a63fad1de5 100644 --- a/src/core/prompts/instructions/create-mcp-server.ts +++ b/src/core/prompts/instructions/create-mcp-server.ts @@ -50,7 +50,7 @@ Common configuration options for both types: - \`disabled\`: (optional) Set to true to temporarily disable the server - \`timeout\`: (optional) Maximum time in seconds to wait for server responses (default: 60) - \`alwaysAllow\`: (optional) Array of tool names that don't require user confirmation -- \`disabledForPromptTools\`: (optional) Array of tool names that are not included in the system prompt and won't be used +- \`disabledTools\`: (optional) Array of tool names that are not included in the system prompt and won't be used ### Example Local MCP Server @@ -277,7 +277,7 @@ 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, alwaysAllow=[] and disabledForPromptTools=[]. +IMPORTANT: Regardless of what else you see in the MCP settings file, you must default any new MCP servers you create to disabled=false, alwaysAllow=[] and disabledTools=[]. \`\`\`json { diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index ea7c605163..e76a3b219d 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -45,7 +45,7 @@ const BaseConfigSchema = z.object({ timeout: z.number().min(1).max(3600).optional().default(60), alwaysAllow: z.array(z.string()).default([]), watchPaths: z.array(z.string()).optional(), // paths to watch for changes and restart server - disabledForPromptTools: z.array(z.string()).default([]), + disabledTools: z.array(z.string()).default([]), }) // Custom error messages for better user feedback @@ -836,7 +836,7 @@ export class McpHub { const actualSource = connection.server.source || "global" let configPath: string let alwaysAllowConfig: string[] = [] - let disabledForPromptToolsList: string[] = [] + let disabledToolsList: string[] = [] // Read from the appropriate config file based on the actual source try { @@ -857,7 +857,7 @@ export class McpHub { } if (serverConfigData) { alwaysAllowConfig = serverConfigData.mcpServers?.[serverName]?.alwaysAllow || [] - disabledForPromptToolsList = serverConfigData.mcpServers?.[serverName]?.disabledForPromptTools || [] + disabledToolsList = serverConfigData.mcpServers?.[serverName]?.disabledTools || [] } } catch (error) { console.error(`Failed to read tool configuration for ${serverName}:`, error) @@ -868,7 +868,7 @@ export class McpHub { const tools = (response?.tools || []).map((tool) => ({ ...tool, alwaysAllow: alwaysAllowConfig.includes(tool.name), - enabledForPrompt: !disabledForPromptToolsList.includes(tool.name), + enabledForPrompt: !disabledToolsList.includes(tool.name), })) return tools @@ -1497,81 +1497,92 @@ export class McpHub { ) } - async toggleToolAlwaysAllow( + /** + * Helper method to update a specific tool list (alwaysAllow or disabledTools) + * in the appropriate settings file. + * @param serverName The name of the server to update + * @param source Whether to update the global or project config + * @param toolName The name of the tool to add or remove + * @param listName The name of the list to modify ("alwaysAllow" or "disabledTools") + * @param addTool Whether to add (true) or remove (false) the tool from the list + */ + private async updateServerToolList( serverName: string, source: "global" | "project", toolName: string, - shouldAllow: boolean, + listName: "alwaysAllow" | "disabledTools", + addTool: boolean, ): Promise { - try { - // Find the connection with matching name and source - const connection = this.findConnection(serverName, source) + // Find the connection with matching name and source + const connection = this.findConnection(serverName, source) - if (!connection) { - throw new Error(`Server ${serverName} with source ${source} not found`) - } + if (!connection) { + throw new Error(`Server ${serverName} with source ${source} not found`) + } - // Determine the correct config path based on the source - let configPath: string - if (source === "project") { - // Get project MCP config path - const projectMcpPath = await this.getProjectMcpPath() - if (!projectMcpPath) { - throw new Error("Project MCP configuration file not found") - } - configPath = projectMcpPath - } else { - // Get global MCP settings path - configPath = await this.getMcpSettingsFilePath() + // Determine the correct config path based on the source + let configPath: string + if (source === "project") { + // Get project MCP config path + const projectMcpPath = await this.getProjectMcpPath() + if (!projectMcpPath) { + throw new Error("Project MCP configuration file not found") } + configPath = projectMcpPath + } else { + // Get global MCP settings path + configPath = await this.getMcpSettingsFilePath() + } - // Normalize path for cross-platform compatibility - // Use a consistent path format for both reading and writing - const normalizedPath = process.platform === "win32" ? configPath.replace(/\\/g, "/") : configPath + // Normalize path for cross-platform compatibility + // Use a consistent path format for both reading and writing + const normalizedPath = process.platform === "win32" ? configPath.replace(/\\/g, "/") : configPath - // Read the appropriate config file - const content = await fs.readFile(normalizedPath, "utf-8") - const config = JSON.parse(content) + // Read the appropriate config file + const content = await fs.readFile(normalizedPath, "utf-8") + const config = JSON.parse(content) - // Initialize mcpServers if it doesn't exist - if (!config.mcpServers) { - config.mcpServers = {} - } + if (!config.mcpServers) { + config.mcpServers = {} + } - // Initialize server config if it doesn't exist - if (!config.mcpServers[serverName]) { - config.mcpServers[serverName] = { - type: "stdio", - command: "node", - args: [], // Default to an empty array; can be set later if needed - } + if (!config.mcpServers[serverName]) { + config.mcpServers[serverName] = { + type: "stdio", + command: "node", + args: [], // Default to an empty array; can be set later if needed } + } - // Initialize alwaysAllow if it doesn't exist - if (!config.mcpServers[serverName].alwaysAllow) { - config.mcpServers[serverName].alwaysAllow = [] - } + if (!config.mcpServers[serverName][listName]) { + config.mcpServers[serverName][listName] = [] + } - const alwaysAllow = config.mcpServers[serverName].alwaysAllow - const toolIndex = alwaysAllow.indexOf(toolName) + const targetList = config.mcpServers[serverName][listName] + const toolIndex = targetList.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) - } + if (addTool && toolIndex === -1) { + targetList.push(toolName) + } else if (!addTool && toolIndex !== -1) { + targetList.splice(toolIndex, 1) + } - // Write updated config back to file - await fs.writeFile(normalizedPath, JSON.stringify(config, null, 2)) + await fs.writeFile(normalizedPath, JSON.stringify(config, null, 2)) - // Update the tools list to reflect the change - if (connection) { - // Explicitly pass the source to ensure we're updating the correct server's tools - connection.server.tools = await this.fetchToolsList(serverName, source) - await this.notifyWebviewOfServerChanges() - } + if (connection) { + connection.server.tools = await this.fetchToolsList(serverName, source) + await this.notifyWebviewOfServerChanges() + } + } + + async toggleToolAlwaysAllow( + serverName: string, + source: "global" | "project", + toolName: string, + shouldAllow: boolean, + ): Promise { + try { + await this.updateServerToolList(serverName, source, toolName, "alwaysAllow", shouldAllow) } catch (error) { this.showErrorMessage( `Failed to toggle always allow for tool "${toolName}" on server "${serverName}" with source "${source}"`, @@ -1588,58 +1599,10 @@ export class McpHub { isEnabled: boolean, ): Promise { try { - const connection = this.findConnection(serverName, source) - if (!connection) { - throw new Error(`Server ${serverName} with source ${source} not found`) - } - - let configPath: string - if (source === "project") { - const projectMcpPath = await this.getProjectMcpPath() - if (!projectMcpPath) { - throw new Error("Project MCP configuration file not found") - } - configPath = projectMcpPath - } else { - configPath = await this.getMcpSettingsFilePath() - } - - const normalizedPath = process.platform === "win32" ? configPath.replace(/\\/g, "/") : configPath - const content = await fs.readFile(normalizedPath, "utf-8") - const config = JSON.parse(content) - - if (!config.mcpServers) { - config.mcpServers = {} - } - if (!config.mcpServers[serverName]) { - // Initialize with minimal valid config if server entry doesn't exist - config.mcpServers[serverName] = { - type: "stdio", - command: "echo", - args: ["MCP server not fully configured"], - } - } - if (!config.mcpServers[serverName].disabledForPromptTools) { - config.mcpServers[serverName].disabledForPromptTools = [] - } - - const disabledList = config.mcpServers[serverName].disabledForPromptTools - const toolIndex = disabledList.indexOf(toolName) - - if (!isEnabled && toolIndex === -1) { - // If tool should be disabled (not included in prompt) and is not in disabled list - disabledList.push(toolName) - } else if (isEnabled && toolIndex !== -1) { - // If tool should be enabled (included in prompt) and is in disabled list - disabledList.splice(toolIndex, 1) - } - - await fs.writeFile(normalizedPath, JSON.stringify(config, null, 2)) - - if (connection) { - connection.server.tools = await this.fetchToolsList(serverName, source) - await this.notifyWebviewOfServerChanges() - } + // When isEnabled is true, we want to remove the tool from the disabledTools list. + // When isEnabled is false, we want to add the tool to the disabledTools list. + const addToolToDisabledList = !isEnabled + await this.updateServerToolList(serverName, source, toolName, "disabledTools", addToolToDisabledList) } catch (error) { this.showErrorMessage(`Failed to update disabledForPromptTool settings for tool ${toolName}`, error) throw error // Re-throw to ensure the error is properly handled diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts index 6bef662c7e..1d81a2674a 100644 --- a/src/services/mcp/__tests__/McpHub.spec.ts +++ b/src/services/mcp/__tests__/McpHub.spec.ts @@ -111,7 +111,7 @@ describe("McpHub", () => { command: "node", args: ["test.js"], alwaysAllow: ["allowed-tool"], - disabledForPromptTools: ["disabled-tool"], + disabledTools: ["disabled-tool"], }, }, }), @@ -274,14 +274,14 @@ describe("McpHub", () => { }) describe("toggleToolEnabledForPrompt", () => { - it("should add tool to disabledForPromptTools list when enabling", async () => { + it("should add tool to disabledTools list when enabling", async () => { const mockConfig = { mcpServers: { "test-server": { type: "stdio", command: "node", args: ["test.js"], - disabledForPromptTools: [], + disabledTools: [], }, }, } @@ -305,17 +305,17 @@ describe("McpHub", () => { expect(writtenConfig.mcpServers).toBeDefined() expect(writtenConfig.mcpServers["test-server"]).toBeDefined() expect(Array.isArray(writtenConfig.mcpServers["test-server"].enabledForPrompt)).toBe(false) - expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).toContain("new-tool") + expect(writtenConfig.mcpServers["test-server"].disabledTools).toContain("new-tool") }) - it("should remove tool from disabledForPromptTools list when disabling", async () => { + it("should remove tool from disabledTools list when disabling", async () => { const mockConfig = { mcpServers: { "test-server": { type: "stdio", command: "node", args: ["test.js"], - disabledForPromptTools: ["existing-tool"], + disabledTools: ["existing-tool"], }, }, } @@ -339,10 +339,10 @@ describe("McpHub", () => { expect(writtenConfig.mcpServers).toBeDefined() expect(writtenConfig.mcpServers["test-server"]).toBeDefined() expect(Array.isArray(writtenConfig.mcpServers["test-server"].enabledForPrompt)).toBe(false) - expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).not.toContain("existing-tool") + expect(writtenConfig.mcpServers["test-server"].disabledTools).not.toContain("existing-tool") }) - it("should initialize disabledForPromptTools if it does not exist", async () => { + it("should initialize disabledTools if it does not exist", async () => { const mockConfig = { mcpServers: { "test-server": { @@ -359,7 +359,7 @@ describe("McpHub", () => { // Call with false because of "true" is default value await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "new-tool", false) - // Verify the config was updated with initialized disabledForPromptTools + // Verify the config was updated with initialized disabledTools // Find the write call with the normalized path const normalizedSettingsPath = "/mock/settings/path/cline_mcp_settings.json" const writeCalls = (fs.writeFile as jest.Mock).mock.calls @@ -369,8 +369,8 @@ describe("McpHub", () => { const callToUse = writeCall || writeCalls[0] const writtenConfig = JSON.parse(callToUse[1]) - expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).toBeDefined() - expect(writtenConfig.mcpServers["test-server"].disabledForPromptTools).toContain("new-tool") + expect(writtenConfig.mcpServers["test-server"].disabledTools).toBeDefined() + expect(writtenConfig.mcpServers["test-server"].disabledTools).toContain("new-tool") }) }) From 5deceac88c7d8186f1d3b864bc20ae77712cc690 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 13 Jun 2025 15:42:08 -0500 Subject: [PATCH 09/18] refactor: improve layout and styling in McpToolRow component --- webview-ui/src/components/mcp/McpToolRow.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 93b15c9550..ca7a818a93 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -44,13 +44,15 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR }}>
e.stopPropagation()}> -
- - {tool.name} +
+ + + {tool.name} +
-
+
{" "} {/* Wrapper for checkboxes */} {serverName && ( From 235312dd9769fa32590a791321f2e428aad5bec3 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Fri, 13 Jun 2025 15:56:30 -0500 Subject: [PATCH 10/18] fix: update new mcp tests --- src/services/mcp/__tests__/McpHub.spec.ts | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts index 1d81a2674a..183fe84857 100644 --- a/src/services/mcp/__tests__/McpHub.spec.ts +++ b/src/services/mcp/__tests__/McpHub.spec.ts @@ -286,6 +286,19 @@ describe("McpHub", () => { }, } + // Set up mock connection + const mockConnection: McpConnection = { + server: { + name: "test-server", + config: "test-server-config", + status: "connected", + source: "global", + }, + client: {} as any, + transport: {} as any, + } + mcpHub.connections = [mockConnection] + // Mock reading initial config ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) @@ -320,6 +333,19 @@ describe("McpHub", () => { }, } + // Set up mock connection + const mockConnection: McpConnection = { + server: { + name: "test-server", + config: "test-server-config", + status: "connected", + source: "global", + }, + client: {} as any, + transport: {} as any, + } + mcpHub.connections = [mockConnection] + // Mock reading initial config ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) @@ -353,6 +379,19 @@ describe("McpHub", () => { }, } + // Set up mock connection + const mockConnection: McpConnection = { + server: { + name: "test-server", + config: "test-server-config", + status: "connected", + source: "global", + }, + client: {} as any, + transport: {} as any, + } + mcpHub.connections = [mockConnection] + // Mock reading initial config ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) From 0bec00698b0e2e1a141821b28a5f3fbb1c905003 Mon Sep 17 00:00:00 2001 From: "rexarrior@yandex.ru" Date: Sat, 14 Jun 2025 20:35:40 +0300 Subject: [PATCH 11/18] tailwind css for disabledTool swipper --- webview-ui/src/components/mcp/McpToolRow.tsx | 30 ++++++-------------- webview-ui/src/index.css | 3 ++ 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index ca7a818a93..74e8964564 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -60,18 +60,11 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR role="switch" aria-checked={tool.enabledForPrompt} tabIndex={0} - style={{ - width: "32px", - height: "16px", - backgroundColor: tool.enabledForPrompt - ? "var(--vscode-button-background)" - : "var(--vscode-titleBar-inactiveForeground)", - borderRadius: "8px", - position: "relative", - cursor: "pointer", - transition: "background-color 0.2s", - opacity: tool.enabledForPrompt ? 1 : 0.6, - }} + className={`relative h-4 w-8 cursor-pointer rounded-full transition-colors ${ + tool.enabledForPrompt + ? "bg-vscode-button-background" + : "bg-vscode-titleBar-inactiveForeground" + } ${tool.enabledForPrompt ? "opacity-100" : "opacity-60"}`} onClick={handleEnabledForPromptChange} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { @@ -82,16 +75,9 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR data-tool-prompt-toggle={tool.name} title={t("mcp:tool.togglePromptInclusion")}>
)} diff --git a/webview-ui/src/index.css b/webview-ui/src/index.css index 388c028c22..fbb362ca8f 100644 --- a/webview-ui/src/index.css +++ b/webview-ui/src/index.css @@ -118,6 +118,9 @@ --color-vscode-sideBarSectionHeader-background: var(--vscode-sideBarSectionHeader-background); --color-vscode-sideBarSectionHeader-border: var(--vscode-sideBarSectionHeader-border); + --color-vscode-titleBar-activeForeground: var(--vscode-titleBar-activeForeground); + --color-vscode-titleBar-inactiveForeground: var(--vscode-titleBar-inactiveForeground); + --color-vscode-charts-green: var(--vscode-charts-green); --color-vscode-charts-yellow: var(--vscode-charts-yellow); From 0f392f08d4cb51dab3b683a3a36b9666cf706147 Mon Sep 17 00:00:00 2001 From: "rexarrior@yandex.ru" Date: Sat, 14 Jun 2025 21:02:38 +0300 Subject: [PATCH 12/18] id locale (from main) --- webview-ui/src/i18n/locales/id/mcp.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webview-ui/src/i18n/locales/id/mcp.json b/webview-ui/src/i18n/locales/id/mcp.json index 03c6e89ccf..96643f41b6 100644 --- a/webview-ui/src/i18n/locales/id/mcp.json +++ b/webview-ui/src/i18n/locales/id/mcp.json @@ -19,7 +19,8 @@ "tool": { "alwaysAllow": "Selalu izinkan", "parameters": "Parameter", - "noDescription": "Tidak ada deskripsi" + "noDescription": "Tidak ada deskripsi", + "togglePromptInclusion": "Aktifkan daya untuk meminta" }, "tabs": { "tools": "Tools", From 0d2947c93114b87eb2492fefa5e9843c8da5df33 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 16 Jun 2025 11:40:57 -0500 Subject: [PATCH 13/18] fix: improve error message for updating tool settings --- src/services/mcp/McpHub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index e76a3b219d..0db6b088fa 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -1604,7 +1604,7 @@ export class McpHub { const addToolToDisabledList = !isEnabled await this.updateServerToolList(serverName, source, toolName, "disabledTools", addToolToDisabledList) } catch (error) { - this.showErrorMessage(`Failed to update disabledForPromptTool settings for tool ${toolName}`, error) + this.showErrorMessage(`Failed to update settings for tool ${toolName}`, error) throw error // Re-throw to ensure the error is properly handled } } From 7bca8fe0e85b473c8fa70d78128fd15d8b95b522 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 16 Jun 2025 11:44:09 -0500 Subject: [PATCH 14/18] fix: specify type for serverConfigData as Record --- src/services/mcp/McpHub.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 0db6b088fa..10a74712ef 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -840,7 +840,7 @@ export class McpHub { // Read from the appropriate config file based on the actual source try { - let serverConfigData: any + let serverConfigData: Record = {} if (actualSource === "project") { // Get project MCP config path const projectMcpPath = await this.getProjectMcpPath() From 13adbd0345a7963ed70d11b354ee56d8a407f207 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Mon, 16 Jun 2025 12:03:03 -0500 Subject: [PATCH 15/18] fix: enhance UI layout and improve accessibility for tool controls --- webview-ui/src/components/mcp/McpToolRow.tsx | 107 +++++++------------ 1 file changed, 38 insertions(+), 69 deletions(-) diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 74e8964564..25bfe2ab41 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -37,34 +37,44 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR } return ( -
+
e.stopPropagation()}> + {/* Tool name section */}
- - + + {tool.name}
-
- {" "} - {/* Wrapper for checkboxes */} - {serverName && ( + + {/* Controls section */} + {serverName && ( +
+ {/* Always Allow checkbox */} + {alwaysAllowMcp && ( + + + {t("mcp:tool.alwaysAllow")} + + + )} + + {/* Enabled switch */}
{ if (e.key === "Enter" || e.key === " ") { @@ -75,46 +85,22 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR data-tool-prompt-toggle={tool.name} title={t("mcp:tool.togglePromptInclusion")}>
- )} - {serverName && alwaysAllowMcp && ( - - {t("mcp:tool.alwaysAllow")} - - )} -
+
+ )}
{tool.description && ( -
- {tool.description} -
+
{tool.description}
)} {tool.inputSchema && "properties" in tool.inputSchema && Object.keys(tool.inputSchema.properties as Record).length > 0 && ( -
-
+
+
{t("mcp:tool.parameters")}
{Object.entries(tool.inputSchema.properties as Record).map( @@ -126,29 +112,12 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR tool.inputSchema.required.includes(paramName) return ( -
- +
+ {paramName} - {isRequired && ( - * - )} + {isRequired && *} - + {schema.description || t("mcp:tool.noDescription")}
From 32cd2773912558bd363524e992775e049e527198 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Tue, 17 Jun 2025 18:02:19 -0500 Subject: [PATCH 16/18] fix: migrate jest.Mock to vitest Mock type --- src/services/mcp/__tests__/McpHub.spec.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts index 183fe84857..1ed2993f2a 100644 --- a/src/services/mcp/__tests__/McpHub.spec.ts +++ b/src/services/mcp/__tests__/McpHub.spec.ts @@ -3,6 +3,7 @@ import type { ClineProvider } from "../../../core/webview/ClineProvider" import type { ExtensionContext, Uri } from "vscode" import { ServerConfigSchema, McpHub } from "../McpHub" import fs from "fs/promises" +import { vi, Mock } from "vitest" vi.mock("vscode", () => ({ workspace: { @@ -300,12 +301,12 @@ describe("McpHub", () => { mcpHub.connections = [mockConnection] // Mock reading initial config - ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) + ;(fs.readFile as Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "new-tool", false) // Verify the config was updated correctly - const writeCalls = (fs.writeFile as jest.Mock).mock.calls + const writeCalls = (fs.writeFile as Mock).mock.calls expect(writeCalls.length).toBeGreaterThan(0) // Find the write call @@ -347,12 +348,12 @@ describe("McpHub", () => { mcpHub.connections = [mockConnection] // Mock reading initial config - ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) + ;(fs.readFile as Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "existing-tool", true) // Verify the config was updated correctly - const writeCalls = (fs.writeFile as jest.Mock).mock.calls + const writeCalls = (fs.writeFile as Mock).mock.calls expect(writeCalls.length).toBeGreaterThan(0) // Find the write call @@ -393,7 +394,7 @@ describe("McpHub", () => { mcpHub.connections = [mockConnection] // Mock reading initial config - ;(fs.readFile as jest.Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) + ;(fs.readFile as Mock).mockResolvedValueOnce(JSON.stringify(mockConfig)) // Call with false because of "true" is default value await mcpHub.toggleToolEnabledForPrompt("test-server", "global", "new-tool", false) @@ -401,7 +402,7 @@ describe("McpHub", () => { // Verify the config was updated with initialized disabledTools // Find the write call with the normalized path const normalizedSettingsPath = "/mock/settings/path/cline_mcp_settings.json" - const writeCalls = (fs.writeFile as jest.Mock).mock.calls + const writeCalls = (fs.writeFile as Mock).mock.calls // Find the write call with the normalized path const writeCall = writeCalls.find((call) => call[0] === normalizedSettingsPath) From 35572c3f010c57e307ee794e6e7565f23414f859 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Tue, 17 Jun 2025 22:54:21 -0400 Subject: [PATCH 17/18] Update .changeset/slimy-years-smell.md --- .changeset/slimy-years-smell.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/slimy-years-smell.md b/.changeset/slimy-years-smell.md index 834933c368..943c31593a 100644 --- a/.changeset/slimy-years-smell.md +++ b/.changeset/slimy-years-smell.md @@ -1,5 +1,5 @@ --- -"roo-cline": minor +"roo-cline": patch --- new feature allowing users to toggle whether an individual MCP (Model Context Protocol) tool is included in the context provided to the AI model From e757bd4d6d75ff77d65a0e05dcc5d920940a5961 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Thu, 19 Jun 2025 15:27:50 -0500 Subject: [PATCH 18/18] fix: replace enabled switch with button --- webview-ui/src/components/mcp/McpToolRow.tsx | 31 ++++++++------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/webview-ui/src/components/mcp/McpToolRow.tsx b/webview-ui/src/components/mcp/McpToolRow.tsx index 25bfe2ab41..76664fea46 100644 --- a/webview-ui/src/components/mcp/McpToolRow.tsx +++ b/webview-ui/src/components/mcp/McpToolRow.tsx @@ -66,30 +66,25 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR )} - {/* Enabled switch */} -
{ - if (e.key === "Enter" || e.key === " ") { - e.preventDefault() - handleEnabledForPromptChange() - } - }} data-tool-prompt-toggle={tool.name} title={t("mcp:tool.togglePromptInclusion")}> -
-
+
)}