| Crates.io | mcp-sync |
| lib.rs | mcp-sync |
| version | 1.0.0 |
| created_at | 2026-01-05 21:13:12.37636+00 |
| updated_at | 2026-01-05 21:13:12.37636+00 |
| description | Sync canonical mcp.yaml to Antigravity, Claude Code, Codex, OpenCode, and custom targets |
| homepage | |
| repository | https://github.com/Dudude-bit/mcp-sync |
| max_upload_size | |
| id | 2024588 |
| size | 196,664 |
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:
cargo install --path .
# 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 --clean
mcp.yaml)version: 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) |
sync (default)One-time sync to all targets.
watchWatch mcp.yaml for changes and re-sync automatically.
initInteractive wizard to create mcp.yaml:
mcp-sync init --output mcp.yaml
validateValidate mcp.yaml syntax and schema:
mcp-sync validate --canon mcp.yaml
diffShow differences between canon and current target configs:
mcp-sync diff --canon mcp.yaml
completionsGenerate shell completions:
# Bash
mcp-sync completions bash >> ~/.bashrc
# Zsh
mcp-sync completions zsh >> ~/.zshrc
# Fish
mcp-sync completions fish > ~/.config/fish/completions/mcp-sync.fish
Load 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.
env-expanderExpands 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.dylib
mcp-sync sync --target ./target/release/libmy_target.dylib
Before modifying any file, mcp-sync creates a timestamped backup:
config.json → config.json.bak.20260105-230041
MIT