Most MCP setup guides start with a wall of JSON. You don't need it. Claude Code can register an MCP server with one CLI command, and for a lot of teams the right number of MCP servers is zero — a shell command in a rules file does the same job with less context overhead. This is the minimal path, the config that actually gets written to disk, and the handful of failure modes that account for almost every "my server doesn't show up" thread.
For a local (stdio) server, this is the whole setup:
claude mcp add github -- npx -y @modelcontextprotocol/server-github
Then inside a session:
/mcp
/mcp
shows every configured server, its connection state, and the tools it exposes. If your server is listed and connected, you're done. If it isn't, skip to the failure modes section — the cause is almost always one of four things.
The --
separator matters: everything after it is the command Claude Code runs to start the server process. Flags before it belong to claude mcp add
itself.
Every MCP server has a scope, and the scope decides which file it's written to and who else gets it. This is the part most people discover by accident when a server follows them into an unrelated project — or doesn't follow them into the one where they wanted it.
| Scope | Flag | Written to | Who sees it |
|---|---|---|---|
| local (default) | — | your user config, keyed to the current project | just you, just this project |
| project | --scope project |
||
.mcp.json at the repo root |
|||
| everyone who clones the repo | |||
| user | --scope user |
||
| your user config, global | just you, every project |
Two practical rules fall out of this:
.mcp.json
claude mcp reset-project-choices
re-asks.claude mcp add --scope user notion ...
once, and it's everywhere..mcp.json
is plain JSON and fine to edit by hand:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
That ${GITHUB_TOKEN}
is not decoration — Claude Code expands environment variable references in .mcp.json
(${VAR}
, with ${VAR:-fallback}
for defaults) at server start. This is how you commit a shared config without committing anyone's token: the file references the variable, each machine supplies its own value.
Servers that live behind a URL instead of a local process use a transport flag:
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
For services that need auth, run /mcp
in a session and pick the server — Claude Code walks the OAuth flow in your browser and stores the token. No key ends up in a config file, which is exactly where keys shouldn't be.
Remote HTTP servers have one operational property worth internalizing: they fail with the network. If a provider's MCP endpoint is down, your session just loses those tools until it recovers. Design your rules so that nothing critical assumes an MCP tool is always present.
1. The command isn't on PATH where Claude Code runs. Your shell has nvm-managed node; the environment Claude Code launched from may not. If /mcp
shows the server as failed and the log says command not found
, use an absolute path to the binary (command: "/Users/you/.nvm/versions/node/v22.17.0/bin/npx"
) or a wrapper script that sources your environment first.
2. The env block is missing. A stdio server is a child process — it inherits nothing you didn't give it. If the server needs GITHUB_TOKEN
and the env
block doesn't pass it, the server usually starts fine and then every tool call fails with an auth error. The confusing symptom: connected server, broken tools.
3. The server is slow to start and gets killed. Claude Code enforces a startup timeout (configurable with the MCP_TIMEOUT
environment variable). First-run npx
downloads are the classic trigger: the download takes longer than the timeout, the server "fails", and the second attempt mysteriously works because the package is now cached. Warm the cache (npx -y <pkg> --help
once) or raise the timeout.
4. It's a scope problem, not a server problem. The server works in the project where you added it and is "missing" everywhere else — because default scope is local. claude mcp list
shows what's configured in the current context; if the server isn't in the list, it's not broken, it's just somewhere else.
Run claude --debug
when none of the above fits — it logs each server's startup and the actual error instead of a silent absence.
Every MCP tool gets a name of the form mcp__<server>__<tool>
, and your permission rules treat it like any other tool. Two useful patterns:
{
"permissions": {
"allow": ["mcp__github__search_repositories"],
"deny": ["mcp__github__delete_repository"]
}
}
mcp__github
(no tool suffix) matches every tool the server exposes. One thing to know: MCP tool rules don't take argument patterns — you can allow or deny a tool, but not "this tool with these arguments". Scope your write-capable tokens accordingly: the permission system controls which tools run, the token controls what they can touch.
Every connected server injects its tool definitions into context — that's capability you pay for in tokens on every request, whether or not the session uses it. Before adding a server, it's worth asking what the integration is actually for:
gh
, aws
, kubectl
, psql
through Bash directly. A line in your rules file ("use gh
for GitHub operations") costs a few tokens once. An MCP server that duplicates a CLI costs schema tokens forever and adds a process to babysit.curl
pattern in a rules file is transparent, debuggable, and versioned with your repo.The heuristic that has held up: if you can express it as a command, write it as a rule; if it needs a connection, make it a server. Teams that follow it end up with one to three servers doing work nothing else can do, instead of eight servers reimplementing their shell.
Rulestack builds config packs for Claude Code, Cursor, and Codex — rules files, hooks, and permission setups tested against current tool behavior: https://rulestack.gumroad.com?ref=devto?ref=devto
Short, practical notes on AI coding agents, most days, on Bluesky: @ai-shop.bsky.social