Hmbown/CodeWhale · warning · anyhow::Error

server '{server_name}' is not registered

Error message

server '{server_name}' is not registered

What it means

McpRegistry::unregister_server() bails when there is no config entry for the given name — the server was never registered or was already unregistered. Note the asymmetry: stop_server() only removes the live client, so stop-then-unregister is a valid sequence; the error fires only when configs has no entry for the name.

Source

Thrown at crates/mcp/src/lib.rs:346

            failed,
            cancelled,
        }
    }

    /// Stop a running server by removing its client.
    pub fn stop_server(&mut self, server_name: &str) -> Result<()> {
        self.clients
            .remove(server_name)
            .with_context(|| format!("server '{server_name}' is not running"))?;
        Ok(())
    }

    /// Remove a server entirely (config and client).
    pub fn unregister_server(&mut self, server_name: &str) -> Result<()> {
        let had_config = self.configs.remove(server_name).is_some();
        self.clients.remove(server_name);
        if !had_config {
            bail!("server '{server_name}' is not registered");
        }
        Ok(())
    }

    /// List all tools from all running servers, applying tool filters.
    pub fn list_tools(&self) -> Result<Vec<McpToolDescriptor>> {
        let mut out = Vec::new();
        for (server_name, (_, filter)) in &self.configs {
            let Some(client) = self.clients.get(server_name) else {
                continue;
            };
            let tools = client.list_tools()?;
            for tool in tools {
                if !allowed_by_filter(&tool.tool_name, filter) {
                    continue;
                }
                let qualified_name = qualify_tool_name(server_name, &tool.tool_name);
                out.push(McpToolDescriptor {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Confirm the exact registered name (the key used in the successful register_server call) before unregistering
  2. Make teardown idempotent by treating this specific bail as success when the goal is 'make sure it is gone'
  3. In setup code, only unregister servers you actually registered — track which registrations succeeded

Example fix

// before
registry.unregister_server(&server_name)?;

// after: idempotent teardown
if registry.unregister_server(&server_name).is_err() {
    tracing::debug!("server '{server_name}' was not registered; nothing to remove");
}
Defensive patterns

Strategy: validation

Validate before calling

// Only unregister names you successfully registered:
let mut registered: Vec<String> = Vec::new();
if registry.register_server(config.clone(), filter, client).is_ok() {
    registered.push(config.name.clone());
}
// at teardown:
for name in registered { let _ = registry.unregister_server(&name); }

Try / catch

match registry.unregister_server(&server_name) {
    Ok(()) => Ok(()),
    Err(err) if err.to_string().ends_with("is not registered") => Ok(()), // idempotent teardown
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: Calling unregister_server("foo") when foo was never registered; calling it twice (double teardown in a shutdown path plus a drop handler); misspelling the server name; unregistering after a register_server call that itself failed earlier in the same setup routine.

Common situations: Error-handling paths that clean up partially-failed registrations; idempotent shutdown code that assumes unregister is a no-op for unknown names; names copied from config that drifted from the registered key.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/0cce34c6762b10bd. Report an issue: GitHub.