From c1502be19acc77baa8ce54a2a99f3d993d091f95 Mon Sep 17 00:00:00 2001 From: chishiyac Date: Thu, 23 Apr 2026 06:08:21 -0400 Subject: [PATCH] feat: add support for Visual Studio Code client configuration --- README.md | 1 + src/client.ts | 15 ++++++++---- src/config.ts | 19 +++++++++++++-- src/index.ts | 10 ++++++-- src/types.ts | 14 +++++++++-- src/utils.ts | 65 ++++++++++++++++++++++++++++++++++++++++++++++----- 6 files changed, 108 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 7aeba31..ba5cda4 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ npx @magicuidesign/cli@latest install - [x] claude - [x] cline - [x] roo-cline +- [x] vscode ## Manual Installation diff --git a/src/client.ts b/src/client.ts index b261f1f..b87753d 100644 --- a/src/client.ts +++ b/src/client.ts @@ -8,7 +8,8 @@ const execAsync = promisify(exec); async function isClientRunning(client: string): Promise { try { const platform = process.platform; - const clientProcess = { claude: "Claude" }[client] || client; + const clientProcess = + { claude: "Claude", vscode: "Code" }[client] || client; if (platform === "win32") { const { stdout } = await execAsync( @@ -31,14 +32,18 @@ async function isClientRunning(client: string): Promise { } async function restartClient(client: string): Promise { - const clientProcess = { claude: "Claude" }[client] || client; + const clientProcess = + { claude: "Claude", vscode: "Code" }[client] || client; const platform = process.platform; + const vscodeDarwinAppName = "Visual Studio Code"; try { if (platform === "win32") { await execAsync(`taskkill /F /IM "${clientProcess}.exe"`); } else if (platform === "darwin") { - await execAsync(`killall "${clientProcess}"`); + const killTarget = + client === "vscode" ? "Code" : clientProcess; + await execAsync(`killall "${killTarget}"`); } else if (platform === "linux") { await execAsync(`pkill -f "${clientProcess.toLowerCase()}"`); } @@ -48,7 +53,9 @@ async function restartClient(client: string): Promise { if (platform === "win32") { await execAsync(`start "" "${clientProcess}.exe"`); } else if (platform === "darwin") { - await execAsync(`open -a "${clientProcess}"`); + const openTarget = + client === "vscode" ? vscodeDarwinAppName : clientProcess; + await execAsync(`open -a "${openTarget}"`); } else if (platform === "linux") { await execAsync(clientProcess.toLowerCase()); } diff --git a/src/config.ts b/src/config.ts index 4e725b1..849055f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import os from "node:os"; import path from "node:path"; +import type { ValidClient, VscodeClientConfig, McpServersClientConfig } from "./types.js"; const homeDir = os.homedir(); @@ -39,6 +40,7 @@ export const clientPaths: Record = { ), windsurf: path.join(homeDir, ".codeium", "windsurf", "mcp_config.json"), cursor: path.join(homeDir, ".cursor", "mcp.json"), + vscode: path.join(baseDir, "Code", "User", "mcp.json"), }; export const createPlatformCommand = (passedArgs: string[]) => { @@ -54,7 +56,20 @@ export const createPlatformCommand = (passedArgs: string[]) => { }; }; -export const getDefaultConfig = () => { +export function getDefaultConfig( + client: ValidClient, +): McpServersClientConfig | VscodeClientConfig { + if (client === "vscode") { + return { + servers: { + magicui: { + command: "npx", + args: ["-y", "@magicuidesign/mcp@latest"], + }, + }, + }; + } + const args = ["-y", "@magicuidesign/mcp@latest"]; const command = createPlatformCommand(args); @@ -63,4 +78,4 @@ export const getDefaultConfig = () => { "@magicuidesign/mcp": command, }, }; -}; +} diff --git a/src/index.ts b/src/index.ts index 6b980c7..a6842a4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,15 +5,21 @@ import { getDefaultConfig } from "./config.js"; import type { ValidClient } from "./types.js"; import { writeConfig } from "./utils.js"; +const CLIENT_DISPLAY_NAMES: Partial> = { + vscode: "VS Code", +}; + export async function install(client: ValidClient): Promise { - const capitalizedClient = client.charAt(0).toUpperCase() + client.slice(1); + const capitalizedClient = + CLIENT_DISPLAY_NAMES[client] ?? + client.charAt(0).toUpperCase() + client.slice(1); const spinner = ora( `Installing configuration for ${capitalizedClient}...`, ).start(); try { - const config = { ...getDefaultConfig() }; + const config = getDefaultConfig(client); writeConfig(client, config); spinner.succeed( diff --git a/src/types.ts b/src/types.ts index 841db21..d7efee3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,8 @@ export type ValidClient = | "cline" | "roo-cline" | "windsurf" - | "cursor"; + | "cursor" + | "vscode"; export const VALID_CLIENTS: ValidClient[] = [ "claude", @@ -11,6 +12,7 @@ export const VALID_CLIENTS: ValidClient[] = [ "roo-cline", "windsurf", "cursor", + "vscode", ]; export interface ServerConfig { @@ -18,10 +20,18 @@ export interface ServerConfig { args: string[]; } -export interface ClientConfig { +/** MCP config shape used by Claude Desktop, Cursor, Windsurf, Cline, etc. */ +export interface McpServersClientConfig { mcpServers: Record; } +/** VS Code native MCP config (`User/mcp.json`) */ +export interface VscodeClientConfig { + servers: Record; +} + +export type ClientConfig = McpServersClientConfig | VscodeClientConfig; + export interface InstallOptions { apiKey?: string; } diff --git a/src/utils.ts b/src/utils.ts index bfbecad..0b1f9e2 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,11 @@ import fs from "node:fs"; import path from "node:path"; -import type { ValidClient, ClientConfig } from "./types.js"; +import type { + ValidClient, + ClientConfig, + McpServersClientConfig, + VscodeClientConfig, +} from "./types.js"; import { clientPaths } from "./config.js"; export function getConfigPath(client: ValidClient): string { @@ -15,17 +20,30 @@ export function readConfig(client: ValidClient): ClientConfig { const configPath = getConfigPath(client); if (!fs.existsSync(configPath)) { - return { mcpServers: {} }; + return client === "vscode" + ? { servers: {} } + : { mcpServers: {} }; } try { const rawConfig = JSON.parse(fs.readFileSync(configPath, "utf8")); + if (client === "vscode") { + return { + ...rawConfig, + servers: + rawConfig.servers && typeof rawConfig.servers === "object" + ? rawConfig.servers + : {}, + }; + } return { ...rawConfig, mcpServers: rawConfig.mcpServers || {}, }; } catch (error) { - return { mcpServers: {} }; + return client === "vscode" + ? { servers: {} } + : { mcpServers: {} }; } } @@ -37,11 +55,46 @@ export function writeConfig(client: ValidClient, config: ClientConfig): void { fs.mkdirSync(configDir, { recursive: true }); } - if (!config.mcpServers || typeof config.mcpServers !== "object") { + if (client === "vscode") { + const vscodeConfig = config as VscodeClientConfig; + if (!vscodeConfig.servers || typeof vscodeConfig.servers !== "object") { + throw new Error("Invalid servers structure"); + } + + let existingRaw: Record = {}; + try { + if (fs.existsSync(configPath)) { + existingRaw = JSON.parse(fs.readFileSync(configPath, "utf8")); + } + } catch { + // If reading fails, continue with empty existing config + } + + const existingServers = + existingRaw.servers && + typeof existingRaw.servers === "object" && + !Array.isArray(existingRaw.servers) + ? (existingRaw.servers as Record) + : {}; + + const mergedConfig = { + ...existingRaw, + servers: { + ...existingServers, + ...vscodeConfig.servers, + }, + }; + + fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2)); + return; + } + + const mcpConfig = config as McpServersClientConfig; + if (!mcpConfig.mcpServers || typeof mcpConfig.mcpServers !== "object") { throw new Error("Invalid mcpServers structure"); } - let existingConfig: ClientConfig = { mcpServers: {} }; + let existingConfig: McpServersClientConfig = { mcpServers: {} }; try { if (fs.existsSync(configPath)) { existingConfig = JSON.parse(fs.readFileSync(configPath, "utf8")); @@ -54,7 +107,7 @@ export function writeConfig(client: ValidClient, config: ClientConfig): void { ...existingConfig, mcpServers: { ...existingConfig.mcpServers, - ...config.mcpServers, + ...mcpConfig.mcpServers, }, };