Kuberwastaken/claurst · error · anyhow::Error
MCP client backend missing
Error message
MCP client backend missing
What it means
McpClient is a lazy wrapper that may be constructed without an actual transport backend (the `backend: Option<Arc<dyn McpClientBackend>>` is None). Any operation requiring the backend calls `backend()` which returns this error, indicating the client was never successfully connected or the backend was not initialized.
Solutions
- Always obtain McpClient through McpClient::connect and propagate its error before use
- Check that connect succeeded (server_type stdio/sse/streamable-http handled) before calling tools
- Ensure the client isn't default-constructed or partially initialized in test code
- Re-run the connect sequence if the backend was dropped/restarted
Example fix
// before
let client = McpClient::default();
client.call_tool("t", None).await?;
// after
let client = McpClient::connect(&config, token).await?;
client.call_tool("t", None).await?; Defensive patterns
Strategy: validation
Validate before calling
// ensure connect was called before use let client = McpClient::connect(&config, token).await?;
Try / catch
match client.call_tool(name, args).await {
Err(e) if e.to_string() == "MCP client backend missing" => {
// re-run connect() before retrying
}
r => r?,
} Prevention
- Never construct McpClient outside connect()
- Treat connect errors as fatal before invoking tools
- Serialize connect and first tool call to avoid init races
When it happens
Trigger: Calling call_tool/list_tools/other backend-dependent methods on an McpClient created without connect (e.g. constructed via the test-only path or before connect_stdio/connect_http completed) or after connect failed and a default-constructed client is reused.
Common situations: Application constructs McpClient directly instead of via McpClient::connect; connect returned a client even though initialization later failed; race where tools are invoked before connection setup finishes.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Bridge session registration failed: authentication error
- MCP server ' ' is configured as ' ' but missing URL
- legacy SSE endpoint event did not include a POST endpoint
- OAuth state mismatch — possible CSRF attack
- MCP server ' ': legacy SSE stream returned HTTP
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/68653bb8b9c8e2eb.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/mcp/src/lib.rs:588
tools: snapshot.tools,
resources: snapshot.resources,
prompts: snapshot.prompts,
instructions: snapshot.instructions,
backend: None,
}
}
fn from_backend(backend: Arc<dyn backend::McpClientBackend>) -> Self {
let snapshot = backend.snapshot();
let mut client = Self::from_snapshot(snapshot);
client.backend = Some(backend);
client
}
fn backend(&self) -> anyhow::Result<&Arc<dyn backend::McpClientBackend>> {
self.backend
.as_ref()
.ok_or_else(|| anyhow::anyhow!("MCP client backend missing"))
}
pub async fn connect(config: &McpServerConfig, auth_token: Option<String>) -> anyhow::Result<Self> {
match config.server_type.as_str() {
"stdio" => Self::connect_stdio(config).await,
"sse" => {
let backend = crate::rmcp_backend::RmcpClientBackend::connect_legacy_sse(
config,
auth_token,
)
.await?;
Ok(Self::from_backend(Arc::new(backend)))
}
"http" => {
let mut last_error = None;
for &protocol_version in transport::STREAMABLE_HTTP_PROTOCOL_VERSIONS {
let protocol_version = serde_json::from_value::<rmcp::model::ProtocolVersion>(
Value::String(protocol_version.to_string()),
View on GitHub (pinned to b0637c97ec)