Skip to content

Commit 593a91e

Browse files
committed
docs: document Windows stdio subprocess stdin
1 parent 9bdc03d commit 593a91e

2 files changed

Lines changed: 45 additions & 0 deletions

File tree

docs/run/index.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,23 @@ Nothing prints, and it doesn't return. It is waiting on stdin for a host to spea
4141

4242
That also means stdout **is the wire**. A stray `print()` corrupts the stream; the `logging` module writes to stderr and is the right tool. That story is in **[Logging](../handlers/logging.md)**.
4343

44+
On Windows, the same rule applies to child processes your tools start. A child
45+
that inherits the stdio server's stdin can block behind the server's protocol
46+
reader. If your tool starts a subprocess and you do not intend to feed it input,
47+
redirect the child's stdin:
48+
49+
```python
50+
process = await asyncio.create_subprocess_exec(
51+
sys.executable,
52+
"script.py",
53+
stdin=subprocess.DEVNULL,
54+
stdout=subprocess.PIPE,
55+
stderr=subprocess.PIPE,
56+
)
57+
```
58+
59+
The matching troubleshooting entry is **[My stdio tool hangs when it starts a subprocess on Windows](../troubleshooting.md#my-stdio-tool-hangs-when-it-starts-a-subprocess-on-windows)**.
60+
4461
### Try it
4562

4663
```console

docs/troubleshooting.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,34 @@ There is no error string for this, which is exactly why it is hard to search. Th
141141

142142
An "invalid" tool name is *not* on that list: a non-conforming name logs a warning but the tool is registered and listed anyway.
143143

144+
## My stdio tool hangs when it starts a subprocess on Windows
145+
146+
Your server is running over `stdio`, and a tool starts another process with
147+
`asyncio.create_subprocess_exec`, `asyncio.create_subprocess_shell`, or
148+
`subprocess.Popen`. The tool call never returns on Windows, while the same code
149+
works over an HTTP transport.
150+
151+
The child inherited the server's stdin. In a stdio server, stdin is the protocol
152+
pipe and the server is already waiting on it for the next JSON-RPC message. A
153+
Python child process on Windows can block during startup when it inherits that
154+
same pipe.
155+
156+
If you do not intend to send input to the child, redirect its stdin:
157+
158+
```python
159+
process = await asyncio.create_subprocess_exec(
160+
sys.executable,
161+
"script.py",
162+
stdin=subprocess.DEVNULL,
163+
stdout=subprocess.PIPE,
164+
stderr=subprocess.PIPE,
165+
)
166+
```
167+
168+
Use the same idea with `subprocess.Popen(..., stdin=subprocess.DEVNULL)`. Also
169+
capture or redirect the child's stdout. The stdio server's stdout is the MCP
170+
wire, so a child that writes there can corrupt the connection.
171+
144172
## `MCPError: Server returned an error response`
145173

146174
The server refused the HTTP request outright, with a body that is not JSON-RPC, so the python `Client` has nothing better to show you than this stand-in.

0 commit comments

Comments
 (0)