Guides

Publish an MCP server so users install it in Claude Code, Cursor, Codex and every other agent host with one command#

You have an MCP server. This page is the publisher side of distribution: how to ship it so that a developer who wants it in Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Zed or any of the other 42 supported agent hosts runs one command, and how agent-connector renders each host's native config, hooks, skills and plugin bundle from a single declaration.

What your users run#

The end state first. Your package ships a bin (here acme-db); the user installs the package and runs its install command. agent-connector detects the hosts on that machine and writes the MCP entry, hooks and other surfaces into each host's own config format.

terminal (your user)
bash
# the consumer installs YOUR package; the acme-db bin is linked.
# no framework global install is needed for branded MCP install commands.
npm install @acme/acme-db-mcp

# deploy the acme-db connector across every detected agent platform.
acme-db install                 # auto-scoped — no --connector needed
acme-db install --dry-run       # preview the plan, nothing written
acme-db upgrade                 # bring everything current (alias: sync, update)
acme-db doctor                  # health-check every platform for acme-db

# telemetry + leaderboards, scoped to the acme-db connector:
acme-db leaderboard             # the 🔌 MCP/plugin section shows acme-db
acme-db telemetry report --by tool   # acme-db's per-tool token footprint
acme-db telemetry leaderboard        # which acme-db tool costs the most

# every agent-connector subcommand is available, branded as acme-db:
acme-db --help

Publisher, not end user

Tools such as add-mcp or agent-install add an existing server to the agents on one machine. agent-connector is the other side: the server's author ships a package that installs itself, with hooks, skills and a plugin bundle, and keeps working when a host changes its config format because the adapters are updated in one place.

1. Add agent-connector to your MCP package#

agent-connector is a dependency of your package, not a global tool your users install. package.json is the identity source: name, mcpName, bin and version become the connector id, display name and version, so nothing is declared twice.

package.json
json
{
  "name": "@acme/acme-db-mcp",
  "version": "1.0.0",
  "description": "Acme DB MCP server with branded install support",
  "type": "module",
  "mcpName": "io.github.acme/acme-db",
  "bin": {
    "acme-db": "./bin.mjs"
  },
  "files": ["bin.mjs", "agent-connector.config.mjs"],
  "dependencies": {
    "@ken-jo/agent-connector": "^0.4.94"
  }
}

2. Declare the server once#

agent-connector.config.mjs holds the one declaration: how to launch the server, which tools to expose, optional hooks with typed handlers, telemetry, and per-host overrides. You never hand-write a host config file; the CLI renders each host's format from this.

agent-connector.config.mjs
ts
import { defineConnector } from "@ken-jo/agent-connector/sdk";

export default defineConnector({
  // package.json / npm metadata is the source of truth.
  // id/displayName/version are derived from name/mcpName/bin/version unless
  // you need a multi-instance alias such as "github-octocorp".
  // Host-native ids are generated during install; don't copy them back here.
  server: {
    transport: "stdio",
    command: "npx",
    args: ["-y", "@acme/acme-db-mcp"],
    env: { ACME_DB_DSN: "${env:ACME_DB_DSN}" },
    tools: { include: ["*"] },
    timeoutMs: 30_000,
  },
  hooks: {
    PreToolUse: {
      matcher: "acme_write",
      async handler(evt) {
        if (evt.toolName === "acme_write")
          return { decision: "ask", reason: "Confirm Acme DB write" };
        return { decision: "allow" };
      },
    },
    SessionStart: {
      async handler() {
        return {
          decision: "context",
          additionalContext: "Acme DB schema v12 is loaded.",
        };
      },
    },
  },
  telemetry: { enabled: true, modelFamilyHint: "auto", measureToolDefs: true },
  platforms: { warp: { hooks: false } }, // Warp is mcp-only: skip hooks
  targets: "auto",
});

Field reference: defineConnector, Server, Hooks, Commands, skills, subagents, memory, statusline and actions.

3. Ship the install command under your own bin#

createConnectorCli() exposes every agent-connector subcommand (detect, install, doctor, upgrade, uninstall, package, telemetry) under your bin, auto-scoped to the connector shipped beside it. Your users never pass --connector and never see the framework name.

bin.mjs
ts
#!/usr/bin/env node
// bin.mjs
import { createConnectorCli } from "@ken-jo/agent-connector/cli";

