fix(cli): fail unknown list targets

This commit is contained in:
Peter Steinberger 2026-06-08 20:02:50 +01:00
parent 68b228943c
commit 14ff39a59b
4 changed files with 32 additions and 19 deletions

View File

@ -10,6 +10,7 @@
- Add `mcporter record` and `mcporter replay` helpers for capturing and replaying MCP JSON-RPC traffic, with server filters and daemon-safe manual env setup. (PR #192, thanks @LDMB123)
- Prevent direct daemon starts from rebinding over an already-running healthy daemon, avoiding orphaned keep-alive processes during foreground or launch races. (PR #195, thanks @zm2231)
- Return a non-zero exit code for explicit `mcporter list <unknown-server>` failures while preserving aggregate list health checks by default. (Issue #203, thanks @theo674)
- Reconcile keep-alive daemon metadata with the responding process and serialize daemon startup across parallel clients, preventing duplicate orphaned daemons. (Issue #191, thanks @dtmsyi)
- Keep daemon-managed stdio servers warm across repeated `mcporter list` requests instead of treating non-interactive tool listing as a throwaway process. (Issue #188, thanks @robertoronderosjr)

View File

@ -175,7 +175,7 @@ export async function handleList(
const resolved = resolveServerDefinition(runtime, target, { quiet: flags.quiet });
if (!resolved) {
maybeSetListExitCode([{ status: 'error' }], flags);
process.exitCode = 1;
return;
}
target = resolved.name;

View File

@ -330,6 +330,8 @@ describe('CLI list classification and routing', () => {
it('suggests a server name when the typo is large', async () => {
const { handleList } = await cliModulePromise;
const previousExitCode = process.exitCode;
process.exitCode = undefined;
const definition = linearDefinition;
const listTools = vi.fn();
const runtime = {
@ -343,13 +345,17 @@ describe('CLI list classification and routing', () => {
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
await handleList(runtime, ['zzz']);
try {
await handleList(runtime, ['zzz']);
const errorLines = errorSpy.mock.calls.map((call) => call.join(' '));
expect(errorLines.some((line) => line.includes('Did you mean linear?'))).toBe(true);
expect(listTools).not.toHaveBeenCalled();
errorSpy.mockRestore();
logSpy.mockRestore();
const errorLines = errorSpy.mock.calls.map((call) => call.join(' '));
expect(errorLines.some((line) => line.includes('Did you mean linear?'))).toBe(true);
expect(listTools).not.toHaveBeenCalled();
expect(process.exitCode).toBe(1);
} finally {
errorSpy.mockRestore();
logSpy.mockRestore();
process.exitCode = previousExitCode;
}
});
});

View File

@ -37,20 +37,26 @@ function createRuntime(): Runtime {
describe('handleList JSON output', () => {
it('emits aggregated status counts', async () => {
const runtime = createRuntime();
const previousExitCode = process.exitCode;
process.exitCode = undefined;
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
await runHandleList(runtime, ['--json']);
try {
await runHandleList(runtime, ['--json']);
const payload = JSON.parse(logSpy.mock.calls.at(-1)?.[0] ?? '{}');
expect(payload.mode).toBe('list');
expect(payload.counts.auth).toBe(1);
const healthyEntry = payload.servers.find((entry: { name: string }) => entry.name === 'healthy');
expect(healthyEntry.status).toBe('ok');
const authEntry = payload.servers.find((entry: { name: string }) => entry.name === 'auth-server');
expect(authEntry.status).toBe('auth');
expect(authEntry.issue.kind).toBe('auth');
logSpy.mockRestore();
const payload = JSON.parse(logSpy.mock.calls.at(-1)?.[0] ?? '{}');
expect(payload.mode).toBe('list');
expect(payload.counts.auth).toBe(1);
const healthyEntry = payload.servers.find((entry: { name: string }) => entry.name === 'healthy');
expect(healthyEntry.status).toBe('ok');
const authEntry = payload.servers.find((entry: { name: string }) => entry.name === 'auth-server');
expect(authEntry.status).toBe('auth');
expect(authEntry.issue.kind).toBe('auth');
expect(process.exitCode).toBeUndefined();
} finally {
logSpy.mockRestore();
process.exitCode = previousExitCode;
}
});
it('sets a non-zero exit code for unhealthy multi-server checks when requested', async () => {