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 placeWhat 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.

pyRPC