Skip to content

add support for windsurf #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 23, 2025
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
5 changes: 5 additions & 0 deletions src/extension/ui/src/assets/windsurf.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions src/extension/ui/src/components/tabs/YourClients.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -36,6 +37,7 @@ const iconMap = {
'Claude Desktop': ClaudeIcon,
Gordon: GordonIcon,
Cursor: CursorIcon,
Windsurf: WindsurfIcon,
};

const MCPClientSettings = ({ appProps }: MCPClientSettingsProps) => {
Expand Down
119 changes: 119 additions & 0 deletions src/extension/ui/src/mcp-clients/Windsurf.ts
Original file line number Diff line number Diff line change
@@ -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 <strong>Windsurf Settings</strong>',
'Click on the <strong>MCP</strong> tab',
'Click on the <strong>Add new MCP server</strong> button',
'Set name: <code>MCP_DOCKER</code>',
'Set command: <pre style="font-family: monospace; white-space: nowrap; overflow: auto; width: 80%; background-color: grey.200; padding: 1; border-radius: 1; font-size: 12px;">' +
DOCKER_MCP_COMMAND +
'</pre>'
];
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();
2 changes: 2 additions & 0 deletions src/extension/ui/src/mcp-clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
]