← Back to Blog

MCP Without a Launcher: The npx @pyrpc/mcp mcp Pattern

·7 min read

Every AI coding agent has its own config format. Cursor uses .cursor/mcp.json, Claude Desktop uses .mcp.json, Windsurf has its own, and opencode.json looks nothing like either. If you want your MCP server to work across agents, you face a choice: ship a launcher that writes all these files, or find a better way.

The config format problem

The MCP ecosystem has at least 19 recognized agent config formats. They differ in location, syntax, nesting depth, and how they reference server commands. A launcher that writes them all must track each format, handle version drift, and cope with agents that reorganize their configs between releases. That is a maintenance surface disguised as a convenience.

What npx @pyrpc/mcp mcp actually wraps

The distribution CLI calls add-mcp@2.3.0, an Apache-2.0 ESM package whose sole job is understanding all 19 agent config formats. It detects which agents are installed, presents a multiselect, and calls upsertServer() per agent to update config files atomically. pyRPC does not shell out to the add-mcp CLI, maintain its own agent database, or handle any protocol details.

npx @pyrpc/mcp mcp
  -> detects installed agents (Cursor, Claude, Windsurf, ...)
  -> multiselect prompt
  -> upsertServer() per selected agent
  -> config files updated in place

What the wrapper adds

The 50-line cli.ts that wraps add-mcp adds only things that matter for user experience: a branded banner so you know which tool ran, scope selection so you choose which agents to configure, per-agent result reporting so you see exactly what changed, and failure remediation hints when a config path is unexpected or a file is unwritable. None of that is protocol code.

What it does not do

The wrapper does not parse JSON-RPC, manage stdio transports, serialize tool schemas, or track agent protocol versions. It delegates all of that to add-mcp for agent configs and to the MCP SDK for the server itself. The separation is deliberate: the wrapper is a thin UX layer, not a second implementation.

The code walkthrough

The entire flow fits in 50 lines. The CLI entry point imports add-mcp programmatically, calls detectAgents() to find installed clients, prompts for selection, calls upsertServer() for each, and reports results. Error handling wraps each upsert in a try-catch that prints the failing agent name and a remediation hint. There are no abstractions, no interfaces, no plugin system. It is a script.

import { detectAgents, upsertServer } from "add-mcp";

const agents = await detectAgents();
const selected = await promptMultiselect(agents);

for (const agent of selected) {
  try {
    await upsertServer(agent, {
      name: "pyrpc",
      command: "npx @pyrpc/mcp mcp",
    });
    report(agent, "updated");
  } catch (e) {
    report(agent, "failed", remediationHint(e));
  }
}

Why this beats a custom launcher

A custom launcher means zero protocol code, zero client-specific code, and upstream handles all agent complexity. When add-mcp adds support for a new agent, pyRPC gets it for free. When an agent changes its config format, add-mcp fixes it, and the wrapper keeps working without a release. The alternative, maintaining a 19-format config writer inside pyRPC, would mean versioned releases for config changes that have nothing to do with RPC or MCP protocol.

The pattern generalizes: if your tool needs to register into agent configs, depend on the package that knows the formats, and wrap it with UX. Do not rewrite it.