fix(core): handle unhandled promise rejection in mcp-client-manager (#14701)

This commit is contained in:
HyeongHo Jun
2026-01-03 01:06:56 +09:00
committed by GitHub
parent c29a8c12b3
commit 8a0190ca3b
2 changed files with 59 additions and 15 deletions

View File

@@ -238,4 +238,40 @@ describe('McpClientManager', () => {
);
});
});
describe('Promise rejection handling', () => {
it('should handle errors thrown during client initialization', async () => {
vi.mocked(McpClient).mockImplementation(() => {
throw new Error('Client initialization failed');
});
mockConfig.getMcpServers.mockReturnValue({
'test-server': {},
});
const manager = new McpClientManager({} as ToolRegistry, mockConfig);
await expect(manager.startConfiguredMcpServers()).resolves.not.toThrow();
});
it('should handle errors thrown in the async IIFE before try block', async () => {
let disconnectCallCount = 0;
mockedMcpClient.disconnect.mockImplementation(async () => {
disconnectCallCount++;
if (disconnectCallCount === 1) {
throw new Error('Disconnect failed unexpectedly');
}
});
mockedMcpClient.getServerConfig.mockReturnValue({});
mockConfig.getMcpServers.mockReturnValue({
'test-server': {},
});
const manager = new McpClientManager({} as ToolRegistry, mockConfig);
await manager.startConfiguredMcpServers();
await expect(manager.restartServer('test-server')).resolves.not.toThrow();
});
});
});

View File

@@ -163,8 +163,7 @@ export class McpClientManager {
return;
}
const currentDiscoveryPromise = new Promise<void>((resolve, _reject) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
const currentDiscoveryPromise = new Promise<void>((resolve, reject) => {
(async () => {
try {
if (existing) {
@@ -212,6 +211,13 @@ export class McpClientManager {
);
}
}
} catch (error) {
const errorMessage = getErrorMessage(error);
coreEvents.emitFeedback(
'error',
`Error initializing MCP server '${name}': ${errorMessage}`,
error,
);
} finally {
// This is required to update the content generator configuration with the
// new tool configuration.
@@ -221,28 +227,30 @@ export class McpClientManager {
}
resolve();
}
})();
})().catch(reject);
});
if (this.discoveryPromise) {
this.discoveryPromise = this.discoveryPromise.then(
() => currentDiscoveryPromise,
);
// Ensure the next discovery starts regardless of the previous one's success/failure
this.discoveryPromise = this.discoveryPromise
.catch(() => {})
.then(() => currentDiscoveryPromise);
} else {
this.discoveryState = MCPDiscoveryState.IN_PROGRESS;
this.discoveryPromise = currentDiscoveryPromise;
}
this.eventEmitter?.emit('mcp-client-update', this.clients);
const currentPromise = this.discoveryPromise;
// eslint-disable-next-line @typescript-eslint/no-floating-promises
currentPromise.then((_) => {
// If we are the last recorded discoveryPromise, then we are done, reset
// the world.
if (currentPromise === this.discoveryPromise) {
this.discoveryPromise = undefined;
this.discoveryState = MCPDiscoveryState.COMPLETED;
}
});
void currentPromise
.finally(() => {
// If we are the last recorded discoveryPromise, then we are done, reset
// the world.
if (currentPromise === this.discoveryPromise) {
this.discoveryPromise = undefined;
this.discoveryState = MCPDiscoveryState.COMPLETED;
}
})
.catch(() => {}); // Prevents unhandled rejection from the .finally branch
return currentPromise;
}