MP-301g · Module 3
Timeout Tuning
3 min read
MCP servers must manage three distinct timeouts: connection timeout (how long to wait for the initial TCP/pipe handshake), request timeout (how long to wait for a response to a JSON-RPC request), and idle timeout (how long to keep a session alive with no activity). Each timeout serves a different purpose and must be tuned independently. Setting all three to the same value — a common shortcut — guarantees at least one is wrong. Connection timeouts should be short (5-10 seconds) because a server that takes longer to accept a connection is probably down. Request timeouts depend on the tool — a filesystem read might need 5 seconds, but an LLM inference call might need 120.
Per-tool timeouts are the expert pattern. Instead of a global request timeout, assign timeouts based on the tool being invoked. Fast tools (list_files, get_status) get 5-second timeouts. Medium tools (search_codebase, query_database) get 30 seconds. Slow tools (run_build, generate_report) get 5 minutes. This prevents fast tools from being masked by a generous global timeout while giving slow tools the time they need. Implement this as a timeout map in the client configuration, with a sensible default for unlisted tools.
Timeout errors must be distinguishable from other failures. When a request times out, the client cancels the pending request but the server may still be processing it. If the client retries immediately, the server now has two concurrent executions of the same tool call — which may cause side effects (duplicate writes, double charges). Use the JSON-RPC cancellation notification to tell the server to abort, and implement idempotency keys on tool calls that have side effects so that retries are safe.
// Per-tool timeout configuration
const TOOL_TIMEOUTS: Record<string, number> = {
// Fast operations (5s)
"list_files": 5_000,
"get_status": 5_000,
"read_file": 5_000,
// Medium operations (30s)
"search_codebase": 30_000,
"query_database": 30_000,
// Slow operations (5min)
"run_build": 300_000,
"generate_report": 300_000,
"run_tests": 300_000,
};
const DEFAULT_TIMEOUT = 30_000;
function getToolTimeout(toolName: string): number {
return TOOL_TIMEOUTS[toolName] ?? DEFAULT_TIMEOUT;
}
// Connection-level timeouts
const CONNECTION_TIMEOUTS = {
connect: 10_000, // TCP/pipe handshake
idle: 300_000, // 5 min idle before session cleanup
keepalive: 30_000, // heartbeat interval
};