diff --git a/src/extension.ts b/src/extension.ts index 792752d..86ca17e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -286,6 +286,15 @@ class ClaudeChatProvider { case 'addPermission': this._addPermission(message.toolName, message.command); return; + case 'loadMCPServers': + this._loadMCPServers(); + return; + case 'saveMCPServer': + this._saveMCPServer(message.name, message.config); + return; + case 'deleteMCPServer': + this._deleteMCPServer(message.name); + return; } } @@ -1451,6 +1460,118 @@ class ClaudeChatProvider { } } + private async _loadMCPServers(): Promise { + try { + const mcpConfigPath = this.getMCPConfigPath(); + if (!mcpConfigPath) { + this._sendAndSaveMessage({ type: 'mcpServers', data: {} }); + return; + } + + const mcpConfigUri = vscode.Uri.file(mcpConfigPath); + let mcpConfig: any = { mcpServers: {} }; + + try { + const content = await vscode.workspace.fs.readFile(mcpConfigUri); + mcpConfig = JSON.parse(new TextDecoder().decode(content)); + } catch { + // File doesn't exist, return empty servers + } + + this._postMessage({ type: 'mcpServers', data: mcpConfig.mcpServers || {} }); + } catch (error) { + console.error('Error loading MCP servers:', error); + this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } }); + } + } + + private async _saveMCPServer(name: string, config: any): Promise { + try { + const mcpConfigPath = this.getMCPConfigPath(); + if (!mcpConfigPath) { + this._postMessage({ type: 'mcpServerError', data: { error: 'Storage path not available' } }); + return; + } + + const mcpConfigUri = vscode.Uri.file(mcpConfigPath); + let mcpConfig: any = { mcpServers: {} }; + + // Load existing config + try { + const content = await vscode.workspace.fs.readFile(mcpConfigUri); + mcpConfig = JSON.parse(new TextDecoder().decode(content)); + } catch { + // File doesn't exist, use default structure + } + + // Ensure mcpServers exists + if (!mcpConfig.mcpServers) { + mcpConfig.mcpServers = {}; + } + + // Add/update the server + mcpConfig.mcpServers[name] = config; + + // Ensure directory exists + const mcpDir = vscode.Uri.file(path.dirname(mcpConfigPath)); + try { + await vscode.workspace.fs.stat(mcpDir); + } catch { + await vscode.workspace.fs.createDirectory(mcpDir); + } + + // Save the config + const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2)); + await vscode.workspace.fs.writeFile(mcpConfigUri, configContent); + + this._postMessage({ type: 'mcpServerSaved', data: { name } }); + console.log(`Saved MCP server: ${name}`); + } catch (error) { + console.error('Error saving MCP server:', error); + this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to save MCP server' } }); + } + } + + private async _deleteMCPServer(name: string): Promise { + try { + const mcpConfigPath = this.getMCPConfigPath(); + if (!mcpConfigPath) { + this._postMessage({ type: 'mcpServerError', data: { error: 'Storage path not available' } }); + return; + } + + const mcpConfigUri = vscode.Uri.file(mcpConfigPath); + let mcpConfig: any = { mcpServers: {} }; + + // Load existing config + try { + const content = await vscode.workspace.fs.readFile(mcpConfigUri); + mcpConfig = JSON.parse(new TextDecoder().decode(content)); + } catch { + // File doesn't exist, nothing to delete + this._postMessage({ type: 'mcpServerError', data: { error: 'MCP config file not found' } }); + return; + } + + // Delete the server + if (mcpConfig.mcpServers && mcpConfig.mcpServers[name]) { + delete mcpConfig.mcpServers[name]; + + // Save the updated config + const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2)); + await vscode.workspace.fs.writeFile(mcpConfigUri, configContent); + + this._postMessage({ type: 'mcpServerDeleted', data: { name } }); + console.log(`Deleted MCP server: ${name}`); + } else { + this._postMessage({ type: 'mcpServerError', data: { error: `Server '${name}' not found` } }); + } + } catch (error) { + console.error('Error deleting MCP server:', error); + this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to delete MCP server' } }); + } + } + public getMCPConfigPath(): string | undefined { const storagePath = this._context.storageUri?.fsPath; if (!storagePath) {return undefined;} diff --git a/src/ui-styles.ts b/src/ui-styles.ts index bfb3336..47b69a7 100644 --- a/src/ui-styles.ts +++ b/src/ui-styles.ts @@ -2307,6 +2307,129 @@ const styles = ` color: var(--vscode-foreground); opacity: 0.8; } + + /* MCP Servers styles */ + .mcp-servers-list { + margin-bottom: 20px; + max-height: 400px; + overflow-y: auto; + } + + .mcp-server-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + margin-bottom: 8px; + background-color: var(--vscode-editor-background); + } + + .server-info { + flex: 1; + } + + .server-name { + font-weight: 600; + font-size: 14px; + color: var(--vscode-foreground); + margin-bottom: 4px; + } + + .server-type { + display: inline-block; + background-color: var(--vscode-badge-background); + color: var(--vscode-badge-foreground); + padding: 2px 6px; + border-radius: 4px; + font-size: 10px; + font-weight: 500; + margin-bottom: 4px; + } + + .server-config { + font-size: 12px; + color: var(--vscode-descriptionForeground); + opacity: 0.8; + } + + .server-delete-btn { + padding: 4px 8px; + font-size: 12px; + color: var(--vscode-errorForeground); + border-color: var(--vscode-errorForeground); + } + + .server-delete-btn:hover { + background-color: var(--vscode-inputValidation-errorBackground); + border-color: var(--vscode-errorForeground); + } + + .mcp-add-server { + text-align: center; + margin-bottom: 20px; + } + + .mcp-add-form { + background-color: var(--vscode-editor-background); + border: 1px solid var(--vscode-panel-border); + border-radius: 6px; + padding: 16px; + margin-top: 16px; + } + + .form-group { + margin-bottom: 16px; + } + + .form-group label { + display: block; + margin-bottom: 6px; + font-weight: 500; + font-size: 13px; + color: var(--vscode-foreground); + } + + .form-group input, + .form-group select, + .form-group textarea { + width: 100%; + padding: 8px 12px; + border: 1px solid var(--vscode-input-border); + border-radius: 4px; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + font-size: 13px; + font-family: var(--vscode-font-family); + } + + .form-group input:focus, + .form-group select:focus, + .form-group textarea:focus { + outline: none; + border-color: var(--vscode-focusBorder); + box-shadow: 0 0 0 1px var(--vscode-focusBorder); + } + + .form-group textarea { + resize: vertical; + min-height: 60px; + } + + .form-buttons { + display: flex; + gap: 8px; + justify-content: flex-end; + margin-top: 20px; + } + + .no-servers { + text-align: center; + color: var(--vscode-descriptionForeground); + font-style: italic; + padding: 40px 20px; + } ` export default styles \ No newline at end of file diff --git a/src/ui.ts b/src/ui.ts index 27d0fd0..f3d6596 100644 --- a/src/ui.ts +++ b/src/ui.ts @@ -75,8 +75,8 @@ const html = ` - + MCP Servers + -
- In Beta: All tools are enabled by default. Use at your own risk. +
+
-
-
- - +
+ +
+