FastMCP/stdio: in-flight tool responses dropped on stdin EOF when input is bash-redirected from a file
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 55/100
Rechercherichtung
Beginne mit dem von mcp.run(transport="stdio") verwendeten mcp.server.stdio-Pfad und reproduziere das Problem mit der bereitgestellten server.py, payload.jsonl und dem Shell-Umleitungsbefehl. Verfolge die Behandlung von stdin-EOF und ausstehenden Writer-Tasks; abgeschlossen ist die Aufgabe, wenn jede verarbeitete Anfrage, einschließlich des letzten Tool-Aufrufs, vor dem Beenden eine JSON-RPC-Antwort auf stdout erzeugt.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
1. Initial Checks
- I have searched the issue tracker for similar bugs (closest existing: #1933, but that's about premature close of real stdio during shutdown — different failure layer)
- I am using a supported MCP SDK version (pinned
mcp>=1.27,<2)
2. Description
Triage note: checked jlowin/fastmcp (now PrefectHQ fork) — FastMCP's server-side stdio delegates to mcp.server.stdio via a transport mixin, so the bug belongs here in the SDK rather than in the FastMCP wrapper.
When driving a FastMCP stdio server with a file-redirected stdin (e.g. python -m my_server < payload.jsonl > response.jsonl), in-flight tool-call responses can be dropped if their response writer hasn't been scheduled when stdin EOF arrives.
The stdio read loop appears to treat stdin EOF as an immediate-shutdown signal, cancelling pending writer tasks before they flush their JSON-RPC responses to stdout. The failure is silent — no traceback, no log line, the response is simply absent from stdout.
Expected: All responses for processed requests appear on stdout before the server exits.
Actual: Responses for the last-issued requests can be missing entirely.
3. Example Code (minimal reproducible)
# server.py
from mcp.server.fastmcp import FastMCP
import asyncio
mcp = FastMCP("repro")
@mcp.tool()
async def slow_echo(text: str) -> str:
await asyncio.sleep(0.05) # guaranteed yield point so writer scheduling is observable
return text
if __name__ == "__main__":
mcp.run(transport="stdio")
# payload.jsonl
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"repro","version":"0.1"}}}
{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"slow_echo","arguments":{"text":"first"}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"slow_echo","arguments":{"text":"second"}}}
python server.py < payload.jsonl > response.jsonl
# Observed: response.jsonl contains id=0 + id=1 results, id=2 is absent.
The race is timing-sensitive; if id=2 surfaces on the first run, increase the asyncio.sleep delay or run repeatedly. We saw it deterministically in production where the tool body did a real HTTP call (~50-200ms).
Diagnostic fingerprint: the difference between this transport race and a quality bug in the tool itself is absence-of-response vs response-with-empty-result. If you ever see id=N missing entirely (not {"id":N,"result":[]}), suspect this race.
4. Python and MCP Python SDK
- Python: 3.12 (
python:3.12-slimDocker base) - MCP SDK: 1.27.x (
mcp>=1.27,<2) - OS: Linux (Debian Bookworm; reproduced on Synology DSM 7.2 host)
- Transport: stdio via
mcp.run(transport="stdio")
Workaround we shipped (in case it helps the fix design): wrote a Python driver that owns both pipes via subprocess.Popen(stdin=PIPE, stdout=PIPE) and refuses to close stdin until the response for the last-issued id is observed on stdout. ~140 lines stdlib-only.
Suggested fix: before exiting on stdin EOF, await any pending writer tasks (with a small timeout) so their JSON-RPC responses reach stdout before the process terminates.
- Vorherrschende Sprache
- Python
- Sterne
- 24.3k
- Forks
- 4k
- Ø Merge
- 1 T. 16 Std.
- Gemergte PRs (30 T.)
- 25
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus modelcontextprotocol/python-sdk
-
v1 v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
modelcontextprotocol/python-sdk#3578 · 1 Kommentar ·
-
v1 v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 65/100
modelcontextprotocol/python-sdk#3573 · 2 Kommentare ·
-
v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
modelcontextprotocol/python-sdk#3566 ·
-
v1 v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
modelcontextprotocol/python-sdk#3546 · 5 Kommentare ·
-
v1 v2
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
modelcontextprotocol/python-sdk#3545 · 2 Kommentare ·
Alle Issues in modelcontextprotocol/python-sdk
Ähnliche Issues
-
area: harness bug status: needs-triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Human-Agent-Society/reef#625 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 80/100
learningequality/kolibri#15351 · 2 Kommentare ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
-
Name consistency Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
eellak/triplestore#65 · 1 Kommentar ·