fix: keep mcporter JSON stdout parseable

Move keep-alive daemon retry diagnostics to stderr so mcporter call --output json keeps stdout parseable after daemon recovery. Current main already covers the structuredContent and raw-envelope JSON fallbacks from the same issue; this PR adds explicit regression coverage for MCP isError envelopes and daemon retry logging.\n\nVerified locally with:\n- pnpm exec vitest run tests/cli-output-utils.test.ts tests/keep-alive-runtime.test.ts tests/result-utils.test.ts\n- pnpm check\n- PNPM_CONFIG_LOGLEVEL=error npm_config_loglevel=error pnpm test\n- pnpm build\n- git diff --check\n\nFixes #160.\n\nCo-authored-by: clawSean <260045960+clawSean@users.noreply.github.com>
This commit is contained in:
clawSean 2026-05-09 01:45:56 -07:00 committed by GitHub
parent 39b49ef3bd
commit e2c0641c1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 17 additions and 2 deletions

View File

@ -4,6 +4,7 @@
### CLI
- Keep keep-alive daemon retry diagnostics on stderr so `mcporter call --output json` stdout stays parseable after a daemon recovery. (PR #163 / issue #160, thanks @clawSean)
- Increase the default OAuth browser wait from 60 seconds to 5 minutes so hosted MCP sign-ins have enough time for account and permission review.
- Skip the redundant daemon `status` preflight for warm keep-alive access, cutting one socket round-trip from each routed list/call/resource request while preserving stale-config and dead-daemon recovery.
- Route explicit default keep-alive calls like `chrome-devtools.list_pages` through a daemon-only fast path, avoiding full runtime startup on warm calls.

View File

@ -160,5 +160,5 @@ function shouldRestartDaemonServer(error: unknown): boolean {
function logDaemonRetry(server: string, operation: string, error: unknown): void {
const reason = error instanceof Error ? error.message : String(error);
console.log(`[mcporter] Restarting '${server}' before retrying ${operation}: ${reason}`);
console.error(`[mcporter] Restarting '${server}' before retrying ${operation}: ${reason}`);
}

View File

@ -87,6 +87,17 @@ describe('printCallOutput format selection', () => {
expect(JSON.parse(String(logged))).toEqual({ content: [{ type: 'text', text: 'no json here' }] });
},
],
[
'json emits valid JSON for MCP error envelopes instead of inspect output',
'json',
{ content: [{ type: 'text', text: 'MCP error -32602: Tool search not found' }], isError: true },
(logged: unknown) => {
expect(JSON.parse(String(logged))).toEqual({
content: [{ type: 'text', text: 'MCP error -32602: Tool search not found' }],
isError: true,
});
},
],
[
'json emits null for undefined raw fallback',
'json',

View File

@ -139,12 +139,15 @@ describe('createKeepAliveRuntime', () => {
keepAliveServers: new Set(['alpha']),
});
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
await expect(keepAliveRuntime.callTool('alpha', 'ping', {})).resolves.toBe('daemon-call');
expect(daemon.callTool).toHaveBeenCalledTimes(2);
expect(daemon.closeServer).toHaveBeenCalledWith({ server: 'alpha' });
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("Restarting 'alpha'"));
expect(logSpy).not.toHaveBeenCalled();
expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining("Restarting 'alpha'"));
logSpy.mockRestore();
errorSpy.mockRestore();
});
it('deduplicates concurrent restarts for the same server', async () => {