Engineering
Setting Up MCP Servers From Scratch, and Telling When They're Broken

Part 1 of the thread Building with Claude and MCP
- An MCP server is a small program that hands Claude a set of tools. It runs locally over stdio or remotely over HTTP.
- In Claude Code it's one
claude mcp addcommand, and the scope decides who else gets the server. - Claude Desktop reads a JSON file and only notices changes after a full quit.
- Most "it's not working" problems are bad JSON, a relative path or an empty environment variable.
My vault note on this started with Docker Desktop, a compose file, a PowerShell startup script and a scheduled task to keep it all alive. When I checked it against the current docs, most of that turned out to be more machinery than the job needs. Here's the shorter version, the one I'd hand a friend.
Checked against the docs in September 2026.
What you're actually setting up
MCPModel Context Protocol. An open standard for plugging tools and data sources into an AI model, so it can call them in the middle of a conversation.More: MCP Tool Poisoning, and Why G8KEPR Fingerprints Every Tool servers are small programs. Each one offers Claude some tools: read these files, search this board, query that database. Claude Code or Claude Desktop is the client, and the client is the thing that connects to them.
There are two shapes, and knowing which one you've got answers most setup questions:
| Local (stdio) | Remote (HTTP) | |
|---|---|---|
| Where it runs | On your machine, started by the client | On someone else's server |
| How you add it | A command, like npx -y <package> |
A URL |
| Secrets live in | Environment variables | Usually a header or a sign-in |
| Keeping it running | The client starts and stops it | Not your problem |
That last row is why my old Docker setup was overkill. With a local stdio server, the client launches the program itself and talks to it over stdin and stdout. There's nothing to keep alive between sessions. There was a third transport, SSE, and the docs now call it deprecated in favor of HTTP.
Adding one to Claude Code
It's a single command. The options go before the name, and everything after the -- is the command that starts the server:
claude mcp add --transport stdio --env API_KEY=<your-key> my-server -- npx -y <package-name>
claude mcp add --transport http docs-server https://mcp.example.com/mcp
The part worth slowing down for is scope, because it decides where the config gets written and who else sees it:
| Scope | Who gets it | Stored in |
|---|---|---|
local (the default) |
Just you, in this project | ~/.claude.json |
project |
Anyone who clones the repo | .mcp.json in the project root |
user |
Just you, in every project | ~/.claude.json |
Project scope is the handy one for a repo you share, since .mcp.json gets committed. It also supports ${VAR} and ${VAR:-default} expansion, so the file can name a secret without containing it:
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["-y", "<package-name>"],
"env": { "API_KEY": "${API_KEY}" }
}
}
}
Claude Code asks before it uses project-scoped servers from someone's .mcp.json in an interactive session, which is the right default. If you change your mind later, claude mcp reset-project-choices clears those answers.
Heads up
A key passed with
--envon the command line gets saved into~/.claude.jsonin plain text. That's fine for a throwaway test key. For anything real, set the variable in your shell and reference it, the way the.mcp.jsonexample does.
Claude Desktop is a different file
Claude Desktop doesn't share Claude Code's config. It reads claude_desktop_config.json, and you can open it from Settings, then Developer, then Edit Config. On Windows it lives at %APPDATA%\Claude\claude_desktop_config.json, and on a Mac at ~/Library/Application Support/Claude/claude_desktop_config.json. There's no Linux version of Claude Desktop, so my note's "macOS/Linux" line was wrong from the start.
The servers go under an mcpServers key, same shape as above. Here's the official filesystem server, which only touches the folders you list:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects/app"]
}
}
}
After any edit, quit the app completely and reopen it. Closing the window isn't enough. Claude Desktop also installs desktop extensions (.mcpb files) with a double-click now, which is the easier road when a server ships one.
When it doesn't show up
This is the part of my old quick reference that earned its place. Boiled down:
- Ask the client what it sees.
claude mcp listfrom the shell, or/mcpinside a session, which shows each server's status and lets you sign in to the ones that need it.claude mcp get <name>shows one server's details. - Validate the JSON. One trailing comma and the whole file is ignored.
- Use absolute paths in Desktop config. Relative paths depend on where the app was started from, and that's rarely where you think.
- Check the variable isn't empty. A server that starts and then fails on every call usually got a blank key.
- Give slow starters longer.
MCP_TIMEOUTsets the startup timeout in milliseconds, for exampleMCP_TIMEOUT=10000 claude. - Run the start command by hand. If
npx -y <package-name>falls over in a terminal, it'll fall over inside Claude too, just more quietly.
Before you add ten of them
Every server is code you're choosing to trust, and its tool descriptions go straight into the model's context. I wrote about how that gets abused in MCP tool poisoning, and it's why I'd rather add servers one at a time than install a pile of them on day one.
Next up in this thread are the three servers I'd reach for first. And if you want Claude to know the house rules before it touches any of these tools, that's what a good CLAUDE.md is for.