Hmbown/CodeWhale · error

MCP server ' ' connection to ' ' blocked by network policy

Error message

MCP server '{name}' connection to '{host}' blocked by network policy

What it means

Before connecting to a remote MCP server, the configured network policy decider evaluates the target host for the 'mcp' purpose. A Decision::Deny causes an immediate bail naming the server and host. This is a hard, intentional block — no connection attempt is made.

Solutions

  1. Check your network policy configuration and add the host to the allowlist if it is trusted
  2. Use `/network allow <host>` in the TUI if the policy supports interactive grants
  3. Remove or correct the MCP server config if the host should not be contacted

Example fix

// before (config)
[network]
default = "deny"
// after (allow the host)
[network]
default = "deny"
allow = ["mcp.example.com"]
Defensive patterns

Strategy: validation

Validate before calling

let host = url::Url::parse(&cfg.url)?.host_str().ok_or("no host")?.to_string();
if network_policy_for("mcp").evaluate(&host) == Decision::Deny {
    // skip or fix config before attempting connection
}

Prevention

When it happens

Trigger: Starting or connecting an MCP server whose url host is explicitly denied by the network policy (network.allowlist/denylist or an interactive policy decider returning Deny).

Common situations: Corporate denylists blocking an internal host; a host matching a deny rule like *.internal.example.com; pointing an MCP server at an unexpected domain after a config edit.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/37306c2fa5083ee2. Report an issue: GitHub.

Appendix: source

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

        // return; a successful connection transfers the task into `Self`.
        let authority_watch = config.reviewed_plugin.clone().map(|source| {
            PendingAuthorityWatch::start(
                source,
                cancel_token.clone(),
                Arc::clone(&authority_revocation_reason),
            )
        });
        let transport: Box<dyn McpTransport> = if let Some(url) = &config.url {
            // Per-domain network policy gate (#135). Only the HTTP/SSE transport
            // is gated; STDIO MCP servers run as local subprocesses and never
            // touch the network from this code path.
            if let Some(decider) = network_policy
                && let Some(host) = host_from_url(url)
            {
                match decider.evaluate(&host, "mcp") {
                    Decision::Allow => {}
                    Decision::Deny => {
                        anyhow::bail!(
                            "MCP server '{name}' connection to '{host}' blocked by network policy"
                        );
                    }
                    Decision::Prompt => {
                        anyhow::bail!(
                            "MCP server '{name}' connection to '{host}' requires approval; \
                             re-run after `/network allow {host}` or set network.default = \"allow\" in config"
                        );
                    }
                }
            }
            let client = http_client::McpHttpClient::new(
                url,
                config.runtime_added,
                config.reviewed_plugin.is_some(),
                config.allow_private_network,
                network_policy,
                Duration::from_secs(connect_timeout_secs),

View on GitHub (pinned to 73e0f67d83)