()
| 290 | /* ---------- Public API ----------------------------------------------- */ |
| 291 | |
| 292 | export async function getMcpClientStatuses(): Promise<McpClientStatus[]> { |
| 293 | const runtime = await getMcpServerRuntime() |
| 294 | const statuses: McpClientStatus[] = [] |
| 295 | for (const descriptor of MCP_CLIENTS) { |
| 296 | const configPath = clientConfigPath(descriptor) |
| 297 | let installed = false |
| 298 | let upToDate = false |
| 299 | let note: string | undefined |
| 300 | |
| 301 | if (!runtime.entryPath) { |
| 302 | note = |
| 303 | 'The MCP server entry has not been built yet. Run `npm run build` once so the installer has something to point at.' |
| 304 | } |
| 305 | |
| 306 | try { |
| 307 | if (descriptor.format === 'json') { |
| 308 | const cfg = await readJsonConfig(configPath) |
| 309 | const entry = cfg.mcpServers?.[descriptor.serverKey] |
| 310 | if (entry) { |
| 311 | installed = true |
| 312 | upToDate = entriesEqual(entry, { |
| 313 | command: runtime.command, |
| 314 | args: runtime.args, |
| 315 | env: runtime.env |
| 316 | }) |
| 317 | } |
| 318 | } else if (descriptor.format === 'opencode') { |
| 319 | const raw = await readTomlFile(configPath) |
| 320 | const cfg = readOpenCodeConfig(raw) |
| 321 | const entry = cfg.mcp?.[descriptor.serverKey] |
| 322 | installed = entry !== undefined |
| 323 | if (entry) { |
| 324 | const expected = buildOpenCodeEntry({ |
| 325 | command: runtime.command, |
| 326 | args: runtime.args, |
| 327 | env: runtime.env |
| 328 | }) |
| 329 | upToDate = |
| 330 | entry.type === 'local' && |
| 331 | JSON.stringify(entry.command) === JSON.stringify(expected.command) && |
| 332 | JSON.stringify(entry.environment ?? {}) === JSON.stringify(expected.environment ?? {}) |
| 333 | } |
| 334 | } else { |
| 335 | const raw = await readTomlFile(configPath) |
| 336 | const expected = renderTomlBlock(descriptor.serverKey, { |
| 337 | command: runtime.command, |
| 338 | args: runtime.args, |
| 339 | env: runtime.env |
| 340 | }) |
| 341 | installed = raw.includes(MARK_START) && raw.includes(MARK_END) |
| 342 | upToDate = tomlBlockMatches(raw, expected) |
| 343 | } |
| 344 | } catch (err) { |
| 345 | note = (err as Error).message |
| 346 | } |
| 347 | |
| 348 | statuses.push({ |
| 349 | id: descriptor.id, |
no test coverage detected