Skip to content

Streamable HTTP client: update the stored endpoint URI after a 308 #1373

Open

Description

@gocamille

Follow-up from #1203 and #1367.

Problem

After #1367, the default Streamable HTTP client follows same-origin 307 and 308 redirects. But it never saves the new location. Every request still goes to the configured URI first and gets redirected again.

A 308 means the move is permanent, so the client could send later requests straight to the new URI.

Current behavior

At a156be8, the URI lives in StreamableHttpClientTransportConfig::uri. The worker clones the config once and never changes it. Each request copies the URI from there:

  1. POST: post_request, and the initialize POSTs at startup and in the fallback path.
  2. Standalone GET stream: resumption with Last-Event-ID uses its own copy in StreamableHttpClientReconnect.
  3. SSE on a POST response: resumption gets a copy too.
  4. Re-initialization after a 404: perform_reinitialization.
  5. DELETE on close: SessionCleanupInfo holds its own copy, used at exit.

All of these requests use the same reqwest::Client, so the redirect policy applies to GET and DELETE too, not only POST.

reqwest gives us the final URL through Response::url(), but not the status of each hop. The Policy::custom closure sees each hop's status. However, one policy is shared by every request on the client, and it can't tell which request a hop belongs to.

Proposal

  • Only save the new URI when every hop in the chain is a 308. If any hop is a 307, keep the configured URI.
  • Have the run loop keep the current URI, since it already owns the session state. Use that URI for new POSTs, new GET streams, re-initialization and the DELETE.
  • Return the final URL from the client to the worker. StreamableHttpPostResponse doesn't carry a URL today.

To find out whether every hop was a 308, we could:

  1. Stop on 308 in the policy and follow it by hand in the reqwest StreamableHttpClient impl, with the same origin and hop-limit checks.
  2. Record hops from the policy closure into shared state. This is unreliable when concurrent requests start from the same URI.

Open questions

  • Same-origin is already enforced, so only the path and query can change. Do we need any other check?
  • Should the session ID carry over to the new URI, or should the client re-initialize?
  • OAuth: AuthorizationManager keeps its own base_url. It's the fallback resource, and the protected resource metadata resource is checked against it. If the transport URI moves and base_url doesn't, they no longer match, and there's no way to update base_url today. Should we skip the update when AuthClient wraps the transport, or move both?
  • Requests and streams already in flight keep the old URI. Is that fine, or should resumption pick up the new one?
  • Default client only, or also clients passed to with_client?

Related

Activity

  1. added
    T-transportTransport layer changes
    enhancementNew feature or request
    P3Low: nice-to-have, long-term, or exploratory
    on Oct 16, 2026
  2. github-actions commented on Oct 16, 2026

    @github-actions
    Contributor

    @connor4312 is on triage rotation this week and is the reviewer for this one.

    Codex labeled this enhancement / P3 (T-transport). Please confirm or correct those labels.

    Automated triage. Labels are a first pass and the assigned maintainer has the final say.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

P3Low: nice-to-have, long-term, or exploratoryT-transportTransport layer changesenhancementNew feature or request

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions