Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions src/client/cross-spawn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,165 @@ describe('StdioClientTransport using cross-spawn', () => {
// verify message is sent correctly
expect(mockProcess.stdin.write).toHaveBeenCalled();
});

describe('windowsHide parameter', () => {
const originalPlatform = process.platform;

afterEach(() => {
// restore original platform
Object.defineProperty(process, 'platform', {
value: originalPlatform,
writable: true
});
});

test('should use explicit windowsHide=true when provided', async () => {
const transport = new StdioClientTransport({
command: 'test-command',
windowsHide: true
});

await transport.start();

// verify windowsHide is set to true
expect(mockSpawn).toHaveBeenCalledWith(
'test-command',
[],
expect.objectContaining({
windowsHide: true
})
);
});

test('should use explicit windowsHide=false when provided', async () => {
const transport = new StdioClientTransport({
command: 'test-command',
windowsHide: false
});

await transport.start();

// verify windowsHide is set to false
expect(mockSpawn).toHaveBeenCalledWith(
'test-command',
[],
expect.objectContaining({
windowsHide: false
})
);
});

test('should default to true on Windows in Electron environment when windowsHide not specified', async () => {
// mock windows platform and electron environment
Object.defineProperty(process, 'platform', {
value: 'win32',
writable: true
});
Object.defineProperty(process, 'type', {
value: 'renderer',
configurable: true
});

const transport = new StdioClientTransport({
command: 'test-command'
});

await transport.start();

// verify windowsHide defaults to true
expect(mockSpawn).toHaveBeenCalledWith(
'test-command',
[],
expect.objectContaining({
windowsHide: true
})
);

// cleanup
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (process as any).type;
});

test('should default to false on Windows in non-Electron environment when windowsHide not specified', async () => {
// mock windows platform without electron
Object.defineProperty(process, 'platform', {
value: 'win32',
writable: true
});

// remove this property used to detect isElectron
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (process as any).type;

const transport = new StdioClientTransport({
command: 'test-command'
});

await transport.start();

// verify windowsHide defaults to false
expect(mockSpawn).toHaveBeenCalledWith(
'test-command',
[],
expect.objectContaining({
windowsHide: false
})
);
});

test('should default to false on non-Windows platforms when windowsHide not specified', async () => {
// mock non-windows platform
Object.defineProperty(process, 'platform', {
value: 'darwin',
writable: true
});

const transport = new StdioClientTransport({
command: 'test-command'
});

await transport.start();

// verify windowsHide defaults to false
expect(mockSpawn).toHaveBeenCalledWith(
'test-command',
[],
expect.objectContaining({
windowsHide: false
})
);
});

test('should override default behavior when windowsHide is explicitly set on Windows', async () => {
// mock windows platform and electron environment
Object.defineProperty(process, 'platform', {
value: 'win32',
writable: true
});
Object.defineProperty(process, 'type', {
value: 'renderer',
configurable: true
});

const transport = new StdioClientTransport({
command: 'test-command',
windowsHide: false
});

await transport.start();

// verify explicit false overrides default true
expect(mockSpawn).toHaveBeenCalledWith(
'test-command',
[],
expect.objectContaining({
windowsHide: false
})
);

// remove the mock electron property.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
delete (process as any).type;
});
});
});
11 changes: 9 additions & 2 deletions src/client/stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export type StdioServerParameters = {
* If not specified, the current working directory will be inherited.
*/
cwd?: string;

/**
* Whether to hide Windows Terminal when spawning the process.
*
* If not specified, the result of isElectron() will be used.
*/
windowsHide?: boolean;
};

/**
Expand Down Expand Up @@ -127,7 +134,7 @@ export class StdioClientTransport implements Transport {
stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'],
shell: false,
signal: this._abortController.signal,
windowsHide: process.platform === 'win32' && isElectron(),
windowsHide: this._serverParams.windowsHide ?? isElectron(),
Copy link
Author

@erran erran Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly I was debating whether hard coding true here or removing the electron check was sufficient due to how it’ll behave on non-Windows systems but I didn’t get around to testing different platforms since I’m usually developing on Mac or Linux so validating the changes in windows took most of my day.

I decided against true or win32 platform check as the following code could break someone else’s assumptions in a way I’m not thinking of right now (although in my experience the terminal window for each stdio MCP server is always blank).

Suggested change
windowsHide: this._serverParams.windowsHide ?? isElectron(),
windowsHide: process.platform === 'win32',

cwd: this._serverParams.cwd
});

Expand Down Expand Up @@ -232,5 +239,5 @@ export class StdioClientTransport implements Transport {
}

function isElectron() {
return 'type' in process;
return process.platform === 'win32' && 'type' in process;
}
Loading