Hmbown/CodeWhale · error
MCP redirect leaves the reviewed plugin origin
Error message
MCP redirect leaves the reviewed plugin origin
What it means
For reviewed plugins, every redirect target must still match the reviewed origin (via reviewed_redirect_matches_origin). A redirect that moves the client to a different origin would silently change which server receives MCP traffic and credentials, so it is blocked.
Solutions
- Re-review the plugin with the new origin so the redirect target is in the reviewed set
- Have the server redirect only within the same origin (same scheme+host+port)
- Contact the plugin reviewer/authority to add the redirect target origin
Example fix
// before // server at https://a.example.com/mcp responds: Location: https://b.example.com/mcp // after // server responds within its reviewed origin: // Location: https://a.example.com/mcp/v2 (same origin)
Defensive patterns
Strategy: validation
Validate before calling
let origin = Url::parse(endpoint)?.origin().ascii_serialization();
// before following redirects, confirm target stays on reviewed origin
let next = base.join(location)?;
if next.origin().ascii_serialization() != origin {
// requires re-review of the plugin
} Try / catch
if let Err(e) = client.execute(req).await {
if e.to_string().contains("leaves the reviewed plugin origin") {
// re-review/re-approve the plugin for the new origin
}
} Prevention
- Keep reviewed plugin endpoints redirect-free or same-origin only
- Re-review plugins whenever their servers change domains
- Pin plugin endpoints to stable hostnames (CNAME) rather than redirecting
When it happens
Trigger: client_for_target (invoked from execute_inner on a redirect) sees a next_url whose origin differs from the client's configured origin while self.reviewed_plugin is true and reviewed_redirect_matches_origin rejects the target.
Common situations: A reviewed plugin endpoint redirects to a CDN or auth domain on another origin; a server migrates domains and redirects old->new host, which reviewed-plugin policy does not allow; load balancer redirecting to a regional hostname.
Understand the failure class
Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.
Related errors
- MCP HTTP discovered URL must not contain credentials
- MCP HTTP redirect must not contain credentials
- MCP HTTP redirect would downgrade HTTPS
- bundle redirects may not change URL scheme
- MCP HTTP destination blocked by network policy
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/243a4c3d9f7480fd.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/mcp/http_client.rs:166
// retaining just Authorization/ Cookie exclusions is insufficient.
let mut headers = header::HeaderMap::new();
for name in [header::ACCEPT, header::CONTENT_TYPE] {
if let Some(value) = request.headers().get(&name) {
headers.insert(name, value.clone());
}
}
*request.headers_mut() = headers;
}
*request.url_mut() = next_url;
}
unreachable!("redirect loop is bounded")
}
async fn client_for_target(&self, url: &Url) -> Result<reqwest::Client> {
validate_url(url)?;
let same_origin = url.origin().ascii_serialization() == self.origin;
if self.reviewed_plugin && !super::reviewed_redirect_matches_origin(url, &self.origin) {
bail!("MCP redirect leaves the reviewed plugin origin");
}
validate_network_policy(url, self.network_policy.as_ref())?;
let operator_origin = self.operator_configured && same_origin;
if !operator_origin && url_has_credentials(url) {
bail!("MCP HTTP discovered URL must not contain credentials");
}
let proxy =
super::configured_mcp_proxy(url, !operator_origin || self.reviewed_plugin, |key| {
std::env::var(key)
})?;
// A selected operator proxy resolves its own destinations. This is
// delegated proxy authority, never evidence of a local DNS pin.
let pin = if (self.private_origin_allowed && same_origin) || proxy.is_some() {
None
} else {
self.public_dns_pin(url).await?
};
// Validate DNS before reusing a client too: a new private answer revokesView on GitHub (pinned to 73e0f67d83)