createConnectorCli({
  // packageJson supplies public identity: name, mcpName, bin, version.
  packageJson: new URL("./package.json", import.meta.url),
  // connector supplies behavior: server, hooks, skills, telemetry.
  // These are two layers, not duplicate id/display-name inputs.
  connector: new URL("./agent-connector.config.mjs", import.meta.url),
})
  .run()
  .then((code) => { process.exitCode = code; })
  .catch((err) => {
    process.stderr.write(`acme-db: fatal: ${err?.stack ?? err}\n`);
    process.exitCode = 1;
  });

4. Verify before you publish#

Run the same commands your users will, against your own machine. Nothing is written until install runs without --dry-run.

terminal (you)
bash
# 1. add agent-connector as a dependency of your connector package
npm install @ken-jo/agent-connector

# 2. write agent-connector.config.mjs (defineConnector — see below)

# 3a. ship a branded CLI so YOUR users drive it (auto-scoped, no --connector):
acme-db detect            # list installed hosts + paradigms
acme-db audit             # catch package/bin/connector identity drift
acme-db install --dry-run # preview the diff
acme-db install           # write native configs everywhere
acme-db doctor            # verify — add --probe for a live MCP handshake (initialize → ping → tools/list)
acme-db upgrade           # day 2: re-render configs + heal the home-binary pointer (aliases: sync, update)
acme-db leaderboard       # acme-db's token footprint vs the boards
acme-db telemetry report --by tool   # which of acme-db's tools cost the most tokens
acme-db uninstall         # full inverse — removes everything install wrote (--purge, --dry-run work too)

# 3b. packaging/distribution artifacts are framework tooling:
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs --format all --out ./dist

# 3c. development fallback only — run the framework from the project:
npx @ken-jo/agent-connector detect
npx @ken-jo/agent-connector install --dry-run
CommandWhat it proves
acme-db auditpackage.json, bin and connector agree on id, name and version.
acme-db install --dry-runThe exact files and JSON patches per detected host, before any write.
acme-db doctor --probeEach host's config resolves, and a live MCP handshake (initialize → ping → tools/list) succeeds through the installed entry.
acme-db uninstall --dry-runThe inverse is complete: everything install wrote is listed for removal.

5. What gets written on the user's machine#

One declaration becomes host-native files: the MCP server entry in each host's config (JSON, TOML, YAML or the host's plugin manifest), hook registrations where the host has hooks, and skills, commands, subagents, memory and statusline files where the host has those surfaces. The per-host paths, root keys and hook events are listed in Platforms and in the agent-readable llms-full.txt. The measured footprint of the example connector is in the README, pinned by a test that re-measures it on every run.

6. Plugin marketplaces and standard artifacts#

Some hosts install from a plugin marketplace rather than a config file. package emits the host plugin formats and the two MCP standard artifacts (mcp-server-json, mcpb) from the same declaration.

terminal (you)
bash
# default format (agent-plugin — the Agent Plugins 1.0.0 bundle) → <cwd>/dist-plugin
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs

# pick a format + output dir; preview without writing
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs --format gemini-extension --out ./dist --dry-run

# emit EVERY feasible format, each into <out>/<format>/
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs --format all --out ./dist

# an unknown format exits 2
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs --format bogus   # → invalid --format "bogus" (exit 2)

# if you already keep the framework CLI globally installed, drop the npx package prefix:
agent-connector package --connector ./agent-connector.config.mjs --format all --out ./dist
terminal (your user)
bash
# the default agent-plugin bundle installs from a local marketplace, two steps —
# the SAME <out> dir serves Codex and GitHub Copilot CLI (VS Code picks up the CLI install):
codex plugin marketplace add ./dist-plugin && codex plugin add acme-db@agent-connector
copilot plugin marketplace add ./dist-plugin && copilot plugin install acme-db@agent-connector
# acme-db is the package-derived connector id inside the generated bundle,
# not an extra id the user enters in defineConnector.

# a claude-plugin bundle (--format claude-plugin) is the Claude Code equivalent:
/plugin marketplace add ./dist-plugin
/plugin install acme-db@agent-connector

# the wrapped MCP entry still routes through the one agent-connector runtime —
# agent-plugin via its bundled launcher (no absolute path in the bundle):
#   node ${PLUGIN_ROOT}/bin/agent-connector.mjs serve --connector acme-db -- <real cmd>
# claude-plugin via the home-bin path:
#   agent-connector serve --connector acme-db --host claude-code -- <real cmd>
# so a marketplace-installed connector STILL reports per-tool tokens.

Formats, marketplace layouts and the Agent Plugins bundle are covered in Packaging & marketplaces. Which hosts support which surface is on the coverage wall.