# One2rule Them All Mcps

> Merge and sync MCP server definitions across every MCP client config on the machine (Claude Desktop/Cowork, Claude Code, Codex, Gemini CLI, Antigravity, Cursor) so each client ends up with the full union of servers, written in that client's native format, with nothing lost. Use this whenever the user wants to reconcile, sync, merge, consolidate, copy, or "share" MCP servers between clients/apps/tools — e.g. "I added a server in Cursor but Claude Desktop doesn't have it", "get all my MCP servers into every tool", "my MCP configs are out of sync", "one config to rule them all", or any request involving MCP server lists drifting apart across clients. Trigger even if the user names only one or two clients or doesn't say the word "reconcile."

- Skill: `yakhandler/one2rule-them-all-mcps` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add yakhandler/one2rule-them-all-mcps`
- Raw SKILL.md: https://api.skillmd.com/api/skills/yakhandler/one2rule-them-all-mcps/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: yakhandler (https://skillmd.com/u/yakhandler)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/yakhandler/one2rule-them-all-mcps

---


# Reconcile MCP Servers Across Clients

Each MCP client keeps its own list of MCP servers in its own file and format, so the
lists drift: a server you add in Cursor never shows up in Codex, one you set up for
Claude Desktop is missing from Gemini, and so on. This skill computes the **union** of
every server across every client present on the machine and writes that complete list
back into each client — in the format that client expects — without losing or silently
changing anything.

All the real work is done by **`scripts/reconcile_mcp.py`**. Your job is to run it,
interpret its output for the user, and handle conflicts. Do not hand-edit config files
yourself — the script handles format conversion, file preservation, and backups
correctly, and hand-editing risks corrupting large files like `~/.claude.json` or
Codex's `config.toml`.

## What it guarantees

These properties are why the tool exists; keep them in mind when explaining results:

- **No server is ever deleted.** Every client ends with at least the servers it started
  with. The merge is purely additive (plus conflict resolution you approve).
- **Only the MCP section is touched.** Everything else in each file — Codex profiles,
  Claude Code's `projects`/history, Gemini settings, etc. — is preserved. Project-scoped
  servers inside `~/.claude.json`'s `projects` block are intentionally left alone; only
  the top-level global `mcpServers` is reconciled.
- **Every file is backed up** before it's written (`<file>.bak-<timestamp>`).
- **Conflicts stop the process.** If the same server *name* has *different* definitions
  in two clients, the tool refuses to guess — it reports both and writes nothing until
  the user decides.

## Workflow

### 1. Run a plan first (always)

Never start with `--apply`. Run the default dry-run so you and the user can see exactly
what would change:

```
python3 <skill_dir>/scripts/reconcile_mcp.py
```

> Use `python3` on macOS/Linux; on Windows use `python` (or `py -3`). The engine needs
> Python 3.11+ for Codex (stdlib `tomllib`); if launched on an older interpreter it
> auto-re-execs into the newest `python3.x` on PATH. If none is found, the JSON clients
> still sync but Codex is skipped (with a message telling you how to fix it).

The script auto-discovers each client's config from standard locations (see
`references/client-config-paths.md`). It only considers clients whose config file
actually exists; ones that aren't installed are listed as skipped.

Read the output and summarize for the user: how many clients were found, how many unique
servers make up the union, and which servers are missing from which clients. The
"Per-client plan" section shows `+ add`, `~ change`, and `= unchanged` counts per client.

### 2. Handle conflicts (exit code 2)

If the report shows **BLOCKING CONFLICTS** (and the script exits with code 2), do not
apply anything by default. Show the user the conflicting definitions the script printed
(it also prints a ready-to-use `--prefer` suggestion and lists the clients involved) and
ask how they want to resolve each one. There are three ways forward:

- **Pick a winning client** — re-run with `--prefer`, a comma-separated priority list of
  client keys. For each conflicting name, the first client in the list that defines it
  wins. Example: `--prefer cursor,claude-code` means "use Cursor's version; if Cursor
  doesn't have it, use Claude Code's."
- **Edit to match** — the user edits one config so both definitions are identical, then
  you re-run the plan and the conflict disappears.
- **Skip them for now** — re-run with `--skip-conflicts` to sync everything *except* the
  conflicting names, leaving each client's own copy of those untouched (nothing is
  overwritten or deleted). The conflicts are still reported and the exit code stays **2**
  so they aren't forgotten. Good when the user wants the non-conflicting servers in place
  immediately and will reconcile the rest later. Confirm with the user before using it,
  since it leaves real divergence unresolved.

When only one or two servers are the problem, prefer the **per-server flags** over the
blunt `--skip-conflicts` (which skips *every* conflict). These can be combined in one run:

- `--skip-servers <name[,name...]>` — leave those servers completely untouched in every
  client (each keeps its own copy); they never block the run and don't count toward the
  exit code. This is the right tool for **app-managed servers** a tool regenerates on its
  own. The canonical example is **`node_repl`**: Codex and Antigravity rewrite it per
  install with machine-specific binary paths and a session pipe, so it can't be unified and
  shouldn't be — when you see `node_repl` as the only remaining conflict, the expected
  resolution is simply `--skip-servers node_repl`.
- `--pin <server>@<client>` — keep one client's copy of a server as-is (never changed,
  never added there) while still syncing that server to the other clients. Use when a
  single client intentionally needs a different definition (a different transport, account,
  or credential) than everyone else.
- `--prefer-server <server>=<client>[,client]` — pick the winner for one specific server,
  overriding the global `--prefer` order. Handy when different servers should be won by
  different clients in the same run.

Valid client keys: `claude-desktop`, `claude-code`, `codex`, `gemini`, `antigravity`,
`cursor`, `agents`. Resolving a conflict by preference will *change* the losing clients'
entries to match the winner — call that out explicitly before applying, since it's the one
case where an existing definition gets overwritten.

`agents` is the vendor-neutral [.agents standard](https://dotagentsprotocol.com/) config at
`~/.agents/mcp.json` (same `mcpServers` JSON schema). It's a first-class source and
destination and is **created if missing**, so the standard location always exists.

### 3. Apply on confirmation

Once the plan looks right and any conflicts are resolved, get the user's go-ahead and
run with `--apply` (carry over the same `--prefer` you used in the plan):

```
python3 <skill_dir>/scripts/reconcile_mcp.py --apply [--prefer <keys>]
```

Report back: which files changed, what was added to each, and where the backups were
written. Remind the user that clients which were running may need a restart to pick up
the new servers.

## Useful options

- `--prefer <keys>` — priority order to auto-resolve name conflicts (see above).
- `--skip-conflicts` — sync the non-conflicting servers and leave conflicting names alone
  instead of blocking the whole run (see above). Exit code stays 2; nothing is overwritten.
- `--skip-servers <names>` — leave specific server names untouched everywhere (each client
  keeps its own); they never block the run. For app-managed servers like `node_repl`.
- `--pin <server>@<client>` — keep that client's own copy of the server, while still syncing
  it to the others. Repeatable; comma-separated tokens allowed.
- `--prefer-server <server>=<client>[,...]` — per-server winner, overriding `--prefer` for
  that one server. Repeatable.
- `--only <keys>` / `--exclude <keys>` — limit which clients participate. Use `--exclude`
  if the user wants to leave a particular client out of the sync.
- `--create-missing` — also create config files for clients that don't have one yet.
  Off by default, because fabricating a config for an app that isn't installed is usually
  not what the user wants. Only use it if they explicitly ask to set up a client that has
  no config.
- `--json` — machine-readable summary instead of the text report; handy if you need to
  reason programmatically about the plan before deciding next steps.
- `--home`, `--appdata`, `--localappdata` — override path roots (used for testing; not
  needed in normal runs).

## Things worth knowing

- **Cosmetic differences are normalized away** so they don't surface as false conflicts.
  The tool compares servers by their *functional* definition and ignores fields that are
  defaults or client-specific decoration:
  - `type: "stdio"` — a command-based server is stdio by definition; Antigravity records it
    explicitly, others omit it. Dropped for comparison, re-added only when writing Antigravity.
  - `enabled: true` — the universal default (no `enabled` key = enabled). `enabled: false`
    is meaningful and kept.
  - `gallery` / `version` — GitHub MCP-registry provenance some installers stamp on and
    Antigravity strips; the real version lives in the command/args, so these are noise.
  - **Remote transport** — `{type:"http", url}`, a bare `{url}`, and Antigravity's
    `{serverUrl}` for the same endpoint compare equal. On write, each client gets its native
    shape (`type`+`url` for most, `serverUrl` for Antigravity). An explicit `type:"sse"` is
    a real transport difference and is kept.
  Existing entries are never reflowed just to shed these — a client is only rewritten when it
  has a genuine add/change, and pinned / `--skip-servers` / unresolved entries are written
  from their raw original, not the normalized form.
- **Codex is the only non-JSON target.** Its `config.toml` uses an `mcp_servers` table
  with snake_case. The tool reads it with stdlib `tomllib`, surgically replaces just the
  `[mcp_servers.*]` tables, and leaves the rest of the file (comments, model settings,
  profiles) intact.
- **JSON files are edited surgically.** Only the top-level `mcpServers` value is replaced
  in the raw text (or inserted if absent); every other byte — other keys, whitespace, key
  order, even project-scoped servers under `projects.*.mcpServers` — is left exactly as it
  was. This matters most for `~/.claude.json`, which holds auth tokens and a lot of other
  state. The replaced block is re-serialized to match the file's own indentation. The edit
  is verified by re-parsing before writing; if that ever fails (it shouldn't), that file is
  **left untouched** and reported rather than reformatted — the run exits non-zero (code 3)
  and the other clients still sync. Tell the user which file was skipped so they can re-run
  or report it.
- **Scope is the six clients above, global configs only.** Project-local configs (e.g. a
  `.cursor/mcp.json` inside a repo, or per-project servers in `~/.claude.json`) are out
  of scope and untouched.

## Running the same server twice (aliasing for multiple accounts)

An entry's *key* (`"supabase-personal"`, `"marq-snowflake"`, …) is an arbitrary label you
choose — nothing validates it against the package or `env` underneath. So you can point
several keys at the **same** MCP package with different settings, and they all run side by
side. The common case is one account per key:

```jsonc
"supabase-work":     { "command": "npx", "args": ["-y", "@supabase/mcp-server-supabase@latest"],
                       "env": { "SUPABASE_ACCESS_TOKEN": "sbp_…work" } },
