# Phase 2: Per-Agent MCP Servers — Implementation Plan

**Priority:** P1 · **Effort:** 2–3 days

---

## Step 1: Define MCP Types

### 1.1. Add to `src/types.ts`

```typescript
/** Configuration for an MCP server that runs as a subprocess. */
export interface McpServerConfig {
  command: string;
  args?: string[];
  env?: Record<string, string>;
  shell?: boolean;
}

/** MCP server spec in agent config — reference or inline definition. */
export type AgentMcpServerSpec =
  | string  // Reference to existing server by name
  | Record<string, McpServerConfig>;  // Inline definition as { name: config }
```

### 1.2. Add `mcpServers` to `AgentConfig`

```typescript
export interface AgentConfig {
  // ...existing fields...
  mcpServers?: AgentMcpServerSpec[];
}
```

---

## Step 2: Create `src/agent-mcp.ts`

New module for MCP server management. Functions:

### 2.1. `connectToMcpServer(name, config): Promise<McpClient>`

Connects to a single MCP server given its config. Uses `child_process.spawn()` to start the server process, then connects via stdio transport. Returns a client object with:
- `name: string`
- `tools: Tool[]`
- `cleanup: () => Promise<void>`

### 2.2. `initializeAgentMcpServers(agentConfig, parentClients)`

The main entry point:

```typescript
async function initializeAgentMcpServers(
  agentConfig: AgentConfig,
  parentClients: McpClientConnection[],
  pi: ExtensionAPI,
): Promise<{
  clients: McpClientConnection[];
  tools: Tool[];
  cleanup: () => Promise<void>;
}> {
  if (!agentConfig.mcpServers?.length) {
    return { clients: parentClients, tools: [], cleanup: async () => {} };
  }

  const agentClients: McpClientConnection[] = [];
  const newlyCreated: McpClientConnection[] = [];
  const agentTools: Tool[] = [];

  for (const spec of agentConfig.mcpServers) {
    if (typeof spec === 'string') {
      // Named reference — look up in parent's MCP server config registry
      // (if pi has one), or skip with a warning
      // Don't add to newlyCreated — it's shared
    } else {
      // Inline definition — create a new connection
      const [name, config] = Object.entries(spec)[0];
      const client = await connectToMcpServer(name, config);
      agentClients.push(client);
      newlyCreated.push(client);
      if (client.tools) agentTools.push(...client.tools);
    }
  }

  // Deduplicate tools by name
  const deduped = uniqBy([...agentTools, .../* parent mcp tools */], 'name');

  const cleanup = async () => {
    for (const client of newlyCreated) {
      try { await client.cleanup(); } catch { /* ignore */ }
    }
  };

  return {
    clients: [...parentClients, ...agentClients],
    tools: deduped,
    cleanup,
  };
}
```

### 2.3. Tool Deduplication

Use a simple `uniqBy` (or inline set-based dedup):

```typescript
function uniqBy<T>(arr: T[], key: (item: T) => string): T[] {
  const seen = new Set<string>();
  return arr.filter(item => {
    const k = key(item);
    if (seen.has(k)) return false;
    seen.add(k);
    return true;
  });
}
```

---

## Step 3: Modify `src/agent-runner.ts`

### 3.1. Add MCP Initialization Step

In `runAgent()` or equivalent function, before creating the agent session:

```typescript
// Initialize agent-specific MCP servers
const {
  clients: mergedMcpClients,
  tools: agentMcpTools,
  cleanup: mcpCleanup,
} = await initializeAgentMcpServers(
  agentConfig,
  parentMcpClients,  // from pi.codingAgent?.getMcpClients() or similar
  pi,
);

try {
  // Create session with merged clients + tools
  // ... existing session creation ...
} finally {
  await mcpCleanup();
}
```

### 3.2. Thread MCP Clients Through

If `runAgent()` already receives MCP clients, merge them before passing to the session. Otherwise, extend the function signature to accept `mcpClients` and `mcpTools`.

---

## Step 4: Modify `src/custom-agents.ts`

Update the YAML frontmatter parser to extract `mcpServers`:

```typescript
// In the frontmatter parsing logic:
const mcpServers = frontmatter.mcpServers;
if (Array.isArray(mcpServers)) {
  config.mcpServers = mcpServers.map((spec: unknown) => {
    if (typeof spec === 'string') return spec;
    if (typeof spec === 'object' && spec !== null) return spec as Record<string, McpServerConfig>;
    return undefined;
  }).filter(Boolean);
}
```

---

## Step 5: Tests — `test/agent-mcp.test.ts`

| # | Test | Expected |
|---|---|---|
| 1 | Empty mcpServers | Returns parent clients unchanged |
| 2 | Named reference only | Returns parent clients (no new connections) |
| 3 | Tool deduplication | Duplicate tool names resolved, parent wins |
| 4 | Cleanup on success | Inline servers cleaned up |
| 5 | Cleanup on error | `cleanup()` called even if agent errors |
| 6 | Multiple inline servers | All connected, all cleaned up |

---

## File Change Summary

| Action | File |
|---|---|
| **MODIFY** | `src/types.ts` — add `McpServerConfig`, `AgentMcpServerSpec`, `mcpServers` field |
| **CREATE** | `src/agent-mcp.ts` (~200 lines) |
| **MODIFY** | `src/agent-runner.ts` — add MCP init step, cleanup in finally |
| **MODIFY** | `src/custom-agents.ts` — parse `mcpServers` from frontmatter |
| **CREATE** | `test/agent-mcp.test.ts` (~100 lines) |
