Sync canonical MCP configuration to multiple AI coding assistants
mcp-sync reads a single mcp.yaml file and syncs the server definitions to configuration files for:
- Antigravity (VS Code extension)
- Claude Code (CLI)
- Codex (OpenAI CLI)
- OpenCode
- Custom targets (dynamic libraries)
cargo install mcp-sync# One-time sync to all targets
mcp-sync sync
# Watch for changes and re-sync
mcp-sync watch
# Sync only global configs
mcp-sync sync --scope global
# Dry run (show what would change)
mcp-sync sync --dry-run --verbose
# Clean up servers not in canonical file
mcp-sync sync --cleanversion: 1
# Optional: plugins for transforming config
plugins:
- name: env-expander
config:
prefix: "${"
suffix: "}"
servers:
# stdio server (local process)
my-server:
command: npx
args: ["-y", "@modelcontextprotocol/server"]
env:
API_KEY: "${API_KEY}"
cwd: /path/to/workdir
enabled: true # default: true
# http server (remote)
remote-server:
kind: http
url: https://mcp.example.com/v1
headers:
Authorization: "Bearer ${TOKEN}"| Option | Description |
|---|---|
--canon <PATH or URL> |
Path or URL to canonical mcp.yaml (default: mcp.yaml) |
--scope <SCOPE> |
Sync scope: global, project, or both (default: both) |
--project-root <PATH> |
Project root (default: auto-detect git root) |
--clean |
Remove servers not present in canonical file |
--dry-run |
Print changes without writing files |
--log-level <LEVEL> |
Log level: trace, debug, info, warn, error (default: info) |
--plugin <PATH> |
Load additional plugin library (can be repeated) |
--target <PATH> |
Load custom target library (can be repeated) |
--only-target <NAMES> |
Sync only to specific targets (comma-separated or all) |
One-time sync to all targets.
Watch mcp.yaml for changes and re-sync automatically.
Interactive wizard to create mcp.yaml:
mcp-sync init --output mcp.yamlValidate mcp.yaml syntax and schema:
mcp-sync validate --canon mcp.yamlShow differences between canon and current target configs:
mcp-sync diff --canon mcp.yamlGenerate shell completions:
# Bash
mcp-sync completions bash >> ~/.bashrc
# Zsh
mcp-sync completions zsh >> ~/.zshrc
# Fish
mcp-sync completions fish > ~/.config/fish/completions/mcp-sync.fishLoad mcp.yaml from a URL:
mcp-sync sync --canon https://example.com/team/mcp.yaml# All built-in targets + custom target
mcp-sync sync --target ./my-target.dylib
# Multiple custom targets
mcp-sync sync --target ./target1.dylib --target ./target2.dylib
# Only Claude + custom target
mcp-sync sync --only-target claude --target ./my-target.dylib# Built-in env-expander + custom plugin
mcp-sync sync --plugin ./secret-injector.dylib# Watch and sync on changes
mcp-sync watch --canon ~/my-mcp.yaml --verbose# Sync global configs, fail on error
mcp-sync sync --scope global --canon mcp.yaml| Target | macOS | Linux | Windows |
|---|---|---|---|
| Antigravity | ~/Library/Application Support/Antigravity/User/mcp.json |
~/.config/Antigravity/User/mcp.json |
%APPDATA%\Antigravity\User\mcp.json |
| Claude | ~/.claude.json |
~/.claude.json |
%USERPROFILE%\.claude.json |
| Codex | ~/.codex/config.toml |
~/.codex/config.toml |
%APPDATA%\codex\config.toml |
| OpenCode | ~/.config/opencode/opencode.json |
~/.config/opencode/opencode.json |
%APPDATA%\opencode\opencode.json |
| Target | Path |
|---|---|
| Antigravity | .vscode/mcp.json |
| Claude | .mcp.json |
| Codex | .codex/config.toml |
| OpenCode | opencode.json |
Plugins can transform configurations during sync.
Expands environment variables in config values:
plugins:
- name: env-expander
config:
prefix: "${"
suffix: "}"Create a dynamic library that exports create_plugin:
use mcp_sync::{Canon, Plugin};
use anyhow::Result;
use serde_json::Value as JsonValue;
pub struct MyPlugin { /* ... */ }
impl Plugin for MyPlugin {
fn name(&self) -> &str { "my-plugin" }
fn on_load(&mut self, config: &JsonValue) -> Result<()> {
Ok(())
}
fn transform_canon(&self, canon: &mut Canon) -> Result<()> {
// Modify config before syncing
Ok(())
}
fn transform_output(&self, target: &str, value: &mut JsonValue) -> Result<()> {
// Modify target-specific output
Ok(())
}
}
#[unsafe(no_mangle)]
pub extern "C" fn create_plugin() -> *mut dyn Plugin {
Box::into_raw(Box::new(MyPlugin::new()))
}Create custom sync targets for unsupported tools.
use mcp_sync::{Canon, SyncOptions, Target};
use anyhow::Result;
use std::path::{Path, PathBuf};
pub struct MyTarget;
impl Target for MyTarget {
fn name(&self) -> &'static str { "MyTarget" }
fn global_path(&self) -> Result<PathBuf> {
Ok(PathBuf::from("/path/to/global/config.json"))
}
fn project_path(&self, root: &Path) -> PathBuf {
root.join(".my-target-config.json")
}
fn sync(&self, path: &Path, canon: &Canon, opts: &SyncOptions) -> Result<()> {
// Write config in your tool's format
Ok(())
}
}
#[unsafe(no_mangle)]
pub extern "C" fn create_target() -> *mut dyn Target {
Box::into_raw(Box::new(MyTarget))
}# Cargo.toml
[lib]
crate-type = ["cdylib"]
[dependencies]
mcp-sync = { path = "..." }
anyhow = "1"cargo build --release
# Output: target/release/libmy_target.dylibmcp-sync sync --target ./target/release/libmy_target.dylibBefore modifying any file, mcp-sync creates a timestamped backup:
config.json → config.json.bak.20260105-230041
MIT