Hacktoberfest 2026: le issue che i maintainer hanno segnato per ottobre, aperte e adatte ai principianti. Sfoglia le issue Hacktoberfest

stdio transport deadlocks on Windows: responses block in nanonext::write_stdout and child-process-spawning tools hang

Aperta
#128 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
4/5
Tempo stimato
3-5 giorni
Idoneità per principianti
55/100
Tipo di issue
Bug
Chiarezza
Abbastanza chiara
Stato di attività
Attiva
Stack tecnologico
r
Ambito
api, backend

Direzione di ricerca

Inizia con mcp_server_stdio(), read_stdin(), cat_json() e le chiamate read/write di nanonext descritte nel report. Riproduci il server stdio di Windows con l’Rscript fornito e lo schema JSON-RPC delimitato da righe, quindi confrontalo con il trasporto HTTP funzionante. Il lavoro è completato quando initialize e tools/call restituiscono risposte complete senza blocchi, inclusi i tools che generano processi figlio.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Title

stdio transport deadlocks on Windows: responses block in nanonext::write_stdout, and tool execution producing child processes hangs to the point where the server is unusable

Environment

  • Windows 11 (10.0.2xxxx), x64
  • R 4.6.1
  • mcptools 1.0.1 (CRAN), btw 1.4.0, ellmer 0.4.2, S7 0.2.2, nanonext 1.10.2
  • MCP client: MetaMCP (stdio spawn), but any stdio client should be affected

Summary

Running a server over the default stdio transport (type = "stdio") on Windows, every tools/call hangs: the client never receives a response, so it times out (typically -32001: Request timed out). I traced two distinct bugs, both in the nanonext-based stdio path. Switching to the HTTP transport (type = "http" using httpuv) makes the identical server work perfectly, which isolates the problem to the stdio transport.

Bug 1 — response writing blocks on the pipe

cat_json() sends responses via nanonext::write_stdout(). Short/empty payloads are fine, but a real response (a multi-line, ~6.7 kB help page) blocks indefinitely, so the client's timeout fires with no reply.

Supported by per-step instrumentation of handle_request():

append_tool_fn       -> ok (list)
execute_tool_call    -> ok (list)          # tool ran, result correct
to_json(result)      -> ok (~6.7 kB)       # serialization is fine
logcat               -> ok (file write)
cat_json(result)     -> HANGS              # nanonext::write_stdout on the pipe

cat_json(list(...)) with a small payload returns fine, and nanonext::write_stdout("plain") alone also returns fine — so the block is specific to writing the large/real string to an OS pipe. Overriding cat_json in the namespace to cat(to_json(x), "\n", sep = "") makes initialize and tools/call responses flow immediately — confirming the nanonext write path is the blocker, not the JSON or the tools.

Bug 2 — child-process spawning deadlocks once read_stdin() owns the stdio pipes

After the fix for Bug 1, initialize works, but tools/call then hangs inside execute_tool_call():

ETC start; tool class: ellmer::ToolDef
about to do.call
# never returns

The same do.call(tool, args) on the same registered ellmer::ToolDef returns in ~2.7 s in an isolated R process; it only hangs when called from inside the mcp_server_stdio() event loop after nanonext::read_stdin() has taken over the stdio pipes. The tool in question (btw_tool_docs_help_page) renders help via pandoc in a child process — once nanonext owns the stdio pipes, that child-process spawn deadlocks. (logcat() itself is fine: it writes to a log file, not stdout.)

Note discover_session_slot() returns NULL on Windows, so this hang is fully in-process, not in the session-forwarding path.

Working reproduction sketch

# Server
Rscript -e "options(repos=c(CRAN='https://cloud.r-project.org')); btw::btw_mcp_server()"
# Client: spawn the process, send line-delimited JSON-RPC over stdin:
# initialize -> responds fine
# tools/call btw_tool_docs_help_page {package: 'terra', topic: 'extract'} -> never responds

Fixing Bug 1 alone (cat_json override) reproduces Bug 2; fixing nothing, Bug 1 ships as the client-observed hang. With the HTTP transport (mcp_server(type = "http")), both initialize and the same tools/call return correct 200 responses with the full payload — no patches needed. That points at the nanonext stdio integration (read/write on OS pipes) as the component needing rework, consistent in spirit with #120's request for non-blocking stdio handling.

Workaround in use

Serve over HTTP and point the MCP client at http://127.0.0.1:<port> (Streamable HTTP, validated working: initialize + tools/call, also at / and /mcp). For a session_tools-free in-process server I call mcptools::mcp_server(tools = btw:::btw_mcp_tools() |> btw:::flatten_and_check_tools(), type = "http", session_tools = FALSE).

Happy to provide more detail, and thanks for a great package — the HTTP transport is rock solid.

Lingua principale
R
Stelle
204
Fork
23
Metriche di merge delle PR
Nessuna PR unita negli ultimi 30g

Preparare l'ambiente

Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di posit-dev/mcptools

Tutte le issue di posit-dev/mcptools

Issue simili

Altre issue su R

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.