fix: suppress MCP server stderr from corrupting alternate buffer UI

Pipe stderr from npx chrome-devtools-mcp instead of inheriting it.
The server's banner warnings were leaking into the terminal and
corrupting the Ink-based UI in alternate buffer mode. Piped output
is forwarded to debugLogger so it remains visible with --debug.
This commit is contained in:
Gaurav Ghosh
2026-02-23 23:35:57 -08:00
parent c991e5b3dc
commit 7d32f2bf88
2 changed files with 37 additions and 17 deletions
@@ -31,6 +31,7 @@ vi.mock('@modelcontextprotocol/sdk/client/index.js', () => ({
vi.mock('@modelcontextprotocol/sdk/client/stdio.js', () => ({
StdioClientTransport: vi.fn().mockImplementation(() => ({
close: vi.fn().mockResolvedValue(undefined),
stderr: null,
})),
}));
@@ -144,14 +145,16 @@ describe('BrowserManager', () => {
await manager.ensureConnection();
// Verify StdioClientTransport was created with correct args
expect(StdioClientTransport).toHaveBeenCalledWith({
command: 'npx',
args: expect.arrayContaining([
'-y',
expect.stringMatching(/chrome-devtools-mcp@/),
'--experimental-vision',
]),
});
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npx',
args: expect.arrayContaining([
'-y',
expect.stringMatching(/chrome-devtools-mcp@/),
'--experimental-vision',
]),
}),
);
// Persistent mode should NOT include --isolated or --autoConnect
const args = vi.mocked(StdioClientTransport).mock.calls[0]?.[0]
?.args as string[];
@@ -180,10 +183,12 @@ describe('BrowserManager', () => {
const manager = new BrowserManager(headlessConfig);
await manager.ensureConnection();
expect(StdioClientTransport).toHaveBeenCalledWith({
command: 'npx',
args: expect.arrayContaining(['--headless']),
});
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npx',
args: expect.arrayContaining(['--headless']),
}),
);
});
it('should pass profilePath as --userDataDir when configured', async () => {
@@ -203,10 +208,12 @@ describe('BrowserManager', () => {
const manager = new BrowserManager(profileConfig);
await manager.ensureConnection();
expect(StdioClientTransport).toHaveBeenCalledWith({
command: 'npx',
args: expect.arrayContaining(['--userDataDir', '/path/to/profile']),
});
expect(StdioClientTransport).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npx',
args: expect.arrayContaining(['--userDataDir', '/path/to/profile']),
}),
);
});
it('should pass --isolated when sessionMode is isolated', async () => {
@@ -279,12 +279,25 @@ export class BrowserManager {
`Launching chrome-devtools-mcp (${sessionMode} mode) with args: ${mcpArgs.join(' ')}`,
);
// Create stdio transport to npx chrome-devtools-mcp
// Create stdio transport to npx chrome-devtools-mcp.
// stderr is piped (not inherited) to prevent MCP server banners and
// warnings from corrupting the UI in alternate buffer mode.
this.mcpTransport = new StdioClientTransport({
command: 'npx',
args: mcpArgs,
stderr: 'pipe',
});
// Forward piped stderr to debugLogger so it's visible with --debug.
const stderrStream = this.mcpTransport.stderr;
if (stderrStream) {
stderrStream.on('data', (chunk: Buffer) => {
debugLogger.log(
`[chrome-devtools-mcp stderr] ${chunk.toString().trimEnd()}`,
);
});
}
this.mcpTransport.onclose = () => {
debugLogger.error(
'chrome-devtools-mcp transport closed unexpectedly. ' +