fix(core): drain stderr stream unconditionally for StdioClientTransport

This commit is contained in:
Spencer
2026-04-23 22:51:17 +00:00
parent 69150e87b2
commit 85f99d174f
2 changed files with 48 additions and 18 deletions
@@ -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')
+27 -18
View File
@@ -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;
}