diff --git a/src/extension/ui/src/assets/windsurf.svg b/src/extension/ui/src/assets/windsurf.svg
new file mode 100644
index 00000000..15789639
--- /dev/null
+++ b/src/extension/ui/src/assets/windsurf.svg
@@ -0,0 +1,5 @@
+
diff --git a/src/extension/ui/src/components/tabs/YourClients.tsx b/src/extension/ui/src/components/tabs/YourClients.tsx
index d95a0904..20be99f8 100644
--- a/src/extension/ui/src/components/tabs/YourClients.tsx
+++ b/src/extension/ui/src/components/tabs/YourClients.tsx
@@ -23,6 +23,7 @@ import ChatGPTIcon from '../../assets/chatgpt.svg';
import ClaudeIcon from '../../assets/claude-ai-icon.svg';
import CursorIcon from '../../assets/cursor.svg';
import GordonIcon from '../../assets/gordon-icon.png';
+import WindsurfIcon from '../../assets/windsurf.svg';
import { CATALOG_LAYOUT_SX, DOCKER_MCP_COMMAND } from '../../Constants';
// Initialize the Docker Desktop client
@@ -36,6 +37,7 @@ const iconMap = {
'Claude Desktop': ClaudeIcon,
Gordon: GordonIcon,
Cursor: CursorIcon,
+ Windsurf: WindsurfIcon,
};
const MCPClientSettings = ({ appProps }: MCPClientSettingsProps) => {
diff --git a/src/extension/ui/src/mcp-clients/Windsurf.ts b/src/extension/ui/src/mcp-clients/Windsurf.ts
new file mode 100644
index 00000000..b273f73e
--- /dev/null
+++ b/src/extension/ui/src/mcp-clients/Windsurf.ts
@@ -0,0 +1,119 @@
+import { v1 } from "@docker/extension-api-client-types";
+import { escapeJSONForPlatformShell, getUser } from "../FileUtils";
+import { MCPClient, SAMPLE_MCP_CONFIG } from "./MCPTypes";
+import { DOCKER_MCP_COMMAND } from "../Constants";
+import { mergeDeep } from "../MergeDeep";
+
+class WindsurfDesktopClient implements MCPClient {
+ name = 'Windsurf';
+ url = 'https://windsurf.com/downloads';
+ manualConfigSteps = [
+ 'Open Windsurf Settings',
+ 'Click on the MCP tab',
+ 'Click on the Add new MCP server button',
+ 'Set name: MCP_DOCKER
',
+ 'Set command:
' + + DOCKER_MCP_COMMAND + + '' + ]; + expectedConfigPath = { + darwin: '$HOME/.codeium/mcp_config.json', + linux: '$HOME/.codeium/mcp_config.json', + win32: '$USERPROFILE\\.codeium\\mcp_config.json' + }; + readConfig = async (client: v1.DockerDesktopClient) => { + const platform = client.host.platform as keyof typeof this.expectedConfigPath; + const configPath = this.expectedConfigPath[platform].replace('$USER', await getUser(client)); + try { + const result = await client.docker.cli.exec('run', ['--rm', '--mount', `type=bind,source=${configPath},target=/codeium_config/mcp_config.json`, 'alpine:latest', 'cat', '/codeium_config/mcp_config.json']); + return { + content: result.stdout, + path: configPath + }; + } catch (e) { + return { + content: null, + path: configPath + }; + } + }; + connect = async (client: v1.DockerDesktopClient) => { + const config = await this.readConfig(client); + let windsurfConfig = null; + try { + windsurfConfig = JSON.parse(config.content || '{}') as typeof SAMPLE_MCP_CONFIG; + if (windsurfConfig.mcpServers?.MCP_DOCKER) { + client.desktopUI.toast.success('Windsurf MCP server already connected.'); + return; + } + } catch (e) { + windsurfConfig = mergeDeep({}, SAMPLE_MCP_CONFIG); + } + const payload = mergeDeep(windsurfConfig, SAMPLE_MCP_CONFIG); + try { + await client.docker.cli.exec('run', + [ + '--rm', + '--mount', + `type=bind,source="${config.path}",target=/codeium_config/mcp_config.json`, + '--workdir', + '/codeium_config', 'vonwig/function_write_files:latest', + escapeJSONForPlatformShell({ files: [{ path: 'mcp_config.json', content: JSON.stringify(payload) }] }, client.host.platform) + ] + ); + client.desktopUI.toast.success('Connected Docker MCP Server to Windsurf.'); + } catch (e) { + if ((e as any).stderr) { + client.desktopUI.toast.error((e as any).stderr); + } else { + client.desktopUI.toast.error((e as Error).message); + } + } + }; + disconnect = async (client: v1.DockerDesktopClient) => { + const config = await this.readConfig(client); + if (!config.content) { + client.desktopUI.toast.error('No config found'); + return; + } + let windsurfConfig = null; + try { + windsurfConfig = JSON.parse(config.content) as typeof SAMPLE_MCP_CONFIG; + if (!windsurfConfig.mcpServers?.MCP_DOCKER) { + client.desktopUI.toast.error('Docker MCP Server not connected to Windsurf'); + return; + } + } catch (e) { + client.desktopUI.toast.error('Failed to disconnect. Invalid Windsurf config found at ' + config.path); + return; + } + const payload = { + ...windsurfConfig, + mcpServers: Object.fromEntries(Object.entries(windsurfConfig.mcpServers).filter(([key]) => key !== 'MCP_DOCKER')) + }; + try { + await client.docker.cli.exec('run', + [ + '--rm', + '--mount', + `type=bind,source="${config.path}",target=/codeium_config/mcp_config.json`, + '--workdir', + '/codeium_config', 'vonwig/function_write_files:latest', + escapeJSONForPlatformShell({ files: [{ path: 'mcp_config.json', content: JSON.stringify(payload) }] }, client.host.platform) + ] + ); + } catch (e) { + if ((e as any).stderr) { + client.desktopUI.toast.error((e as any).stderr); + } else { + client.desktopUI.toast.error((e as Error).message); + } + } + }; + validateConfig = (content: string) => { + const config = JSON.parse(content || '{}') as typeof SAMPLE_MCP_CONFIG; + return !!config.mcpServers?.MCP_DOCKER; + }; +} + +export default new WindsurfDesktopClient(); diff --git a/src/extension/ui/src/mcp-clients/index.ts b/src/extension/ui/src/mcp-clients/index.ts index 101c19d5..a67f5ec7 100644 --- a/src/extension/ui/src/mcp-clients/index.ts +++ b/src/extension/ui/src/mcp-clients/index.ts @@ -8,10 +8,12 @@ import Cursor from "./Cursor"; import ClaudeDesktop from "./ClaudeDesktop"; import Gordon from "./Gordon"; +import Windsurf from "./Windsurf"; import { MCPClient } from "./MCPTypes"; export const SUPPORTED_MCP_CLIENTS: MCPClient[] = [ Gordon, ClaudeDesktop, Cursor, + Windsurf, ] \ No newline at end of file