mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 14:04:41 -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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDiscoveryPromise = new Promise<void>((resolve, _reject) => {
|
const currentDiscoveryPromise = new Promise<void>((resolve, reject) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
if (existing) {
|
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 {
|
} finally {
|
||||||
// This is required to update the content generator configuration with the
|
// This is required to update the content generator configuration with the
|
||||||
// new tool configuration.
|
// new tool configuration.
|
||||||
@@ -221,28 +227,30 @@ export class McpClientManager {
|
|||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
})();
|
})().catch(reject);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.discoveryPromise) {
|
if (this.discoveryPromise) {
|
||||||
this.discoveryPromise = this.discoveryPromise.then(
|
// Ensure the next discovery starts regardless of the previous one's success/failure
|
||||||
() => currentDiscoveryPromise,
|
this.discoveryPromise = this.discoveryPromise
|
||||||
);
|
.catch(() => {})
|
||||||
|
.then(() => currentDiscoveryPromise);
|
||||||
} else {
|
} else {
|
||||||
this.discoveryState = MCPDiscoveryState.IN_PROGRESS;
|
this.discoveryState = MCPDiscoveryState.IN_PROGRESS;
|
||||||
this.discoveryPromise = currentDiscoveryPromise;
|
this.discoveryPromise = currentDiscoveryPromise;
|
||||||
}
|
}
|
||||||
this.eventEmitter?.emit('mcp-client-update', this.clients);
|
this.eventEmitter?.emit('mcp-client-update', this.clients);
|
||||||
const currentPromise = this.discoveryPromise;
|
const currentPromise = this.discoveryPromise;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
void currentPromise
|
||||||
currentPromise.then((_) => {
|
.finally(() => {
|
||||||
// If we are the last recorded discoveryPromise, then we are done, reset
|
// If we are the last recorded discoveryPromise, then we are done, reset
|
||||||
// the world.
|
// the world.
|
||||||
if (currentPromise === this.discoveryPromise) {
|
if (currentPromise === this.discoveryPromise) {
|
||||||
this.discoveryPromise = undefined;
|
this.discoveryPromise = undefined;
|
||||||
this.discoveryState = MCPDiscoveryState.COMPLETED;
|
this.discoveryState = MCPDiscoveryState.COMPLETED;
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.catch(() => {}); // Prevents unhandled rejection from the .finally branch
|
||||||
return currentPromise;
|
return currentPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user