One control panel for Model Context Protocol (MCP) configs across Windows + WSL and across Windsurf, LM Studio, VS Code, and Devin.
Diagnose what's broken, push your master config to every tool, and keep them in sync on a schedule — with dry-runs, diffs, backups, and desktop notifications.
Stdlib-only Python. No pip install. Cross-platform.
2026-05 update: Added
mcp_memory_sync.pyto mirror the Memory MCP's knowledge-graph file between Windows and WSL. Previously the toolkit only synced configs, leaving the two memory graphs to silently diverge — and in one observed case lose 39 entities + 60 relations across the OS boundary. See "Memory sync" below.
If you use MCP servers (Atlassian, GitHub, Postgres, custom servers, …) across multiple AI tools and OSes, you'll eventually hit some combination of:
- 5 different config files with 2 different schemas (Windsurf/Cursor/Claude/Devin use
mcpServers, VS Code usesservers). - Secrets scattered across
secrets.env,.env, inline JSONenvblocks, and OS-level env vars. - Wrapper scripts on Linux (the
bash mcp-foo.shpattern) that don't run on Windows-native. - Drift — you change one config, forget the others, and your friend asks "why doesn't your
mcp-doctorsetup work on my machine?" - Silent failures — Windsurf says "no MCP access" with no clue why; toasts get suppressed by an unregistered AppId;
npxpackage cold-start exceeds the IDE's discovery timeout.
mcp-toolkit solves all of that with four small Python scripts that compose into one safe, interactive tool.
| Script | One-line summary |
|---|---|
mcp.py |
Front-door interactive menu (or subcommand). Always shows dry-run + diff before applying. Recommended default entry point. |
mcp_doctor.py |
Smoke-test every MCP server in your config. Auto-detects Windsurf/Cursor/Claude/VS Code/Devin configs across Windows/macOS/Linux/WSL. |
mcp_sync.py |
Translate-and-distribute one master config to all tools. Unwraps bash wrapper scripts. Skips servers it can't translate. Backs up before every write. |
mcp_sync_daemon.py |
One-shot scheduled wrapper (cron / Task Scheduler). Markdown reports + desktop toasts. Skips silently if not due. |
mcp_memory_sync.py |
Mirror the Memory MCP knowledge-graph (MEMORY_FILE_PATH JSONL) across Windows + WSL. Bidirectional union merge by default; backs up before every write. |
mcp.py invokes the others under the hood. Power users can call any script directly.
# Inside the repo:
uv run mcp.py
# or, if you don't have uv:
python3 mcp.pyYou'll see:
=========================================
MCP — main menu
=========================================
Pick an action:
1) Diagnose — test which MCPs are working right now
2) Sync — push master config to other tools (safe: dry-run first)
3) Memory sync — mirror Memory MCP knowledge-graph across OSes
4) Schedule — set up periodic sync
5) Topology — show where MCP files live across OSes
q) Quit
# Find out what's working now
uv run mcp.py doctor
# Translate-and-distribute (interactive, dry-run -> confirm -> apply)
uv run mcp.py sync
# Mirror the Memory MCP knowledge-graph across OSes (interactive, dry-run -> confirm -> apply)
uv run mcp.py memory
# Show the env-var config + cron / schtasks command
uv run mcp.py schedule
# Where does everything live?
uv run mcp.py topology
# Fix a broken master JSON (missing/trailing comma) — dry-run, then --apply
uv run mcp.py repair ~/.config/mcp/servers.json
uv run mcp.py repair ~/.config/mcp/servers.json --applyThe easiest path: run uv run mcp.py schedule from inside this repo. It prints the install command with the actual absolute path of mcp_sync_daemon.py filled in for whatever location you cloned the repo into.
If you'd rather write it by hand, replace <abs path> with your install location (e.g. ~/code/mcp-toolkit, D:\projects\mcp-toolkit, /opt/mcp-toolkit — wherever you put it):
:: Windows (run once)
schtasks /create /tn "MCP Sync" /sc daily /st 09:00 /tr ^
"uv run \"<abs path>\mcp_sync_daemon.py\""# WSL / Linux (crontab -e), default frequency = 1 day
0 9 * * * /usr/bin/python3 <abs path>/mcp_sync_daemon.py >> ~/.local/state/mcp-sync/cron.log 2>&1The daemon de-bounces — running it more frequently just causes silent skips until MCP_SYNC_FREQUENCY elapses.
+-------------------+
user ─► │ mcp.py │ interactive menu / subcommands
+---------+---------+
│
┌───────────┬───────┼────────────────┬──────────────────┐
▼ ▼ ▼ ▼ ▼
mcp_doctor.py mcp_sync.py mcp_sync_daemon.py mcp_memory_sync.py
(diagnose) (translator (scheduler wrapper: (mirror Memory MCP
+ distributor) calls mcp_sync.py, knowledge-graph
writes report, JSONL across
fires toast) Windows + WSL)
Each script is self-contained, stdlib-only, and re-runnable. They share no state in memory — only files (config JSON, secrets .env, state file, reports).
uv run mcp.py doctorSteps:
- Auto-detects all known MCP config locations on this machine.
- Lists them; you pick one (or accept the first detected).
- Optionally narrows to a single server.
- Spawns each server, sends JSON-RPC
initialize+tools/list, reports:OK <name> (<latency>ms, <N> tools)— server started and listed its toolsSLOW <name>— responded but past the slow threshold (cold start)FAIL <name>: <reason or first stderr line>— wrapper exited or never responded
- Pre-flight checks
docker,npx,uvx,python3so a missing binary on the launcher's PATH is surfaced immediately.
uv run mcp.py sync=====================================================
Sync — translate-and-distribute master MCP config
=====================================================
This walks you through:
1) Pick the SOURCE (which config is the master).
2) Pick the TARGET OS + targets to update.
3) Decide on secrets mirroring.
4) See a DRY-RUN diff of every change.
5) Confirm before any file is written.
Sync direction:
1) * WSL master → Windows tools — WSL is source-of-truth
2) Windows master → WSL tools — Windows is source-of-truth
3) Same OS (sync within current OS)
4) Custom source path
Choose [1-4, default 1]: 1
Source file [\\wsl.localhost\<distro>\home\<user>\.codeium\windsurf\mcp_config.json]:
source_os=wsl target_os=windows
Targets to update:
[x] 1) windsurf — Windsurf IDE config
[x] 2) lmstudio — LM Studio MCP config
[x] 3) vscode — VS Code (schema rewrite)
[x] 4) devin — Devin (merges into existing config.json)
[ ] 5) master — Canonical master file
Toggle (e.g. '1,3' to flip), 'a'=all, 'n'=none, Enter=continue: 3
...vscode flipped off, Enter to continue...
selected targets: windsurf, lmstudio, devin
Mirror missing secrets from source secrets file to target?
(plaintext tokens cross the OS boundary) [y/N]: N
===============================================
Dry-run preview (no files will be modified)
===============================================
Parsed 14 servers (5 disabled).
Skipped 5 servers (Postgres MCPs using the mcp-tool-rename.py shim — POSIX-only;
identifiers redacted in this excerpt):
- postgresql: ...primary local
- postgresql-<env-A>: ...same reason
- postgresql-<env-A>-...: ...same reason
- postgresql-<env-B>: ...same reason
- postgresql-<env-C>: ...same reason
Secrets check: 2 key(s) needed in target secrets but missing:
- GITHUB_PERSONAL_ACCESS_TOKEN (source has it: yes)
- HARNESS_API_KEY (source has it: yes)
Pass --mirror-secrets to copy these from source -> target (with consent).
--- Target: windsurf -> C:\Users\<user>\.codeium\windsurf\mcp_config.json
@@ -1,37 +1,271 @@
{
"mcpServers": {
+ "fetch": { "command": "uvx", ... },
+ "filesystem": { "command": "npx", ... },
+ "github-mcp-server": { "command": "docker",
+ "args": ["run","--env-file",
+ "C:\\Users\\<user>\\.config\\mcp\\.env", ...] },
+ "harness": { ..., "disabled": true },
+ "mcp-atlassian": { "command": "docker",
+ "args": ["run","--env-file", ... ] },
...full diff continues — truncated here for the README,
you'll see all of it in your terminal
}
(dry-run exit code: 3)
====================
Confirm
====================
Above is the DRY-RUN. The following will happen on apply:
- 3 target(s) will be backed up to .bak.<timestamp> then overwritten.
Apply these changes now? [y/N]: N
Cancelled. No files modified.
Walks you through:
- Direction — WSL master → Windows tools, or Windows master → WSL tools, or same-OS, or custom.
- Source path — pre-filled with the right default for the chosen direction.
- Targets — toggleable multi-select for
windsurf,lmstudio,vscode,devin,master. - Mirror secrets? — explicit yes/no (the warning about plaintext crossing OS boundaries is shown).
- Dry-run preview — full unified diff for each target file, plus skipped servers and any missing secret keys.
- Confirm —
Apply these changes now? [y/N]. Default is N. Press Enter to cancel. - Apply — only after explicit
y. Each target file is backed up to<file>.bak.<timestamp>before being overwritten.
When syncing WSL → Windows (or any direction crossing the OS boundary), the translator handles:
| Source pattern | Target translation |
|---|---|
bash /home/.../wrappers/mcp-foo.sh |
Unwrapped to inline docker run --env-file <target secrets path> (or npx/uvx) |
secrets.env referenced by a wrapper |
Path translated to the target OS's secrets file (.env on Windows) |
Schema mcpServers |
Stays as mcpServers, except VS Code targets get servers |
VS Code-specific type: "stdio" |
Auto-injected for VS Code targets |
| Devin's other top-level fields (permissions, theme, agent) | Preserved (only mcpServers block is replaced) |
Servers that cannot be safely translated — for example, Postgres MCPs that depend on the mcp-tool-rename.py shim (POSIX-only) — are skipped with a clear reason. No silently-broken configs ever get written.
uv run mcp.py scheduleShows your resolved env-var config (MCP_SYNC_*) and prints the OS-native install command for either Windows Task Scheduler or WSL cron. Nothing is installed automatically — you copy the command yourself so you stay in control.
The daemon (mcp_sync_daemon.py):
- Reads config from env vars (with sensible defaults).
- Skips silently if the last successful run was within
MCP_SYNC_FREQUENCY. - Otherwise calls
mcp_sync.pywith the configured flags. - Writes a Markdown report (
sync-<ts>.md+latest.md) toMCP_SYNC_REPORT_DIR. - Updates state only on non-fatal exits, so persistent failures keep retrying on each scheduled invocation (and keep firing notifications until you fix the cause).
- Sends a desktop toast on error / partial-success / configurable.
uv run mcp.py memorymcp_sync.py syncs MCP configs across OSes; it deliberately doesn't touch the data files those servers write to. For most MCPs that's correct. For @modelcontextprotocol/server-memory, though, the data file IS the knowledge graph — it's the entire point of the server — and having two un-synced graphs (one on Windows, one on WSL) is exactly the kind of silent-data-loss trap that the toolkit exists to prevent.
mcp_memory_sync.py closes that gap.
- Reads
MEMORY_FILE_PATHfrom each side's Windsurf MCP config (Windows + WSL). Refuses with a clear message if either side is unset — that's the "graph is silently ephemeral and resets on restart" anti-pattern called out in the Memory MCP limitation below. - Resolves both files into paths this process can open — cross-OS via
\\wsl.localhost\<distro>\...(Windows → WSL) or/mnt/c/Users/<user>/...(WSL → Windows). Honors the sameMCP_WSL_DISTRO/MCP_WSL_USER/MCP_WINDOWS_USERenv-var overrides used bymcp_sync.py. - Parses each side's JSONL graph: one
{"type":"entity", "name":..., "observations":[...]}or{"type":"relation", "from":..., "to":..., "relationType":...}per line. - Computes a bidirectional union merge:
- Entities are deduped by
name. Observations are merged preserving order (entries unique to the other side appended after the local ones). If the two sides disagree onentityTypefor the same name, the local (Windows) side'sentityTypeis kept and a warning is printed naming the conflict. - Relations are deduped by the triple
(from, to, relationType). - Unrecognized lines are preserved verbatim (extras) so the round-trip is safe even if upstream introduces new node kinds.
- Entities are deduped by
- Prints the planned diff per side (counts of entities/relations/observations gained).
- On
--apply, backs up each side to.bak.<timestamp>next to the original and atomically writes the merged graph. Without--applyit's a pure dry-run.
| Mode | Effect |
|---|---|
bidirectional (default) |
Both sides receive the union. Symmetric. Use this 99% of the time. |
wsl-to-windows |
WSL graph overwrites Windows graph. Windows file is backed up. WSL untouched. |
windows-to-wsl |
Windows graph overwrites WSL graph. WSL file is backed up. Windows untouched. |
The one-way modes are useful when one side has been actively corrupted (e.g. truncated or rewritten by a script) and you want to forcibly resync from the known-good side.
Memory MCP graph sync
direction: bidirectional
mode: DRY-RUN (no writes)
Windows side (windows): /mnt/c/Users/<user>/.config/mcp/memory.json
declared in config as: C:\Users\<user>\.config\mcp\memory.json
entities: 12 relations: 18 extras: 0
WSL side (wsl): /home/<user>/.codeium/windsurf/memory-graph.json
declared in config as: /home/<user>/.codeium/windsurf/memory-graph.json
entities: 39 relations: 60 extras: 0
Plan:
Windows will gain +30 entities, +45 relations, 3 entities with new observations
WSL will gain +3 entities, +3 relations, 0 entities with new observations
(dry-run -- pass --apply to write)
If you want to merge two specific files (e.g. a backup against the live graph), use the override flags:
uv run mcp_memory_sync.py \
--windows-file "C:\Users\me\memory.json.bak.20260520-104900" \
--wsl-file "/home/me/.codeium/windsurf/memory-graph.json" \
--direction wsl-to-windows --apply| Code | Meaning |
|---|---|
| 0 | No changes needed (graphs already in sync, or --apply succeeded). |
| 1 | Changes pending (dry-run with diffs). |
| 2 | Error (file read, parse, etc.). |
| 3 | One side missing MEMORY_FILE_PATH — refused. Fix the config and re-run, or use --windows-file / --wsl-file. |
uv run mcp.py topologyPrints the complete cross-OS map of master configs, secrets, wrappers, reports, and where each tool reads its config from.
# Dry-run: show error context + suggested fix + diff
uv run mcp.py repair ~/.config/mcp/servers.json
# Apply the fix in place (hardlink-safe, backs up to .bak.<ts> first)
uv run mcp.py repair ~/.config/mcp/servers.json --applyAuto-fixes the two JSON syntax errors that bite this setup most often:
- Missing comma between two sibling values inside an object or array (the classic copy-paste / hand-edit hazard).
- Trailing comma before
}or](legal in JSONC, illegal in strict JSON).
For anything else — unterminated strings, bad escapes, malformed numbers, mismatched braces — it refuses to guess and tells you to fix manually. There is no --force mode for unsafe repairs.
Hardlink-safe by design: writes use open(mode='r+') + seek(0) + truncate() + write() so the file's inode is preserved and the NTFS hardlink chain (servers.json ↔ mcp_config.json ↔ mcp.json) survives. The implementation asserts st_nlink is unchanged across every write — any future regression that breaks the chain raises RuntimeError.
mcp_doctor.py references this command in its parse-error suggestion text, so the typical recovery flow is: doctor surfaces the parse error → copy the suggested repair command → run it dry-run → confirm → --apply.
| Code | Meaning |
|---|---|
| 0 | File already parses (no work needed), or --apply succeeded. |
| 1 | Repair available but not applied (dry-run output). |
| 2 | No safe repair available (manual fix required), or --apply failed. |
All env vars are optional; sensible defaults come from your current OS.
| Variable | Default | Purpose |
|---|---|---|
MCP_SYNC_USER |
current user | Used in report headers / greetings |
MCP_SYNC_FREQUENCY |
1d |
Min interval between scheduled syncs (60s, 30m, 12h, 1d, 1w) |
MCP_SYNC_SOURCE |
OS-aware master path | WSL: ~/.codeium/windsurf/mcp_config.json; Windows: ~/.config/mcp/servers.json |
MCP_SYNC_TARGET_OS |
opposite of current OS | windows | wsl |
MCP_SYNC_TARGETS |
windsurf,lmstudio,vscode,devin |
Comma list or all |
MCP_SYNC_APPLY |
yes |
Periodic sync should sync. Set no for a recurring dry-run alarm. |
MCP_SYNC_MIRROR_SECRETS |
no |
Plaintext tokens crossing OS boundary requires explicit opt-in. |
MCP_SYNC_NOTIFY_ON |
error |
error | always | never |
MCP_SYNC_REPORT_DIR |
OS-aware | Windows: %LOCALAPPDATA%\mcp-sync\reports; Linux: ~/.local/share/mcp-sync/reports |
MCP_SYNC_STATE_DIR |
OS-aware | %LOCALAPPDATA%\mcp-sync / ~/.local/state/mcp-sync |
MCP_WINDOWS_USER |
auto-detected | Windows username for cross-OS path translation. Set if Windows + WSL usernames differ. |
MCP_WSL_USER |
auto-detected | WSL username for cross-OS path translation. Same caveat. |
MCP_WSL_DISTRO |
auto-detected | WSL distribution name (Ubuntu, Debian, kali-linux, openSUSE-Leap-15.5, ...). Auto-detected via WSL_DISTRO_NAME env var inside WSL, or by listing \\wsl.localhost\ from Windows. Set this if you have multiple distros installed and the auto-pick chooses the wrong one. |
To set them persistently on Windows: setx VAR value. On WSL: append export VAR=value to ~/.bashrc or ~/.profile.
| Path (Windows) | Path (WSL/Linux) | Owner |
|---|---|---|
%LOCALAPPDATA%\mcp-sync\last-run.json |
~/.local/state/mcp-sync/last-run.json |
daemon (state) |
%LOCALAPPDATA%\mcp-sync\reports\sync-<ts>.md |
~/.local/share/mcp-sync/reports/sync-<ts>.md |
daemon (report) |
%LOCALAPPDATA%\mcp-sync\reports\latest.md |
~/.local/share/mcp-sync/reports/latest.md |
daemon (latest report copy) |
%TEMP%\mcp_sync_toast.ps1 |
n/a | daemon (notification helper) |
<target file>.bak.<ts> |
(same) | sync (backup before overwrite) |
<MEMORY_FILE_PATH> (read from Windsurf MCP config) |
<MEMORY_FILE_PATH> (read from Windsurf MCP config) |
memory-sync (read + write the knowledge-graph JSONL) |
<memory file>.bak.<ts> |
(same) | memory-sync (backup before overwrite) |
The toolkit never writes to secrets.env / .env automatically — only when you pass --mirror-secrets and confirm yes at the prompt.
These are real, by-design boundaries — not bugs.
Sync from a 2-server WSL master to a 3-server Windows Windsurf config will produce a 2-server result on Windows. The 3rd server (which only existed on Windows) is gone from the live file but recoverable from the .bak.<ts> backup.
Practical implication: if you have an MCP server that only makes sense on one OS, either:
- Add it to the master and accept that translation will skip it (e.g.
mcp-tool-rename.py-shimmed Postgres MCPs naturally do this), or - Use
--targets <subset>to exclude the target file that has the OS-specific server, or - Restore from
.bak.<ts>if you sync and lose something.
There's currently no "sync_only_to": ["windows"] per-server marker. It could be added later if needed.
The translator handles wrappers that follow this exact shape:
#!/usr/bin/env bash
set -euo pipefail
set -a; source "$(dirname "$0")/../secrets.env"; set +a
export VAR1="literal" # zero or more of these
exec docker run … # or npx / uvx / python3If your wrapper does anything else (multiple exec lines, custom logic, conditional if blocks, etc.), it will be skipped with the reason unsupported wrapper: …. Skipping is the safe default — never silently emits broken config.
This shim is a Linux-only Python helper that lives next to the wrapper. The translator detects it and skips with the reason uses mcp-tool-rename.py shim (POSIX-only). To use those servers on Windows, you'd need to port the shim or use a different collision-avoidance strategy (e.g. running each Postgres MCP in its own container with separate names).
For UNC paths (\\wsl.localhost\<distro>\home\<user>) and /mnt/c/Users/<user> translation, the toolkit resolves three things:
| Component | How it's resolved |
|---|---|
| WSL distro | MCP_WSL_DISTRO env var → WSL_DISTRO_NAME (set automatically inside WSL) → first dir under \\wsl.localhost\ → Ubuntu fallback |
| WSL username | MCP_WSL_USER env var → first dir under \\wsl.localhost\<distro>\home\ → $USER / %USERNAME% fallback |
| Windows username | MCP_WINDOWS_USER env var → first non-system dir under /mnt/c/Users/ → %USERNAME% / $USER fallback |
On most setups (single distro, same username on both sides) this just works. Set the env vars explicitly when:
- You have multiple WSL distros installed (e.g. Ubuntu + Debian + Kali) and the auto-pick chooses wrong:
export MCP_WSL_DISTRO=Debian - Your Windows + WSL usernames differ (e.g.
John.Doevsjohn):export MCP_WINDOWS_USER=John.Doe export MCP_WSL_USER=john
For same-OS sync, this isn't an issue — ${USERPROFILE} / $HOME resolve normally and no UNC translation happens.
mcp_doctor.py— fully supported. Auto-detects~/Library/Application Support/Claude/...(macOS),~/.config/Claude/...(Linux), VS Code, Cursor, Windsurf, Devin.mcp_sync.py— works for same-OS sync (e.g. Linux master → Linux Windsurf). Cross-OS translation is currently Windows ↔ WSL specific.mcp_sync_daemon.py— works on Linux (notifications vianotify-send) and macOS (notifications viaosascript).
Newer Windows silently dismisses toasts whose AppUserModelID isn't registered. The daemon's first call writes:
HKCU\Software\Classes\AppUserModelId\MCP.Sync
└─ DisplayName = "MCP Sync"
This is a single registry value, no admin needed. If you ever wipe HKCU (unusual) or your friend doesn't see notifications, run uv run mcp_sync_daemon.py --dry-notify once to re-register.
If the toast still doesn't show, check:
- Settings → System → Notifications → "MCP Sync" is allowed
- Focus Assist / Do Not Disturb is off
- Notifications are globally enabled
Accepts <int><suffix> with suffix in s|m|h|d|w. No "every weekday", no "9:30 PM", no cron expressions. The OS-native scheduler handles the recurrence pattern; this script just de-bounces.
If MEMORY_FILE_PATH is empty (the default of @modelcontextprotocol/server-memory), the graph is silently ephemeral and resets on restart. The toolkit assumes you set it explicitly (master configs in this setup do; bare npx invocations elsewhere may not).
mcp_memory_sync.py enforces this: if either side's Windsurf MCP config has an empty (or absent) MEMORY_FILE_PATH, the script refuses with exit code 3 and tells you which side to fix. There's no --allow-empty opt-out — the failure mode (silent data loss across restarts) is too bad to make easy.
mcp_memory_sync.py defaults to a bidirectional union merge: nothing is ever deleted. If you delete an entity on one side and then sync, the entity will come back from the other side. That's the right default for a knowledge graph (you really, really don't want a stale tab on the wrong OS to silently nuke your memory), but it means:
- To actually delete an entity, delete it on both sides before syncing, or
- Use one of the one-way modes (
--direction wsl-to-windows/windows-to-wsl) which overwrite the target without trying to preserve its content. The target is still backed up to.bak.<ts>before the overwrite.
Each side's write is atomic on that side (write-temp + rename). But there's a gap between the Windows write and the WSL write during which a third party (e.g. the live Memory MCP server) could read a state where Windows is updated but WSL isn't. In practice this is fine because:
- Memory MCP doesn't continuously poll its file — it reads on startup and writes on mutation.
- The graphs are eventually consistent: any divergence introduced during the gap will be merged on the next sync run.
If you ever need true atomicity, stop both Memory MCP servers, sync, then restart them.
Run uv run mcp.py doctor from inside WSL (or wherever the IDE is running). The most common causes (in order):
- Docker Desktop down or WSL2 integration off → wrappers that use
docker runall silently fail. - PATH problem — Windsurf launches wrappers with a minimal env;
docker/npx/uvxaren't visible if they live innvm/pyenv/~/.local/bin. - Cold-start latency on many servers exceeds the IDE's MCP discovery timeout. Reduce the number of enabled servers or pre-pull packages (
npx -y <pkg> --help,docker pull <image>). set -euo pipefailin a wrapper exits on a typo insecrets.env.
Read the skip reason. It will be one of:
unsupported wrapper: uses mcp-tool-rename.py shim (POSIX-only)— Linux-only by design; can't translate to Windows.unsupported wrapper: expected exactly one 'exec' line, found 0— your wrapper doesn't have a recognizableexecinvocation. Edit the wrapper to fit the pattern, or sync this server manually.unsupported wrapper: exec command 'foo' not in (docker|npx|uvx|python3)— the toolkit only knows how to inline these four. PR the others if needed.command not found— the source command isn't on PATH for your sync session.
Run uv run mcp.py doctor against the target config (not the source). The doctor's failure hints will pinpoint PATH / Docker / auth issues that are specific to the target machine.
Each target file has a <file>.bak.<timestamp> next to it. Just rename the most recent backup back over the live file:
cd ~/.codeium/windsurf
mv mcp_config.json.bak.20260520-205530 mcp_config.jsonSame fix as above. To prevent it: either add the server to the master, or omit the target from --targets next time.
- Stdlib only. No
pip install, norequirements.txt, nopyproject.toml. Each script has a PEP 723 header souv run <script>.pyworks out of the box (with no deps to actually fetch — keepsuv runfast and consistent across machines). - Pre-publish leak scan. A pre-push hook runs a layered grep (custom patterns) +
gitleaksagainst every push. Any contributor running the scanner (orgit pushwith the hook installed) gets a non-zero exit if the diff or commit messages match an org/identity/secret pattern. Configure patterns under~/.local/share/git-leak-scan/. (External scanner — not bundled in this repo to avoid duplication.) - Dry-run by default. Every destructive action is preceded by a diff and a
[y/N]prompt withNas default. - Backup before every write. No exceptions.
- Skip > silently break. When in doubt,
mcp_sync.pyskips with a reason rather than emitting config the target can't actually run. - State advances only on non-fatal exits. Persistent failures stay loud (next scheduled run retries + notifies again).
- No secrets in scripts or commits. Secrets live in
secrets.env(POSIX) /.env(Windows). Tools reference them via--env-fileor wrapper sourcing — never hard-coded. - One job per script. Composition is via subprocess; state is via files.
mcp-toolkit/
├── mcp.py # unified entry point (interactive + subcommands incl. `repair`)
├── mcp_doctor.py # diagnose
├── mcp_sync.py # translate-and-distribute master config
├── mcp_sync_daemon.py # scheduled wrapper for sync
├── mcp_memory_sync.py # mirror Memory MCP graph across OSes
├── README.md # this file
├── CHANGELOG.md # release history (Keep a Changelog format)
├── LICENSE # MIT
└── .gitignore
- Clone or copy this repo.
- Make sure they have Python 3.10+ and (optionally) uv.
- Tell them to run
uv run mcp.py(orpython3 mcp.py) and pick from the menu.
That's it. There is no install step. Stdlib-only Python means nothing to compile and nothing to lock down to a version.
"sync_only_to": ["windows"]per-server marker for OS-specific MCPs.- Bidirectional / merge sync (currently strictly source-of-truth replace).
- Auto-install OS scheduler tasks (currently prints commands; you copy).
- Pluggable wrapper-pattern parser for non-bash wrappers.
- macOS / Linux-native cross-OS path translation.
--save-as <name>to bookmark a sync recipe (source + targets + flags) for one-flag invocation.
See CHANGELOG.md — Keep a Changelog format, SemVer.
MIT — see LICENSE. Use, fork, modify, share. No warranty.