Content
<div align="center">
# 🔄 mcp-sync
### one config to rule them all (Claude Code, Cursor & VS Code)
[](./LICENSE)
[](https://nodejs.org)
[](https://www.typescriptlang.org/)
[](https://github.com/sbotargues/mcpctl/pulls)
**stop hand-editing 3 different JSON files just to install one (1) MCP server 😭**
</div>
---
## 🤔 the problem
MCP servers give AI coding tools superpowers — reading files, hitting a
database, searching the web. Cool. Except every tool wants to hear about
them in its **own** slightly different JSON dialect:
| | wants its config in... |
| -------------- | ------------------------- |
| 🟠 Claude Code | `.mcp.json` |
| ▲ Cursor | `.cursor/mcp.json` |
| 🔵 VS Code | `.vscode/mcp.json` |
So either you commit those files (👋 hello, leaked tokens in git history)
or your whole team is manually keeping 3 near-identical JSON files in sync
by hand, forever, for every new server. No.
## ✨ the fix
You write **one** list. `mcp-sync` figures out the rest.
```sh
mcp-sync sync
```
```
? Select which tools to configure
❯◉ Claude Code
◯ Cursor
◯ VS Code
? Select the MCP servers to configure
❯◉ filesystem - Read and write files in an allowed local directory
◉ memory - Persistent knowledge graph memory across sessions
◯ github - Search and manage GitHub repos, issues and pull requests
...
✔ Synced 2 MCP server(s) for Claude Code.
```
That's it. Pick your tool(s), pick your servers, done. Re-run it later and
it only touches what changed — anything you added by hand stays put.
> [!TIP]
> Servers that need a personal API token (`github`, `slack`, `brave-search`)
> come **unchecked by default**. Nobody gets forced to go dig up a token
> just to try the thing.
## 🚀 quick start (no need to read the rest, we promise)
**1. Get the code**
<table>
<tr><td>
**npm**
```sh
git clone https://github.com/sbotargues/mcpctl.git
cd mcpctl
npm install
npm run build
```
</td><td>
**yarn**
```sh
git clone https://github.com/sbotargues/mcpctl.git
cd mcpctl
yarn install
yarn build
```
</td></tr>
</table>
**2. Run it**
```sh
node dist/cli.js sync
```
Want to just type `mcp-sync` instead? Run `npm link` (or `yarn link`) once
after building — links it globally from your own clone. Then:
```sh
mcp-sync sync
```
**3. Answer the questions on screen** — space to select, enter to confirm.
Paste a token if one is asked for (it's hidden as you type).
**4. Restart your AI tool.** Your servers show up. 🎉
Run `sync` again any time you want to add or drop a server.
### 🧩 want your own servers instead of the examples?
Open `.mcp-sync.json` in literally any text editor. Delete what you don't
want, copy an entry and change the name/command for your own. **Zero code
required** — see the [manifest format](#-the-manifest-mcp-syncjson) below.
## 📖 commands
| Command | What it does |
| ----------------- | -------------------------------------------------------------------- |
| `mcp-sync init` | Creates a starter `.mcp-sync.json` with a few well-known MCP servers |
| `mcp-sync list` | Lists the servers defined in `.mcp-sync.json` |
| `mcp-sync sync` | Interactive: pick tools → pick servers → generate personal configs |
*(swap `mcp-sync` for `node dist/cli.js` if you didn't `link` it — see
[quick start](#-quick-start-no-need-to-read-the-rest-we-promise))*
`sync` supports `--dry-run` to preview without writing anything.
## ⚙️ how `sync` actually works
1. **Pick your tool(s)** — Claude Code, Cursor, VS Code, any combo.
2. **Pick your servers** — pre-checked by `defaultEnabled`, unless you
already have a config, in which case it reflects what's already there.
3. **Missing secrets only** — asks once, reuses it forever after.
4. **Merge, never nuke** — anything you added by hand that isn't in
`.mcp-sync.json` survives. Deselecting a server removes only that entry.
> [!NOTE]
> All three generated files (`.mcp.json`, `.cursor/mcp.json`,
> `.vscode/mcp.json`) are meant to be **gitignored**. They're personal.
## 🔒 where do my tokens go?
Nowhere they shouldn't. Specifically:
- The manifest (`.mcp-sync.json`) only ever stores the *name* of the env
var a server needs (e.g. `GITHUB_PERSONAL_ACCESS_TOKEN`), never a real
value. That's the whole point of committing it.
- If a token's missing, it's typed straight into your own terminal with
a masked/hidden input — nothing gets sent anywhere, logged, or shown on
screen.
- It only gets written to your local, gitignored `.mcp.json` /
`.cursor/mcp.json`. It never touches GitHub, npm, or this project's code.
- For VS Code specifically, it's not even written as plain text — VS Code
prompts for it itself and keeps it in its own secure storage.
## 🗂 the manifest: `.mcp-sync.json`
```jsonc
{
"servers": [
{
"name": "github",
"description": "Search and manage GitHub repos, issues and pull requests",
"defaultEnabled": false,
"secrets": [
{ "env": "GITHUB_PERSONAL_ACCESS_TOKEN", "description": "GitHub personal access token" }
],
"targets": {
"claude": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
"cursor": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] },
"vscode": { "type": "stdio", "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] }
}
}
]
}
```
- `defaultEnabled: false` → for servers that need a personal secret. Opt-in,
not opt-out.
- `targets` is a **map**, not fixed fields — adding a 4th tool later doesn't
need a schema change.
- No `vscode` target on a server? It just won't be written to
`.vscode/mcp.json`. Simple.
This repo dogfoods itself — [`.mcp-sync.json`](./.mcp-sync.json) is a real,
working example.
## 🙅 why not just commit the config files?
Because secrets end up in them, and because three tools want three
different shapes for the same idea. `mcp-sync` keeps one source of truth
and adapts it per tool, per person — so nobody's copy-pasting JSON by hand
ever again.
---
<div align="center">
made because editing the same JSON three times felt like a personality flaw
⭐ star it if it saved you from JSON hell · [issues & PRs welcome](https://github.com/sbotargues/mcpctl/issues)
**MIT licensed**
</div>