From 7fa1b32962a3aefab8de373ab263afaeeadb32c7 Mon Sep 17 00:00:00 2001 From: Mark IJbema Date: Wed, 18 Mar 2026 14:55:16 +0100 Subject: [PATCH] fix(vscode): normalize marketplace MCP format to CLI schema on install The marketplace API returns MCP entries in the old Kilocode format: { command: "npx", args: [...], env: {...} } { type: "sse", url: "...", ... } But the CLI's Config.Mcp schema requires: { type: "local", command: ["npx", ...], environment: {...} } { type: "remote", url: "..." } The installed entries failed Zod validation (strict schemas, wrong discriminant) so the CLI silently dropped them from the merged config, meaning they never appeared in the Agent Behaviour settings tab. Fix: normalize entries in buildMcpEntry() before writing to kilo.json. --- .../src/services/marketplace/installer.ts | 45 ++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/kilo-vscode/src/services/marketplace/installer.ts b/packages/kilo-vscode/src/services/marketplace/installer.ts index b1d2bcb9b58..bac22e79f4b 100644 --- a/packages/kilo-vscode/src/services/marketplace/installer.ts +++ b/packages/kilo-vscode/src/services/marketplace/installer.ts @@ -80,7 +80,8 @@ export class MarketplaceInstaller { private buildMcpEntry(content: string, params?: Record): Record { const filtered = Object.fromEntries(Object.entries(params ?? {}).filter(([k]) => k !== "__method")) const replaced = Object.keys(filtered).length > 0 ? substituteParams(content, filtered) : content - return JSON.parse(replaced) + const raw = JSON.parse(replaced) as Record + return normalizeMcpEntry(raw) } // ── Mode ──────────────────────────────────────────────────────────── @@ -273,6 +274,48 @@ export class MarketplaceInstaller { // ── Helpers ───────────────────────────────────────────────────────── +/** + * Normalize a marketplace MCP entry from the old Kilocode format to the CLI's expected format. + * + * Old format (from marketplace API): + * { "command": "npx", "args": [...], "env": {...} } + * { "type": "sse"|"streamable-http", "url": "...", "headers": {...} } + * + * New format (CLI Config.Mcp schema): + * { "type": "local", "command": ["npx", ...], "environment": {...} } + * { "type": "remote", "url": "...", "headers": {...} } + */ +function normalizeMcpEntry(raw: Record): Record { + // Already in new format + if (raw.type === "local" || raw.type === "remote") return raw + + // Remote MCP (sse / streamable-http) → type: "remote" + if (typeof raw.url === "string") { + const { type: _type, url, headers, ...rest } = raw + const entry: Record = { type: "remote", url } + if (headers && typeof headers === "object") entry.headers = headers + // Carry through any other recognized fields (enabled, timeout, oauth) + for (const key of ["enabled", "timeout", "oauth"] as const) { + if (key in rest) entry[key] = rest[key] + } + return entry + } + + // Local MCP (command string + args array) → type: "local", command array + if (typeof raw.command === "string") { + const args = (raw.args as string[] | undefined) ?? [] + const env = raw.env + const entry: Record = { type: "local", command: [raw.command, ...args] } + if (env && typeof env === "object" && Object.keys(env as object).length > 0) entry.environment = env + for (const key of ["enabled", "timeout"] as const) { + if (key in raw) entry[key] = raw[key] + } + return entry + } + + return raw +} + function isSafeId(id: string): boolean { if (!id || id.includes("..") || id.includes("/") || id.includes("\\")) return false return /^[\w\-@.]+$/.test(id)