From 044d628a47f063bcbbd9adba7860542156a0c66e Mon Sep 17 00:00:00 2001 From: Clawd Date: Sun, 1 Mar 2026 10:15:14 -0800 Subject: feat(mcp): add tool discovery and per-tool permissions - Add MCP protocol client for tool discovery (initialize + tools/list) - Show discovered tools in settings UI with enable/disable checkboxes - Build explicit allowedTools list from enabled MCP tools - Remove bypassPermissions hack - now uses proper tool allowlisting - Format: mcp__servername__toolname for SDK allowedTools --- renderer/src/components/settings/McpSettings.tsx | 200 ++++++++++++++++++----- 1 file changed, 163 insertions(+), 37 deletions(-) (limited to 'renderer/src/components/settings/McpSettings.tsx') diff --git a/renderer/src/components/settings/McpSettings.tsx b/renderer/src/components/settings/McpSettings.tsx index 7d23fe4..3c4f8e5 100644 --- a/renderer/src/components/settings/McpSettings.tsx +++ b/renderer/src/components/settings/McpSettings.tsx @@ -4,6 +4,11 @@ const api = window.api; type McpServerType = "stdio" | "sse" | "http"; +interface McpTool { + name: string; + description?: string; +} + interface McpServerConfig { type: McpServerType; command?: string; @@ -11,6 +16,9 @@ interface McpServerConfig { env?: Record; url?: string; headers?: Record; + // Tool management + discoveredTools?: McpTool[]; + enabledTools?: string[]; // If undefined/empty after discovery, all tools enabled } type McpServersConfig = Record; @@ -33,6 +41,7 @@ export function McpSettings() { const [editing, setEditing] = useState(null); const [saveStatus, setSaveStatus] = useState<"idle" | "saved" | "error">("idle"); const [error, setError] = useState(null); + const [discovering, setDiscovering] = useState(null); // server name being discovered useEffect(() => { api.getSettings(["mcpServers"]).then((settings) => { @@ -70,6 +79,62 @@ export function McpSettings() { await saveServers(rest); }; + const handleDiscoverTools = async (name: string) => { + if (!servers) return; + const config = servers[name]; + + setDiscovering(name); + setError(null); + + try { + const result = await api.discoverMcpTools({ + type: config.type, + command: config.command, + args: config.args, + env: config.env, + url: config.url, + }); + + if (result.error) { + setError(`Discovery failed: ${result.error}`); + } else { + // Save discovered tools, enable all by default + const newServers = { + ...servers, + [name]: { + ...config, + discoveredTools: result.tools, + enabledTools: result.tools.map((t) => t.name), + }, + }; + await saveServers(newServers); + } + } catch (e) { + setError(`Discovery failed: ${String(e)}`); + } finally { + setDiscovering(null); + } + }; + + const handleToggleTool = async (serverName: string, toolName: string, enabled: boolean) => { + if (!servers) return; + const config = servers[serverName]; + const currentEnabled = config.enabledTools || config.discoveredTools?.map((t) => t.name) || []; + + const newEnabled = enabled + ? [...currentEnabled, toolName] + : currentEnabled.filter((t) => t !== toolName); + + const newServers = { + ...servers, + [serverName]: { + ...config, + enabledTools: newEnabled, + }, + }; + await saveServers(newServers); + }; + const handleSaveEdit = async () => { if (!editing || !servers) return; const { originalName, name, config } = editing; @@ -113,6 +178,21 @@ export function McpSettings() { } } + // Preserve discovered tools if config didn't change substantially + const oldConfig = originalName ? servers[originalName] : null; + if (oldConfig?.discoveredTools) { + const configChanged = + config.type !== oldConfig.type || + config.command !== oldConfig.command || + JSON.stringify(config.args) !== JSON.stringify(oldConfig.args) || + config.url !== oldConfig.url; + + if (!configChanged) { + cleanConfig.discoveredTools = oldConfig.discoveredTools; + cleanConfig.enabledTools = oldConfig.enabledTools; + } + } + newServers[name.trim()] = cleanConfig; await saveServers(newServers); setEditing(null); @@ -139,13 +219,11 @@ export function McpSettings() {
MCP Servers
Configure Model Context Protocol servers to give Claude access to - external data sources like Jira, documentation, or databases. Servers - are available in all phases. + external data sources. Click "Discover Tools" to fetch available tools + and select which ones to enable.
- {error && ( -
{error}
- )} + {error &&
{error}
} {/* Server List */} {!editing && ( @@ -154,39 +232,87 @@ export function McpSettings() { {serverNames.length === 0 ? (
No MCP servers configured
) : ( - serverNames.map((name) => ( -
-
- {name} - {servers[name].type} - - {servers[name].type === "stdio" - ? servers[name].command - : servers[name].url} - + serverNames.map((name) => { + const config = servers[name]; + const hasTools = config.discoveredTools && config.discoveredTools.length > 0; + const enabledTools = config.enabledTools || config.discoveredTools?.map((t) => t.name) || []; + + return ( +
+
+
+ {name} + {config.type} + + {config.type === "stdio" ? config.command : config.url} + +
+
+ + + +
+
+ + {/* Tools Section */} + {hasTools && ( +
+
+ Tools ({enabledTools.length}/{config.discoveredTools!.length} enabled) +
+
+ {config.discoveredTools!.map((tool) => { + const isEnabled = enabledTools.includes(tool.name); + return ( + + ); + })} +
+
+ )} + + {!hasTools && ( +
+ Click "Discover Tools" to see available tools +
+ )}
-
- - -
-
- )) + ); + }) )}
-- cgit v1.2.3