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)