{"record":{"id":"134b1c7e115646fa","repo":"zed-industries/zed","slug":"mcp-tool-cancelled-by-user","errorCode":null,"errorMessage":"MCP tool cancelled by user","messagePattern":"MCP tool cancelled by user","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/agent/src/tools/context_server_registry.rs","lineNumber":391,"sourceCode":"\n            log::trace!(\n                \"Running tool: {} with arguments: {:?}\",\n                tool_name,\n                arguments\n            );\n\n            let request = protocol.request::<context_server::types::requests::CallTool>(\n                context_server::types::CallToolParams {\n                    name: tool_name,\n                    arguments,\n                    meta: None,\n                },\n            );\n\n            let response = futures::select! {\n                response = request.fuse() => response?,\n                _ = event_stream.cancelled_by_user().fuse() => {\n                    return Err(anyhow::anyhow!(\"MCP tool cancelled by user\").into());\n                }\n            };\n\n            if response.is_error == Some(true) {\n                let error_message: String =\n                    response.content.iter().filter_map(|c| c.text()).collect();\n                return Err(anyhow::anyhow!(error_message).into());\n            }\n\n            let mut llm_output = Vec::new();\n            let mut tool_call_content = Vec::new();\n            let mut concatenated_text = String::new();\n            for content in response.content {\n                match content {\n                    context_server::types::ToolResponseContent::Text { text } => {\n                        concatenated_text.push_str(&text);\n                        tool_call_content.push(acp::ToolCallContent::Content(acp::Content::new(\n                            acp::ContentBlock::Text(acp::TextContent::new(text.clone())),","sourceCodeStart":373,"sourceCodeEnd":409,"githubUrl":"https://github.com/zed-industries/zed/blob/bc538def4545534201bbfcac4e95ac34ea6501b6/crates/agent/src/tools/context_server_registry.rs#L373-L409","documentation":"The user cancelled the agent run while an MCP tool was executing: in the `futures::select!`, `event_stream.cancelled_by_user()` won the race against the in-flight JSON-RPC `CallTool` request. The call is deliberately aborted with this message; the underlying request future is dropped without a response.","triggerScenarios":"Pressing stop/cancel during a long-running MCP tool (builds, test suites, web fetches served by MCP) or closing the agent panel while the request is in flight.","commonSituations":"Slow MCP servers whose tools run for minutes; accidental stops; tools invoked twice where the user cancels one.","solutions":["Treat it as a normal cancellation: stop the run cleanly and do not retry automatically.","Make long-running MCP tools report progress so users wait instead of cancelling.","Design server-side tools idempotent so an aborted call can be safely re-run later."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let response = futures::select! {\n    response = request.fuse() => response?,\n    _ = event_stream.cancelled_by_user().fuse() => {\n        // User pressed stop: end as a cancellation, not a tool failure.\n        return Err(anyhow::anyhow!(\"tool run cancelled\").into());\n    }\n};","preventionTips":["Treat user cancellation as a normal end state in run loops; do not auto-retry.","Make long-running MCP tools emit progress so users wait rather than cancel.","Design server tools idempotent so re-running after a cancel is safe."],"tags":["mcp","cancellation","user-interaction","async","race-condition"],"backgroundTag":null,"analyzedSha":"bc538def4545534201bbfcac4e95ac34ea6501b6","analyzedAt":"2026-08-16T07:30:46.435Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}