# How MCP Servers Are Handled During Plugin Conversion in Compound Engineering

> Learn how MCP servers are handled during plugin conversion. Discover parsing, translation, and persistence methods within the compound engineering plugin for seamless integration.

- Repository: [Every/compound-engineering-plugin](https://github.com/everyinc/compound-engineering-plugin)
- Tags: how-to-guide
- Published: 2026-02-16

---

**During plugin conversion, MCP servers are parsed from [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) or manifest files, translated into target-specific formats via dedicated converter functions, and persisted to configuration files through target-specific sync modules.**

The Compound Engineering Plugin (`EveryInc/compound-engineering-plugin`) automates the conversion of Claude Code plugins to other AI coding environments like Pi, OpenCode, Gemini, Cursor, and Codex. When converting plugins that use Model Context Protocol (MCP) servers, the tool preserves tool configurations through a three-stage pipeline that ensures compatibility across all target platforms.

## Parsing MCP Servers from Claude Code Plugins

The conversion process begins in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts), where the parser looks for MCP server definitions in two locations: the `manifest.mcpServers` field or an optional [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) sidecar file.

### Loading from Manifest or Sidecar Files

The `loadMcpServers` function first checks the manifest, then falls back to the sidecar file:

```typescript
// src/parsers/claude.ts
const mcpServers = await loadMcpServers(root, manifest)

if (manifest.mcpServers) {
  return manifest.mcpServers
}
const path = resolveWithinRoot(root, entry, ".mcp.json")

```

The resulting `ClaudeMcpServer` definitions are stored on the `ClaudePlugin` type as `mcpServers?: Record<string, ClaudeMcpServer>` (defined in [`src/types/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/types/claude.ts)).

## Converting MCP Definitions to Target Formats

Each target platform has its own converter that maps the generic `ClaudeMcpServer` shape into the target's required configuration format.

### Pi and MCPorter Configuration

For Pi, the `convertMcpToMcporter` function in [`src/converters/claude-to-pi.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-pi.ts) emits an MCPorter JSON config that distinguishes between local command-based tools and remote HTTP endpoints:

```typescript
// src/converters/claude-to-pi.ts
function convertMcpToMcporter(servers: Record<string, ClaudeMcpServer>): PiMcporterConfig {
  const mcpServers: Record<string, PiMcporterServer> = {}
  for (const [name, server] of Object.entries(servers)) {
    if (server.command) {
      // Local tool – keep command, args, env, headers
      mcpServers[name] = { command: server.command, args: server.args, env: server.env, headers: server.headers }
    } else if (server.url) {
      // Remote HTTP endpoint
      mcpServers[name] = { baseUrl: server.url, headers: server.headers }
    }
  }
  return { mcpServers }
}

```

The Pi converter also injects a compatibility note into generated prompts when MCP servers are present, instructing users to interact through the generated `mcporter_list` and `mcporter_call` tools.

### OpenCode Local and Remote Servers

The OpenCode converter in [`src/converters/claude-to-opencode.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-opencode.ts) produces a configuration that explicitly tags servers as `"local"` or `"remote"`:

```typescript
// src/converters/claude-to-opencode.ts
function convertMcp(servers: Record<string, ClaudeMcpServer>): Record<string, OpenCodeMcpServer> {
  const result: Record<string, OpenCodeMcpServer> = {}
  for (const [name, server] of Object.entries(servers)) {
    if (server.command) {
      result[name] = { type: "local", command: [server.command, ...(server.args ?? [])], environment: server.env, enabled: true }
    } else if (server.url) {
      result[name] = { type: "remote", url: server.url, headers: server.headers, enabled: true }
    }
  }
  return result
}

```

### Gemini, Cursor, and Codex Variations

- **Gemini** and **Cursor** use similar JSON structures (defined in [`src/converters/claude-to-gemini.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-gemini.ts) and [`src/converters/claude-to-cursor.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/converters/claude-to-cursor.ts)), wrapping the server definitions for their respective settings files.
- **Codex** performs a direct pass-through, keeping the `ClaudeMcpServer` map unchanged and rendering it to a TOML configuration section in [`src/sync/codex.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/sync/codex.ts).

## Syncing MCP Configuration to Target Files

The final stage persists the converted MCP configuration to the target environment's configuration files.

### Merging into Existing Configurations

The Pi sync module in [`src/sync/pi.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/sync/pi.ts) demonstrates the merge strategy, backing up existing files before writing:

```typescript
// src/sync/pi.ts
if (Object.keys(config.mcpServers).length > 0) {
  const converted = convertMcpToMcporter(config.mcpServers)
  const existing = await readJson(mcpPath) ?? {}
  const merged = { mcpServers: { ...(existing.mcpServers ?? {}), ...converted.mcpServers } }
  await writeJson(mcpPath, merged)
}

```

### TOML Generation for Codex

For Codex, the sync module generates a TOML snippet:

```typescript
// src/sync/codex.ts
function renderCodexConfig(mcpServers?: Record<string, ClaudeMcpServer>): string | null {
  if (!mcpServers || Object.keys(mcpServers).length === 0) return null
  const lines = ["# MCP servers synced from Claude Code", "[mcp]"]

  for (const [name, server] of Object.entries(mcpServers)) {
    if (server.command) {
      lines.push(`[mcp.${name}]`, `type = "local"`, `command = "${server.command}"`)
    } else if (server.url) {
      lines.push(`[mcp.${name}]`, `type = "remote"`, `url = "${server.url}"`)
    }
  }
  return lines.join("\n")
}

```

## Summary

- **Parsing**: The [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts) module loads MCP server definitions from `manifest.mcpServers` or [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) sidecar files into the `ClaudePlugin.mcpServers` map.
- **Conversion**: Target-specific converters in `src/converters/` translate `ClaudeMcpServer` objects into platform-native formats—MCPorter JSON for Pi, typed local/remote objects for OpenCode, and TOML for Codex.
- **Sync**: Modules in `src/sync/` persist the converted configurations by merging with existing user settings, backing up previous files, and injecting compatibility notes where necessary.

## Frequently Asked Questions

### What file does the parser look for when loading MCP servers?

The parser in [`src/parsers/claude.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/parsers/claude.ts) first checks the `manifest.mcpServers` field. If that field is empty, it falls back to a [`.mcp.json`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/.mcp.json) sidecar file located in the plugin root directory using `resolveWithinRoot(root, entry, ".mcp.json")`.

### How does the plugin handle existing MCP configurations during sync?

During the sync phase, target modules such as [`src/sync/pi.ts`](https://github.com/EveryInc/compound-engineering-plugin/blob/main/src/sync/pi.ts) read existing configuration files first, then merge the newly converted MCP servers into the existing `mcpServers` map. This preserves user-defined servers from other sources while adding the converted ones.

### Which targets require special formatting for MCP servers?

Pi requires conversion to MCPorter JSON format via `convertMcpToMcporter`, while OpenCode requires explicit `type: "local"` or `type: "remote"` discrimination via `convertMcp`. Codex requires TOML rendering through `renderCodexConfig`, and Gemini and Cursor use JSON structures similar to each other but distinct from the source format.

### Are remote HTTP MCP servers supported across all targets?

Yes, remote HTTP endpoints are supported across all targets. The converters check for the `url` property on `ClaudeMcpServer` objects and map them to target-specific remote configurations—such as `baseUrl` for Pi, `url` with `type: "remote"` for OpenCode, and direct URL mapping for Gemini, Cursor, and Codex.