|
| 1 | +--- |
| 2 | +phase: design |
| 3 | +title: "MCP Config Standardization — Design" |
| 4 | +description: "Architecture and data models for universal MCP server configuration" |
| 5 | +--- |
| 6 | + |
| 7 | +# Design: MCP Config Standardization |
| 8 | + |
| 9 | +## Architecture Overview |
| 10 | + |
| 11 | +```mermaid |
| 12 | +graph TD |
| 13 | + subgraph Input |
| 14 | + T[YAML/JSON Template] -->|ai-devkit init -t| IT[InitTemplate] |
| 15 | + C[.ai-devkit.json] -->|ai-devkit install| CM[ConfigManager] |
| 16 | + end |
| 17 | +
|
| 18 | + subgraph Core |
| 19 | + IT -->|persist mcpServers| CM |
| 20 | + CM -->|read mcpServers| IS[InstallService] |
| 21 | + IS --> MG[McpConfigGenerator] |
| 22 | + end |
| 23 | +
|
| 24 | + subgraph Generators |
| 25 | + MG --> CG[ClaudeCodeGenerator] |
| 26 | + MG --> XG[CodexGenerator] |
| 27 | + end |
| 28 | +
|
| 29 | + subgraph Output |
| 30 | + CG -->|write/merge| MJ[.mcp.json] |
| 31 | + XG -->|write/merge| CT[.codex/config.toml] |
| 32 | + end |
| 33 | +
|
| 34 | + subgraph Safety |
| 35 | + CG -->|detect existing| EP[ExistingConfigParser] |
| 36 | + XG -->|detect existing| EP |
| 37 | + EP -->|conflicts?| PR[Prompt User] |
| 38 | + end |
| 39 | +``` |
| 40 | + |
| 41 | +**Flow:** |
| 42 | +1. User defines `mcpServers` in `.ai-devkit.json` directly or via a YAML template (`ai-devkit init -t`) |
| 43 | +2. `ai-devkit install` reads `mcpServers` from config |
| 44 | +3. `McpConfigGenerator` dispatches to per-agent generators based on `config.environments` |
| 45 | +4. Each generator reads the existing agent config (if any), computes a diff, and prompts the user before writing |
| 46 | + |
| 47 | +## Data Models |
| 48 | + |
| 49 | +### Universal MCP Server Schema (in `.ai-devkit.json`) |
| 50 | + |
| 51 | +```typescript |
| 52 | +// Added to DevKitConfig |
| 53 | +interface DevKitConfig { |
| 54 | + // ... existing fields ... |
| 55 | + mcpServers?: Record<string, McpServerDefinition>; |
| 56 | +} |
| 57 | + |
| 58 | +// Universal MCP server definition |
| 59 | +interface McpServerDefinition { |
| 60 | + // Transport type |
| 61 | + transport: 'stdio' | 'http' | 'sse'; |
| 62 | + |
| 63 | + // stdio transport fields |
| 64 | + command?: string; // Required for stdio |
| 65 | + args?: string[]; // Optional for stdio |
| 66 | + env?: Record<string, string>; // Optional environment variables |
| 67 | + |
| 68 | + // http/sse transport fields |
| 69 | + url?: string; // Required for http/sse |
| 70 | + headers?: Record<string, string>; // Optional HTTP headers (auth, etc.) |
| 71 | +} |
| 72 | +``` |
| 73 | + |
| 74 | +### Template Schema Extension |
| 75 | + |
| 76 | +```typescript |
| 77 | +// Added to InitTemplateConfig |
| 78 | +interface InitTemplateConfig { |
| 79 | + // ... existing fields ... |
| 80 | + mcpServers?: Record<string, McpServerDefinition>; |
| 81 | +} |
| 82 | +``` |
| 83 | + |
| 84 | +### Example `.ai-devkit.json` |
| 85 | + |
| 86 | +```json |
| 87 | +{ |
| 88 | + "version": "0.5.0", |
| 89 | + "environments": ["claude", "codex"], |
| 90 | + "phases": ["requirements", "design", "planning", "implementation", "testing"], |
| 91 | + "mcpServers": { |
| 92 | + "memory": { |
| 93 | + "transport": "stdio", |
| 94 | + "command": "npx", |
| 95 | + "args": ["-y", "@ai-devkit/memory"], |
| 96 | + "env": { "MEMORY_DB_PATH": "./memory.db" } |
| 97 | + }, |
| 98 | + "filesystem": { |
| 99 | + "transport": "stdio", |
| 100 | + "command": "npx", |
| 101 | + "args": ["-y", "@modelcontextprotocol/server-filesystem", "./src"] |
| 102 | + }, |
| 103 | + "notion": { |
| 104 | + "transport": "http", |
| 105 | + "url": "https://mcp.notion.com/mcp" |
| 106 | + }, |
| 107 | + "secure-api": { |
| 108 | + "transport": "http", |
| 109 | + "url": "https://api.example.com/mcp", |
| 110 | + "headers": { |
| 111 | + "Authorization": "Bearer ${API_KEY}" |
| 112 | + } |
| 113 | + } |
| 114 | + } |
| 115 | +} |
| 116 | +``` |
| 117 | + |
| 118 | +### Example YAML Template |
| 119 | + |
| 120 | +```yaml |
| 121 | +environments: |
| 122 | + - claude |
| 123 | + - codex |
| 124 | +phases: |
| 125 | + - requirements |
| 126 | + - design |
| 127 | + - planning |
| 128 | + - implementation |
| 129 | + - testing |
| 130 | +skills: |
| 131 | + - registry: codeaholicguy/ai-devkit |
| 132 | + skill: memory |
| 133 | +mcpServers: |
| 134 | + memory: |
| 135 | + transport: stdio |
| 136 | + command: npx |
| 137 | + args: ["-y", "@ai-devkit/memory"] |
| 138 | + env: |
| 139 | + MEMORY_DB_PATH: "./memory.db" |
| 140 | + notion: |
| 141 | + transport: http |
| 142 | + url: https://mcp.notion.com/mcp |
| 143 | +``` |
| 144 | +
|
| 145 | +## Agent-Specific Output Formats |
| 146 | +
|
| 147 | +### Claude Code (`.mcp.json`) |
| 148 | + |
| 149 | +stdio servers omit the `type` field (inferred). HTTP/SSE servers use `"type": "http"` or `"type": "sse"`. |
| 150 | + |
| 151 | +```json |
| 152 | +{ |
| 153 | + "mcpServers": { |
| 154 | + "memory": { |
| 155 | + "command": "npx", |
| 156 | + "args": ["-y", "@ai-devkit/memory"], |
| 157 | + "env": { "MEMORY_DB_PATH": "./memory.db" } |
| 158 | + }, |
| 159 | + "notion": { |
| 160 | + "type": "http", |
| 161 | + "url": "https://mcp.notion.com/mcp" |
| 162 | + }, |
| 163 | + "secure-api": { |
| 164 | + "type": "http", |
| 165 | + "url": "https://api.example.com/mcp", |
| 166 | + "headers": { |
| 167 | + "Authorization": "Bearer ${API_KEY}" |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +**Mapping rules (universal → Claude Code):** |
| 175 | +- `transport: "stdio"` → omit `type`, emit `command`/`args`/`env` |
| 176 | +- `transport: "http"` → `"type": "http"`, emit `url`/`headers` |
| 177 | +- `transport: "sse"` → `"type": "sse"`, emit `url`/`headers` |
| 178 | +- `headers` → `headers` (direct pass-through) |
| 179 | + |
| 180 | +### Codex (`.codex/config.toml`) |
| 181 | + |
| 182 | +```toml |
| 183 | +[mcp_servers.memory] |
| 184 | +command = "npx" |
| 185 | +args = ["-y", "@ai-devkit/memory"] |
| 186 | +
|
| 187 | +[mcp_servers.memory.env] |
| 188 | +MEMORY_DB_PATH = "./memory.db" |
| 189 | +
|
| 190 | +[mcp_servers.notion] |
| 191 | +url = "https://mcp.notion.com/mcp" |
| 192 | +
|
| 193 | +[mcp_servers.secure-api] |
| 194 | +url = "https://api.example.com/mcp" |
| 195 | +
|
| 196 | +[mcp_servers.secure-api.http_headers] |
| 197 | +Authorization = "Bearer ${API_KEY}" |
| 198 | +``` |
| 199 | + |
| 200 | +**Mapping rules (universal → Codex):** |
| 201 | +- `transport: "stdio"` → emit `command`/`args`; `env` as `[mcp_servers.<name>.env]` table |
| 202 | +- `transport: "http"` or `"sse"` → emit `url`; `headers` as `[mcp_servers.<name>.http_headers]` table |
| 203 | +- Codex-specific fields (`startup_timeout_sec`, `enabled_tools`, etc.) are not generated in v1 |
| 204 | + |
| 205 | +## Component Breakdown |
| 206 | + |
| 207 | +### 1. Schema & Validation (`types.ts` + `InitTemplate.ts`) |
| 208 | +- `McpTransport` type and `McpServerDefinition` interface in `types.ts` |
| 209 | +- `mcpServers` added to `DevKitConfig` (optional) |
| 210 | +- `mcpServers` added to `InitTemplateConfig` and `ALLOWED_TEMPLATE_FIELDS` |
| 211 | +- Validation extracted to `validateMcpServers()` and `validateStringRecord()` helpers |
| 212 | +- Validate: transport required (`stdio`|`http`|`sse`), stdio requires `command`, http/sse requires `url`, `headers` optional for http/sse |
| 213 | + |
| 214 | +### 2. ConfigManager (`lib/Config.ts`) |
| 215 | +- No code changes needed — existing generic `update()`/`read()` handles `mcpServers` via the updated `DevKitConfig` type |
| 216 | + |
| 217 | +### 3. MCP Generators (`services/install/mcp/`) |
| 218 | + |
| 219 | +``` |
| 220 | +services/install/mcp/ |
| 221 | +├── index.ts # Re-exports: installMcpServers, McpInstallOptions, McpInstallReport |
| 222 | +├── types.ts # McpAgentGenerator interface, McpMergePlan, McpInstallReport |
| 223 | +├── BaseMcpGenerator.ts # Abstract base: shared plan() + apply() diff-and-merge logic |
| 224 | +├── McpConfigGenerator.ts # Orchestrator: dispatch to generators, conflict resolution, CI mode |
| 225 | +├── ClaudeCodeMcpGenerator.ts # .mcp.json: toAgentFormat + read/write JSON |
| 226 | +└── CodexMcpGenerator.ts # .codex/config.toml: toAgentFormat + read/write TOML |
| 227 | +``` |
| 228 | +
|
| 229 | +**Key interfaces:** |
| 230 | +```typescript |
| 231 | +interface McpAgentGenerator { |
| 232 | + readonly agentType: EnvironmentCode; |
| 233 | + plan(servers: Record<string, McpServerDefinition>, projectRoot: string): Promise<McpMergePlan>; |
| 234 | + apply(plan: McpMergePlan, servers: Record<string, McpServerDefinition>, projectRoot: string): Promise<void>; |
| 235 | +} |
| 236 | +
|
| 237 | +interface McpMergePlan { |
| 238 | + agentType: EnvironmentCode; |
| 239 | + newServers: string[]; |
| 240 | + conflictServers: string[]; |
| 241 | + skippedServers: string[]; |
| 242 | + resolvedConflicts: string[]; // filled after user prompt or --overwrite |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +**`BaseMcpGenerator`** provides shared `plan()` and `apply()` logic. Subclasses implement three abstract methods: |
| 247 | +- `toAgentFormat(def)` — convert universal schema to agent-native format |
| 248 | +- `readExistingServers(projectRoot)` — read and parse agent config file |
| 249 | +- `writeServers(projectRoot, mergedServers)` — serialize and write back |
| 250 | + |
| 251 | +### 4. Merge & Conflict Resolution |
| 252 | +- **Comparison**: `deepEqual()` (shared util at `util/object.ts`) on agent-format output |
| 253 | +- **Interactive mode**: prompt user with skip all / overwrite all / choose per server |
| 254 | +- **Non-interactive (CI)**: `--overwrite` flag → overwrite all; default → skip all conflicts |
| 255 | +- **TTY detection**: `isInteractiveTerminal()` (shared util at `util/terminal.ts`) |
| 256 | +- **Preservation**: both generators read the full config, modify only MCP server entries, and preserve all other content |
| 257 | + |
| 258 | +### 5. Install Service Extension (`services/install/install.service.ts`) |
| 259 | +- Calls `installMcpServers()` after skills install, passing `{ overwrite }` from CLI options |
| 260 | +- Uses union of existing + newly installed environments to determine which generators to run |
| 261 | +- `McpInstallReport` added to `InstallReport` (installed/skipped/conflicts/failed counts) |
| 262 | +- Persists `mcpServers` to `.ai-devkit.json` via `configManager.update()` |
| 263 | + |
| 264 | +### 6. Install Config Validation (`util/config.ts`) |
| 265 | +- `mcpServers` added to `InstallConfigData` with Zod schema validation |
| 266 | +- Defaults to `{}` when absent — existing configs work unchanged |
| 267 | + |
| 268 | +### 7. Init Command (`commands/init.ts`) |
| 269 | +- Persists `mcpServers` from template to `.ai-devkit.json` after skills install |
| 270 | +- Displays count and suggests running `ai-devkit install` to generate agent configs |
| 271 | + |
| 272 | +## Design Decisions |
| 273 | + |
| 274 | +| Decision | Choice | Rationale | |
| 275 | +|----------|--------|-----------| |
| 276 | +| Config location | `mcpServers` in `.ai-devkit.json` | Single source of truth, no new files | |
| 277 | +| Transport field | Explicit `transport: 'stdio' \| 'http' \| 'sse'` | Clear intent, easy validation; maps to agent-specific format per generator | |
| 278 | +| `http` naming | Use `http` not `streamable-http` | Matches Claude Code's `type: "http"` convention; shorter; the MCP spec calls it "Streamable HTTP" but agents use `http` | |
| 279 | +| SSE support | Include but mark deprecated | SSE is deprecated in MCP spec (replaced by streamable-http) but some servers still use it | |
| 280 | +| Merge strategy | Additive with prompt on conflict | Prevents data loss, respects user customizations | |
| 281 | +| CI / non-interactive | Skip conflicts by default, overwrite with `--overwrite` | Safe default for CI pipelines; no hanging prompts | |
| 282 | +| TOML library | `smol-toml` | Codex config needs both read and write; `smol-toml` handles nested tables correctly | |
| 283 | +| Generator pattern | Abstract base class + subclasses | Shared plan/apply logic in `BaseMcpGenerator`; subclasses only provide format-specific I/O | |
| 284 | +| Scope | Project-level only | `.mcp.json` for Claude Code, `.codex/config.toml` for Codex — no user-level configs | |
| 285 | + |
| 286 | +## Non-Functional Requirements |
| 287 | + |
| 288 | +- **Performance**: Config generation is fast (file I/O only, no network) |
| 289 | +- **Safety**: Never silently overwrite existing configs; prompt in interactive mode, skip in CI |
| 290 | +- **Extensibility**: Adding a new agent generator requires only a new subclass of `BaseMcpGenerator` |
0 commit comments