From 7bb8edf72003621216d4094a7eaba866d22fcbc9 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 18 Feb 2025 11:19:08 -0700 Subject: [PATCH 1/2] Added MCP server deletion with confirmation dialog - Added delete button with trash icon to MCP server rows - Implemented confirmation dialog using existing Dialog component - Added "deleteMcpServer" message type to WebviewMessage interface - Added handler in ClineProvider to process deletion: - Removes server from MCP settings file - Closes server connection - Updates UI to reflect changes - Added user feedback with success/error notifications - Ensured consistent modal design with rest of application --- src/core/webview/ClineProvider.ts | 73 +++++++++++++++++++++++ src/shared/WebviewMessage.ts | 1 + webview-ui/src/components/mcp/McpView.tsx | 37 ++++++++++++ 3 files changed, 111 insertions(+) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 2f3cf4ed540..32aef5969bd 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -941,6 +941,79 @@ export class ClineProvider implements vscode.WebviewViewProvider { } break } + case "deleteMcpServer": { + if (!message.serverName) { + break + } + + try { + this.outputChannel.appendLine(`Attempting to delete MCP server: ${message.serverName}`) + + const settingsPath = await this.mcpHub?.getMcpSettingsFilePath() + if (!settingsPath) { + throw new Error("Could not get MCP settings file path") + } + + // Ensure the settings file exists and is accessible + try { + await fs.access(settingsPath) + } catch (error) { + this.outputChannel.appendLine("Settings file not accessible") + throw new Error("Settings file not accessible") + } + + this.outputChannel.appendLine(`Reading MCP settings from: ${settingsPath}`) + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + + // Validate the config structure + if (!config || typeof config !== "object") { + throw new Error("Invalid config structure") + } + + if (!config.mcpServers || typeof config.mcpServers !== "object") { + config.mcpServers = {} + } + + // Remove the server from the settings + if (config.mcpServers[message.serverName]) { + this.outputChannel.appendLine( + `Removing server ${message.serverName} from configuration`, + ) + delete config.mcpServers[message.serverName] + + // Write the entire config back + const updatedConfig = { + mcpServers: config.mcpServers, + } + + await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2)) + + // Update server connections through McpHub + this.outputChannel.appendLine("Updating server connections") + await this.mcpHub?.updateServerConnections(config.mcpServers) + + this.outputChannel.appendLine(`Successfully deleted MCP server: ${message.serverName}`) + vscode.window.showInformationMessage(`Deleted MCP server: ${message.serverName}`) + } else { + this.outputChannel.appendLine(`Server ${message.serverName} not found in configuration`) + vscode.window.showWarningMessage( + `Server "${message.serverName}" not found in configuration`, + ) + } + } catch (error) { + console.error("Failed to delete MCP server:", error) + if (error instanceof Error) { + console.error("Error details:", error.message, error.stack) + } + const errorMessage = error instanceof Error ? error.message : String(error) + this.outputChannel.appendLine(`Failed to delete MCP server: ${errorMessage}`) + vscode.window.showErrorMessage( + `Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`, + ) + } + break + } case "restartMcpServer": { try { await this.mcpHub?.restartConnection(message.text!) diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 4dde38dfcde..106e6d243b9 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -93,6 +93,7 @@ export interface WebviewMessage { | "openCustomModesSettings" | "checkpointDiff" | "checkpointRestore" + | "deleteMcpServer" | "maxOpenTabsContext" text?: string disabled?: boolean diff --git a/webview-ui/src/components/mcp/McpView.tsx b/webview-ui/src/components/mcp/McpView.tsx index b5dd59c8282..adb6b47343d 100644 --- a/webview-ui/src/components/mcp/McpView.tsx +++ b/webview-ui/src/components/mcp/McpView.tsx @@ -13,6 +13,7 @@ import { McpServer } from "../../../../src/shared/mcp" import McpToolRow from "./McpToolRow" import McpResourceRow from "./McpResourceRow" import McpEnabledToggle from "./McpEnabledToggle" +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "../ui/dialog" type McpViewProps = { onDone: () => void @@ -129,6 +130,7 @@ const McpView = ({ onDone }: McpViewProps) => { // Server Row Component const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowMcp?: boolean }) => { const [isExpanded, setIsExpanded] = useState(false) + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false) const [timeoutValue, setTimeoutValue] = useState(() => { const configTimeout = JSON.parse(server.config)?.timeout return configTimeout ?? 60 // Default 1 minute (60 seconds) @@ -179,6 +181,14 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM }) } + const handleDelete = () => { + vscode.postMessage({ + type: "deleteMcpServer", + serverName: server.name, + }) + setShowDeleteConfirm(false) + } + return (
e.stopPropagation()}> + setShowDeleteConfirm(true)} + style={{ marginRight: "8px" }}> + + ) )} + + {/* Delete Confirmation Dialog */} + + + + Delete MCP Server + + Are you sure you want to delete the MCP server "{server.name}"? This action cannot be + undone. + + + + setShowDeleteConfirm(false)}> + Cancel + + + Delete + + + +
) } From bfa73fb69d169ae14775da7254475a92eea540e8 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 18 Feb 2025 21:16:16 -0700 Subject: [PATCH 2/2] Does that work? --- src/core/webview/ClineProvider.ts | 63 ++----------------------------- src/services/mcp/McpHub.ts | 53 ++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 32aef5969bd..43ba3a90846 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -948,69 +948,12 @@ export class ClineProvider implements vscode.WebviewViewProvider { try { this.outputChannel.appendLine(`Attempting to delete MCP server: ${message.serverName}`) - - const settingsPath = await this.mcpHub?.getMcpSettingsFilePath() - if (!settingsPath) { - throw new Error("Could not get MCP settings file path") - } - - // Ensure the settings file exists and is accessible - try { - await fs.access(settingsPath) - } catch (error) { - this.outputChannel.appendLine("Settings file not accessible") - throw new Error("Settings file not accessible") - } - - this.outputChannel.appendLine(`Reading MCP settings from: ${settingsPath}`) - const content = await fs.readFile(settingsPath, "utf-8") - const config = JSON.parse(content) - - // Validate the config structure - if (!config || typeof config !== "object") { - throw new Error("Invalid config structure") - } - - if (!config.mcpServers || typeof config.mcpServers !== "object") { - config.mcpServers = {} - } - - // Remove the server from the settings - if (config.mcpServers[message.serverName]) { - this.outputChannel.appendLine( - `Removing server ${message.serverName} from configuration`, - ) - delete config.mcpServers[message.serverName] - - // Write the entire config back - const updatedConfig = { - mcpServers: config.mcpServers, - } - - await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2)) - - // Update server connections through McpHub - this.outputChannel.appendLine("Updating server connections") - await this.mcpHub?.updateServerConnections(config.mcpServers) - - this.outputChannel.appendLine(`Successfully deleted MCP server: ${message.serverName}`) - vscode.window.showInformationMessage(`Deleted MCP server: ${message.serverName}`) - } else { - this.outputChannel.appendLine(`Server ${message.serverName} not found in configuration`) - vscode.window.showWarningMessage( - `Server "${message.serverName}" not found in configuration`, - ) - } + await this.mcpHub?.deleteServer(message.serverName) + this.outputChannel.appendLine(`Successfully deleted MCP server: ${message.serverName}`) } catch (error) { - console.error("Failed to delete MCP server:", error) - if (error instanceof Error) { - console.error("Error details:", error.message, error.stack) - } const errorMessage = error instanceof Error ? error.message : String(error) this.outputChannel.appendLine(`Failed to delete MCP server: ${errorMessage}`) - vscode.window.showErrorMessage( - `Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`, - ) + // Error messages are already handled by McpHub.deleteServer } break } diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index 209df3dbe68..6e3f39fa7db 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -576,6 +576,59 @@ export class McpHub { } } + public async deleteServer(serverName: string): Promise { + try { + const settingsPath = await this.getMcpSettingsFilePath() + + // Ensure the settings file exists and is accessible + try { + await fs.access(settingsPath) + } catch (error) { + throw new Error("Settings file not accessible") + } + + const content = await fs.readFile(settingsPath, "utf-8") + const config = JSON.parse(content) + + // Validate the config structure + if (!config || typeof config !== "object") { + throw new Error("Invalid config structure") + } + + if (!config.mcpServers || typeof config.mcpServers !== "object") { + config.mcpServers = {} + } + + // Remove the server from the settings + if (config.mcpServers[serverName]) { + delete config.mcpServers[serverName] + + // Write the entire config back + const updatedConfig = { + mcpServers: config.mcpServers, + } + + await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2)) + + // Update server connections + await this.updateServerConnections(config.mcpServers) + + vscode.window.showInformationMessage(`Deleted MCP server: ${serverName}`) + } else { + vscode.window.showWarningMessage(`Server "${serverName}" not found in configuration`) + } + } catch (error) { + console.error("Failed to delete MCP server:", error) + if (error instanceof Error) { + console.error("Error details:", error.message, error.stack) + } + vscode.window.showErrorMessage( + `Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`, + ) + throw error + } + } + async readResource(serverName: string, uri: string): Promise { const connection = this.connections.find((conn) => conn.server.name === serverName) if (!connection) {