Hmbown/CodeWhale · error · anyhow::Error

MCP resource URI '{uri}' was not advertised by server '{serv

Error message

MCP resource URI '{uri}' was not advertised by server '{server_name}'

What it means

read_resource_advertised (the guarded read path) first fetches the connection's advertised catalog and checks the requested uri against both literal resource URIs and resource templates (resource_uri_matches_template). Only URIs the server advertised in resources/list or resources/templates/list are allowed through; anything else is rejected before a read is sent, so the server never receives a request for an unadvertised URI. This prevents authority smuggling via crafted URIs.

Source

Thrown at crates/tui/src/mcp.rs:2986

        }
        prompts
    }

    /// Read a resource from a specific server
    pub async fn read_resource(
        &mut self,
        server_name: &str,
        uri: &str,
    ) -> Result<serde_json::Value> {
        let global_timeouts = self.config.timeouts;
        let conn = self.get_or_connect(server_name).await?;
        let advertised_literal = conn.resources().iter().any(|resource| resource.uri == uri);
        let advertised_template = conn
            .resource_templates()
            .iter()
            .any(|template| resource_uri_matches_template(uri, &template.uri_template));
        if !advertised_literal && !advertised_template {
            anyhow::bail!("MCP resource URI '{uri}' was not advertised by server '{server_name}'");
        }
        let timeout = conn.config().effective_read_timeout(&global_timeouts);
        conn.read_resource(uri, timeout).await
    }

    /// Get a prompt from a specific server
    pub async fn get_prompt(
        &mut self,
        server_name: &str,
        prompt_name: &str,
        arguments: serde_json::Value,
    ) -> Result<serde_json::Value> {
        let global_timeouts = self.config.timeouts;
        let conn = self.get_or_connect(server_name).await?;
        if !conn
            .prompts()
            .iter()
            .any(|prompt| prompt.name == prompt_name)

View on GitHub (pinned to 8880682c63)

Solutions

  1. List the advertised resources first (list_mcp_resources / conn.resources()) and use an exact advertised URI, or expand a template from resource_templates() verbatim.
  2. If the catalog may be stale (server restarted or config reloaded), force a reconnect/refresh so resources/list is re-fetched, then retry with the fresh URI.
  3. For template resources, validate your substitution against the template pattern before calling.
  4. Fix typos and encoding — the check is exact-match on literals and pattern-match on templates.

Example fix

// before
pool.read_resource_advertised("docs", "file:///proj/readme").await?;
// Err: 'MCP resource URI ... was not advertised' (server advertises project://proj/readme)

// after
let uri = pool.get_or_connect("docs").await?
    .resources().iter().find(|r| r.uri.ends_with("readme"))
    .map(|r| r.uri.clone()).expect("readme resource");
pool.read_resource_advertised("docs", &uri).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Rust: verify the URI is advertised before reading
fn advertised(pool: &McpPool, server: &str, uri: &str) -> Result<()> {
    let conn = pool.get_or_connect(server).await?;
    let lit = conn.resources().iter().any(|r| r.uri == uri);
    let tpl = conn.resource_templates().iter()
        .any(|t| resource_uri_matches_template(uri, &t.uri_template));
    ensure!(lit || tpl, "URI '{uri}' not advertised");
    Ok(())
}

Try / catch

// Rust: on failure, refresh the catalog and retry with an advertised URI
match pool.read_resource_advertised(server, uri).await {
    Err(e) if e.to_string().contains("was not advertised") => {
        let fresh = pool.get_or_connect(server).await?.resources().to_vec();
        // re-pick `uri` from `fresh`, then retry
    }
    o => o,
}

Prevention

When it happens

Trigger: Calling the resource-read API with a hand-typed URI that has a typo, a URI built from a template with wrong substitutions (so it matches neither literal nor template), or a URI remembered from an older catalog after the server changed its resource set.

Common situations: The model or user constructs URIs instead of copying them from list_mcp_resources; template parameter formatting drift (missing percent-encoding, wrong case); server restarted with fewer resources while the client cached the old list; URI scheme differences (file:// vs path).

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/1eef524d9f299095. Report an issue: GitHub.