Skip to content

mcp client times out after 60 seconds (ignoring timeout option) #1

@andrea-tomassi

Description

@andrea-tomassi

Describe the bug
The JS/TS client sdk has 60 seconds timeout that is not affected by server sending progress update.
On the same mcp server tool, the python client sdk works as expected but the js/ts sdk times out with:

file:///.../@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:282
const timeoutHandler = () => cancel(new McpError(ErrorCode.RequestTimeout, "Request timed out", { timeout }));
^

McpError: MCP error -32001: Request timed out
at Timeout.timeoutHandler (file:///.../@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:282:49)
at listOnTimeout (node:internal/timers:611:17)
at process.processTimers (node:internal/timers:546:7) {
code: -32001,
data: { timeout: 60000 }
}

Node.js v23.10.0
I have created an mcp server that is doing some "heavy" task that takes more than 60 seconds, though this server sends progress updates every 5 seconds to keep the connection alive.

You can run the server like this:
uvx --quiet --refresh git+https://github.com/emsi/slow-mcp --transport sse
(requires uvx! https://docs.astral.sh/uv/getting-started/installation/)

I've created two clients. Python and Typescript:

https://github.com/emsi/slow-mcp/blob/master/src/client.py
https://github.com/emsi/slow-mcp/blob/master/src/client.mjs

When I run the tests with python client it works as expected:

$ python3 ./src/client.py
[03/31/25 22:05:22] INFO Processing request of type ListResourcesRequest server.py:534
INFO Processing request of type ListToolsRequest server.py:534
Tools: meta=None nextCursor=None tools=[Tool(name='run_command', description='Run command and report progress.', inputSchema={'properties': {'timeout': {'default': 300, 'title': 'Timeout', 'type': 'integer'}}, 'title': 'run_commandArguments', 'type': 'object'})]
INFO Processing request of type CallToolRequest server.py:534
Result: meta=None content=[TextContent(type='text', text='1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22\n23\n24\n25\n26\n27\n28\n29\n30\n31\n32\n33\n34\n35\n36\n37\n38\n39\n40\n41\n42\n43\n44\n45\n46\n47\n48\n49\n50\n51\n52\n53\n54\n55\n56\n57\n58\n59\n60\n61\n62\n63\n64\n65\n66\n67\n68\n69\n70\n71\n72\n73\n74\n75\n76\n77\n78\n79\n80\n81\n82\n83\n84\n85\n86\n87\n88\n89\n90\n91\n92\n93\n94\n95\n96\n97\n98\n99\n100\n101\n102\n103\n104\n105\n106\n107\n108\n109\n110\n111\n112\n113\n114\n115\n116\n117\n118\n119\n120\n121\n122\n123\n124\n125\n', annotations=None)] isError=False
The server just calls the following script that prints number of seconds lapsed:

#!/bin/sh

for sec in $(seq 1 125); do
sleep 1
echo "$sec"
done
However the Typescript/JS client times out after 60000 milliseconds!

To Reproduce

Make sure to install uv: curl -LsSf https://astral.sh/uv/install.sh | sh (my slow_mcp server requires it)
Download example client: https://github.com/emsi/slow-mcp/blob/master/src/client.mjs
Install sdk npm install @modelcontextprotocol/sdk :)
Run the client: node src/client.mjs
Expected behavior
The timeout should be longer. Some tasks might easily take more than few minutes and if the server is sending updates or pings the timeout should reset.
At the very least both python and js/ts sdk should have identical, longer, timeouts.

I'd like to share my findings which may be applicable to those experiencing the 'McpError: MCP error -32001: Request timed out errors.

Symptoms
I found that this happened consistently when I had 2 clients connected concurrently (primarily Claude and Postman), and 1 client would always timeout on tool calls.

Debugging
To debug the issue, I used Cursor to help me implement 2 clients, and run them concurrently. This successfully reproduced the issue and identified the root cause.

Root Cause
The issue was my server application was only creating 1 instance of McpServer. Even though each client connection created a new transport instance, the McpServer instance itself couldn't handle it, and so whichever client connected last stole the McpServer instance.

Solution:
Instantiate 1 McpServer instance per client session/transport.

Environment
Macbook Pro M1, 32GB RAM
Node version: v22.15.0
Server Dependencies:

"@modelcontextprotocol/sdk": "^1.13.0",
"express": "^4.18.2"

Working Sample Server Code

This is the simple working server with a setTimeout for making the tool call last a little longer to help debug the concurrency issue experienced.

Note: A new McpServer is created per client connection.

import express from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { z } from 'zod';

// Enable debug logging if DEBUG environment variable is set
const debug = process.env.DEBUG ? console.log : () => {};

/**

  • Registers all the tools for a given McpServer instance.
  • This ensures that each client session gets a server with the same capabilities.
  • @param server The McpServer instance to configure.
    */
    function registerServerFeatures(server: McpServer) {
    // Register a simple tool
    server.tool(
    "hello_world",
    "A simple tool that returns a greeting",
    {
    name: z.string().describe("The name to greet")
    },
    async (args) => {
    debug(hello_world tool called with args:, args);
    // Simulate some work to make concurrency more apparent
    await new Promise(resolve => setTimeout(resolve, 200));
    return {
    content: [{
    type: "text",
    text: Hello, ${args.name}! Welcome to the MCP server.
    }]
    };
    }
    );

// Register another tool for server info
server.tool(
"get_server_info",
"Get information about the server",
{},
async () => {
debug(get_server_info tool called);
return {
content: [{
type: "text",
text: JSON.stringify({
name: "Simple SSE MCP Server",
version: "1.0.0",
features: ["tools"],
timestamp: new Date().toISOString()
}, null, 2)
}]
};
}
);
debug('Tools registered for new server session');
}

// Set up Express app
const app = express();
app.use(express.json());

// Store transports for each session
const transports: Record<string, SSEServerTransport> = {};

// SSE endpoint for client connections
app.get('/sse', async (req, res) => {
console.log('New SSE connection established');
debug('SSE connection request received');

// Create a new McpServer instance FOR EACH CLIENT CONNECTION.
// This is critical for handling concurrent clients correctly.
const server = new McpServer({
name: "simple-sse-mcp-server",
version: "1.0.0"
});

// Register all available tools and features for this new server instance.
registerServerFeatures(server);

// Create SSE transport
const transport = new SSEServerTransport('/messages', res);
transports[transport.sessionId] = transport;

debug(SSE transport created with sessionId: ${transport.sessionId});

// Clean up when connection closes
res.on("close", () => {
console.log(SSE connection closed for session: ${transport.sessionId});
debug(SSE connection closed, removing transport for session: ${transport.sessionId});
delete transports[transport.sessionId];
});

// Connect the transport to this session's dedicated server instance
debug(Connecting transport to session's MCP server: ${transport.sessionId});
await server.connect(transport);
debug(Transport connected successfully to session server: ${transport.sessionId});
});

// Message endpoint for handling requests
app.post('/messages', async (req, res) => {
const sessionId = req.query.sessionId as string;
debug(Message received for sessionId: ${sessionId});
debug('Message body:', req.body);

const transport = transports[sessionId];

if (transport) {
debug(Transport found, handling message for session: ${sessionId});
await transport.handlePostMessage(req, res, req.body);
} else {
debug('No transport found for sessionId:', sessionId);
res.status(400).json({ error: 'No transport found for sessionId' });
}
});

// Health check endpoint
app.get('/health', (req, res) => {
debug('Health check requested');
res.json({
status: 'ok',
activeSessions: Object.keys(transports).length,
server: {
name: "simple-sse-mcp-server",
version: "1.0.0"
}
});
});

// Start the server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(🚀 Simple SSE MCP Server running on port ${PORT});
console.log(📡 SSE endpoint: http://localhost:${PORT}/sse);
console.log(📨 Messages endpoint: http://localhost:${PORT}/messages);
console.log(❤️ Health check: http://localhost:${PORT}/health);
console.log(🛠️ Available tools: hello_world, get_server_info);
debug('Server started successfully');
});

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions