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
- Reconnect/re-handshake the MCP connection to obtain a fresh session id, then retry the request.
- Reduce idle time between requests or configure server keepalives.
- Check server session TTL settings and raise them if operations are long-lived.
- 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
- Keep session affinity when routing through load balancers/proxies.
- Avoid long idle gaps on HTTP-streamable MCP transports or send periodic keepalives.
- Match client reconnect-on-stale-session handling to the server's session TTL.
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
- MCP server rejected the request with ; the session is no…
- MCP Streamable HTTP session expired; retry with a new…
- {backoff.last_error}
- failed to fetch from
- MCP catalog changed after tool resolution; retry the call
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)