Skip to content

Multi-Agent MCP Configuration Generation

Status: Accepted Date: 2026-04-09

Motivation

Projio scaffolds .mcp.json and .claude/settings.json for Claude Code, but the ecosystem now includes multiple MCP-capable coding agents: Codex, VS Code / Copilot, Cursor, Continue, and MCPHost. Each uses a different config file, format, and root key. Teams where members use different agents need consistent MCP setup.

Currently there is no single portable MCP config format. The gap is small — all systems share the same semantic config (server name, command, args, env, transport) — but the serialization differs enough to require per-agent emitters.

Design Decision

Projio generates per-agent MCP configs from a single internal server representation. Three emitters cover all major MCP clients.

Agent Landscape

Agent Config path Format Root key Notes
Claude Code .mcp.json JSON "mcpServers" Also used by Cursor, Continue (JSON compat), MCPHost
Codex .codex/config.toml TOML [mcp_servers.<name>] CLI + IDE share config
VS Code / Copilot .vscode/mcp.json JSON "servers" Editor-centric, separate from Copilot MCP

Cross-compatibility

The "mcpServers" JSON format is the de facto standard — it works across: - Claude Code (.mcp.json) - Claude Desktop (claude_desktop_config.json) - Cursor (.cursor/mcp.json) - Continue (.continue/mcpServers/*.json — drop-in JSON compat) - MCPHost (mcp-servers.json)

So .mcp.json generated by projio add claude already covers five clients. Only Codex (TOML) and VS Code/Copilot (different root key) need distinct output.

Architecture

Canonical Server Representation

The internal representation is a list of server definitions:

@dataclass
class McpServerDef:
    name: str                           # e.g. "projio", "worklog"
    command: str                        # e.g. "/path/to/python"
    args: list[str]                     # e.g. ["-m", "projio.mcp.server"]
    env: dict[str, str] | None = None  # e.g. {"PROJIO_ROOT": "/path"}

Built by generate_server_defs(root, python_bin=...) from project config. This replaces the current generate_mcp_config() which returns a Claude-Code-specific dict directly.

Emitters

Each emitter takes list[McpServerDef] and produces the target file:

Emitter Output Format
emit_claude(servers, root) .mcp.json JSON {"mcpServers": {...}}
emit_codex(servers, root) .codex/config.toml TOML [mcp_servers.<name>]
emit_copilot(servers, root) .vscode/mcp.json JSON {"servers": {...}}

All emitters: - Merge into existing files (preserve non-projio entries) - Return the output path - Support dry_run mode (print, don't write)

CLI Surface

projio add claude      # existing — .mcp.json + .claude/settings.json + CLAUDE.md
projio add codex       # new — .codex/config.toml
projio add copilot     # new — .vscode/mcp.json

These are registered in KNOWN_PACKAGES alongside the existing subsystem packages. Each has a scaffold function in init.py.

projio add cursor and projio add continue are not needed — both read .mcp.json directly. Document this in help text.

Sync

projio sync already regenerates Claude Code configs. Extended to also update: - .codex/config.toml — if .codex/ directory exists (i.e., codex was added) - .vscode/mcp.json — if the file exists AND contains projio-managed servers

Detection: check packages.yml for codex.enabled / copilot.enabled.

Gitignore

New entries added to the projio-managed gitignore block:

# Codex
.codex/config.toml

# VS Code MCP (projio-managed)
.vscode/mcp.json

.vscode/mcp.json is gitignored because it contains absolute paths (like .mcp.json). Teams sharing MCP config should use projio add copilot on each machine rather than committing machine-specific paths.

File Format Details

Claude Code (existing)

{
  "mcpServers": {
    "projio": {
      "command": "/path/to/python",
      "args": ["-m", "projio.mcp.server"],
      "env": {"PROJIO_ROOT": "/path/to/project"}
    }
  }
}

Codex

[mcp_servers.projio]
command = "/path/to/python"
args = ["-m", "projio.mcp.server"]

[mcp_servers.projio.env]
PROJIO_ROOT = "/path/to/project"

[mcp_servers.worklog]
command = "/path/to/python"
args = ["-m", "worklog.mcp_server"]

VS Code / Copilot

{
  "servers": {
    "projio": {
      "command": "/path/to/python",
      "args": ["-m", "projio.mcp.server"],
      "env": {"PROJIO_ROOT": "/path/to/project"}
    }
  }
}

Merge Strategy

JSON files (.mcp.json, .vscode/mcp.json)

Same as current: parse existing file, update projio-managed server entries, preserve all other entries. Write back with json.dumps(indent=2).

TOML file (.codex/config.toml)

Codex config can contain non-MCP settings (model, approval rules, etc.). Strategy:

  1. Parse existing TOML (if present)
  2. Update only mcp_servers.* entries for projio-managed servers
  3. Preserve all other top-level keys and non-projio MCP servers
  4. Write back with tomli_w (or stdlib tomllib for reading + manual write)

Since tomli_w is an optional dependency, fall back to a simple string-based TOML emitter for the MCP section only. The MCP server TOML format is flat enough that a template approach is reliable.

Non-Goals

  • Canonical user-facing YAML file: The source of truth is projio config + runtime conventions, not a new config file users must manage.
  • Permission abstraction across agents: Claude Code has settings.json with allowedTools; Codex has approval overrides; Copilot has nothing. Each scaffold owns its permission story independently.
  • HTTP/SSE transport: All projio MCP servers use stdio. Remote transport support is out of scope.
  • Cursor/Continue-specific commands: .mcp.json already works for them.

Implementation Plan

  1. Refactor config_gen.py: Extract McpServerDef dataclass and generate_server_defs(). Rewrite generate_mcp_config() as a thin wrapper calling emit_claude().
  2. Add emitters: emit_codex(), emit_copilot() in same module.
  3. Add write functions: write_codex_config(), write_copilot_config() matching the existing write_mcp_config() pattern.
  4. Extend init.py: Add "codex" and "copilot" to KNOWN_PACKAGES, add _scaffold_codex() and _scaffold_copilot() functions.
  5. Extend gitignore: Add codex/copilot entries to _gitignore_entries_for_framework().
  6. Extend sync.py: Detect enabled agents in packages.yml, regenerate their configs during projio sync.

Key Files

File Role
src/projio/mcp/config_gen.py Server defs + all emitters
src/projio/init.py Scaffold functions, KNOWN_PACKAGES, gitignore
src/projio/sync.py sync_workspace() — agent config refresh
src/projio/cli.py CLI routing for mcp-config