Browse Source

feature: add toggle for disable mcp server tool from prompt (#3551)

* add toggle for disable mcp server tool from prompt

* languages

* fix error message

* Update src/core/prompts/instructions/create-mcp-server.ts

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>

* fix build

* typo

* refactor: make the switch style consistent

* respect review

* refactor: improve layout and styling in McpToolRow component

* fix: update new mcp tests

* tailwind css for disabledTool swipper

* id locale (from main)

* fix: improve error message for updating tool settings

* fix: specify type for serverConfigData as Record<string, any>

* fix: enhance UI layout and improve accessibility for tool controls

* fix: migrate jest.Mock to vitest Mock type

* Update .changeset/slimy-years-smell.md

* fix: replace enabled switch with button

---------

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
Co-authored-by: Daniel <[email protected]>
Co-authored-by: Daniel Riccio <[email protected]>
Co-authored-by: Matt Rubens <[email protected]>
Ksandr 8 months ago
parent
commit
d99ae582d0

+ 5 - 0
.changeset/slimy-years-smell.md

@@ -0,0 +1,5 @@
+---
+"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

+ 1 - 0
src/__mocks__/fs/promises.ts

@@ -168,6 +168,7 @@ const mockFs = {
 						args: ["test.js"],
 						disabled: false,
 						alwaysAllow: ["existing-tool"],
+						disabledTools: [],
 					},
 				},
 			}),

+ 2 - 1
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
+- \`disabledTools\`: (optional) Array of tool names that are not included in the 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 disabledTools=[].
 
 \`\`\`json
 {

+ 1 - 0
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:

+ 17 - 0
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

+ 103 - 66
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
+	disabledTools: 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 disabledToolsList: string[] = []
 
 			// Read from the appropriate config file based on the actual source
 			try {
+				let serverConfigData: Record<string, 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 || []
+					disabledToolsList = serverConfigData.mcpServers?.[serverName]?.disabledTools || []
 				}
 			} 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: !disabledToolsList.includes(tool.name),
 			}))
 
 			return tools
@@ -1491,83 +1497,114 @@ 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<void> {
-		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<void> {
+		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}"`,
+				error,
+			)
+			throw error
+		}
+	}
+
+	async toggleToolEnabledForPrompt(
+		serverName: string,
+		source: "global" | "project",
+		toolName: string,
+		isEnabled: boolean,
+	): Promise<void> {
+		try {
+			// 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 always allow 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
 		}
 	}

+ 142 - 0
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: {
@@ -111,6 +112,7 @@ describe("McpHub", () => {
 						command: "node",
 						args: ["test.js"],
 						alwaysAllow: ["allowed-tool"],
+						disabledTools: ["disabled-tool"],
 					},
 				},
 			}),
@@ -272,6 +274,146 @@ describe("McpHub", () => {
 		})
 	})
 
