Kuberwastaken/claurst · error · anyhow::Error

invalid legacy SSE base URL

Error message

invalid legacy SSE base URL '{}': {}

What it means

When a legacy SSE MCP server advertises an `endpoint` event whose URL is not absolute, the library resolves it against the configured base URL. This error is thrown if the base URL itself fails url::Url::parse, i.e. the server's configured base URL is not a valid absolute URL.

Solutions

  1. Fix the server URL in the MCP config to a valid absolute URL including scheme (http:// or https://)
  2. Trim whitespace/control characters from the configured URL
  3. Validate the URL with url::Url::parse before storing it in config
  4. If the endpoint should be absolute, correct the server's advertised endpoint event

Example fix

// before
"url": "localhost:8080/sse"
// after
"url": "http://localhost:8080/sse"
Defensive patterns

Strategy: validation

Validate before calling

fn valid_base_url(s: &str) -> bool {
    url::Url::parse(s).map(|u| u.scheme().starts_with("http")).unwrap_or(false)
}

Prevention

When it happens

Trigger: Legacy SSE transport receives an `endpoint` event with a relative path, and the McpServerConfig URL used as base_url is malformed (missing scheme, spaces, control characters) so url::Url::parse fails.

Common situations: Config typo like `localhost:8080/sse` (no scheme) or `http://se rver/sse`; env-var interpolated URL with stray spaces; base URL read from an unvalidated settings field.

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 Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/ebcca7867c3eb379. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/mcp/src/lib.rs:423

    pub(crate) fn is_event_stream_response(response: &reqwest::Response) -> bool {
        response
            .headers()
            .get(CONTENT_TYPE)
            .and_then(|value| value.to_str().ok())
            .map(|value| value.contains("text/event-stream"))
            .unwrap_or(false)
    }

    pub(crate) fn resolve_legacy_endpoint(base_url: &str, endpoint: &str) -> anyhow::Result<String> {
        let endpoint = endpoint.trim();
        if endpoint.is_empty() {
            anyhow::bail!("legacy SSE endpoint event did not include a POST endpoint");
        }
        if let Ok(url) = url::Url::parse(endpoint) {
            return Ok(url.to_string());
        }
        let base = url::Url::parse(base_url)
            .map_err(|e| anyhow::anyhow!("invalid legacy SSE base URL '{}': {}", base_url, e))?;
        base.join(endpoint)
            .map(|url| url.to_string())
            .map_err(|e| anyhow::anyhow!("failed to resolve legacy SSE endpoint '{}': {}", endpoint, e))
    }

    #[cfg(test)]
    pub(super) fn route_incoming_value(
        server_name: &str,
        value: serde_json::Value,
        response_tx: &mpsc::UnboundedSender<JsonRpcResponse>,
        notification_tx: &mpsc::UnboundedSender<serde_json::Value>,
    ) -> anyhow::Result<()> {
        let is_response = value.get("id").map(|id| !id.is_null()).unwrap_or(false)
            || value.get("result").is_some()
            || value.get("error").is_some();

        if is_response {
            let response: JsonRpcResponse = serde_json::from_value(value).map_err(|e| {

View on GitHub (pinned to b0637c97ec)