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

  1. Always obtain McpClient through McpClient::connect and propagate its error before use
  2. Check that connect succeeded (server_type stdio/sse/streamable-http handled) before calling tools
  3. Ensure the client isn't default-constructed or partially initialized in test code
  4. 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

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


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)