diff --git a/packages/core/src/tools/mcp-client.test.ts b/packages/core/src/tools/mcp-client.test.ts index 50b17aa735..2ddd8c4825 100644 --- a/packages/core/src/tools/mcp-client.test.ts +++ b/packages/core/src/tools/mcp-client.test.ts @@ -1993,6 +1993,27 @@ describe('mcp-client', () => { }); }); + it('should unconditionally attach a data listener to stderr to prevent process hang', async () => { + const mockStderr = { + on: vi.fn(), + }; + + vi.spyOn(SdkClientStdioLib, 'StdioClientTransport').mockReturnValue({ + stderr: mockStderr, + } as unknown as SdkClientStdioLib.StdioClientTransport); + + await createTransport( + 'test-server', + { + command: 'test-command', + }, + false, // debugMode = false + MOCK_CONTEXT, + ); + + expect(mockStderr.on).toHaveBeenCalledWith('data', expect.any(Function)); + }); + it('sets an env variable GEMINI_CLI=1 for stdio MCP servers', async () => { const mockedTransport = vi .spyOn(SdkClientStdioLib, 'StdioClientTransport') diff --git a/packages/core/src/tools/mcp-client.ts b/packages/core/src/tools/mcp-client.ts index 0441063f81..791b5cf45c 100644 --- a/packages/core/src/tools/mcp-client.ts +++ b/packages/core/src/tools/mcp-client.ts @@ -2291,31 +2291,40 @@ export async function createTransport( transport = new XcodeMcpBridgeFixTransport(transport); } - if (debugMode) { - // The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`, - // which exposes `stderr` for debug logging. We need to unwrap it to attach the listener. + // The `XcodeMcpBridgeFixTransport` wrapper hides the underlying `StdioClientTransport`, + // which exposes `stderr` for debug logging. We need to unwrap it to attach the listener. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const underlyingTransport = - transport instanceof XcodeMcpBridgeFixTransport - ? // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-type-assertion - (transport as any).transport - : transport; + const underlyingTransport = + transport instanceof XcodeMcpBridgeFixTransport + ? // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + (transport as unknown as { transport: unknown }).transport + : transport; - if ( - underlyingTransport instanceof StdioClientTransport && - underlyingTransport.stderr - ) { - underlyingTransport.stderr.on('data', (data) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const stderrStr = data.toString().trim(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion + const streamTransport = underlyingTransport as { + stderr?: { + on: (event: string, listener: (data: unknown) => void) => void; + }; + }; + + if ( + streamTransport && + typeof streamTransport === 'object' && + 'stderr' in streamTransport && + streamTransport.stderr && + typeof streamTransport.stderr.on === 'function' + ) { + streamTransport.stderr.on('data', (data: unknown) => { + if (debugMode) { + const stderrStr = String(data).trim(); debugLogger.debug( `[DEBUG] [MCP STDERR (${mcpServerName})]: `, stderrStr, ); - }); - } + } + }); } + return transport; }