From 77906481270a92e12b47aedbab62c24e430b1833 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 11 Sep 2026 22:41:02 +0000 Subject: [PATCH] feat: map add-mcp hosts added after 2.0 CI installs add-mcp without a lockfile. 2.1.0 added kilo-code and later releases added fx, kimi-code, kiro-cli, mastracode, and pi, so the catalog-coverage test failed on main after a docs-only push. Co-authored-by: Kent C. Dodds --- package.json | 2 +- src/add-mcp-install.ts | 11 ++++++ src/host-catalog.ts | 74 +++++++++++++++++++++++++++++++++++++++ src/install-hosts.ts | 6 ++++ test/host-catalog.test.ts | 49 ++++++++++++++++++++++++++ test/install.test.ts | 22 ++++++++++++ 6 files changed, 163 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 2e09d8c..04c6933 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@inquirer/checkbox": "^5.2.2", "@modelcontextprotocol/client": "2.0.0", "@napi-rs/keyring": "^1.3.0", - "add-mcp": "^2.0.0", + "add-mcp": "^2.4.0", "ps-list": "^9.0.0" }, "devDependencies": { diff --git a/src/add-mcp-install.ts b/src/add-mcp-install.ts index a7f7ee1..2dd8820 100644 --- a/src/add-mcp-install.ts +++ b/src/add-mcp-install.ts @@ -18,12 +18,18 @@ export const addMcpAgentByHostId = { 'claude-code': 'claude-code', codex: 'codex', cursor: 'cursor', + fx: 'fx', 'gemini-cli': 'gemini-cli', goose: 'goose', 'copilot-cli': 'github-copilot-cli', 'grok-build': 'grok-build', + 'kilo-code': 'kilo-code', + 'kimi-code': 'kimi-code', + 'kiro-cli': 'kiro-cli', + mastracode: 'mastracode', mcporter: 'mcporter', opencode: 'opencode', + pi: 'pi', vscode: 'vscode', windsurf: 'windsurf', zed: 'zed', @@ -38,8 +44,13 @@ const addMcpProjectHosts = new Set([ 'gemini-cli', 'copilot-cli', 'grok-build', + 'kilo-code', + 'kimi-code', + 'kiro-cli', + 'mastracode', 'mcporter', 'opencode', + 'pi', 'vscode', 'zed', ]) diff --git a/src/host-catalog.ts b/src/host-catalog.ts index d0fb183..9a84ee5 100644 --- a/src/host-catalog.ts +++ b/src/host-catalog.ts @@ -11,12 +11,18 @@ export const hostIds = [ 'codex', 'copilot-cli', 'cursor', + 'fx', 'gemini-cli', 'goose', 'grok-build', 'jetbrains', + 'kilo-code', + 'kimi-code', + 'kiro-cli', + 'mastracode', 'mcporter', 'opencode', + 'pi', 'qwen-code', 'visual-studio', 'vscode', @@ -42,6 +48,12 @@ const hostAliases: Record = { cascade: 'windsurf', gemini: 'gemini-cli', 'github-copilot-cli': 'copilot-cli', + kilo: 'kilo-code', + kilocode: 'kilo-code', + kimi: 'kimi-code', + kiro: 'kiro-cli', + mastra: 'mastracode', + 'pi-agent': 'pi', } function isClaudeDesktop(process: ProcessInfo): boolean { @@ -230,6 +242,45 @@ export const hostCatalog: ReadonlyArray = [ kind: 'file', matches: (process) => nameEquals(process, 'mcporter'), }, + { + id: 'kilo-code', + label: 'Kilo Code', + kind: 'file', + matches: (process) => + nameEquals(process, 'kilo') || + haystackMatches(process, /Kilo\.app|kilocode\.kilo-code|kilo-code/iu), + }, + { + id: 'kimi-code', + label: 'Kimi Code', + kind: 'command', + matches: (process) => nameEquals(process, 'kimi'), + }, + { + id: 'kiro-cli', + label: 'Kiro CLI', + kind: 'file', + matches: (process) => + nameEquals(process, 'kiro') || haystackMatches(process, /Kiro\.app/iu), + }, + { + id: 'mastracode', + label: 'Mastra Code', + kind: 'file', + matches: (process) => nameEquals(process, 'mastra', 'mastracode'), + }, + { + id: 'fx', + label: 'fx', + kind: 'file', + matches: (process) => nameEquals(process, 'fx'), + }, + { + id: 'pi', + label: 'Pi', + kind: 'file', + matches: (process) => nameEquals(process, 'pi'), + }, { id: 'visual-studio', label: 'Visual Studio', @@ -303,10 +354,21 @@ export function hostConfigPath(input: { return join(input.cwd, '.grok', 'config.toml') case 'mcporter': return join(input.cwd, 'config', 'mcporter.json') + case 'kilo-code': + return join(input.cwd, 'kilo.json') + case 'kimi-code': + return join(input.cwd, '.kimi-code', 'mcp.json') + case 'kiro-cli': + return join(input.cwd, '.kiro', 'settings', 'mcp.json') + case 'mastracode': + return join(input.cwd, '.mastracode', 'mcp.json') + case 'pi': + return join(input.cwd, '.pi', 'mcp.json') case 'antigravity': case 'claude-desktop': case 'cline': case 'cline-cli': + case 'fx': case 'goose': case 'windsurf': case 'visual-studio': @@ -362,6 +424,18 @@ export function hostConfigPath(input: { return join(input.home, '.grok', 'config.toml') case 'mcporter': return join(input.home, '.mcporter', 'mcporter.json') + case 'kilo-code': + return join(configHome(input.home, platform), 'kilo', 'kilo.json') + case 'kimi-code': + return join(input.home, '.kimi-code', 'mcp.json') + case 'kiro-cli': + return join(input.home, '.kiro', 'settings', 'mcp.json') + case 'mastracode': + return join(input.home, '.mastracode', 'mcp.json') + case 'fx': + return join(input.home, '.fx', 'mcp.json') + case 'pi': + return join(input.home, '.pi', 'agent', 'mcp.json') case 'visual-studio': return join(input.home, '.mcp.json') case 'claude-desktop': diff --git a/src/install-hosts.ts b/src/install-hosts.ts index 09a0434..14206f5 100644 --- a/src/install-hosts.ts +++ b/src/install-hosts.ts @@ -318,8 +318,14 @@ export async function applyHost(input: { case 'antigravity': case 'cline': case 'cline-cli': + case 'fx': case 'grok-build': + case 'kilo-code': + case 'kimi-code': + case 'kiro-cli': + case 'mastracode': case 'mcporter': + case 'pi': return addMcpResult(input, [ `Restart ${host.label} if the new server does not appear.`, oauthNote, diff --git a/test/host-catalog.test.ts b/test/host-catalog.test.ts index 5bfa70a..748a223 100644 --- a/test/host-catalog.test.ts +++ b/test/host-catalog.test.ts @@ -13,6 +13,13 @@ import { test('parseHostIds rejects web clients and accepts local aliases', () => { assert.deepEqual(parseHostIds('cursor,vscode'), ['cursor', 'vscode']) assert.deepEqual(parseHostIds('github-copilot-cli,gemini'), ['copilot-cli', 'gemini-cli']) + assert.deepEqual(parseHostIds('kilo,kimi,kiro,mastra,pi-agent'), [ + 'kilo-code', + 'kimi-code', + 'kiro-cli', + 'mastracode', + 'pi', + ]) assert.throws(() => parseHostIds('chatgpt'), /web client/) assert.throws(() => parseHostIds('grok'), /grok-build/) assert.throws(() => parseHostIds('claude.ai'), /web client/) @@ -35,6 +42,22 @@ test('detectRunningHosts only matches local process names', () => { ) }) +test('add-mcp hosts added after 2.0 match local process names', () => { + const detected = detectRunningHosts([ + { name: 'kilo', cmd: '/usr/local/bin/kilo' }, + { name: 'kimi', cmd: '/usr/local/bin/kimi' }, + { name: 'Kiro', cmd: '/Applications/Kiro.app/Contents/MacOS/Kiro' }, + { name: 'mastra', cmd: '/usr/local/bin/mastra' }, + { name: 'fx', cmd: '/usr/local/bin/fx' }, + { name: 'pi', cmd: '/usr/local/bin/pi' }, + { name: 'Safari', cmd: '/Applications/Safari.app/Contents/MacOS/Safari' }, + ]) + assert.deepEqual( + detected.map((host) => host.id).sort(), + ['fx', 'kilo-code', 'kimi-code', 'kiro-cli', 'mastracode', 'pi'], + ) +}) + test('Cursor is not classified as VS Code', () => { const detected = detectRunningHosts([ { name: 'Cursor Helper', cmd: '/Applications/Cursor.app/Contents/Frameworks/Cursor Helper.app' }, @@ -77,6 +100,32 @@ test('hostConfigPath uses user-level files by default', () => { hostConfigPath({ id: 'cursor', home: '/home/me', cwd: '/proj', project: true }), '/proj/.cursor/mcp.json', ) + assert.equal( + hostConfigPath({ + id: 'kilo-code', + home: '/home/me', + cwd: '/proj', + project: false, + platform: 'linux', + }), + '/home/me/.config/kilo/kilo.json', + ) + assert.equal( + hostConfigPath({ id: 'kilo-code', home: '/home/me', cwd: '/proj', project: true }), + '/proj/kilo.json', + ) + assert.equal( + hostConfigPath({ id: 'fx', home: '/home/me', cwd: '/proj', project: true }), + null, + ) + assert.equal( + hostConfigPath({ id: 'fx', home: '/home/me', cwd: '/proj', project: false }), + '/home/me/.fx/mcp.json', + ) + assert.equal( + hostConfigPath({ id: 'pi', home: '/home/me', cwd: '/proj', project: false }), + '/home/me/.pi/agent/mcp.json', + ) }) test('install deeplinks encode the MCP URL', () => { diff --git a/test/install.test.ts b/test/install.test.ts index 0302d96..490816f 100644 --- a/test/install.test.ts +++ b/test/install.test.ts @@ -77,6 +77,28 @@ test('runInstall does not list web clients when nothing local is running', async assert.doesNotMatch(output, /chatgpt/) }) +test('runInstall writes Kilo Code config through add-mcp', async () => { + const upserts: Array<{ agent: string; local?: boolean }> = [] + const result = await runInstall( + { mcpUrl, clients: 'kilo-code' }, + { + stdout: () => undefined, + home: '/home/me', + cwd: '/proj', + isTTY: false, + listProcesses: async () => [], + runtime: { + upsertServer: (agent, _name, _config, options) => { + upserts.push({ agent, local: options?.local }) + return { success: true, path: '/home/me/.config/kilo/kilo.json' } + }, + }, + }, + ) + assert.equal(result.results[0]?.status, 'wrote') + assert.deepEqual(upserts, [{ agent: 'kilo-code', local: false }]) +}) + test('runInstall writes leftover VS Code Insiders config without add-mcp', async () => { const files = new Map() const upserts: Array = []