MCP servers are configured with JSON. You add a server name, a transport type, and either a command to run locally or a URL to reach remotely. Every major AI client - Claude Desktop, Claude Code, VS Code, Cursor, Windsurf, Cline, Continue, Amazon Q, Yaw Terminal - uses the same basic format. But they all store it in different places, with different scopes and different quirks.
This guide covers everything: the config format, where each client keeps it, how project-level and global configs interact, and the gotchas that trip people up.
The config format
Every MCP config file has the same shape. A top-level mcpServers object where each key is a server name and each value describes how to connect.
There are two transport types that matter: stdio for local servers and Streamable HTTP for remote servers.
stdio - local servers
The client launches the MCP server as a child process on your machine. Communication happens over stdin/stdout. You specify a command, optional arguments, and optional environment variables.
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"],
"env": {
"DEBUG": "true"
}
}
}
}When you save this config and your client reads it, it will run npx -y @modelcontextprotocol/server-filesystem /Users/me/projects as a subprocess. The MCP client and server talk over that process’s stdin and stdout using JSON-RPC.
stdio is the simplest transport. No network, no auth, no ports. But it only works when the server and client are on the same machine.
Streamable HTTP - remote servers
For remote servers, the client connects to a URL over HTTP. The server lives somewhere else - a cloud VM, a container, a managed platform.
{
"mcpServers": {
"my-api": {
"type": "http",
"url": "https://my-api.example.com/mcp",
"headers": {
"Authorization": "Bearer sk-abc123"
}
}
}
}Streamable HTTP replaced the older SSE transport starting with the March 2025 spec revision (formalized in the 2025-11-25 spec). It uses a single endpoint instead of two, supports stateless operation, and works with standard proxies and load balancers. If your server still uses SSE, it will work for now, but new servers should use Streamable HTTP.
Where configs live
This is where things get fragmented. Every client stores MCP config in a different location, and most support multiple scopes - project-level, user-level, or both. Here is the full map.
Want to skip the file-path archaeology? Run npx -y @yawlabs/mcph install <claude-code | claude-desktop | cursor | vscode> once and mcph picks the right path for your OS, merges the launch entry without clobbering anything you already have, and saves your token to ~/.mcph.json so every other MCP client on the machine inherits it. The full per-client map below is here for the cases where you want to control scope by hand.
Claude Code (CLI)
Claude Code has the most sophisticated config system with three scopes:
- Project -
.mcp.jsonat the project root. Checked into version control, shared with the whole team. - User -
~/.claude/settings.json(top-levelmcpServersfield). Available to you across every project. Not~/.claude.json- that file is Claude Code’s per-project state file (rewritten constantly), and pasting a globalmcpServersblock there doesn’t load globally and risks overwriting conversation/project state. - Local -
<project>/.claude/settings.local.json. Private overrides for one project, typically gitignored.
You can also manage servers with CLI commands instead of editing JSON directly:
# Add a remote server to the current project
claude mcp add my-server --type http --url https://my-server.example.com/mcp
# Add a local stdio server globally
claude mcp add --scope user db-tools -- npx -y @modelcontextprotocol/server-sqlite ./db.sqlite
# List all configured servers
claude mcp list
# Import servers from Claude Desktop
claude mcp add-from-claude-desktopClaude Desktop
Single config file, no scope hierarchy:
- macOS -
~/Library/Application Support/Claude/claude_desktop_config.json - Windows -
%APPDATA%\Claude\claude_desktop_config.json
Same mcpServers format as everything else. No project/user distinction - all servers are global.
Claude.ai (web)
Claude.ai supports MCP through custom connectors in Settings → Integrations. But there is an important limitation: connectors only support remote servers over Streamable HTTP. There is no stdio option - you cannot launch a local process from a browser.
This means if you have a stdio MCP server that you want to use with Claude.ai, you need to host it somewhere as a remote HTTP endpoint first. More on that below.
VS Code (GitHub Copilot)
- Workspace -
.vscode/mcp.json - User - via the “MCP: Open User Configuration” command
Important: VS Code uses servers as the root key, not mcpServers. It is the only major client that does this.
// .vscode/mcp.json
{
"servers": {
"playwright": {
"command": "npx",
"args": ["-y", "@microsoft/mcp-server-playwright"]
}
}
}Cursor
- Project -
.cursor/mcp.json - Global -
~/.cursor/mcp.json
Uses mcpServers as root key. One quirk: environment variable references use ${env:VAR_NAME} syntax instead of the more common ${VAR_NAME}.
Windsurf
- Global only -
~/.codeium/windsurf/mcp_config.json
Uses mcpServers root key. No project-level config support.
Cline
- Global only - stored in the VS Code extension globalStorage directory under
saoudrizwan.claude-dev/settings/cline_mcp_settings.json
Uses mcpServers root key. Adds disabled (boolean) and autoApprove (array of tool names) fields per server.
Amazon Q Developer
- Global -
~/.aws/amazonq/mcp.json - Workspace -
.amazonq/mcp.json
Uses mcpServers root key. Server names must be unique across both scopes.
Yaw Terminal
- Global -
~/.yaw/mcp.json - Project -
.mcp.jsonat the project root (same as Claude Code)
Uses mcpServers root key. Yaw Terminal supports both stdio and Streamable HTTP transports, and reads the same .mcp.json project config that Claude Code uses.
Project-level config: .mcp.json
The .mcp.json file at your project root is the most important config file if you work on a team. You check it into version control, and every teammate gets the same MCP servers when they clone the repo. No setup docs, no “add this to your local config” Slack messages.
// .mcp.json (project root)
{
"mcpServers": {
"database": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-sqlite", "./dev.db"]
},
"staging-api": {
"type": "http",
"url": "https://staging-api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${STAGING_API_KEY}"
}
}
}
}When a teammate opens the project in Claude Code, they get prompted to approve the project-scoped servers on first use. This is a security feature - project configs run commands on your machine, so you should review them before approving.
Notice the ${STAGING_API_KEY} in the config. Claude Code expands environment variables in commands, args, URLs, and headers. This means you can check in the config without hardcoding secrets - each developer sets the variable in their own shell environment.
Override behavior
When the same server name exists at multiple scopes, the most specific scope wins:
- Local (highest priority) - your personal config for this project
- Project - the
.mcp.jsonin the repo - User/Global (lowest priority) - your global config
This means a team can define shared defaults in .mcp.json, and individual developers can override specific servers locally. For example, the project config might point at a shared staging server, but you override it locally to point at your own dev instance.
Common pitfalls
Windows and npx
On native Windows (not WSL), npx commands in stdio configs need a cmd /c wrapper:
{
"mcpServers": {
"my-server": {
"command": "cmd",
"args": ["/c", "npx", "-y", "@some/mcp-server"]
}
}
}Without the wrapper, the process often fails to spawn. This trips up a lot of people.
VS Code uses a different root key
If you copy a config from Claude Desktop or Cursor into .vscode/mcp.json, it will not work. VS Code expects servers, not mcpServers. Every other client uses mcpServers.
Env var syntax varies by client
- Claude Code:
${VAR}or${VAR:-default} - Cursor:
${env:VAR} - Continue:
${{ secrets.VAR }}
There is no standard. If you switch clients, double-check how environment variables are referenced.
SSE is deprecated
If your config uses "type": "sse", it still works in most clients but is deprecated as of the March 2025 spec update. Switch to "type": "http" when you can.
stdio vs. remote: the trade-off
stdio servers are simple. No infrastructure, no auth, no deployment. Clone the repo, run the server, done. But they have real limitations:
- They only work on the machine running the client - no sharing across devices.
- Claude.ai custom connectors do not support stdio at all. Browser-based clients cannot launch local processes.
- Every teammate runs their own copy, which means inconsistent versions, missing dependencies, and “works on my machine” issues.
- No centralized logging, no rate limiting, no usage metrics.
Remote servers solve all of this but add deployment complexity. You need a host, a domain, TLS, auth, and monitoring. That is a lot of infrastructure work for what started as a simple tool.
There is a middle ground for teams that want remote access without public exposure: tailscale-mcp lets you run MCP servers over a Tailscale private network. Your server stays on your infrastructure with no public endpoint, but any teammate on the tailnet can reach it as a remote Streamable HTTP server. It bridges the gap between “localhost only” and “fully public.”
The orchestration option
Once you are running more than two or three MCP servers, the config file itself becomes the problem. Every new machine is another round of JSON edits, every secret rotation is a cross-device chore, and every teammate has their own slightly-different setup.
mcp.hosting collapses all of that into a single install. You add servers in a web dashboard, install the mcph CLI once, and drop one entry into your client config:
{
"mcpServers": {
"mcph": {
"command": "npx",
"args": ["-y", "@yawlabs/mcph"],
"env": { "MCPH_TOKEN": "mcp_pat_..." }
}
}
}Same config on every machine. Same servers for every teammate. Add, remove, or rotate credentials from the dashboard and every device picks up the change on next run - no JSON edits, no version drift.
Jeff Yaw, Yaw Labs. Follow along at tokenlimit.news for weekly notes on AI infrastructure.