From 1c5be2094d753d23ed029f5996c923abd1e78687 Mon Sep 17 00:00:00 2001 From: Eyejoker Date: Mon, 4 May 2026 02:38:21 +0900 Subject: [PATCH] Fix runtime inventory MCP JSON parsing (#131) * add gated codex goals support * sync README SDK versions * bump claude agent sdk * add codex goals settings toggle * reuse status dashboard message and simplify settings actions * refine settings page UX * fix settings nav hash routing * add dashboard UX verification * refine dashboard settings and inbox UX * remove inbox top-level navigation * remove dashboard health top-level navigation * fix: allow runtime image attachment paths * feat: configure attachment allowlist dirs * fix: clean duplicate dashboard status messages * fix: poll dashboard duplicate cleanup between status updates * fix: delete dashboard duplicates on create * Refine settings IA with tabbed sections * Add read-only runtime inventory settings * Fix runtime inventory MCP JSON parsing --- src/runtime-inventory.test.ts | 19 ++++++++++++- src/runtime-inventory.ts | 50 ++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/src/runtime-inventory.test.ts b/src/runtime-inventory.test.ts index e214aa1..d67c9c7 100644 --- a/src/runtime-inventory.test.ts +++ b/src/runtime-inventory.test.ts @@ -34,7 +34,19 @@ describe('runtime inventory', () => { ); fs.writeFileSync( path.join(claudeDir, 'settings.json'), - '{"fastMode":true,"secret":"should-not-leak"}\n', + JSON.stringify({ + fastMode: true, + secret: 'should-not-leak', + mcpServers: { + ejclaw: { + command: 'node', + args: ['secret-arg-should-not-leak'], + }, + filesystem: { + command: 'node', + }, + }, + }), ); fs.writeFileSync( path.join(codexDir, 'auth.json'), @@ -75,6 +87,10 @@ describe('runtime inventory', () => { ejclawConfigured: true, serverCount: 2, }); + expect(snapshot.claude.mcp).toMatchObject({ + ejclawConfigured: true, + serverCount: 2, + }); expect(snapshot.codex.skillDirs[0]).toMatchObject({ count: 1, skills: [{ name: 'browser', description: 'Browser automation' }], @@ -87,5 +103,6 @@ describe('runtime inventory', () => { const serialized = JSON.stringify(snapshot); expect(serialized).not.toContain('sk-secret'); expect(serialized).not.toContain('should-not-leak'); + expect(serialized).not.toContain('secret-arg-should-not-leak'); }); }); diff --git a/src/runtime-inventory.ts b/src/runtime-inventory.ts index f6d83c1..c89f4ed 100644 --- a/src/runtime-inventory.ts +++ b/src/runtime-inventory.ts @@ -61,6 +61,11 @@ interface RuntimeInventoryOptions { generatedAt?: string; } +interface McpConfigSummary { + ejclawConfigured: boolean; + serverCount: number; +} + function pathSnapshot(label: string, filePath: string): RuntimePathSnapshot { return { label, @@ -119,6 +124,42 @@ function readSkillDir(label: string, dirPath: string): RuntimeSkillDirSnapshot { }; } +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function summarizeTomlMcpConfig(content: string): McpConfigSummary { + const serverMatches = content.match(/^\s*\[mcp_servers\.[^\]]+\]/gm) ?? []; + return { + ejclawConfigured: /^\s*\[mcp_servers\.ejclaw(?:\.[^\]]+)?\]/m.test(content), + serverCount: serverMatches.length, + }; +} + +function summarizeJsonMcpConfig(content: string): McpConfigSummary | null { + let parsed: unknown; + try { + parsed = JSON.parse(content); + } catch { + return null; + } + if (!isRecord(parsed)) return null; + + const servers = parsed.mcpServers ?? parsed.mcp_servers; + if (!isRecord(servers)) { + return { + ejclawConfigured: false, + serverCount: 0, + }; + } + + const names = Object.keys(servers); + return { + ejclawConfigured: names.includes('ejclaw'), + serverCount: names.length, + }; +} + function readMcpSnapshot( label: string, configPath: string, @@ -132,11 +173,14 @@ function readMcpSnapshot( } } - const serverMatches = content.match(/^\s*\[mcp_servers\.[^\]]+\]/gm) ?? []; + const summary = + path.extname(configPath) === '.json' + ? (summarizeJsonMcpConfig(content) ?? summarizeTomlMcpConfig(content)) + : summarizeTomlMcpConfig(content); + return { configPath: pathSnapshot(label, configPath), - ejclawConfigured: /^\s*\[mcp_servers\.ejclaw(?:\.[^\]]+)?\]/m.test(content), - serverCount: serverMatches.length, + ...summary, }; }