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:
- Parse existing TOML (if present)
- Update only
mcp_servers.*entries for projio-managed servers - Preserve all other top-level keys and non-projio MCP servers
- Write back with
tomli_w(or stdlibtomllibfor 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.jsonwithallowedTools; 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.jsonalready works for them.
Implementation Plan¶
- Refactor
config_gen.py: ExtractMcpServerDefdataclass andgenerate_server_defs(). Rewritegenerate_mcp_config()as a thin wrapper callingemit_claude(). - Add emitters:
emit_codex(),emit_copilot()in same module. - Add write functions:
write_codex_config(),write_copilot_config()matching the existingwrite_mcp_config()pattern. - Extend
init.py: Add"codex"and"copilot"toKNOWN_PACKAGES, add_scaffold_codex()and_scaffold_copilot()functions. - Extend gitignore: Add codex/copilot entries to
_gitignore_entries_for_framework(). - Extend
sync.py: Detect enabled agents inpackages.yml, regenerate their configs duringprojio 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 |