Compare commits
3 Commits
main
...
td-4ada4b/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b554ab399b | ||
|
|
b691380914 | ||
|
|
d78a7954cb |
@ -3,6 +3,7 @@
|
||||
## [Unreleased]
|
||||
|
||||
### CLI
|
||||
- Deduplicate concurrent keep-alive daemon restarts per server so repeated fatal errors only force-close the cached daemon transport once before retrying. (PR #125, thanks @zm2231)
|
||||
- Keep `mcporter call --output json` parseable by emitting valid JSON even when the command falls back to raw output. (PR #128, thanks @armanddp)
|
||||
- Ignore static `Authorization` headers once OAuth is active so imported editor configs cannot override fresh OAuth tokens. (PR #123, thanks @ahonn)
|
||||
- Preserve full JSON/error payloads when `data` is just one field instead of collapsing the response to `data` alone. (PR #106, thanks @AielloChan)
|
||||
|
||||
@ -18,6 +18,8 @@ export function createKeepAliveRuntime(base: Runtime, options: KeepAliveRuntimeO
|
||||
}
|
||||
|
||||
class KeepAliveRuntime implements Runtime {
|
||||
private readonly restartPromises = new Map<string, Promise<void>>();
|
||||
|
||||
constructor(
|
||||
private readonly base: Runtime,
|
||||
private readonly daemon: DaemonClient,
|
||||
@ -111,10 +113,26 @@ class KeepAliveRuntime implements Runtime {
|
||||
// The daemon keeps STDIO transports warm; if a call fails due to a fatal error,
|
||||
// force-close the cached server so the retry launches a fresh Chrome instance.
|
||||
logDaemonRetry(server, operation, error);
|
||||
await this.daemon.closeServer({ server }).catch(() => {});
|
||||
await this.restartServer(server);
|
||||
return action();
|
||||
}
|
||||
}
|
||||
|
||||
private async restartServer(server: string): Promise<void> {
|
||||
const existing = this.restartPromises.get(server);
|
||||
if (existing) {
|
||||
await existing;
|
||||
return;
|
||||
}
|
||||
|
||||
const restart = this.daemon.closeServer({ server }).catch(() => {});
|
||||
this.restartPromises.set(server, restart);
|
||||
try {
|
||||
await restart;
|
||||
} finally {
|
||||
this.restartPromises.delete(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NON_FATAL_CODES = new Set([ErrorCode.InvalidRequest, ErrorCode.MethodNotFound, ErrorCode.InvalidParams]);
|
||||
|
||||
@ -131,6 +131,39 @@ describe('createKeepAliveRuntime', () => {
|
||||
logSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('deduplicates concurrent restarts for the same server', async () => {
|
||||
const runtime = new FakeRuntime(definitions);
|
||||
let releaseClose!: () => void;
|
||||
const closePromise = new Promise<void>((resolve) => {
|
||||
releaseClose = resolve;
|
||||
});
|
||||
const daemon = {
|
||||
callTool: vi
|
||||
.fn()
|
||||
.mockRejectedValueOnce(new Error('transport hung up'))
|
||||
.mockRejectedValueOnce(new Error('transport hung up'))
|
||||
.mockResolvedValue('daemon-call'),
|
||||
closeServer: vi.fn().mockImplementation(async () => {
|
||||
await closePromise;
|
||||
}),
|
||||
listTools: vi.fn(),
|
||||
listResources: vi.fn(),
|
||||
};
|
||||
const keepAliveRuntime = createKeepAliveRuntime(runtime as unknown as Runtime, {
|
||||
daemonClient: daemon as never,
|
||||
keepAliveServers: new Set(['alpha']),
|
||||
});
|
||||
|
||||
const first = keepAliveRuntime.callTool('alpha', 'ping', {});
|
||||
const second = keepAliveRuntime.callTool('alpha', 'pong', {});
|
||||
await Promise.resolve();
|
||||
expect(daemon.closeServer).toHaveBeenCalledTimes(1);
|
||||
releaseClose();
|
||||
|
||||
await expect(Promise.all([first, second])).resolves.toEqual(['daemon-call', 'daemon-call']);
|
||||
expect(daemon.closeServer).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('does not restart daemon servers for InvalidParams errors', async () => {
|
||||
const runtime = new FakeRuntime(definitions);
|
||||
const error = new McpError(ErrorCode.InvalidParams, 'Tool not found');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user