From 1436936ef8401bc464eb448598334b008767fad9 Mon Sep 17 00:00:00 2001 From: Jake Strawn Date: Thu, 26 Mar 2026 18:18:12 -0400 Subject: [PATCH] feat(vscode): add Configure for Cursor/Windsurf command --- packages/vscode/package.json | 5 + .../src/commands/configureCursorWindsurf.ts | 114 ++++++++++++++++++ packages/vscode/src/extension.ts | 2 + 3 files changed, 121 insertions(+) create mode 100644 packages/vscode/src/commands/configureCursorWindsurf.ts diff --git a/packages/vscode/package.json b/packages/vscode/package.json index 5d419f4..b33a626 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -35,6 +35,11 @@ "command": "helixir.runHealthCheck", "title": "Helixir: Run Health Check", "category": "Helixir" + }, + { + "command": "helixir.configureCursorWindsurf", + "title": "Helixir: Configure for Cursor/Windsurf", + "category": "Helixir" } ], "configuration": { diff --git a/packages/vscode/src/commands/configureCursorWindsurf.ts b/packages/vscode/src/commands/configureCursorWindsurf.ts new file mode 100644 index 0000000..ffb6ab9 --- /dev/null +++ b/packages/vscode/src/commands/configureCursorWindsurf.ts @@ -0,0 +1,114 @@ +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +/** + * Detects whether the current host is Cursor editor by inspecting the + * application name reported by the VS Code API and common environment + * variables set by the Cursor process. + */ +function isCursor(): boolean { + const appName = vscode.env.appName ?? ''; + return ( + appName.toLowerCase().includes('cursor') || + (process.env['CURSOR_TRACE_ID'] !== undefined) || + (process.env['CURSOR_APP_PATH'] !== undefined) + ); +} + +/** + * Returns the directory name (.cursor or .windsurf) and a human-readable + * editor label based on the detected editor. + */ +function resolveEditorConfig(): { dirName: string; label: string } { + if (isCursor()) { + return { dirName: '.cursor', label: 'Cursor' }; + } + return { dirName: '.windsurf', label: 'Windsurf' }; +} + +interface McpServerEntry { + command: string; + args: string[]; + env: Record; +} + +interface McpJson { + mcpServers: Record; +} + +/** + * Registers the "Helixir: Configure for Cursor/Windsurf" command. + * + * When invoked the command: + * 1. Detects whether the host is Cursor or Windsurf/other. + * 2. Resolves the target mcp.json path inside the workspace root (or $HOME as + * fallback when no workspace is open). + * 3. Reads any existing mcp.json so that pre-existing server entries are + * preserved. + * 4. Upserts the "helixir" entry pointing at the bundled mcp-server.js. + * 5. Writes the file and shows an information notification. + */ +export function registerConfigureCursorWindsurfCommand( + context: vscode.ExtensionContext +): void { + const command = vscode.commands.registerCommand( + 'helixir.configureCursorWindsurf', + async () => { + const { dirName, label } = resolveEditorConfig(); + + // Resolve the base directory (workspace root or home directory). + const baseDir = + vscode.workspace.workspaceFolders?.[0]?.uri.fsPath ?? os.homedir(); + + const configDir = path.join(baseDir, dirName); + const configFilePath = path.join(configDir, 'mcp.json'); + + // Path to the bundled MCP server shipped with this extension. + const serverScriptPath = path.join( + context.extensionPath, + 'dist', + 'mcp-server.js' + ); + + // Read existing config (if any) so we don't stomp other servers. + let existing: McpJson = { mcpServers: {} }; + if (fs.existsSync(configFilePath)) { + try { + const raw = fs.readFileSync(configFilePath, 'utf8'); + const parsed = JSON.parse(raw) as Partial; + existing = { + mcpServers: parsed.mcpServers ?? {}, + }; + } catch { + // If the file is malformed, start fresh but preserve the attempt. + existing = { mcpServers: {} }; + } + } + + // Upsert the helixir entry. + existing.mcpServers['helixir'] = { + command: 'node', + args: [serverScriptPath], + env: {}, + }; + + // Ensure the config directory exists. + fs.mkdirSync(configDir, { recursive: true }); + + // Write the updated config. + fs.writeFileSync( + configFilePath, + JSON.stringify(existing, null, 2) + '\n', + 'utf8' + ); + + await vscode.window.showInformationMessage( + `Helixir: MCP server entry written to ${configFilePath} (${label}).` + ); + } + ); + + context.subscriptions.push(command); +} diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 47192ac..7480d62 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { registerConfigureCursorWindsurfCommand } from './commands/configureCursorWindsurf.js'; import { registerMcpProvider } from './mcpProvider.js'; /** @@ -8,6 +9,7 @@ import { registerMcpProvider } from './mcpProvider.js'; */ export function activate(context: vscode.ExtensionContext): void { registerMcpProvider(context); + registerConfigureCursorWindsurfCommand(context); const healthCheckCommand = vscode.commands.registerCommand( 'helixir.runHealthCheck',