Hmbown/CodeWhale · warning · anyhow::Error

MCP connection '{}' was cancelled

Error message

MCP connection '{}' was cancelled

What it means

Raised by McpConnection::send when the connection-wide CancellationToken fires while a JSON-RPC request is still being written to the transport. The tokio::select! is biased so cancellation always wins over the pending transport.send, and the connection state is set to Disconnected before the error is returned. It is a cooperative shutdown signal, not a transport failure: some other component (close(), a config reload dropping the connection, a workspace switch, or plugin-authority revocation) decided this connection must stop.

Source

Thrown at crates/tui/src/mcp.rs:2174

    }

    /// Get connection state
    #[allow(dead_code)] // Public API for MCP consumers
    pub fn state(&self) -> ConnectionState {
        self.state
    }

    fn next_id(&self) -> String {
        self.request_id.fetch_add(1, Ordering::SeqCst).to_string()
    }

    async fn send(&mut self, msg: serde_json::Value) -> Result<()> {
        let bytes = serde_json::to_vec(&msg).context("Failed to serialize MCP JSON-RPC message")?;
        tokio::select! {
            biased;
            _ = self.cancel_token.cancelled() => {
                self.state = ConnectionState::Disconnected;
                anyhow::bail!("MCP connection '{}' was cancelled", self.name)
            }
            result = self.transport.send(bytes) => result,
        }
    }

    async fn recv(&mut self, expected_id: String) -> Result<serde_json::Value> {
        loop {
            let bytes = match tokio::time::timeout(
                Duration::from_secs(self.read_timeout_secs),
                async {
                    tokio::select! {
                        biased;
                        _ = self.cancel_token.cancelled() => {
                            anyhow::bail!("MCP connection '{}' was cancelled", self.name)
                        }
                        result = self.transport.recv() => result,
                    }
                },

View on GitHub (pinned to 8880682c63)

Solutions

  1. Treat as a transient, expected shutdown error: drop the stale McpConnection reference and retry the operation through McpPool::get_or_connect, which transparently reconnects.
  2. If it happens mid-turn during normal use, check whether the MCP config file changed on disk (mtime-triggered reload) and let the reconnect path rebuild the connection.
  3. Avoid holding a cached &mut McpConnection across await points that can span a config reload; always re-fetch via the pool.
  4. If you intentionally cancelled (close()), simply propagate or ignore the error — do not retry.

Example fix

// before
let conn = pool.get_or_connect("github").await?;
let tools = conn.list_tools().await?; // fails with 'was cancelled' after reload

// after
let tools = match pool.get_or_connect("github").await?.list_tools().await {
    Ok(tools) => tools,
    Err(e) if e.to_string().contains("was cancelled") => {
        pool.get_or_connect("github").await?.list_tools().await?
    }
    Err(e) => return Err(e),
};
Defensive patterns

Strategy: retry

Try / catch

// Rust: match on the error, distinguish cancellation from real failures
match conn.send_request(...).await {
    Err(e) if e.to_string().contains("was cancelled") => {
        // connection torn down (reload/close/authority): rebuild it
        let conn = pool.get_or_connect(server).await?;
        conn.send_request(...).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Calling any MCP method (tools/list, initialize, call_tool) on a connection whose cancel_token has been cancelled — e.g. after McpConnection::close(), after the pool drops the connection during reload_from_config_sources, or during switch_workspace_config_source_and_connect_all. Any in-flight request that reaches send() after cancellation gets this error.

Common situations: User edits .mcp config while a tool call is in flight (lazy auto-reload drops the connection); a reviewed plugin's catalog changes and the old connection is torn down; a sub-agent finishes and the shared pool is closed; tests cancel a connection to exercise shutdown paths.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/e360511cb918d6d3. Report an issue: GitHub.