Skip to content

Checklist for a stdio server that will not start

Spec
2026-07-28
Evidence
E2Reproducedarm64
TypeScript SDK (server)
@modelcontextprotocol/server@2.4.0
TypeScript SDK (client)
@modelcontextprotocol/client@2.4.0
TypeScript SDK v1
@modelcontextprotocol/sdk@1.32.2
Python SDK
mcp@2.3.0
MCP Inspector
@modelcontextprotocol/inspector@2.12.0
Verified

A stdio server is a child process. The client starts it with a command, writes JSON-RPC messages to its standard input and reads replies from its standard output. Almost every failure comes from one of four things: the process exits, its stdout carries something besides protocol messages, it gets a different environment than your terminal, or a relative path points somewhere else.

Work through the checks in order. Each one rules out a cause before the next.

TerminalCopy command and args from the client's configuration and run them
Terminal window
node /absolute/path/to/server.js

ExpectedThe process starts and waits silently. Output on stderr is fine.

If it exits, the message it prints is the cause. Fix that first. Typical messages are a missing module, a missing environment variable, or a syntax error from a runtime that is too old.

If it keeps running, the server itself can start. Press Ctrl+C and continue.

The specification says a stdio server must not write anything to stdout that is not a valid MCP message. To see what happens when one does, we wrote servers that break this rule in different ways and connected them with four clients:

What the server writes to stdout TS client v2 TS client v1 Python client Claude Code
A full text line at startup (console.log('starting…')) Works Works, logs Unexpected token 'h', "hello-bann"... is not valid JSON Works, logs a ValidationError ✔ Connected
A full text line during a tool call Works Works, logs … is not valid JSON — —
Text without a newline during a tool call (process.stdout.write('working... ')) Request timed out — TimeoutError —

Whole lines that are not JSON are usually skipped, but they leave error lines in the client’s log that look alarming and hide the real problem. Text without a newline is worse: it sticks to the front of the next JSON message, the client cannot parse that message, and the request waits until it times out.

So the rule stands: write nothing to stdout yourself. Use stderr for logs:

Language Safe Writes to stdout, avoid
TypeScript / JavaScript console.error(…) console.log, console.info, process.stdout.write
Python print(…, file=sys.stderr), the logging module with its default handler print(…)
Java System.err.println(…), a logging framework whose console appender targets System.err System.out.println
Go the log package (writes to stderr by default) fmt.Println, fmt.Printf

Check your dependencies too. A library that prints a banner or a progress bar to stdout breaks the protocol just as well.

3. Read the server’s stderr through the client

Section titled “3. Read the server’s stderr through the client”

Clients capture what the server writes to stderr. When a server exits during startup, its last stderr lines usually name the cause. Where each client puts it is listed in where clients write MCP logs.

The Inspector prints it straight to your terminal. For a server that exits when a variable is missing:

needs-env: DEMO_API_KEY is not set
{"error":{"code":"error","message":"Version negotiation probe failed: Connection closed during the version negotiation probe", …}}

The first line is the server’s own message. The second is how the Inspector reports the closed connection.

A client does not start the server from your shell, so it may not see the variables and the PATH your terminal has. You can see the effect by clearing the environment:

/home/me/.nvm/versions/node/v24.21.0/bin/node
which node
env -i /bin/sh -c 'echo $PATH; command -v node'
# PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# /usr/bin/node

On our machine, the same node command resolved to Node 24 in the shell and to the system’s Node 20 without the shell’s setup. A server that needs Node 22 would work in the terminal and fail in a client that starts with a minimal environment.

What to do:

  • Write runtimes as absolute paths in the configuration: the full path to node, python, uv or npx. Get it with which node (or where node on Windows).
  • Pass variables explicitly. Put them in the client’s env setting, or use the client’s way of forwarding them. Codex, for example, does not forward your shell variables to stdio servers unless you list them in env_vars; see Codex.
  • Test with a clean environment before blaming the client: env -i /absolute/path/to/node /absolute/path/to/server.js. If it fails like this, it will fail in some client.

Clients start servers in a working directory of their choosing, not your project. A relative path such as ./build/index.js or ../data then points somewhere else. Use absolute paths in args, and in any files your server opens. If the server reads a .env file, resolve it relative to the script, not to the current directory.

If the command runs in a clean environment and stdout is clean, connect with the Inspector:

Terminal window
npx -y @modelcontextprotocol/inspector@2.12.0 --cli \
/absolute/path/to/node /absolute/path/to/server.js --protocol-era auto --method tools/list

If the Inspector lists the tools, the server is fine and the problem is in the client’s configuration: the file location, the JSON or TOML syntax, the server name, or an approval step. Check the page for your client.

These points come from public reports; we have not tested them on Windows yet.

  • npx is a batch file (npx.cmd). Clients that cannot start batch files directly need "command": "cmd" with "args": ["/c", "npx", …].
  • In JSON, every backslash in a path is doubled: "C:\\Users\\me\\server.js". In TOML, use single quotes: 'C:\Users\me\server.js'.
  • Apps started from the Start menu may not see variables you set in a terminal session. Set them in the client’s configuration.

Sources

MCPDev is community documentation. It is not affiliated with the Model Context Protocol project or with the vendors of the clients it covers.