Przeglądaj źródła

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
Roo Code 10 miesięcy temu
rodzic
commit
7bb8edf720

+ 73 - 0
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!)

+ 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>
 	)
 }