Claude MCP Server Setup: Config, Scopes, and Transport
Connecting a Claude MCP server is two jobs depending on the client. Claude Desktop reads one JSON file you edit by hand; Claude Code adds servers with a command, stored at one of three scopes — local, project, or user. In both, the server is a separate process, the transport is a local stdio command or a remote HTTP URL, and most visible failures are configuration, scope, or PATH problems rather…
Short answer: Connecting a Claude MCP server is two jobs depending on the client. Claude Desktop reads one JSON file you edit by hand; Claude Code adds servers with a command, stored at one of three scopes — local, project, or user. In both, the server is a separate process, the transport is a local stdio command or a remote HTTP URL, and most visible failures are configuration, scope, or PATH problems rather than protocol ones.
Key takeaways
- Two hosts, one protocol. Claude Desktop and Claude Code are both MCP clients; the server you connect is a third thing that runs locally or answers at a URL.
- Desktop is one file; Code is three scopes, and only the project scope travels with a clone.
- A project-scoped server needs approval, and a cloned repo cannot self-approve.
- stdio for a local process, HTTP for anything remote. A
urlwith notypeis read as stdio and skipped: the most common paste error. - Config values have sharp edges. A token pasted with a trailing newline warns; some credential variables read as empty toward a remote server and surface as a 401.
- Debug in one order:
claude mcp list, thenclaude mcp get <name>, then the command itself. Do that next before editing anything else.
What a Claude MCP server actually is: host, server, and the gap between them
Claude is the host: it holds the conversation, decides which tools the model sees, and asks your permission. An MCP server is a separate program that advertises tools over JSON-RPC. Claude Desktop launches it on your machine; Claude Code can launch it locally or reach it over the network. The protocol defines the wire format, the transports, and the call lifecycle, and deliberately says nothing about where a client keeps its configuration or how it scopes it (MCP introduction).
That gap is where the work lives. A reader searching claude mcp server is past the protocol and stuck
on one of four operational questions: which file, which scope, which transport, and why it did not
connect. This page answers those four in that order for both Claude clients, and leaves "write the
server yourself" to the pages that own it. The demand shape agrees: claude mcp server draws about
1,300 US searches a month, ahead of narrower task terms such as mcp server list at about 880 and
behind the more generic python mcp server at about 1,900 — people with a specific client and a
specific server, asking a setup question rather than a design one (DataForSEO Google Ads, United
States, measured 2026-10-02). For the per-editor field shapes this page does not repeat, the cluster
centre is build an MCP-compatible client.
Where the claude mcp server config lives, and which scope owns it
The first difference between the clients is the shape of the storage.
Claude Desktop has one configuration file per machine, opened from the app: Claude menu → Settings
→ Developer → Edit Config. It sits at ~/Library/Application Support/Claude/claude_desktop_config.json
on macOS and at %APPDATA%\Claude\claude_desktop_config.json on Windows. It holds a single top-level
mcpServers object whose keys are server names; there is no project or workspace scope, so whatever you
put there is available in every conversation and applies after the app restarts
(Connect to local MCP servers).
Claude Code has three scopes, and the scope decides both where the configuration is written and whether teammates get it:
| Scope | Loads in | Shared with team | Stored in |
|---|---|---|---|
| local (default) | current project only | no | the project's entry inside ~/.claude.json |
| project | current project only | yes, via version control | .mcp.json at the project root |
| user | all your projects | no | ~/.claude.json |
Three consequences follow. A local server lives in your home directory even though it is
project-specific — the name is about visibility, not file location, and it is frequently confused with
the unrelated settings.local.json. A project server is the only scope that travels with a clone,
so it is the right home for shared tooling and the wrong home for a credential. A user server
follows you across every repository, which suits a personal utility and not a client-specific
integration. The .mcp.json that project scope writes is the same standard mcpServers block other
clients use, so a block copied from a README is close to portable — close, not identical.
The day-to-day surface is small: claude mcp list prints every server with a health status,
claude mcp get <name> prints one server's configuration, claude mcp remove <name> --scope <scope>
removes it from a scope, and the /mcp panel shows connection state and a tool count. For a team,
"how do I reproduce this" reduces to committing one .mcp.json and documenting its variables.
Scope precedence: which server wins when the same name appears twice
Real machines accumulate duplicates. When the same name is defined in more than one place, Claude Code
connects once using the highest-precedence source — and uses the entire entry from that source,
never merging fields across scopes. The order is local, project, user, plugin-provided servers, then
claude.ai connectors; an organization's managed server ranks above all of them. Duplicates across the
three scopes are matched by name; plugins and connectors by endpoint, where two spellings count
as one endpoint if they differ only in scheme or host case, a default port such as :443 on https,
or a trailing slash.
Two rules fall out. If you edit a project-scoped server and see no change, check whether a local-scoped
server of the same name is shadowing it, and fix that with claude mcp remove <name> --scope local.
And when one name points at two endpoints in two scopes, Claude Code warns in claude mcp list and
/mcp, and it stores OAuth sign-ins per endpoint — authenticating the definition that loads in one
project does not carry to a project where a different definition loads. A team that keeps one definition
per name, in one scope, meets neither problem. The per-editor field names inside that definition are a
separate question.
Adding a server three ways: from a URL, from a command, from a pasted block
Claude Code gives three entry points. A url means the server is remote: add it with --transport http and, if the instructions include a token, an --header value. A launch command means a local
stdio process: put the whole command after a -- separator so flags such as -y reach the server
rather than the Claude CLI, and pass environment variables with --env after the server name and
before the separator. An mcpServers block copied from another client's docs goes in through
claude mcp add-json <name> <json>, which takes the object inside the mcpServers wrapper, not the
wrapper itself.
The pasted-block path has two repairs that catch most people. A url entry with no type is read as a
stdio server, so Claude Code skips it and reports that the entry has a url but no type; add type
as http (or sse / ws) to match the endpoint. And a server key containing anything but letters,
numbers, hyphens, and underscores is rejected, because claude mcp commands enforce that name shape —
pick a compliant name. There is also a migration path from the other client:
claude mcp add-from-claude-desktop opens a dialog that imports your Desktop servers, on macOS and
Windows Subsystem for Linux, and it reports and skips any name it cannot accept while importing the
rest.
Every claude mcp add and claude mcp add-json prints an Added ... line on success, and that line
means the configuration was written — not that the server connected. The command saves the entry
without validating credentials, so a placeholder token is accepted here and fails later. To confirm a
connection, run claude mcp get <name> or /mcp and read the status.
stdio or HTTP: choosing the transport for your Claude server
The transport is the declaration with the most operational consequence, and the rule is simple: stdio when the server runs on the same machine as Claude, HTTP when it does not.
A stdio server is described by how to start it — a command, its arguments, its environment. Claude
launches the process and speaks JSON-RPC over its standard input and output: no port, no URL, no network
surface, which is why it is the default for a local tool that touches your filesystem or a local
database. The cost is that stdout becomes a protocol channel — any banner, console.log, or print
corrupts the stream and the server looks broken though it ran — and a local process is not reconnected
automatically the way a remote server is.
A remote server is described by its URL. --transport http is the recommended option for cloud
services, and type accepts streamable-http as an alias for http, so a block copied from a server's
own documentation usually works unmodified. The older SSE transport is deprecated but still present;
Claude Code tries HTTP first and switches to SSE when the server does not accept it. A WebSocket
endpoint is a fourth case, added differently because --transport does not take ws — and WebSocket
servers do not appear in claude mcp list, so they are checked with claude mcp get or /mcp.
Two timeouts are worth setting on purpose. MCP_TIMEOUT bounds how long Claude Code waits for a server
to start, and raising it to several seconds is the documented fix when a server that begins with a slow
install or an OAuth round-trip fails to come up. And a single server entry can carry a timeout field
in milliseconds that bounds one tool execution, which is how you stop a slow call from holding a session
open. For a server you write yourself, the same build-and-govern split appears from the other side in
the Python MCP server tutorial; what an endpoint, a registry,
and a session actually are is in what an MCP server is.
Permission and authorization prompts: what Claude asks, and what it never asks
Two controls are easy to conflate and they fail differently.
The first is the approval prompt. Claude Desktop asks you to approve file operations the filesystem
server proposes, so you keep a veto over writes. Claude Code asks a different kind: because a
project-scoped server lives in a .mcp.json anyone with commit access can edit, Code prompts in
interactive sessions before using it and lists the server as pending approval until you accept. That
prompt is not a bug to disable. A cloned repository must not be able to approve its own servers, so
.mcp.json approvals are read only from settings files that are not checked in until you trust the
workspace; a server that committed enableAllProjectMcpServers into the project's own settings is
ignored in an untrusted folder. To re-arm the prompt later, claude mcp reset-project-choices.
The second control is authentication. A remote server answering 401 or 403 is flagged so you can
complete an OAuth flow, which is the intended way to hand a hosted server a credential. The exception
is a server whose Authorization header you configured yourself: there a 401 or 403 is reported as a
failed connection rather than an auth prompt, because the credential to fix is the one you wrote. And
there is a sharp edge specific to shared configs: for a remote server's url and headers, Claude
Code reads certain credential variable names from your environment as empty instead of expanding
them, with no warning, so the header goes out with no credential and the server rejects it — usually as
a 401 that reads like a mystery. A name outside that covered set expands as written.
State the boundary plainly: the approval prompt bounds what Claude asks before acting; authentication bounds which server may act. Neither decides what a key may spend. If your concern is the bill rather than the write, that is a quota question one layer in front of the server — the enforcement point the MCP tools reference describes from the annotation side and the MCP inspector alternatives approach from the debugging side.
The troubleshooting order that finds the cause fastest
Most failed connections are found in the first three commands, in this order; editing the config before running them is what turns a five-minute fix into an afternoon.
claude mcp list. It prints a status per server without connecting, so it is cheap. Read the exact status: connected, needs authentication, failed to connect, pending approval, rejected by your settings, or disabled for this project. The status names the category before you open anything.claude mcp get <name>. It prints the exact configuration Claude Code will use, including which scope it came from — which is how you find the local definition shadowing your project one.- Run the configured command yourself, from the same working directory. For a stdio server this is
decisive: if it fails in your terminal it will fail in Claude, and the error names one of four
suspects — the executable is not on
PATH, a referenced variable is unset, the working directory differs, or the server writes non-JSON to stdout.
That third step is where the transports diverge. A stdio failure is local: a spawn ENOENT means
the command was not found, which on Windows often means a wrapping shell is needed for npx; a JSON
parse error at the first byte means a banner on stdout; a missing variable means the env block or the
parent environment does not supply it. A remote failure is an HTTP answer: a 404 means the URL or
transport is wrong, a 401 or 403 means authentication (and if you configured the header, reread the
empty-credential edge above), a timeout or refused connection means reachability. When neither the
command nor the URL is wrong, claude --mcp-debug gives the client-side diagnostics, and the MCP
Inspector is the reference client you point at the same server to prove the server itself works
(Inspector).
Retry behaviour is worth reading before calling a server broken. A remote server that drops mid-session
reconnects with exponential backoff, up to five attempts starting at one second. A failed first
connection is retried up to three times for transient errors — a 5xx, a refused connection, a timeout —
but never for an authentication or not-found error, which needs a config change. Discovery requests
after a connect are retried a few times too, not for auth errors, 4xx, or timeouts. To retry everything
that failed without restarting, run /mcp reconnect all; a session that needed tools from a server
still connecting waits internally rather than needing a re-add.
Claude Desktop specifics: logs, ENOENT, and the shell PATH
The Desktop client fails differently enough to deserve its own order, starting with the restart: a config edit needs a full quit and relaunch, because servers start at launch.
When a Desktop server does not appear, the documented order is: restart completely; check the JSON
syntax of claude_desktop_config.json; check that every path is absolute rather than relative; read the
logs; and finally run the server's command by hand outside the app to see the real error. The logs are
the part people miss. On macOS they are under ~/Library/Logs/Claude, on Windows under
%APPDATA%\Claude\logs. The file mcp.log holds connection and connection-failure logging, while
files named mcp-server-<NAME>.log hold the named server's own standard-error output — and because a
stdio server often logs everything to stderr, those per-server files are not limited to errors.
Two Desktop failures are common enough to name. The ${APPDATA}-in-path problem: if a server's log
shows that literal token where a path should be, add the expanded APPDATA value to the entry's env
block so the child process sees a real path. And the npx problem: if npm is not installed globally,
the launcher that starts the server through npx keeps failing. Both are environment problems that
present as "the server does not show up", which is why the manual-run step comes before any config
editing. The filesystem example is the canonical walk from absolute paths to a restart to a visible
connector, in the worked MCP server example.
Making the claude mcp server config reproducible for a team
A working single-developer config is not yet a team artifact. Turning it into one is three decisions.
Commit the shared servers, keep the credentials out. A project-scoped .mcp.json is designed for
version control, and Claude Code supports environment-variable expansion inside it: a reference expands
to its value, and the same reference with a fallback expands to the variable when set and to the
fallback otherwise. The fallback form is how you commit a working default endpoint without committing a
secret. Expansion applies to the launch command, its arguments, the environment block, and — for remote
servers — the URL and the headers. Two behaviours are worth documenting: an unset variable with no
fallback still loads, but warns in claude mcp list and /mcp and uses the reference text as-is; and
the covered credential names do the opposite in a remote URL or header, reading as empty with no
warning. So keep committed references on your own variable names with fallbacks, and keep covered
credentials in a variable each engineer exports locally.
Pin the server, not just the name. A stdio entry that launches a package should pin a version, because an unpinned launcher can change behaviour under a teammate without any config change.
Agree on names and scopes. Server names may contain only letters, numbers, hyphens, and underscores, and reserved names are rejected. Pick one scope per server and stay there: team tooling under project scope, personal utilities under user scope, experiments under local scope where they cannot leak into a commit. The disable lists are the escape hatch when you inherit a cluster — a per-project opt-out and a separate opt-in list for built-in servers that ship off.
Finally, two warnings are worth watching in review because they are invisible in a diff: hidden leading or trailing whitespace in a config value — usually a token pasted with a trailing newline — reported by field name without echoing the value and never trimmed; and the same name in two scopes with different endpoints, reported as a conflict with each endpoint quoted as written. Both name the field and neither prints a secret, so both are safe to paste into a team chat.
How SmartGate compares with a hand-maintained Claude config
For one developer and one local server, a JSON entry is the right answer and costs nothing.
| What you configure | Who handles drift | What it costs | |
|---|---|---|---|
| A hand-edited Claude config | one entry per server, per client, per scope | you, on every rename or endpoint change | your time; nothing else |
A committed team .mcp.json |
one shared file with variable references | the team, through review | your time; nothing else |
| SmartGate (hosted MCP endpoint) | one URL and one key; no local process | the endpoint: one address, per-key limits, an audit row per call | Free tier: 2M tokens/mo, 120 MCP requests/min/key; Pro from $18/mo (pricing) |
The distinction that matters is who absorbs the change when a server moves. Hand editing makes an endpoint change one edit per scope per machine, with the shadowed-duplicate failure above as the usual outcome; a shared committed file makes it one reviewed edit and a pull; a hosted endpoint keeps clients on a stable URL and puts the governance layer — per-key request rate, monthly token cap, log retention — in one place instead of on each laptop. The four tiers set those limits at 2M, 20M, 100M and 200M+ tokens a month; 120, 300, 600 and 1200 MCP requests a minute per key; audit-log retention of 7, 30, 90 and 180 days; and a team cap of 2, 10, 30 and effectively unlimited keys; the pricing page is authoritative for a decision.
What the compatibility layer repairs, read from our own shims
Every protocol that ships to real clients grows a compatibility layer. The interesting questions are
what it repairs and where it lives. Ours, read on 2026-10-07 from
backend/smartgate/api/mcp_sse_compat.py.
- The defects are specific and they are named. One host sends an empty array where the protocol expects an object; another sets the JSON-RPC method to a tool name instead of the standard call method. Both are client bugs, and both are silent failures if the server refuses to bend.
- The repairs happen outside the protocol core, in an ASGI shim, so the gateway's own handling stays standard and a client that behaves can be served without the shim doing anything.
- They are named after the clients they serve. A shim that describes the client is removable once that client is fixed; a shim that describes the workaround grows into an undocumented dialect.
- The practical rule for anyone running an MCP server: compare what your server received with what the protocol requires before blaming your parser. The most common "invalid request" is a valid request from a client that speaks its own dialect.
- And the honest trade: every accepted deviation is a small debt. The shim logs what it repaired so the debt is visible — a compatibility layer that is quiet about its repairs eventually becomes the protocol, and then no client can be fixed because nobody can tell what is agreed and what is tolerated.
How to get started
- Decide which Claude you are configuring. Desktop means one file opened from the app; Claude Code
means a scope. If unsure, run
claude mcp listand see what is already configured. - Add one server and read its status before adding a second. A URL becomes an HTTP server; a launch
command becomes a stdio server after the
--separator; a pasted block goes throughadd-json. - Connect it and check
/mcp. A connected status and a tool count is the success signal; pending approval means the project-scope prompt is working. - When it fails, run the command by hand before editing anything.
claude mcp get <name>shows the configuration and scope; the manual run shows the real error;claude --mcp-debugshows the client's view. - Make it reproducible last. Move shared servers into a committed
.mcp.json, replace secrets with variable references that have fallbacks, and pin the versions you launch.
If you would rather not run a local process or maintain a config per machine, point the same clients at
a hosted endpoint instead: one URL, one key, no PATH to debug. The seven smart_* tools are reachable
from any MCP host and the free tier needs no card — start
free — then check which caps your fleet would hit on
the pricing page. For a fleet large enough that you want the
platform header mapped to your own identities, the
contact form is the enterprise path.
Frequently Asked Questions
Where is the Claude MCP config file?
It depends on the client. Claude Desktop uses one file, opened from Settings then Developer then Edit Config, in the per-user Claude directory. Claude Code stores local-scoped and user-scoped servers in its own home-directory settings file and project-scoped servers in a file at the project root. The client, not the protocol, decides the location.
My server has a url but Claude says it has no type. What happened?
An entry with a URL and no transport key is read as a stdio server, so Claude Code skips it and asks for a type. Set the type to the HTTP form for a streamable endpoint, or the SSE form for a legacy one, and add it again. The specification's name for the HTTP transport is accepted as an alias, so a copied block usually needs only the type key.
Why does my token read as empty and the connection fail with a 401?
In a remote server's URL or headers, Claude Code deliberately reads certain known credential variable names from your environment as empty rather than expanding them, so a committed config cannot forward your own credentials to a server it names. Copy the credential into a variable with a name of your own; an unset variable with no fallback is a different, warned-about case.
stdio or HTTP — which should I declare?
stdio when the server runs on the same machine as Claude, because it needs no port or URL and fits local files and databases. HTTP when the server is remote or shared, because that is the transport a network client can reach and a gateway can meter. A stdio server's standard output is a protocol channel, so any non-JSON output on it breaks the connection.
Limitations and what this does not do
- This page is operational, not a protocol reference. Where the config lives, which scope wins, which transport to declare, how to debug — not the message format or the lifecycle fields. Those belong to the specification and the cluster's protocol pages.
- Client details move. File locations, flags, scope semantics, and warning text are properties of the client versions current when this was written; the vendor documentation for your installed version is the authority.
- It quotes no code, on purpose. The slice matcher found no unique symbol for any of this page's sections, so every claim comes from the vendors' published documentation and this cluster's own measurements, and nothing here is a transcribed implementation.
- Approval prompts are not a sandbox. The project-scope prompt and the Desktop file-operation prompt are consent gates, not containment; an approved server runs with your account's permissions.
- A hosted endpoint is not the answer to every setup. For one developer and one local server, a single config entry is correct and this page does not argue otherwise.
Sources
-
Claude Code — Connect Claude Code to tools via MCP (scopes, precedence, transports, environment expansion, warnings, retry behaviour): https://code.claude.com/docs/en/mcp
-
Model Context Protocol — Connect to local MCP servers in Claude Desktop (config locations, the Developer → Edit Config flow, logs, ENOENT and npm troubleshooting): https://modelcontextprotocol.io/docs/develop/connect-local-servers
-
Model Context Protocol — introduction and architecture: https://modelcontextprotocol.io/introduction
-
Model Context Protocol — transports, including stdio and streamable HTTP: https://modelcontextprotocol.io/specification/2026-07-28/basic/transports
-
Model Context Protocol — the MCP Inspector, the reference debugging client: https://modelcontextprotocol.io/docs/tools/inspector
-
Anthropic — introducing the Model Context Protocol: https://www.anthropic.com/news/model-context-protocol
-
Demand figures here are this project's own measurements: DataForSEO Google Ads, United States, measured 2026-10-02, recorded in the project's
search_volume.jsonandresearch_brief.md. -
SmartGate — product documentation and the authoritative plan table: https://smartgate.network/docs · https://smartgate.network/pricing
-
The section "What the compatibility layer repairs, read from our own shims" is our own implementation, read on 2026-10-07 from
backend/smartgate/api/mcp_sse_compat.py(origin/main). It states only what those files state.
Method note
This page carries no code excerpt, and that is a finding rather than an omission. The slice
matcher pinned 0 of 8 sections for this page (1 abstention(s),
7 no-slice verdict(s)): rule A found no unique symbol in the scanned repository for any of
the section keywords, and the one section that matched a generic local name failed the service's
slot-proof step, so it is recorded as an abstention rather than quoted. A pinned generic name would
have given the page the shape of a verified article with none of the substance, so every section but one
above is written from the vendors' published documentation — the Claude Code MCP reference and the
protocol's own Claude Desktop guide — and from this cluster's measured demand. The exception is "What the compatibility layer repairs, read from our own shims": our own implementation, read on 2026-10-07 from backend/smartgate/api/mcp_sse_compat.py, and it states only what is written there. Nothing is
transcribed, so there is no provenance table and nothing here has to be asserted verbatim.
The demand figures in the prose come from this project's own paid measurement run, not a third-party
estimate, and the client behaviours described are those the referenced documentation records for the
client versions current when this page was written. No batch fingerprints, auction data, or internal
hosts are present: the task id, scan id, and repository commit stay in this project's
pipeline_results.json and in this file's docstring, never on the page.