Hmbown/CodeWhale · error

MCP SSE endpoint is not same-origin as — refusing to send…

Error message

MCP SSE endpoint {} is not same-origin as {} — refusing to send authenticated requests cross-origin

What it means

The MCP SSE transport resolves a relative endpoint against a base URL and refuses to proceed when the resolved endpoint is not same-origin (scheme, host, and port) as the base. This is a security guard: sending authenticated requests (cookies/tokens) to a different origin would leak credentials and enable SSRF/policy bypass. Relative endpoints are same-origin by construction, so only explicitly absolute cross-origin endpoints trip this.

Solutions

  1. Make the endpoint URL same-origin with the base URL (same scheme, host, and port).
  2. Use a relative endpoint path (e.g. /sse) so it resolves against the base origin automatically.
  3. If the endpoint truly lives on another origin, change the base URL to match that origin, or run a same-origin reverse proxy in front of it.
  4. Verify with a URL parser that scheme, host, and effective port are identical before restarting.

Example fix

// before (codewhale config, MCP server entry)
url = "https://mcp.example.com"
endpoint = "https://api.example.com/sse"

// after
url = "https://mcp.example.com"
endpoint = "/sse"
Defensive patterns

Strategy: validation

Validate before calling

let base = url::Url::parse(base_url)?;
let resolved = base.join(endpoint)?;
if resolved.scheme() != base.scheme()
    || resolved.host_str() != base.host_str()
    || resolved.port_or_known_default() != base.port_or_known_default()
{
    return Err("endpoint must be same-origin as base".into());
}

Prevention

When it happens

Trigger: Configuring an MCP server with an SSE endpoint URL whose scheme, host, or port differs from the base URL — e.g. base https://mcp.example.com with endpoint https://api.example.com/sse, or http base with https endpoint, or a different port.

Common situations: Copy-pasting an endpoint from another MCP provider; a load balancer or proxy exposing the SSE stream on a different host/port than the API base; switching from http to https (or vice versa) in only one of the two URL fields; pointing the endpoint at localhost while the base points at a remote host.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

Thrown at crates/tui/src/mcp/sse.rs:260

        let base = reqwest::Url::parse(base_url)?;
        let resolved =
            if endpoint_url.starts_with("http://") || endpoint_url.starts_with("https://") {
                reqwest::Url::parse(endpoint_url)?
            } else {
                base.join(endpoint_url)?
            };
        // Security: the server-supplied `endpoint` event must stay same-origin
        // as the connect URL. The connect host is vetted by network policy
        // once, but the endpoint host is never re-checked — so an absolute
        // cross-origin endpoint would let a malicious MCP server redirect the
        // client's *authenticated* POSTs (Bearer/OAuth headers attached) to an
        // internal host (169.254.169.254, localhost admin ports, …): an SSRF /
        // policy bypass. Relative endpoints are same-origin by construction.
        if resolved.scheme() != base.scheme()
            || resolved.host_str() != base.host_str()
            || resolved.port_or_known_default() != base.port_or_known_default()
        {
            anyhow::bail!(
                "MCP SSE endpoint {} is not same-origin as {} — refusing to send \
                 authenticated requests cross-origin",
                mask_url_secrets(resolved.as_str()),
                mask_url_secrets(base.as_str()),
            );
        }
        Ok(resolved.to_string())
    }
}

#[async_trait::async_trait]
impl McpTransport for SseTransport {
    async fn send(&mut self, msg: Vec<u8>) -> Result<()> {
        let endpoint = self
            .endpoint_url
            .as_ref()
            .context("SSE endpoint not yet discovered")?
            .clone();

View on GitHub (pinned to 73e0f67d83)