Skip to content

Commit fc1b5c5

Browse files
committed
fix(client): Accept windowsHide in STDIO transport
Previously windowsHide was only available when running inside of an Electron process by default. If this library is run through a single application executable the NodeJS runtime will cause windowsHide to be set to false thus spawning terminals for each spawned STDIO MCP server.
1 parent 324d471 commit fc1b5c5

File tree

2 files changed

+170
-2
lines changed

2 files changed

+170
-2
lines changed

src/client/cross-spawn.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,165 @@ describe('StdioClientTransport using cross-spawn', () => {
149149
// verify message is sent correctly
150150
expect(mockProcess.stdin.write).toHaveBeenCalled();
151151
});
152+
153+
describe('windowsHide parameter', () => {
154+
const originalPlatform = process.platform;
155+
156+
afterEach(() => {
157+
// restore original platform
158+
Object.defineProperty(process, 'platform', {
159+
value: originalPlatform,
160+
writable: true
161+
});
162+
});
163+
164+
test('should use explicit windowsHide=true when provided', async () => {
165+
const transport = new StdioClientTransport({
166+
command: 'test-command',
167+
windowsHide: true
168+
});
169+
170+
await transport.start();
171+
172+
// verify windowsHide is set to true
173+
expect(mockSpawn).toHaveBeenCalledWith(
174+
'test-command',
175+
[],
176+
expect.objectContaining({
177+
windowsHide: true
178+
})
179+
);
180+
});
181+
182+
test('should use explicit windowsHide=false when provided', async () => {
183+
const transport = new StdioClientTransport({
184+
command: 'test-command',
185+
windowsHide: false
186+
});
187+
188+
await transport.start();
189+
190+
// verify windowsHide is set to false
191+
expect(mockSpawn).toHaveBeenCalledWith(
192+
'test-command',
193+
[],
194+
expect.objectContaining({
195+
windowsHide: false
196+
})
197+
);
198+
});
199+
200+
test('should default to true on Windows in Electron environment when windowsHide not specified', async () => {
201+
// mock windows platform and electron environment
202+
Object.defineProperty(process, 'platform', {
203+
value: 'win32',
204+
writable: true
205+
});
206+
Object.defineProperty(process, 'type', {
207+
value: 'renderer',
208+
configurable: true
209+
});
210+
211+
const transport = new StdioClientTransport({
212+
command: 'test-command'
213+
});
214+
215+
await transport.start();
216+
217+
// verify windowsHide defaults to true
218+
expect(mockSpawn).toHaveBeenCalledWith(
219+
'test-command',
220+
[],
221+
expect.objectContaining({
222+
windowsHide: true
223+
})
224+
);
225+
226+
// cleanup
227+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
228+
delete (process as any).type;
229+
});
230+
231+
test('should default to false on Windows in non-Electron environment when windowsHide not specified', async () => {
232+
// mock windows platform without electron
233+
Object.defineProperty(process, 'platform', {
234+
value: 'win32',
235+
writable: true
236+
});
237+
238+
// remove this property used to detect isElectron
239+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
240+
delete (process as any).type;
241+
242+
const transport = new StdioClientTransport({
243+
command: 'test-command'
244+
});
245+
246+
await transport.start();
247+
248+
// verify windowsHide defaults to false
249+
expect(mockSpawn).toHaveBeenCalledWith(
250+
'test-command',
251+
[],
252+
expect.objectContaining({
253+
windowsHide: false
254+
})
255+
);
256+
});
257+
258+
test('should default to false on non-Windows platforms when windowsHide not specified', async () => {
259+
// mock non-windows platform
260+
Object.defineProperty(process, 'platform', {
261+
value: 'darwin',
262+
writable: true
263+
});
264+
265+
const transport = new StdioClientTransport({
266+
command: 'test-command'
267+
});
268+
269+
await transport.start();
270+
271+
// verify windowsHide defaults to false
272+
expect(mockSpawn).toHaveBeenCalledWith(
273+
'test-command',
274+
[],
275+
expect.objectContaining({
276+
windowsHide: false
277+
})
278+
);
279+
});
280+
281+
test('should override default behavior when windowsHide is explicitly set on Windows', async () => {
282+
// mock windows platform and electron environment
283+
Object.defineProperty(process, 'platform', {
284+
value: 'win32',
285+
writable: true
286+
});
287+
Object.defineProperty(process, 'type', {
288+
value: 'renderer',
289+
configurable: true
290+
});
291+
292+
const transport = new StdioClientTransport({
293+
command: 'test-command',
294+
windowsHide: false
295+
});
296+
297+
await transport.start();
298+
299+
// verify explicit false overrides default true
300+
expect(mockSpawn).toHaveBeenCalledWith(
301+
'test-command',
302+
[],
303+
expect.objectContaining({
304+
windowsHide: false
305+
})
306+
);
307+
308+
// remove the mock electron property.
309+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
310+
delete (process as any).type;
311+
});
312+
});
152313
});

src/client/stdio.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ export type StdioServerParameters = {
3737
* If not specified, the current working directory will be inherited.
3838
*/
3939
cwd?: string;
40+
41+
/**
42+
* Whether to hide Windows Terminal when spawning the process.
43+
*
44+
* If not specified, the result of isElectron() will be used.
45+
*/
46+
windowsHide?: boolean;
4047
};
4148

4249
/**
@@ -127,7 +134,7 @@ export class StdioClientTransport implements Transport {
127134
stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'],
128135
shell: false,
129136
signal: this._abortController.signal,
130-
windowsHide: process.platform === 'win32' && isElectron(),
137+
windowsHide: this._serverParams.windowsHide ?? isElectron(),
131138
cwd: this._serverParams.cwd
132139
});
133140

@@ -232,5 +239,5 @@ export class StdioClientTransport implements Transport {
232239
}
233240

234241
function isElectron() {
235-
return 'type' in process;
242+
return process.platform === 'win32' && 'type' in process;
236243
}

0 commit comments

Comments
 (0)