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
This commit is contained in:
Eyejoker
2026-05-04 02:38:21 +09:00
committed by GitHub
parent 2da6052eff
commit 1c5be2094d
2 changed files with 65 additions and 4 deletions

View File

@@ -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');
});
});

View File

@@ -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<string, unknown> {
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,
};
}