Running an MCP server locally over stdio
On this page
An MCP server gives an AI assistant tools it doesn’t have on its own, such as querying a database or filing a ticket. A server can run on a shared machine that many people connect to, or on your own laptop, started by the assistant itself. The second way is called stdio, and it is also the quickest way to try a change to a server you’re writing, because nothing has to be deployed first.
A few terms
- MCP (Model Context Protocol): an open standard for connecting AI applications to tools and data [1].
- JSON-RPC: the message format MCP uses. Each message is a small JSON object that names a method, like
tools/call, and carries its arguments or its result [1]. - Client: the AI application, such as an editor or a command-line assistant (a CLI, a program you run in a terminal). It decides which servers to talk to.
- Server: the program that offers the tools. In this primer, a server you run or are building.
- Transport: how the messages travel between the two. The spec defines two standard ones, stdio and Streamable HTTP [2].
- uv: a Python project and package manager.
uv runruns a command inside a project’s environment, which is how the example server below is started [3]. - Subprocess: a program started by another program, which can then read its output and write to its input.
- stdin, stdout, stderr: the three text streams every process on macOS, Linux, and Windows has. A program reads stdin (standard input), writes results to stdout (standard output), and writes diagnostics to stderr (standard error).
How stdio works
The examples in this primer use one made-up server called notes-server, a Python program that lets an assistant search a folder of notes. It lives in /Users/example/projects/notes-server.
With stdio, you never start notes-server yourself. You tell the client the command that starts it, and the client runs that command as a subprocess. From then on the client writes requests to the server’s stdin and reads replies from its stdout. The server may write log lines to stderr, which the client can capture, forward, or ignore [2].
Replies and logs travel on separate streams. Only valid MCP messages may go on stdout.
The spec’s rules for stdio [2]:
- Each message is one line of JSON, with no line breaks inside it.
- The server must not write anything to stdout that isn’t a valid MCP message.
- The client must not write anything to the server’s stdin that isn’t a valid MCP message.
The second rule explains the most common stdio bug. A stray print("starting up") in the server lands on stdout, the client reads it as a message, and the connection fails. Logging belongs on stderr.
Registering a server with a client
A client needs three things to start a stdio server: the command, its arguments, and any environment variables. The same server looks like this in a portable config file, which is the mcpServers format that several clients read [4]:
{
"mcpServers": {
"notes-local": {
"command": "uv",
"args": ["run", "--directory", "/Users/example/projects/notes-server", "notes-server"],
"env": { "NOTES_DIR": "/Users/example/notes" }
}
}
}
VS Code reads the same entry from .vscode/mcp.json, where the top-level key is servers and not mcpServers [4]. A server given as command and args runs locally, while one given as a url with "type": "http" is a remote one [4].
Three details in that entry prevent most “it works in my terminal but not in the client” problems:
- Use an absolute directory. The client’s working directory is whatever it was launched from, not your project. The
--directoryflag makesuvchange to the project’s directory before running anything [5]. Other runners have an equivalent, or you can give the full path to the script. - Put secrets in
env, or use the client’s input prompts, and keep them out of the file you commit. VS Code’s docs warn against hardcoding API keys inmcp.json[4]. - Check where the config applies. A workspace file applies to one project, and a user-level file applies everywhere. Registering the same server in both can show every tool twice.
Many clients also have an “add server” command that writes this entry for you. VS Code’s is MCP: Add Server [4].
Trust: a local server can run any code
A stdio server is a program running on your machine with your permissions. VS Code’s documentation warns that local servers can run arbitrary code and asks you to add only servers from sources you trust [4]. That includes your own half-finished one, if it shells out or deletes files.
The edit, restart, test loop
The client starts the server when a session begins and ends it when the session closes: it closes the server’s stdin, waits for it to exit, and stops it by force only if it doesn’t. A server should exit promptly when its stdin closes [2]. Either way, a running server never sees edits made after it started. The loop for changing a server is:
- Edit the server’s code.
- Restart the client session, or use the client’s restart command for that one server.
- Call the changed tool from the assistant.
- Run the project’s own tests before committing.
A session with an assistant exercises one path once, while a unit test exercises the tool’s logic every time, without needing the client at all.
Testing without a client
The MCP Inspector is the reference tool for poking at a server by hand. It takes the same command you would put in the config and starts the server itself [6]:
npx @modelcontextprotocol/inspector uv run --directory /Users/example/projects/notes-server notes-server
It needs Node 22.19.0 or newer. The command opens a browser UI where you can list the server’s tools, call one with arguments, and read the raw JSON-RPC exchange, which is where a stray stdout line shows up [6]. The same package has a scriptable mode that lists tools and exits [6]:
npx @modelcontextprotocol/inspector --cli uv run --directory /Users/example/projects/notes-server notes-server --method tools/list
If a tool works in the Inspector but not in the client, the problem is in the client’s config. If it fails in both, the problem is in the server.
Common problems
| Symptom | Likely cause | Fix |
|---|---|---|
| Client reports a parse or connection error right after launch | Something other than MCP messages on stdout | Send logs to stderr; remove print calls |
| “command not found” in the client only | A graphical client has a shorter PATH than your terminal | Use the absolute path from which uv (macOS, Linux) or Get-Command uv (PowerShell) |
| Server starts but can’t find its files | Working directory is not the project | Pass the directory flag or an absolute path |
| Every tool appears twice | Same server registered in two config files | Remove one registration |
| Edits have no effect | Server still running from before the edit | Restart the client session |
| Works locally, fails against a shared copy | Local runs use your credentials, a shared server uses its own | Test the shared deployment’s permissions separately |
Trying the HTTP transport on your own machine
Streamable HTTP runs the server as an independent process that clients connect to by URL [7]. To test that path locally, start the server yourself and register http://localhost:8000/mcp as a url entry (port and path depend on the server). The spec says a server running locally should bind to 127.0.0.1 and not 0.0.0.0, and that it must validate the Origin header, because otherwise a web page could reach it through DNS rebinding [7].
Quick reference
| Goal | Command |
|---|---|
| Run the server by hand (it waits on stdin) | the same command the client runs, for example uv run notes-server |
| Register it | add command, args, env under mcpServers (or servers in .vscode/mcp.json) |
| Poke at it without a client | npx @modelcontextprotocol/inspector <command> <args> |
| Load an edit | restart the client session |
| Go shared | switch to the HTTP transport and a url entry |
References
Show all 7 sourcesHide sources
- [1] ↩ a b Model Context Protocol project, “Specification,” Model Context Protocol Specification (2026-07-28). Accessed: Oct. 13, 2026. [Online]. Available: https://modelcontextprotocol.io/specification/2026-07-28
- [2] ↩ a b c d Model Context Protocol project, “stdio,” Model Context Protocol Specification (2026-07-28). Accessed: Oct. 13, 2026. [Online]. Available: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio
- [3] ↩ Astral, “Running commands,” uv Documentation. Accessed: Oct. 13, 2026. [Online]. Available: https://docs.astral.sh/uv/concepts/projects/run/
- [4] ↩ a b c d e f Microsoft, “Add and manage MCP servers in VS Code,” Visual Studio Code Documentation. Accessed: Oct. 13, 2026. [Online]. Available: https://code.visualstudio.com/docs/agent-customization/mcp-servers
- [5] ↩ Astral, “Commands,” uv Documentation (CLI reference). Accessed: Oct. 13, 2026. [Online]. Available: https://docs.astral.sh/uv/reference/cli/
- [6] ↩ a b c Model Context Protocol project, “MCP Inspector,” Model Context Protocol Documentation. Accessed: Oct. 13, 2026. [Online]. Available: https://modelcontextprotocol.io/docs/tools/inspector
- [7] ↩ a b Model Context Protocol project, “Streamable HTTP,” Model Context Protocol Specification (2026-07-28). Accessed: Oct. 13, 2026. [Online]. Available: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http
Every source cited across the primers is collected on one page.