+	describe("toggleToolEnabledForPrompt", () => {
+		it("should add tool to disabledTools list when enabling", async () => {
+			const mockConfig = {
+				mcpServers: {
+					"test-server": {
+						type: "stdio",
+						command: "node",
+						args: ["test.js"],
+						disabledTools: [],
+					},
+				},
+			}
+
+			// 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 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 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"].disabledTools).toContain("new-tool")
+		})
+
+		it("should remove tool from disabledTools list when disabling", async () => {
+			const mockConfig = {
+				mcpServers: {
+					"test-server": {
+						type: "stdio",
+						command: "node",
+						args: ["test.js"],
+						disabledTools: ["existing-tool"],
+					},
+				},
+			}
+
+			// 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 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 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"].disabledTools).not.toContain("existing-tool")
+		})
+
+		it("should initialize disabledTools if it does not exist", async () => {
+			const mockConfig = {
+				mcpServers: {
+					"test-server": {
+						type: "stdio",
+						command: "node",
+						args: ["test.js"],
+					},
+				},
+			}
+
+			// 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 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 disabledTools
+			// Find the write call with the normalized path
+			const normalizedSettingsPath = "/mock/settings/path/cline_mcp_settings.json"
+			const writeCalls = (fs.writeFile as 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"].disabledTools).toBeDefined()
+			expect(writtenConfig.mcpServers["test-server"].disabledTools).toContain("new-tool")
+		})
+	})
+
 	describe("server disabled state", () => {
 		it("should toggle server disabled state", async () => {
 			const mockConfig = {

+ 2 - 0
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

+ 1 - 0
src/shared/mcp.ts

@@ -25,6 +25,7 @@ export type McpTool = {
 	description?: string
 	inputSchema?: object
 	alwaysAllow?: boolean
+	enabledForPrompt?: boolean
 }
 
 export type McpResource = {

+ 63 - 53
webview-ui/src/components/mcp/McpToolRow.tsx

@@ -25,50 +25,77 @@ 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 (
-		<div
-			key={tool.name}
-			style={{
-				padding: "3px 0",
-			}}>
+		<div key={tool.name} className="py-2 border-b border-vscode-panel-border last:border-b-0">
 			<div
 				data-testid="tool-row-container"
-				style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}
+				className="flex items-center gap-4"
 				onClick={(e) => e.stopPropagation()}>
-				<div style={{ display: "flex", alignItems: "center" }}>
-					<span className="codicon codicon-symbol-method" style={{ marginRight: "6px" }}></span>
-					<span style={{ fontWeight: 500 }}>{tool.name}</span>
+				{/* Tool name section */}
+				<div className="flex items-center min-w-0 flex-1">
+					<span className="codicon codicon-symbol-method mr-2 flex-shrink-0 text-vscode-symbolIcon-methodForeground"></span>
+					<span className="font-medium truncate text-vscode-foreground" title={tool.name}>
+						{tool.name}
+					</span>
 				</div>
-				{serverName && alwaysAllowMcp && (
-					<VSCodeCheckbox checked={tool.alwaysAllow} onChange={handleAlwaysAllowChange} data-tool={tool.name}>
-						{t("mcp:tool.alwaysAllow")}
-					</VSCodeCheckbox>
+
+				{/* Controls section */}
+				{serverName && (
+					<div className="flex items-center gap-4 flex-shrink-0">
+						{/* Always Allow checkbox */}
+						{alwaysAllowMcp && (
+							<VSCodeCheckbox
+								checked={tool.alwaysAllow}
+								onChange={handleAlwaysAllowChange}
+								data-tool={tool.name}
+								className="text-xs">
+								<span className="text-vscode-descriptionForeground whitespace-nowrap">
+									{t("mcp:tool.alwaysAllow")}
+								</span>
+							</VSCodeCheckbox>
+						)}
+
+						{/* Enabled eye button */}
+						<button
+							role="button"
+							aria-pressed={tool.enabledForPrompt}
+							aria-label={t("mcp:tool.togglePromptInclusion")}
+							className={`p-1 rounded hover:bg-vscode-toolbar-hoverBackground transition-colors ${
+								tool.enabledForPrompt
+									? "text-vscode-foreground"
+									: "text-vscode-descriptionForeground opacity-60"
+							}`}
+							onClick={handleEnabledForPromptChange}
+							data-tool-prompt-toggle={tool.name}
+							title={t("mcp:tool.togglePromptInclusion")}>
+							<span
+								className={`codicon ${
+									tool.enabledForPrompt ? "codicon-eye" : "codicon-eye-closed"
+								} text-base`}
+							/>
+						</button>
+					</div>
 				)}
 			</div>
 			{tool.description && (
-				<div
-					style={{
-						marginLeft: "0px",
-						marginTop: "4px",
-						opacity: 0.8,
-						fontSize: "12px",
-					}}>
-					{tool.description}
-				</div>
+				<div className="mt-1 text-xs text-vscode-descriptionForeground opacity-80">{tool.description}</div>
 			)}
 			{tool.inputSchema &&
 				"properties" in tool.inputSchema &&
 				Object.keys(tool.inputSchema.properties as Record<string, any>).length > 0 && (
-					<div
-						style={{
-							marginTop: "8px",
-							fontSize: "12px",
-							border: "1px solid color-mix(in srgb, var(--vscode-descriptionForeground) 30%, transparent)",
-							borderRadius: "3px",
-							padding: "8px",
-						}}>
-						<div
-							style={{ marginBottom: "4px", opacity: 0.8, fontSize: "11px", textTransform: "uppercase" }}>
+					<div className="mt-2 text-xs border border-vscode-panel-border rounded p-2">
+						<div className="mb-1 text-[11px] uppercase opacity-80 text-vscode-descriptionForeground">
 							{t("mcp:tool.parameters")}
 						</div>
 						{Object.entries(tool.inputSchema.properties as Record<string, any>).map(
@@ -80,29 +107,12 @@ const McpToolRow = ({ tool, serverName, serverSource, alwaysAllowMcp }: McpToolR
 									tool.inputSchema.required.includes(paramName)
 
 								return (
-									<div
-										key={paramName}
-										style={{
-											display: "flex",
-											alignItems: "baseline",
-											marginTop: "4px",
-										}}>
-										<code
-											style={{
-												color: "var(--vscode-textPreformat-foreground)",
-												marginRight: "8px",
-											}}>
+									<div key={paramName} className="flex items-baseline mt-1">
+										<code className="text-vscode-textPreformat-foreground mr-2">
 											{paramName}
-											{isRequired && (
-												<span style={{ color: "var(--vscode-errorForeground)" }}>*</span>
-											)}
+											{isRequired && <span className="text-vscode-errorForeground">*</span>}
 										</code>
-										<span
-											style={{
-												opacity: 0.8,
-												overflowWrap: "break-word",
-												wordBreak: "break-word",
-											}}>
+										<span className="opacity-80 break-words text-vscode-descriptionForeground">
 											{schema.description || t("mcp:tool.noDescription")}
 										</span>
 									</div>

+ 2 - 1
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",

+ 2 - 1
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",

+ 2 - 1
webview-ui/src/i18n/locales/en/mcp.json

@@ -19,7 +19,8 @@
 	"tool": {
 		"alwaysAllow": "Always allow",
 		"parameters": "Parameters",
-		"noDescription": "No description"
+		"noDescription": "No description",
+		"togglePromptInclusion": "Toggle inclusion in prompt"
 	},
 	"tabs": {
 		"tools": "Tools",

+ 2 - 1
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",

+ 2 - 1
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",

+ 2 - 1
webview-ui/src/i18n/locales/hi/mcp.json

@@ -18,7 +18,8 @@
 	"tool": {
 		"alwaysAllow": "हमेशा अनुमति दें",
 		"parameters": "पैरामीटर",
-		"noDescription": "कोई विवरण नहीं"
+		"noDescription": "कोई विवरण नहीं",
+		"togglePromptInclusion": "प्रॉम्प्ट में शामिल करना टॉगल करें"
 	},
 	"tabs": {
 		"tools": "टूल्स",

+ 2 - 1
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",

+ 2 - 1
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",

+ 3 - 2
webview-ui/src/i18n/locales/ja/mcp.json

@@ -17,8 +17,9 @@
 	"learnMoreEditingSettings": "MCP設定ファイルの編集方法を詳しく見る",
 	"tool": {
 		"alwaysAllow": "常に許可",
-		"parameters": "パラメーター",
-		"noDescription": "説明なし"
+		"parameters": "パラメータ",
+		"noDescription": "説明なし",
+		"togglePromptInclusion": "プロンプトへの含有を切り替える"
 	},
 	"tabs": {
 		"tools": "ツール",

+ 3 - 2
webview-ui/src/i18n/locales/ko/mcp.json

@@ -17,8 +17,9 @@
 	"learnMoreEditingSettings": "MCP 설정 파일 편집 방법 더 알아보기",
 	"tool": {
 		"alwaysAllow": "항상 허용",
-		"parameters": "파라미터",
-		"noDescription": "설명 없음"
+		"parameters": "매개변수",
+		"noDescription": "설명 없음",
+		"togglePromptInclusion": "프롬프트 포함 여부 전환"
 	},
 	"tabs": {
 		"tools": "도구",

+ 3 - 1
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": {

+ 2 - 1
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",

+ 2 - 1
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",

+ 2 - 1
webview-ui/src/i18n/locales/ru/mcp.json

@@ -18,7 +18,8 @@
 	"tool": {
 		"alwaysAllow": "Всегда разрешать",
 		"parameters": "Параметры",
-		"noDescription": "Нет описания"
+		"noDescription": "Нет описания",
+		"togglePromptInclusion": "Переключить включение в промпт"
 	},
 	"tabs": {
 		"tools": "Инструменты",

+ 2 - 1
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",

+ 2 - 1
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ụ",

+ 2 - 1
webview-ui/src/i18n/locales/zh-CN/mcp.json

@@ -18,7 +18,8 @@
 	"tool": {
 		"alwaysAllow": "始终允许",
 		"parameters": "参数",
-		"noDescription": "无描述"
+		"noDescription": "无描述",
+		"togglePromptInclusion": "切换在提示中的包含"
 	},
 	"tabs": {
 		"tools": "工具",

+ 2 - 1
webview-ui/src/i18n/locales/zh-TW/mcp.json

@@ -18,7 +18,8 @@
 	"tool": {
 		"alwaysAllow": "總是允許",
 		"parameters": "參數",
-		"noDescription": "無說明"
+		"noDescription": "無說明",
+		"togglePromptInclusion": "切換在提示中的包含"
 	},
 	"tabs": {
 		"tools": "工具",

+ 3 - 0
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);