sync_cursor_mcp_to_codex.py 2.93 KB
#!/usr/bin/env python3
"""把项目内 .cursor/mcp.json 同步到 Codex 的 MCP 配置。"""

from __future__ import annotations

import argparse
import json
import os
import subprocess
import sys
from pathlib import Path


def resolve_repo_root() -> Path:
    return Path(__file__).resolve().parents[3]


def load_cursor_config(repo_root: Path) -> dict:
    config_path = repo_root / ".cursor" / "mcp.json"
    with config_path.open("r", encoding="utf-8") as f:
        return json.load(f)


def apply_overrides(name: str, args: list[str], repo_root: Path) -> list[str]:
    new_args = list(args)

    if name == "filesystem":
        fs_root = os.environ.get("LQERP_MCP_FILESYSTEM_ROOT", str(repo_root))
        new_args = [fs_root if arg == "." else arg for arg in new_args]

    if name == "my-sql-db":
        mysql_url = os.environ.get("LQERP_MCP_MYSQL_URL")
        if mysql_url:
            new_args = [mysql_url if arg.startswith("mysql://") else arg for arg in new_args]

    if name == "my-api-spec":
        openapi_spec = os.environ.get("LQERP_MCP_OPENAPI_SPEC_URL")
        api_base = os.environ.get("LQERP_MCP_OPENAPI_BASE_URL")
        for index, arg in enumerate(new_args):
            if openapi_spec and arg == "--openapi-spec" and index + 1 < len(new_args):
                new_args[index + 1] = openapi_spec
            if api_base and arg == "--api-base-url" and index + 1 < len(new_args):
                new_args[index + 1] = api_base

    return new_args


def run_command(command: list[str], dry_run: bool) -> None:
    print("$", " ".join(command))
    if not dry_run:
        subprocess.run(command, check=True)


def sync_servers(repo_root: Path, dry_run: bool) -> None:
    config = load_cursor_config(repo_root)
    servers = config.get("mcpServers", {})

    if not servers:
        raise SystemExit("未在 .cursor/mcp.json 中找到 mcpServers 配置。")

    for name, server in servers.items():
        command = server.get("command")
        args = server.get("args", [])
        if not command:
            print(f"跳过 {name}: 缺少 command", file=sys.stderr)
            continue

        final_args = apply_overrides(name, args, repo_root)

        remove_cmd = ["codex", "mcp", "remove", name]
        add_cmd = ["codex", "mcp", "add", name, "--", command, *final_args]

        print(f"\n=== 同步 MCP: {name} ===")
        if dry_run:
            print("$", " ".join(remove_cmd), "  # ignore failure if not exists")
            print("$", " ".join(add_cmd))
            continue

        subprocess.run(remove_cmd, check=False)
        run_command(add_cmd, dry_run=False)


def main() -> None:
    parser = argparse.ArgumentParser(description="把 .cursor/mcp.json 同步到 Codex MCP 配置")
    parser.add_argument("--dry-run", action="store_true", help="只打印将要执行的命令")
    args = parser.parse_args()

    repo_root = resolve_repo_root()
    sync_servers(repo_root, dry_run=args.dry_run)


if __name__ == "__main__":
    main()