DEV Community

ChairmanKaga
ChairmanKaga

Posted on Originally published at appcontext.dev

Configure MCP servers once for Claude Code, Cursor, and Windsurf

If you run more than one AI coding tool, you've done this: added an MCP server, then added it again, then added it a third time. No client reads another client's config. Claude Code, Cursor, and Windsurf each keep their own file in their own place.

There are exactly two ways out — sync one canonical block into every file, or run a local hub that every client points at once. Here's the map first, then the tradeoffs.

Where each tool actually keeps its MCP config

As of August 2026, from each vendor's current docs:

Tool Config file Top-level key
Claude Code — user scope ~/.claude.json mcpServers
Claude Code — local scope (default) ~/.claude.json, nested under the project path mcpServers
Claude Code — project scope .mcp.json in the project root mcpServers
Cursor — global ~/.cursor/mcp.json mcpServers
Cursor — project .cursor/mcp.json mcpServers
Windsurf (Cascade) ~/.codeium/windsurf/mcp_config.json mcpServers
Claude Desktop (macOS) ~/Library/Application Support/Claude/claude_desktop_config.json mcpServers

They all use mcpServers, which is why copy-paste mostly works — and why this problem looks smaller than it is.

Four ways the copies drift

1. Claude Code resolves duplicates by precedence and doesn't merge. Local beats project beats user, and the winning entry is used whole — fields are not merged across scopes. Rotate a key in your committed .mcp.json while a stale local-scoped entry with the same name sits in ~/.claude.json, and the stale one silently wins. claude mcp list shows what actually resolved.

2. Remote-server key names differ. stdio servers are fine everywhere (command + args). Remote is where it breaks: Claude Code and Cursor use url, but Windsurf's documented example uses serverUrl. Paste a Cursor block into Windsurf and it may just not connect.

3. Transport names are moving. Claude Code's docs now say the SSE transport is deprecated, use HTTP where available. It also takes streamable-http as an alias for http. Old configs keep working but stop matching the docs.

4. Every client pays for every server, with different budgets. Windsurf's docs state Cascade has a limit of 100 total tools at any given time. Claude Code instead warns above 10,000 tokens of MCP output and caps at 25,000 by default (MAX_MCP_OUTPUT_TOKENS). One chatty server added to four files pushes on four different limits.

Option 1: sync a canonical block

Keep the master mcpServers block in dotfiles and write it into each client's file. Costs nothing, works fine. Two honest limits: you own the sync forever including the per-client key differences above, and nothing changes at runtime — four clients still spawn four copies of every stdio server.

Option 2: one endpoint

A personal MCP hub is itself an MCP server, but instead of exposing its own tools it connects to your upstream servers and re-exposes theirs behind one address. Each client gets exactly one entry, forever. Tools get namespaced (filesystem:read_file) so two servers that both define search don't collide.

I build AppContext, a macOS menu bar app that does this — it serves its endpoint at http://localhost:7777/sse. Concretely:

Get existing servers in. The Servers tab imports from Claude Code (~/.claude.json), Claude Desktop, and Cursor (~/.cursor/mcp.json), and discovers project .mcp.json files. Windsurf isn't an import source today, so anything living only in mcp_config.json gets added by hand.

Point each client at it. Claude Code, at user scope:

claude mcp add --transport sse --scope user appcontext http://localhost:7777/sse
Enter fullscreen mode Exit fullscreen mode

AppContext serves SSE today, so that's the right flag even though Claude Code labels SSE deprecated in favour of HTTP.

Cursor, in ~/.cursor/mcp.json:

{
  "mcpServers": {
    "appcontext": {
      "url": "http://localhost:7777/sse"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Windsurf, in ~/.codeium/windsurf/mcp_config.json — different key:

{
  "mcpServers": {
    "appcontext": {
      "serverUrl": "http://localhost:7777/sse"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Then delete the old direct entries. Skipping this is the worst of both worlds — every tool exposed twice, once direct and once namespaced.

The part that's actually useful

One endpoint means every tool call from every client passes through one place. Blocking an individual tool — the destructive filesystem call, the repo-creating GitHub tool — applies to every client at once instead of being re-done per client, and the free tier covers up to three blocks in total across all your servers. Beyond three, allowlist mode and per-tool rate limiting are Pro. Blocked calls return POLICY_BLOCKED to the agent rather than failing silently.

Caveats

A hub is a component between your agents and your tools. If it's not running, no client has MCP tools — versus per-client configs, where a broken config only breaks one tool.

AppContext is macOS-only today. It serves SSE rather than Streamable HTTP. And it has no audit log or activity dashboard yet — you can block and rate-limit calls, but you can't review a history of what was called.

If you only use one AI coding tool, don't bother: edit the one file. Once you're maintaining the same list in two or more places, the copies will drift, and the only question is whether you'd rather own a sync script or a single endpoint.

Full version with sources: appcontext.dev/blog/configure-mcp-servers-once-claude-code-cursor-windsurf

Top comments (0)