Просмотр исходного кода

Merge pull request #1056 from hannesrudolph/add-delete-mcp-button

Added MCP server deletion with confirmation dialog
Matt Rubens 10 месяцев назад
Родитель
Сommit
39173711ce

+ 16 - 0
src/core/webview/ClineProvider.ts

@@ -941,6 +941,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
 						}
 						break
 					}
+					case "deleteMcpServer": {
+						if (!message.serverName) {
+							break
+						}
+
+						try {
+							this.outputChannel.appendLine(`Attempting to delete MCP server: ${message.serverName}`)
+							await this.mcpHub?.deleteServer(message.serverName)
+							this.outputChannel.appendLine(`Successfully deleted MCP server: ${message.serverName}`)
+						} catch (error) {
+							const errorMessage = error instanceof Error ? error.message : String(error)
+							this.outputChannel.appendLine(`Failed to delete MCP server: ${errorMessage}`)
+							// Error messages are already handled by McpHub.deleteServer
+						}
+						break
+					}
 					case "restartMcpServer": {
 						try {
 							await this.mcpHub?.restartConnection(message.text!)

+ 53 - 0
src/services/mcp/McpHub.ts

@@ -576,6 +576,59 @@ export class McpHub {
 		}
 	}
 
+	public async deleteServer(serverName: string): Promise<void> {
+		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<McpResourceResponse> {
 		const connection = this.connections.find((conn) => conn.server.name === serverName)
 		if (!connection) {

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -93,6 +93,7 @@ export interface WebviewMessage {
 		| "openCustomModesSettings"
 		| "checkpointDiff"
 		| "checkpointRestore"
+		| "deleteMcpServer"
 		| "maxOpenTabsContext"
 	text?: string
 	disabled?: boolean

+ 37 - 0
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 (
 		<div style={{ marginBottom: "10px" }}>
 			<div
@@ -202,6 +212,12 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
 				<div
 					style={{ display: "flex", alignItems: "center", marginRight: "8px" }}
 					onClick={(e) => e.stopPropagation()}>
+					<VSCodeButton
+						appearance="icon"
+						onClick={() => setShowDeleteConfirm(true)}
+						style={{ marginRight: "8px" }}>
+						<span className="codicon codicon-trash" style={{ fontSize: "14px" }}></span>
+					</VSCodeButton>
 					<VSCodeButton
 						appearance="icon"
 						onClick={handleRestart}
@@ -393,6 +409,27 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
 					</div>
 				)
 			)}
+
+			{/* Delete Confirmation Dialog */}
+			<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
+				<DialogContent>
+					<DialogHeader>
+						<DialogTitle>Delete MCP Server</DialogTitle>
+						<DialogDescription>
+							Are you sure you want to delete the MCP server "{server.name}"? This action cannot be
+							undone.
+						</DialogDescription>
+					</DialogHeader>
+					<DialogFooter>
+						<VSCodeButton appearance="secondary" onClick={() => setShowDeleteConfirm(false)}>
+							Cancel
+						</VSCodeButton>
+						<VSCodeButton appearance="primary" onClick={handleDelete}>
+							Delete
+						</VSCodeButton>
+					</DialogFooter>
+				</DialogContent>
+			</Dialog>
 		</div>
 	)
 }