MP-301a · Module 3
Timeout Handling
3 min read
Every MCP tool call has an implicit timeout — the client will not wait forever. Claude Code uses a 60-second default; other clients may be shorter. If your tool regularly takes 45 seconds, you are one slow database query away from a timeout that returns nothing to the LLM. The fix is not to increase the timeout — it is to design your tool to return something useful within the budget. Set an internal timeout shorter than the client timeout (e.g., 30 seconds for a 60-second client), and use the remaining time to package a partial result.
Timeout strategies follow a hierarchy. First, prevent timeouts by making tools fast — index your queries, cache frequently accessed data, set aggressive timeouts on external API calls. Second, detect timeouts early by tracking elapsed time inside long-running operations and bailing out when you approach the budget. Third, return partial results when full results are not achievable within the budget — "here are the first 50 results, 200 more are available, call with offset=50 to continue."
// Timeout-aware search that returns partial results
async function searchWithBudget(
query: string,
sources: string[],
budgetMs: number = 25_000,
) {
const deadline = Date.now() + budgetMs;
const results: SearchResult[] = [];
const skipped: string[] = [];
for (const source of sources) {
const remaining = deadline - Date.now();
if (remaining < 2000) {
// Not enough time — record skipped sources
skipped.push(source);
continue;
}
try {
const result = await Promise.race([
searchSource(source, query),
rejectAfter(Math.min(remaining - 1000, 5000)),
]);
results.push(result);
} catch {
skipped.push(source);
}
}
return {
content: [{ type: "text" as const, text: JSON.stringify({
results,
complete: skipped.length === 0,
skippedSources: skipped,
hint: skipped.length > 0
? `${skipped.length} sources skipped due to time budget. Call search_source for individual results.`
: undefined,
}, null, 2) }],
};
}
Do This
- Set internal timeouts shorter than the client timeout to leave room for response packaging
- Return partial results with metadata about what was completed and what was skipped
- Include a hint in partial results telling the LLM how to get the remaining data
- Track elapsed time inside loops and bail out before the budget expires
Avoid This
- Let external API calls run without individual timeouts — one slow call blocks everything
- Return an empty error on timeout without explaining what happened
- Assume the client timeout is infinite — every client has a limit
- Retry the entire operation on timeout — the same slow path will time out again