Hmbown/CodeWhale · error

MCP session expired

Error message

MCP session expired: {error}

What it means

The MCP server replied with a JSON-RPC error object whose body matches the stale-session detector (`is_mcp_stale_session_body`) — typically an HTTP-streamable server whose session id expired server-side. The client fails the pending request with this error so higher layers can re-handshake and retry with a fresh session.

Solutions

  1. Reconnect/re-handshake the MCP connection to obtain a fresh session id, then retry the request.
  2. Reduce idle time between requests or configure server keepalives.
  3. Check server session TTL settings and raise them if operations are long-lived.
  4. If behind a proxy/load balancer, enable sticky sessions for the MCP endpoint.
Defensive patterns

Strategy: retry

Try / catch

if let Err(e) = op(&mut conn).await {
    if e.to_string().starts_with("MCP session expired") {
        conn = McpConnection::connect(name, cfg).await?; // fresh session
        return op(&mut conn).await;
    }
    return Err(e);
}

Prevention

When it happens

Trigger: A request over an HTTP/SSE MCP transport where the response `error` text matches stale-session signatures (e.g. 'Session not found', expired session id) while the response id matches the expected id.

Common situations: Server restarted or load-balancer routed to a different instance, session TTL elapsed during a long idle period, proxy dropped session affinity.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/c3ead62d5122024b. Report an issue: GitHub.

Appendix: source

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

                    };
                    return Err(err).with_context(|| {
                        format!(
                            "Invalid MCP JSON-RPC message from server '{}': {}",
                            self.name, preview
                        )
                    });
                }
            };

            // Check if this is a response with the expected id. We emit
            // string IDs because some MCP gateways reject numeric JSON-RPC
            // IDs, but accept numeric echoes for compatibility with older
            // servers and tests.
            if response_id_matches(value.get("id"), &expected_id) {
                if let Some(error) = value.get("error")
                    && is_mcp_stale_session_body(&error.to_string())
                {
                    anyhow::bail!("MCP session expired: {error}");
                }
                return Ok(value);
            }
            // Skip notifications (no id) and responses with different ids
        }
    }

    /// Gracefully close the connection
    #[allow(dead_code)] // Public API for MCP consumers
    pub fn close(&mut self) {
        self.cancel_token.cancel();
        self.state = ConnectionState::Disconnected;
    }

    fn catalog_authorized(&self) -> bool {
        self.config
            .reviewed_plugin
            .as_ref()

View on GitHub (pinned to 73e0f67d83)