Fix runtime inventory MCP JSON parsing

This commit is contained in:
ejclaw
2026-05-04 02:34:14 +09:00
parent 8e377c278c
commit f958b361d7
2 changed files with 65 additions and 4 deletions

View File

@@ -34,7 +34,19 @@ describe('runtime inventory', () => {
); );
fs.writeFileSync( fs.writeFileSync(
path.join(claudeDir, 'settings.json'), 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( fs.writeFileSync(
path.join(codexDir, 'auth.json'), path.join(codexDir, 'auth.json'),
@@ -75,6 +87,10 @@ describe('runtime inventory', () => {
ejclawConfigured: true, ejclawConfigured: true,
serverCount: 2, serverCount: 2,
}); });
expect(snapshot.claude.mcp).toMatchObject({
ejclawConfigured: true,
serverCount: 2,
});
expect(snapshot.codex.skillDirs[0]).toMatchObject({ expect(snapshot.codex.skillDirs[0]).toMatchObject({
count: 1, count: 1,
skills: [{ name: 'browser', description: 'Browser automation' }], skills: [{ name: 'browser', description: 'Browser automation' }],
@@ -87,5 +103,6 @@ describe('runtime inventory', () => {
const serialized = JSON.stringify(snapshot); const serialized = JSON.stringify(snapshot);
expect(serialized).not.toContain('sk-secret'); expect(serialized).not.toContain('sk-secret');
expect(serialized).not.toContain('should-not-leak'); 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; generatedAt?: string;
} }
interface McpConfigSummary {
ejclawConfigured: boolean;
serverCount: number;
}
function pathSnapshot(label: string, filePath: string): RuntimePathSnapshot { function pathSnapshot(label: string, filePath: string): RuntimePathSnapshot {
return { return {
label, 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( function readMcpSnapshot(
label: string, label: string,
configPath: 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 { return {
configPath: pathSnapshot(label, configPath), configPath: pathSnapshot(label, configPath),
ejclawConfigured: /^\s*\[mcp_servers\.ejclaw(?:\.[^\]]+)?\]/m.test(content), ...summary,
serverCount: serverMatches.length,
}; };
} }