Connect to a Local Server
Connecting over stdio is a process launch, not a connection. Absolute paths, the rule against printing to stdout, and where the logs are.
How a stdio connection to a local server works
There is no network involved, and once you know that, most of the failures explain themselves.
The host starts your server as a child process and talks to it over the pipes
every process already has. The specification describes it plainly: "the client
launches the MCP server as a subprocess", and "The server reads JSON-RPC
messages from stdin and writes JSON-RPC messages to stdout." One message per
line, newline-delimited, and messages "MUST NOT contain embedded newlines."
So "connecting" is not a connection in the usual sense. It is a process launch. Your configuration is not an address; it is a command line. Almost everything that goes wrong goes wrong the way a command line goes wrong.
Transport layer covers the choice between stdio and streamable HTTP, and why you would pick one. What follows assumes you picked stdio and want it running.
The configuration entry, field by field
Configuration lives in a JSON file the client reads at startup. Here is a filesystem server, from the protocol's own documentation:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/username/Desktop"]
}
}
}
Four things are being specified:
- The key,
filesystemhere, is a label for the server in the client's own interface. It is not sent anywhere. commandis the executable to launch.argsare its arguments. Anything after the package name in this example is the server's own configuration, not the protocol's.env, not shown, supplies environment variables. It is where API keys go, because the server inherits a deliberately sparse environment rather than your shell's.
One caution before you copy this. That file is one client's format, not the
protocol's. The example above is Claude Desktop's, which the documentation
itself frames as "one of the many clients that support MCP", and it lives at
~/Library/Application Support/Claude/claude_desktop_config.json on macOS or
%APPDATA%\Claude\claude_desktop_config.json on Windows. Other clients use
different paths and sometimes different key names. The shape (a named entry with
a command, arguments, and an environment) is what transfers.
Absolute paths, and why relative ones fail
The most common failure has the least interesting cause.
Your server is not launched from your project directory. It is launched by a
desktop application whose working directory is something you did not choose and
would not guess. A relative path in command or args resolves against that,
and the process dies before it speaks a word of protocol.
The documentation's troubleshooting list says it directly: make sure paths "are valid and that they are absolute and not relative." Write out the full path to your interpreter and to your script. It looks verbose and it removes an entire class of problem.
Windows has a sharper version of the same issue. If a path in the configuration
contains an unexpanded ${APPDATA}, the launch fails with ENOENT, and the
documented fix is to put the expanded value into the entry's env.
Why your server must not print to stdout
This rule produces the most confusing failure of the set.
The specification: "The server MUST NOT write anything to its stdout that
is not a valid MCP message."
Standard output is the wire. A console.log in your server is not a debug
message. It is a corrupt frame injected into the middle of a JSON-RPC stream.
The client reads a line, fails to parse it, and what you see is a connection
that drops or a server that never finishes starting. The print statement you
added to debug the problem is now causing a different problem.
Logging goes to standard error, and the specification allows it explicitly: the
server "MAY write UTF-8 strings to stderr for any logging purposes
including informational, debug, and error messages." Clients are told not to
read anything into it. The client "SHOULD NOT assume stderr output
indicates error conditions."
In practice: replace console.log with console.error in an MCP server, or
configure your logging library to write to stderr, before you write anything
else.
Debugging a failed connection in order
When a server does not appear, work forward through the stages rather than guessing. Each stage has a different symptom and a different fix.
Does the process start? Run the exact command from the configuration in your own terminal, with the same arguments. Most failures surface here as a missing interpreter, a bad path, or a stack trace on startup. The documentation recommends this as the first step, and it is the fastest one.
Does it stay running? A server that starts and exits immediately usually threw during initialization. Running it by hand shows you that too; the process returns to your prompt instead of waiting.
Does it speak? A process that runs and stays up but shows no tools is usually the stdout problem above, or a server that never registered anything. Check its stderr.
Are the tools there? If the client lists the server but no tools, the server is connected and the gap is in what it exposes. That is a server question, not a connection one.
Reading the client's MCP logs
The client keeps its own record, which is where to look when running the command by hand works and the client still shows nothing.
For the documented example, mcp.log holds general connection logging and
failures, and mcp-server-SERVERNAME.log holds one named server's stderr. Note
what follows from the stdout rule: since "Stdio servers may use stderr for all
their logging", those per-server files "are not limited to errors." Your
server's ordinary logging is in there too.
On macOS both sit in ~/Library/Logs/Claude, and following them while you
restart the client is the fastest way to watch a connection fail:
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
One last behavior worth expecting. When the client shuts down it closes your server's standard input and waits for the process to exit, escalating to a forced kill if it does not. Servers "SHOULD exit promptly when their standard input is closed". A server that ignores end-of-file gets killed instead, every time. And if it dies unexpectedly the client is expected to restart it; because the protocol is stateless, "any in-flight requests are simply lost and the client can retry them against the fresh process."
Further reading
- MCP, Connect to local MCP servers: the configuration walkthrough, log locations, and troubleshooting list quoted above.
- MCP, stdio transport, revision 2026-07-28: the normative rules about stdout, stderr, shutdown, and restart.
- Transport layer: choosing stdio in the first place.
- MCP host: what the application launching your server is responsible for.
Knowledge check
Question 1 of 4
Sign in to save your progress and pick up where you left off.