Skip to content

Commit 3917371

Browse files
authored
Merge pull request #1056 from hannesrudolph/add-delete-mcp-button
Added MCP server deletion with confirmation dialog
2 parents 0b89d87 + bfa73fb commit 3917371

File tree

4 files changed

+107
-0
lines changed

4 files changed

+107
-0
lines changed

src/core/webview/ClineProvider.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,22 @@ export class ClineProvider implements vscode.WebviewViewProvider {
941941
}
942942
break
943943
}
944+
case "deleteMcpServer": {
945+
if (!message.serverName) {
946+
break
947+
}
948+
949+
try {
950+
this.outputChannel.appendLine(`Attempting to delete MCP server: ${message.serverName}`)
951+
await this.mcpHub?.deleteServer(message.serverName)
952+
this.outputChannel.appendLine(`Successfully deleted MCP server: ${message.serverName}`)
953+
} catch (error) {
954+
const errorMessage = error instanceof Error ? error.message : String(error)
955+
this.outputChannel.appendLine(`Failed to delete MCP server: ${errorMessage}`)
956+
// Error messages are already handled by McpHub.deleteServer
957+
}
958+
break
959+
}
944960
case "restartMcpServer": {
945961
try {
946962
await this.mcpHub?.restartConnection(message.text!)

src/services/mcp/McpHub.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,59 @@ export class McpHub {
576576
}
577577
}
578578

579+
public async deleteServer(serverName: string): Promise<void> {
580+
try {
581+
const settingsPath = await this.getMcpSettingsFilePath()
582+
583+
// Ensure the settings file exists and is accessible
584+
try {
585+
await fs.access(settingsPath)
586+
} catch (error) {
587+
throw new Error("Settings file not accessible")
588+
}
589+
590+
const content = await fs.readFile(settingsPath, "utf-8")
591+
const config = JSON.parse(content)
592+
593+
// Validate the config structure
594+
if (!config || typeof config !== "object") {
595+
throw new Error("Invalid config structure")
596+
}
597+
598+
if (!config.mcpServers || typeof config.mcpServers !== "object") {
599+
config.mcpServers = {}
600+
}
601+
602+
// Remove the server from the settings
603+
if (config.mcpServers[serverName]) {
604+
delete config.mcpServers[serverName]
605+
606+
// Write the entire config back
607+
const updatedConfig = {
608+
mcpServers: config.mcpServers,
609+
}
610+
611+
await fs.writeFile(settingsPath, JSON.stringify(updatedConfig, null, 2))
612+
613+
// Update server connections
614+
await this.updateServerConnections(config.mcpServers)
615+
616+
vscode.window.showInformationMessage(`Deleted MCP server: ${serverName}`)
617+
} else {
618+
vscode.window.showWarningMessage(`Server "${serverName}" not found in configuration`)
619+
}
620+
} catch (error) {
621+
console.error("Failed to delete MCP server:", error)
622+
if (error instanceof Error) {
623+
console.error("Error details:", error.message, error.stack)
624+
}
625+
vscode.window.showErrorMessage(
626+
`Failed to delete MCP server: ${error instanceof Error ? error.message : String(error)}`,
627+
)
628+
throw error
629+
}
630+
}
631+
579632
async readResource(serverName: string, uri: string): Promise<McpResourceResponse> {
580633
const connection = this.connections.find((conn) => conn.server.name === serverName)
581634
if (!connection) {

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export interface WebviewMessage {
9393
| "openCustomModesSettings"
9494
| "checkpointDiff"
9595
| "checkpointRestore"
96+
| "deleteMcpServer"
9697
| "maxOpenTabsContext"
9798
text?: string
9899
disabled?: boolean

webview-ui/src/components/mcp/McpView.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { McpServer } from "../../../../src/shared/mcp"
1313
import McpToolRow from "./McpToolRow"
1414
import McpResourceRow from "./McpResourceRow"
1515
import McpEnabledToggle from "./McpEnabledToggle"
16+
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "../ui/dialog"
1617

1718
type McpViewProps = {
1819
onDone: () => void
@@ -129,6 +130,7 @@ const McpView = ({ onDone }: McpViewProps) => {
129130
// Server Row Component
130131
const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowMcp?: boolean }) => {
131132
const [isExpanded, setIsExpanded] = useState(false)
133+
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false)
132134
const [timeoutValue, setTimeoutValue] = useState(() => {
133135
const configTimeout = JSON.parse(server.config)?.timeout
134136
return configTimeout ?? 60 // Default 1 minute (60 seconds)
@@ -179,6 +181,14 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
179181
})
180182
}
181183

184+
const handleDelete = () => {
185+
vscode.postMessage({
186+
type: "deleteMcpServer",
187+
serverName: server.name,
188+
})
189+
setShowDeleteConfirm(false)
190+
}
191+
182192
return (
183193
<div style={{ marginBottom: "10px" }}>
184194
<div
@@ -202,6 +212,12 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
202212
<div
203213
style={{ display: "flex", alignItems: "center", marginRight: "8px" }}
204214
onClick={(e) => e.stopPropagation()}>
215+
<VSCodeButton
216+
appearance="icon"
217+
onClick={() => setShowDeleteConfirm(true)}
218+
style={{ marginRight: "8px" }}>
219+
<span className="codicon codicon-trash" style={{ fontSize: "14px" }}></span>
220+
</VSCodeButton>
205221
<VSCodeButton
206222
appearance="icon"
207223
onClick={handleRestart}
@@ -393,6 +409,27 @@ const ServerRow = ({ server, alwaysAllowMcp }: { server: McpServer; alwaysAllowM
393409
</div>
394410
)
395411
)}
412+
413+
{/* Delete Confirmation Dialog */}
414+
<Dialog open={showDeleteConfirm} onOpenChange={setShowDeleteConfirm}>
415+
<DialogContent>
416+
<DialogHeader>
417+
<DialogTitle>Delete MCP Server</DialogTitle>
418+
<DialogDescription>
419+
Are you sure you want to delete the MCP server "{server.name}"? This action cannot be
420+
undone.
421+
</DialogDescription>
422+
</DialogHeader>
423+
<DialogFooter>
424+
<VSCodeButton appearance="secondary" onClick={() => setShowDeleteConfirm(false)}>
425+
Cancel
426+
</VSCodeButton>
427+
<VSCodeButton appearance="primary" onClick={handleDelete}>
428+
Delete
429+
</VSCodeButton>
430+
</DialogFooter>
431+
</DialogContent>
432+
</Dialog>
396433
</div>
397434
)
398435
}

0 commit comments

Comments
 (0)