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:
@@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user