mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-11 06:31:01 -07:00
fix(core): handle unhandled promise rejection in mcp-client-manager (#14701)
This commit is contained in:
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user