Skip to content

Commit 9cdf7bd

Browse files
authored
fix(core): show clear error when MCP server cwd does not exist (#3192)
* fix(core): show clear error when MCP server cwd does not exist Validate that the configured cwd directory exists before spawning the MCP server process. Previously, a non-existent cwd caused Node.js to emit "spawn <cmd> ENOENT" — indistinguishable from the command binary being missing. Now throws a descriptive error naming the server and the missing path. Fixes #3163 * test(core): add test for MCP stdio transport without cwd
1 parent 9a889dc commit 9cdf7bd

File tree

2 files changed

+52
-0
lines changed

2 files changed

+52
-0
lines changed

packages/core/src/tools/mcp-client.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import {
2323
} from './mcp-client.js';
2424
import type { ToolRegistry } from './tool-registry.js';
2525

26+
const mockExistsSync = vi.hoisted(() => vi.fn(() => true));
27+
28+
vi.mock('node:fs', () => ({
29+
existsSync: mockExistsSync,
30+
}));
2631
vi.mock('@modelcontextprotocol/sdk/client/stdio.js');
2732
vi.mock('@modelcontextprotocol/sdk/client/index.js');
2833
vi.mock('@google/genai');
@@ -289,6 +294,46 @@ describe('mcp-client', () => {
289294
});
290295
});
291296

297+
it('should connect via command without cwd', async () => {
298+
const mockedTransport = vi
299+
.spyOn(SdkClientStdioLib, 'StdioClientTransport')
300+
.mockReturnValue({} as SdkClientStdioLib.StdioClientTransport);
301+
302+
await createTransport(
303+
'test-server',
304+
{
305+
command: 'test-command',
306+
args: ['--foo', 'bar'],
307+
},
308+
false,
309+
);
310+
311+
expect(mockedTransport).toHaveBeenCalledWith({
312+
command: 'test-command',
313+
args: ['--foo', 'bar'],
314+
cwd: undefined,
315+
env: expect.any(Object),
316+
stderr: 'pipe',
317+
});
318+
});
319+
320+
it('should throw if cwd does not exist', async () => {
321+
mockExistsSync.mockReturnValueOnce(false);
322+
323+
await expect(
324+
createTransport(
325+
'test-server',
326+
{
327+
command: 'test-command',
328+
cwd: '/nonexistent/path',
329+
},
330+
false,
331+
),
332+
).rejects.toThrow(
333+
"MCP server 'test-server': configured cwd does not exist: /nonexistent/path",
334+
);
335+
});
336+
292337
describe('useGoogleCredentialProvider', () => {
293338
it('should use GoogleCredentialProvider when specified', async () => {
294339
const transport = await createTransport(

packages/core/src/tools/mcp-client.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { SdkControlClientTransport } from './sdk-control-client-transport.js';
3434

3535
import type { FunctionDeclaration } from '@google/genai';
3636
import { mcpToTool } from '@google/genai';
37+
import { existsSync } from 'node:fs';
3738
import { basename } from 'node:path';
3839
import { pathToFileURL } from 'node:url';
3940
import { MCPOAuthProvider } from '../mcp/oauth-provider.js';
@@ -1402,6 +1403,12 @@ export async function createTransport(
14021403
}
14031404

14041405
if (mcpServerConfig.command) {
1406+
if (mcpServerConfig.cwd && !existsSync(mcpServerConfig.cwd)) {
1407+
throw new Error(
1408+
`MCP server '${mcpServerName}': configured cwd does not exist: ${mcpServerConfig.cwd}`,
1409+
);
1410+
}
1411+
14051412
const transport = new StdioClientTransport({
14061413
command: mcpServerConfig.command,
14071414
args: mcpServerConfig.args || [],

0 commit comments

Comments
 (0)