Hacktoberfest 2026: die Issues, die Maintainer für den Oktober markiert haben – offen und einsteigerfreundlich. Hacktoberfest-Issues durchsuchen

Bug: OAuth provider holds context.lock for the whole request, so the standalone GET SSE stream stalls the first tools/call by ~15s

Offen
#3,209 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

Bewertung

Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Anfängerfreundlichkeit
25/100
Issue-Typ
Bug
Klarheit
Klar beschrieben
Aktivitätsstatus
Veraltet
Tech-Stack
python
Bereich
api, authentication

Rechercherichtung

Beginne mit src/mcp/client/auth/oauth2.py ungefähr bei Zeile 492 und dem Einstiegspunkt streamablehttp_client. Vergleiche anschließend die vorgeschlagene Behebung in #2858. Reproduziere den eigenständigen GET-SSE-Ablauf mit dem bereitgestellten Skript und überprüfe, dass die Anfrage nicht mehr auf die OAuth-Kontext-Sperre wartet und dass der erste tools/call die Verzögerung durch die Stream-Lebensdauer vermeidet.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Beschreibung

P2 v1 v2

Summary

With auth=OAuthClientProvider on a streamable-HTTP connection, the first tools/call of every session blocks for ~15 s. No error, no warning — just latency. Nothing is concurrent here: one server, one client, one session, sequential calls.

Root cause is the same context.lock-held-across-yield problem as #2847 / #2644, but the symptom is different enough that it seems worth recording separately: #2847 is about RuntimeError: The current task is not holding this lock under concurrent OAuth connections, while this is a silent latency tax paid by every OAuth streamable-HTTP user, concurrency or not. Both are fixed by #2858.

Environment

  • mcp==1.29.0, anyio==4.13.0, httpx==0.28.1
  • Python 3.14.6, macOS arm64
  • Server: FastMCP behind Google Cloud Run, streamable HTTP, OAuth 2.1 + PKCE

Measurement

Same server, same tool, one fresh session per call, three calls per arm. The only variable is how the bearer token reaches the request:

auth=OAuthClientProvider   : 15.68s / 15.74s / 15.80s
static Authorization header:  0.70s /  0.62s /  0.67s

Raw HTTP JSON-RPC against the same endpoint (no SDK) answers the same tool in ~0.07 s, so the useful work is ~100 ms and effectively all of the 15.7 s is the wait.

Repro:

import time, anyio
from mcp.client.session import ClientSession
from mcp.client.streamable_http import streamablehttp_client

async def one(*, use_provider: bool) -> float:
    headers = None if use_provider else {"Authorization": f"Bearer {ACCESS_TOKEN}"}
    auth = build_provider() if use_provider else None   # OAuthClientProvider
    t0 = time.perf_counter()
    async with streamablehttp_client(URL, headers=headers, auth=auth) as (r, w, _):
        async with ClientSession(r, w) as session:
            await session.initialize()
            await session.call_tool("get_last_modified", {})
    return time.perf_counter() - t0

Why it happens

  1. async_auth_flow takes context.lock and holds it for the whole lifetime of the request, not just while it obtains a token — the async with self.context.lock: at src/mcp/client/auth/oauth2.py:492 wraps response = yield request.
  2. After initialized, streamablehttp_client opens the long-lived standalone GET SSE stream.
  3. That GET goes through async_auth_flow, takes the lock, and does not release it until the stream ends — 15–60 s in our logs, dictated by the server's/proxy's idle timeout, not by the client.
  4. The first tools/call therefore waits on the lock for as long as the GET stream lives.
  5. Once the stream closes, subsequent calls run at ~100 ms until the next stream is opened.

So the stall is not a fixed 15 s: it is "however long the server keeps an idle SSE stream open", which for Cloud Run happens to be ~15 s and for other deployments in this thread ~60 s.

The fix

#2858 (rebase of #2660 by @peisuke) already does the right thing: no HTTP yield — including the standalone GET SSE long-poll — runs while context.lock is held. Verified locally that with the lock out of the request path the same call is ~0.4–0.7 s.

Our workaround in the meantime is to keep OAuthClientProvider out of the request path entirely: read the token from storage and pass it as a plain Authorization header, refreshing it ourselves. That is a fine escape hatch for an application, but it means giving up the SDK's auth handling, which most users reasonably will not want to do.

Would a maintainer be able to look at #2858? It fixes a correctness bug for concurrent users and a ~15 s per-session latency bug for everyone else.

Vorherrschende Sprache
Python
Sterne
24.3k
Forks
4k
Ø Merge
1 T. 11 Std.
Gemergte PRs (30 T.)
30

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
  3. Forken Sie das Repository und arbeiten Sie in einem Branch.
  4. Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.

Mehr aus modelcontextprotocol/python-sdk

Alle Issues in modelcontextprotocol/python-sdk

Ähnliche Issues

Weitere Issues zu Python

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.