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

Fechada
#1,373 4 comentários 0 reações 1 responsável Ver no GitHub

Mantenedores costumam responder em até 3 dias

@connor4312 já está trabalhando nisso.

Desde 16/10/2026.

Avaliação

Esta issue ainda não foi avaliada.

Descrição

enhancement P3 T-transport

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

  • #1203: the original redirect report
  • #1367: follows same-origin 307/308 in the default client
  • The TypeScript and Python SDKs don't update the stored URL either. Both follow same-origin redirects and keep the configured URL (TypeScript, Python).
Linguagem predominante
Rust
Estrelas
4.1k
Forks
694
Merge médio
4d 16h
PRs com merge (30d)
43

Preparar o ambiente

Abrir no Codespaces

Inicia o contêiner de desenvolvimento do projeto no navegador, com a sua própria conta do GitHub.

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Mais de modelcontextprotocol/rust-sdk

Todas as issues de modelcontextprotocol/rust-sdk

Issues semelhantes

Mais issues de Rust

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.