"supabase-personal": { "command": "npx", "args": ["-y", "@supabase/mcp-server-supabase@latest"],
                       "env": { "SUPABASE_ACCESS_TOKEN": "sbp_…personal" } }
```

Why this works, and how to do it cleanly:

- **A credential is fixed at launch.** A server reads its token from `env` once when it
  starts — there's no runtime account switch — so two accounts genuinely need two entries.
- **Tools are namespaced by key.** The client exposes each entry's tools as
  `mcp__<key>__<tool>`, so `mcp__supabase-work__execute_sql` and
  `mcp__supabase-personal__execute_sql` never collide. Name keys by account/purpose so both
  you and the agent can tell them apart.
- **Keys must be unique per file** (it's a dict — a duplicate silently overwrites) and
  should stick to `[A-Za-z0-9_-]`, since the key becomes part of the tool names.
- **Reconciling treats aliases as ordinary, distinct servers.** To have an alias in every
  client, just let the union spread it. To keep one alias in only *one* client (e.g. a work
  account only in Claude Code), that's an intentional divergence — `--pin <alias>@<client>`
  the others, or `--skip-servers <alias>`, so a later run doesn't copy it everywhere.

### Creating aliases with `scripts/clone_server.py`

To avoid hand-editing every client to set this up, use the companion **`clone_server.py`**.
It duplicates an existing server into new aliased entries across clients, applying field
overrides, in each client's native format — reusing reconcile's writers, adaptation, and
`.bak-<stamp>` backups, with the same dry-run-first / `--apply` flow. Run a plan first.

```
# Split one entry into two account-scoped aliases everywhere, dropping the original:
python <skill_dir>/scripts/clone_server.py --from supabase-mcp-server \
    --to supabase-work     --set supabase-work.env.SUPABASE_ACCESS_TOKEN=sbp_work \
    --to supabase-personal --set supabase-personal.env.SUPABASE_ACCESS_TOKEN=sbp_personal \
    --remove-source --apply
```

Key points:
- `--from <name>` is the template (resolved from whichever client has it; if it differs
  across clients, pass `--from-client <key>` to choose). `--to <name>` is repeatable.
- `--set <alias>.<field.path>=<value>` overrides a field on one alias (the first segment is
  the alias name; the rest is a dotted path, e.g. `env.SUPABASE_ACCESS_TOKEN`). Values are
  strings; prefix with `json:` for a non-string (`--set a.disabled=json:true`).
- `--clients <keys>` limits which clients are written (default: all that exist).
- `--remove-source` deletes the `--from` server after creating the aliases — the one delete
  in the toolkit, opt-in, still backed up. Omit it to keep the original alongside the copies.
- Existing alias names are **not** clobbered unless you pass `--force`.

For the exact paths, formats, and per-client quirks, see
`references/client-config-paths.md`.

