modelcontextprotocol / modelcontextprotocol/python-sdk

Heavy sync init work (e.g. local ML models) starves stdio initialize/tool calls even with threadpool offload -- Windows GIL contention

Offen
#3,089 3 Kommentare 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

documentation enhancement needs decision P3 v1 v2
Vorherrschende Sprache
Python
Sterne
24.3k
Forks
4k
Ø Merge
1 T. 1 Std.
Gemergte PRs (30 T.)
31

Beschreibung

Environment
  • mcp 1.28.1 (Python SDK, mcp.server.fastmcp.FastMCP)
  • stdio transport, Windows 11
  • Blocking work: sentence-transformers / torch (local embedding model), but this
    generalizes to any CPU/GIL-bound library load (onnxruntime, local LLM inference, etc.)
Summary

Related to #1839 / #1909, but a distinct failure mode that threadpool offload does not
fix: GIL-bound CPU work (loading a torch model) contends with the anyio stdio
transport's Windows pipe-reader thread for the GIL, regardless of where it runs in-process.
This makes several reasonable-looking designs fail in ways that are easy to ship and hard
to reproduce in a quick manual test.

Measurements

Same model load (sentence-transformers/all-MiniLM-L6-v2), only the location changes:

Where the load runs initialize First tool call
Standalone script, no event loop ~8s
Synchronously before mcp.run() ("warm on start") blocks 8–70s instant
In a background thread, started before mcp.run() delayed deadlocks
Lazily on first call, offloaded via anyio.to_thread / threadpool instant ~70s
In a separate subprocess, talked to over a pipe ~1s ~8–9s

Only the subprocess isolation keeps both connection time and first-call time bounded.
The variance (8s → 70s) is driven by contention between the model-loading thread and
anyio's Windows stdio reader thread — both fighting for the GIL.

Why this matters beyond my case

It's tempting to "fix" a slow first tool call by warming eagerly before mcp.run().
That instead moves the stall onto the initialize handshake — which is worse, because a
client that doesn't get the handshake in time just drops the server with no visible
error. That's a silent failure (tools missing, no exception) that's intermittent
(fine when warm/cached, broken on a cold start), so it's easy to ship and hard to catch
in CI or a quick manual check.

Suggested fix / ask

Not asking for an SDK-level fix necessarily — run_in_threadpool from #1909 is the right
answer for I/O-bound blocking calls. But it'd help other implementers to:

  1. Note in the docs/guidance for FastMCP that CPU/GIL-bound initialization (local model
    loads, etc.) should NOT be threadpool-offloaded in-process — it should run in a
    separate subprocess — since threadpool offload does not release the GIL contention
    the way it does for I/O-bound blocking calls.
  2. Consider whether initialize should have an explicit, documented deadline/backpressure
    behavior so implementers know exactly how much startup latency is safe.

Disclaimer: I'm not a Python/asyncio expert — this diagnosis, the measurements, and
this write-up were produced by Claude Code (Anthropic's coding agent) while it was
building a local memory/retrieval MCP server for me and debugging why it intermittently
failed to connect. I'm filing it because the finding looked substantive and reproducible,
but I likely can't answer deep follow-up questions about the internals myself.

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Beginne mit der FastMCP-Dokumentation und dem im Bericht beschriebenen stdio-Initialisierungspfad; vergleiche die vorhandenen Hinweise zu anyio.to_thread mit dem beobachteten Windows-Verhalten. Unter „Done“ sollten dokumentierte Hinweise zur CPU-/GIL-gebundenen Initialisierung und zur Isolation von Subprozessen stehen, wobei jede initialize-Frist oder Entscheidung, etwas als out of scope zu behandeln, klar angegeben werden sollte.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
backend-api-design, documentation
Issue-Typ
Dokumentation
Schwierigkeit
3/5
Geschätzter Aufwand
1-2 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

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