Description
Describe the bug
When an McpServer using StdioServerTransport is run as a Node.js subprocess (spawned by an McpClient using StdioClientTransport), the server successfully initializes, registers tools, and the client connects. However, when the client calls a registered tool using client.callTool()
, the server-side tool handler is never invoked (no logs from the handler appear), and the client eventually times out with an McpError -32001: Request timed out.
Interestingly, if the client sends a JSON-RPC request for a non-existent method (e.g., trying various "getCapabilities" methods), the server does respond correctly with a "Method not found" JSON-RPC error. This indicates that basic STDIN/STDOUT communication for request/response is established. The issue seems specific to the tool call dispatching mechanism for callTool requests, as the server does not enter the registered tool handler.
To Reproduce
Steps to reproduce the behavior:
- Create a minimal MCP server (
my-mcp-server.js
) as provided in the "Code Snippets" section below. - Create a minimal MCP client (
client-test.js
) as provided in the "Code Snippets" section below, which spawns the server script as a subprocess. - Place both files in the same directory with a
package.json
(details also in "Code Snippets"). - Run
npm install
to install dependencies. - Run
node client-test.js
. - Observe the logs from both client and server.
Expected behavior
The server-side tool handler (async (params) => { ... }
for "simple-ping" in my-mcp-server.js
) should be invoked, and its console.error
logs (e.g., [Server-SIMPLIFIED-HANDLER] "simple-ping" called...
) should appear. The client should receive a successful JSON-RPC response from the tool, not a timeout.
Logs
The observed log output when running node client-test.js
is as follows:
[Client] Spawning server: node /home/slavek/Projects/context7-mcp-server/my-mcp-server.js
[Client] Connecting to server...
-----------------------------------------------------
[Server-SIMPLIFIED] Initializing simplified server (tool with no args)...
[Server-SIMPLIFIED] McpServer instance created.
[Server-SIMPLIFIED] Tool "simple-ping" registered.
[Server-SIMPLIFIED] StdioServerTransport instance created.
[Server-SIMPLIFIED] Simplified MCP Server connected and ready.
[Client] Client connected.
[Client] Calling tool 'simple-ping' (no arguments)...
// <-- Hangs here for 60 seconds, then client times out -->
[Client] Main error during client execution: McpError: MCP error -32001: Request timed out
at Timeout.timeoutHandler (file:///home/slavek/Projects/context7-mcp-server/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:295:49)
at listOnTimeout (node:internal/timers:588:17)
at process.processTimers (node:internal/timers:523:7)
[Client] Error data: { timeout: 60000 }
[Client] Closing connection...
[Client] Client disconnected.
Code Snippets
my-mcp-server.js (Simplified Server):
// my-mcp-server.js (simplified, tool with no args)
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
async function initializeSimplifiedServer() {
console.error("-----------------------------------------------------");
console.error("[Server-SIMPLIFIED] Initializing simplified server (tool with no args)...");
const server = new McpServer({
name: "SimplifiedNoArgsMcpServer",
version: "1.0.0",
capabilities: { tools: { "simple-ping": { description: "A simple ping tool." } } }
});
console.error("[Server-SIMPLIFIED] McpServer instance created.");
server.tool(
"simple-ping",
z.undefined(),
async (params) => {
// This log is crucial and is never reached
console.error(`[Server-SIMPLIFIED-HANDLER] "simple-ping" called. Params received:`, JSON.stringify(params));
return { content: [{ type: 'text', text: "Pong!" }] };
}
);
console.error('[Server-SIMPLIFIED] Tool "simple-ping" registered.');
const transport = new StdioServerTransport();
console.error("[Server-SIMPLIFIED] StdioServerTransport instance created.");
try {
await server.connect(transport);
console.error("[Server-SIMPLIFIED] Simplified MCP Server connected and ready.");
} catch (error) {
console.error("[Server-SIMPLIFIED-ERROR] Failed to connect server to transport:", error);
process.exit(1);
}
}
initializeSimplifiedServer().catch(err => {
console.error("[Server-SIMPLIFIED-FATAL] Failed to initialize simplified server:", err);
process.exit(1);
});
client-test.js (Simplified Client):
// client-test.js (calling "simple-ping" with no args)
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
async function runClient() {
const serverScriptPath = path.resolve(__dirname, 'my-mcp-server.js');
console.log(`[Client] Spawning server: node ${serverScriptPath}`);
const transport = new StdioClientTransport({ command: "node", args: [serverScriptPath] });
const client = new Client({ name: "my-test-client-noargs", version: "1.0.0" });
try {
console.log("[Client] Connecting to server...");
await client.connect(transport);
console.log("[Client] Client connected.");
console.log("[Client] Calling tool 'simple-ping' (no arguments)...");
const result = await client.callTool("simple-ping");
console.log("[Client] Result from 'simple-ping':", JSON.stringify(result, null, 2));
} catch (error) {
console.error("[Client] Main error during client execution:", error);
if (error.data) console.error("[Client] Error data:", error.data);
} finally {
if (client.isConnected) {
console.log("[Client] Closing connection...");
await client.close();
console.log("[Client] Client disconnected.");
}
}
}
runClient();
package.json (relevant parts):
{
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.9.0",
"zod": "^3.23.8"
}
}
Note: The issue was also reproduced with @modelcontextprotocol/sdk@^1.12.1
yielding the same timeout result.
Environment:
- @modelcontextprotocol/sdk version(s): 1.12.1, 1.9.0 (behavior is identical for both)
- Node.js version: v22.15.0
- OS: Linux Ubuntu 24.04.2 LTS (Noble Numbat)
- NPM version: 10.9.2
Additional context
This issue appears highly related to, or an instance of, what might be described in Issue #509 ("Tool Handlers Not Invoked with StdioServerTransport on macOS"), although this is observed on Linux. The core problem is that the registered tool handler on the server is not being called when a callTool request is made by the client via StdioClientTransport managing a subprocess. The server does respond to requests for non-existent JSON-RPC methods (e.g., various "getCapabilities" attempts) with a "Method not found" error, suggesting basic STDIN/STDOUT communication for request/response is established at some level. However, the specific dispatching to the tool handler for callTool seems to fail silently from the server's perspective, leading to a client timeout.