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
- Make the endpoint URL same-origin with the base URL (same scheme, host, and port).
- Use a relative endpoint path (e.g. /sse) so it resolves against the base origin automatically.
- 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.
- 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
- Prefer relative endpoint paths so same-origin holds by construction.
- Lint MCP configs for cross-origin endpoint/base pairs at load time.
- Never point authenticated MCP endpoints at a different origin to work around routing; use a proxy instead.
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.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- MCP HTTP destination blocked by network policy
- MCP HTTP destination is a restricted IP address
- reviewed plugin MCP endpoint has an unsafe origin
- Invalid MCP command
- MCP config path cannot contain '..' components
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)