Your MCP tool takes three minutes. Now what?
I maintain an MCP server that generates music. One call takes anywhere from 40 seconds to three minutes, because there is a model rendering audio on the other end. That does not fit the shape MCP tools are usually written in: call it, get an answer, move on. Everything about the transport assumes the answer is close by. It is worth writing down what actually breaks when it isn't, because "my tool is slow" turns out to be three separate problems wearing one coat. What breaks The obvious first version is a single tool that kicks off the job and awaits the result. server . registerTool ( ' generate_music ' , { /* ... */ }, async ( args ) => { const task = await lacuna . music . generations . create ( args ) const done = await waitUntilReady ( task . id ) // three minutes later return { content : [{ type : ' text ' , text : JSON . stringify ( done ) }] } }) 1. You do not own the timeout. The MCP client does. Claude Desktop, Claude Code, Cursor and the rest each pick their own tool-call deadline, and none of them ask you what yours is. A tool that usually returns in 90 seconds and occasionally takes 200 will work on your machine and fail on someone else's, which is the worst possible failure distribution to debug. 2. If you hand the polling to the model, the model quits. The obvious fix is to return a pending task immediately and expose a get_generation tool so the agent can check on it. The agent will check on it. Twice. Maybe three times. Then it decides the job is wedged and tells the user "this seems to be taking a while, would you like me to keep checking?" — which is a reasonable thing for a helpful assistant to say and a terrible thing for a job that had 40 seconds left. You have converted a slow tool into an unreliable one. 3. Polling burns the context window. Every poll puts a full task object back in the transcript. Twenty polls of a JSON blob is real budget, spent entirely on the word pending . Three tools instead of one What ended up working is splitting the