mcp-sync

Crates.iomcp-sync
lib.rsmcp-sync
version1.0.0
created_at2026-01-05 21:13:12.37636+00
updated_at2026-01-05 21:13:12.37636+00
descriptionSync canonical mcp.yaml to Antigravity, Claude Code, Codex, OpenCode, and custom targets
homepage
repositoryhttps://github.com/Dudude-bit/mcp-sync
max_upload_size
id2024588
size196,664
(Dudude-bit)

documentation

README

mcp-sync

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)

Installation

cargo install --path .

Quick Start

# 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

Canonical Format (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}"

CLI Options

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)

Commands

sync (default)

One-time sync to all targets.

watch

Watch mcp.yaml for changes and re-sync automatically.

init

Interactive wizard to create mcp.yaml:

mcp-sync init --output mcp.yaml

validate

Validate mcp.yaml syntax and schema:

mcp-sync validate --canon mcp.yaml

diff

Show differences between canon and current target configs:

mcp-sync diff --canon mcp.yaml

completions

Generate 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

Remote Canon

Load mcp.yaml from a URL:

mcp-sync sync --canon https://example.com/team/mcp.yaml

Examples

Sync with Custom Target

# 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

Sync with Custom Plugin

# Built-in env-expander + custom plugin
mcp-sync sync --plugin ./secret-injector.dylib

Watch Mode with Custom Config

# Watch and sync on changes
mcp-sync watch --canon ~/my-mcp.yaml --verbose

CI/CD Integration

# Sync global configs, fail on error
mcp-sync sync --scope global --canon mcp.yaml

Target File Locations

Global (User-level)

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

Project-level

Target Path
Antigravity .vscode/mcp.json
Claude .mcp.json
Codex .codex/config.toml
OpenCode opencode.json

Plugin System

Plugins can transform configurations during sync.

Built-in: env-expander

Expands environment variables in config values:

plugins:
  - name: env-expander
    config:
      prefix: "${"
      suffix: "}"

Custom Plugins

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()))
}

Custom Targets

Create custom sync targets for unsupported tools.

Implementation

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))
}

Build

# Cargo.toml
[lib]
crate-type = ["cdylib"]

[dependencies]
mcp-sync = { path = "..." }
anyhow = "1"
cargo build --release
# Output: target/release/libmy_target.dylib

Use

mcp-sync sync --target ./target/release/libmy_target.dylib

Backups

Before modifying any file, mcp-sync creates a timestamped backup:

config.json → config.json.bak.20260105-230041

License

MIT

Commit count: 2

cargo fmt