Wes Ellis./ a personal notebook
Technology. Stories. Side projects.
A few things worth writing down.
← Back to Engineering

Engineering

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

Teal fiber patch cables plugged into rows of ports on a patch panel.

Part 1 of the thread Building with Claude and MCP

THE SHORT VERSION4 points
  • 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 add command, 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 --env on the command line gets saved into ~/.claude.json in 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.json example 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:

  1. Ask the client what it sees. claude mcp list from the shell, or /mcp inside 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.
  2. Validate the JSON. One trailing comma and the whole file is ignored.
  3. Use absolute paths in Desktop config. Relative paths depend on where the app was started from, and that's rarely where you think.
  4. Check the variable isn't empty. A server that starts and then fails on every call usually got a blank key.
  5. Give slow starters longer. MCP_TIMEOUT sets the startup timeout in milliseconds, for example MCP_TIMEOUT=10000 claude.
  6. 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.