Problem · April 20, 2026 · 8 min read

MCP config hell: why every AI coding tool stores the same thing differently

Claude wants JSON with `mcpServers`. VS Code wants a `type` field. Zed nests under `context_servers`. Codex uses TOML. Continue uses YAML. Here's why that happened — and the cost every developer is paying.

It starts simply enough. You read a tweet about the Supabase MCP server, click through to the docs, and find a neat JSON snippet. You paste it into Claude Desktop's config file. It works. You close the laptop feeling clever.

Three days later you're setting up Cursor on a new machine. You want the same Supabase server. You open ~/.cursor/mcp.json, paste the same snippet, reload Cursor. Red error. The schema is slightly different. You spend twenty minutes debugging.

A week after that, your team adopts VS Code's agent mode. Now you need the server in VS Code too. Same story, different shape.

By the end of the month you've also got Codex CLI, Windsurf, and Zed in your stack. Five tools. Five config files. Five slightly different representations of the same one-sentence fact: “there is a Supabase MCP server at this URL.”

The actual config shapes, side by side

This is not a hypothetical. These are the real formats each tool expects, taken directly from their documentation.

Claude Desktop, Cursor, Windsurf all expect the mcpServers key with command and args nested under the server name:

{
  "mcpServers": {
    "supabase": {
      "command": "npx",
      "args": ["-y", "@supabase/mcp-server-supabase@latest"],
      "env": { "SUPABASE_ACCESS_TOKEN": "YOUR_TOKEN" }
    }
  }
}

VS Code uses a servers key and requires an explicit typefield that the other clients don't use:

{
  "servers": {
    "supabase": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@supabase/mcp-server-supabase@latest"],
      "env": { "SUPABASE_ACCESS_TOKEN": "YOUR_TOKEN" }
    }
  }
}

Zed nests everything under context_servers and then command is itself a nested object with a path key:

{
  "context_servers": {
    "supabase": {
      "command": {
        "path": "npx",
        "args": ["-y", "@supabase/mcp-server-supabase@latest"]
      },
      "settings": {}
    }
  }
}

Codex CLI abandons JSON entirely in favor of TOML:

[mcp_servers.supabase]
command = "npx"
args = ["-y", "@supabase/mcp-server-supabase@latest"]

[mcp_servers.supabase.env]
SUPABASE_ACCESS_TOKEN = "YOUR_TOKEN"

Continue uses YAML with a list structure instead of a map:

mcpServers:
  - name: supabase
    command: npx
    args: ["-y", "@supabase/mcp-server-supabase@latest"]
    env:
      SUPABASE_ACCESS_TOKEN: YOUR_TOKEN
Count the differences. The same server entry changes format five times across five tools. Some use mcpServers, some servers, some context_servers. Some require type, some don't. Some are JSON, one is TOML, one is YAML. One nests command inside an object; the others treat it as a string.

Why did this happen?

The Model Context Protocol defines what a server does(the JSON-RPC messages it exchanges with clients) but not how client applications should store server configuration on disk. That's a deliberate scope choice: MCP is a network protocol, not a package manager.

So each tool team made their own call. Cursor looked at their existing settings JSON and added an mcpServerskey to match Claude Desktop's published format. VS Code had an existing settings architecture that called for a type discriminator on every server entry, so they added one. Zed had context_servers already in their settings file from an earlier feature. The Codex CLI team defaulted to TOML because OpenAI's other config files use TOML. Continue, as a YAML-first tool, kept it YAML.

None of these decisions were wrong, exactly. They each made sense given each team's existing code and conventions. But the aggregate result is a maintenance burden that falls entirely on the developer installing servers.

What this costs in practice

The time cost is the obvious part. Fifteen minutes of frustration every time you add a new server is bad, but it's bounded. The subtler costs compound over time.

Sync drift.Once you've hand-copied a server config into five files, those five files immediately start diverging. You update the Supabase token in Claude Desktop. You forget to update it in Cursor. Two weeks later you file a bug about Cursor not having database access, and spend 40 minutes before you realize the token expired everywhere else too.

Source-of-truth confusion.Which config file is canonical? If a colleague asks “what MCP servers do we use?,” the answer is scattered across five files in three different formats. There's no single list. You develop the habit of checking each file manually, which means you frequently miss one.

Onboarding friction.When someone new joins your team, they need to install all the servers. There's no package.jsonequivalent for MCP configs. The documentation is “look at John's Claude Desktop config and translate it for whatever tools you use.” John uses Cursor. You use VS Code. The translation is manual.

Format regressions. Each of these tools updates. VS Code added the typefield requirement mid-way through 2024. If you'd copied a VS Code config from before that change, it would silently stop working after an update. With five tools, you have five independent versioned config schemas to track.

The .bak problem. Most developers know to make a backup before editing any config file by hand. So now you also have five claude_desktop_config.json.bak, mcp.json.bak, settings.json.bak files floating around, each potentially containing a different version of the same server list. Good luck knowing which one is current.

The abstraction that's missing

What all five formats have in common is the same underlying information: a server has a name, a transport (stdio or HTTP), and either a command with arguments or a URL with headers. That's it. Every format difference above is just a different rendering of that four-field record.

The missing piece is a tool that holds one copy of that record and renders it into each format on demand. Something you paste your config into once, and it figures out the rest.

That's what MCPBolt is. But the config-hell problem is real regardless of how you solve it, and understanding it fully is the prerequisite to appreciating why any solution has to work at the format-translation layer, not just at the GUI layer.

In the next post, we walk through what MCPBolt does and why it was built the way it was.