Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
}
break
}
case "deleteMcpServer": {
if (!message.serverName) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the missing serverName case more explicitly (e.g., providing an error message to the user) rather than just breaking silently.

Suggested change
if (!message.serverName) {
if (!message.serverName) { vscode.window.showErrorMessage('Server name is missing.'); break; }

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!)
Expand Down
53 changes: 53 additions & 0 deletions src/services/mcp/McpHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/shared/WebviewMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export interface WebviewMessage {
| "openCustomModesSettings"
| "checkpointDiff"
| "checkpointRestore"
| "deleteMcpServer"
| "maxOpenTabsContext"
text?: string
disabled?: boolean
Expand Down
37 changes: 37 additions & 0 deletions webview-ui/src/components/mcp/McpView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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}
Expand Down Expand Up @@ -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>
)
}
Expand Down
Loading