How MCP Servers Are Handled During Plugin Conversion in Compound Engineering

During plugin conversion, MCP servers are parsed from .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, where the parser looks for MCP server definitions in two locations: the manifest.mcpServers field or an optional .mcp.json sidecar file.

Loading from Manifest or Sidecar Files

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

// 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).

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 emits an MCPorter JSON config that distinguishes between local command-based tools and remote HTTP endpoints:

// 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 produces a configuration that explicitly tags servers as "local" or "remote":

// 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

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 demonstrates the merge strategy, backing up existing files before writing:

// 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:

// 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 module loads MCP server definitions from manifest.mcpServers or .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 first checks the manifest.mcpServers field. If that field is empty, it falls back to a .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 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →