Hmbown/CodeWhale · error · anyhow::Error
MCP session expired: {error}
Error message
MCP session expired: {error} What it means
After matching the expected JSON-RPC id, recv inspects the response's error member with is_mcp_stale_session_body; if the server reports an expired/unknown session (typical of MCP streamable-HTTP servers whose session id has aged out or been forgotten), this error is raised instead of returning the raw error. It signals that the MCP session established at initialize time is no longer valid on the server side, while the transport itself may still be alive.
Source
Thrown at crates/tui/src/mcp.rs:2234
};
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 8880682c63)
Solutions
- Reconnect: drop the connection and call get_or_connect again — a fresh initialize handshake obtains a new session id.
- If it recurs frequently, check the server/gateway's session TTL and align it with expected idle periods, or enable client-side periodic activity.
- Verify nothing between client and server (proxy, LB) strips the session header on responses.
- If the server is supposed to be stateless, check its MCP implementation version — a bug may invalidate sessions prematurely.
Example fix
// before
let result = pool.get_or_connect("remote").await?.call_tool(...).await;
// Err: MCP session expired: {...}
// after — drop and re-establish the session
if let Err(e) = pool.get_or_connect("remote").await?.call_tool(...).await {
if e.to_string().contains("MCP session expired") {
pool.drop_connection("remote", "stale session");
return pool.get_or_connect("remote").await?.call_tool(...).await;
}
return Err(e);
} Defensive patterns
Strategy: retry
Try / catch
// Rust: stale MCP session -> drop connection, re-initialize, retry once
let r = pool.get_or_connect(server).await?.call_tool(n, a, t).await;
match r {
Err(e) if e.to_string().contains("MCP session expired") => {
pool.drop_connection(server, "stale session");
pool.get_or_connect(server).await?.call_tool(n, a, t).await
}
o => o,
} Prevention
- Treat session expiry as expected on long-lived HTTP MCP connections; implement one automatic re-init.
- Align gateway/session TTLs with your session lifetime to avoid constant re-auth.
- Don't reuse a connection handle after expiry — the session id is baked into it.
When it happens
Trigger: A streamable-HTTP MCP server issues a session id at initialize, then expires or restarts its session store; the next request on the old session returns a JSON-RPC error whose body matches the stale-session detector, and recv converts it to this message.
Common situations: Long-lived TUI sessions against an MCP gateway behind a load balancer that rotates sessions; server redeploy mid-session; gateway idle-timeout shorter than the client's connection lifetime; resuming a laptop from sleep with the HTTP session long expired server-side.
Related errors
- MCP Streamable HTTP session expired; retry with a new sessio
- GET timeout
- MCP SSE rejected (transport=http url={} status={}): {}
- MCP session expired (transport=sse endpoint={} status={}): {
- MCP SSE POST rejected (transport=sse endpoint={} status={}):
AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16).
Data as JSON: /api/errors/c3ead62d5122024b.
Report an issue: GitHub.