npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ken-jo/agent-connector

v0.8.0

Published

Deploy one MCP server to every agent host. Declare it once with defineConnector() and ship a branded package that installs itself into Claude Code, Codex, Cursor, Copilot, Gemini CLI, Windsurf, Zed, Kiro, Warp, OpenCode and 42 agent hosts in all — native

Readme

agent-connector

Deploy one MCP to every agent host.

Focus on the implementation, not the distribution.

Write your server + hooks once with defineConnector(), then install it into the native config — or package it as a real plugin — across every detected agent host: terminal CLIs, IDE extensions and desktop apps alike (Claude Code, Codex, Cursor, Copilot, Gemini, OpenCode, Warp, Zed…).

Agent-readable docs: llms.txt (route map) · llms-full.txt (per-host reference) · SKILL.md (agent skill) · guide: Publish an MCP server so users install it in every agent host.

npm license agent hosts surfaces hook paradigms install verified headless runtime package formats agent plugins tests

By the numbers. Every figure is derived from the adapter registry or measured by a test on each run, and a drift test fails if the README and the source disagree.

| | | |---|---| | Agent hosts with an adapter | 42 — terminal CLIs, IDE extensions and desktop apps (coverage wall) | | Surfaces rendered per host | 8 — MCP server (41 hosts), memory (41), skills (36), hooks (32), commands (19), subagents (16), actions (9), status line (4) | | Hook events normalized | 13, dispatched through 3 paradigms (json-stdio 24 hosts · mcp-only 10 · ts-plugin 8) | | Package formats emitted | 9 host plugin formats + 2 MCP standard artifacts (mcp-server-json, mcpb) | | Secret backends | 4 — macOS Keychain, Linux Secret Service, Windows Credential Manager, opt-in file store; a ${secret:NAME} in a stdio server's env never reaches a host config | | OAuth provider presets | 5 — google, microsoft, github, bing-webmaster, posthog (+ generic) | | Hosts verified against the real host binary | 29 of 42 (22 of them end-to-end through a model tool call); the other 13 by the registry install harness in an isolated HOME | | Measured footprint | one 135-line defineConnector()66 host-native files in 6 file extensions across 41 of 42 hosts at user scope (63 at project scope) — npm run measure:footprint | | Test suite | 174 test files |

Who it is for. agent-connector is the publisher side of MCP distribution: you wrote (or are writing) an MCP server and want it to install itself into your users' agent hosts — with hooks, skills and a plugin bundle — from your own package, npx <your-package> install. Tools such as add-mcp and agent-install are the end-user side: they add an existing server to the agents on one machine. The two compose; agent-connector is what the server's author ships.

Two audiences: connector developers start at Quick start; if you already run an agent host and just want token totals, jump straight to usage.

Quick start

agent-connector is an SDK connector developers depend on. Add it to the package that holds your connector, declare the connector once, then ship a branded MCP package/bin such as npx @acme/acme-db-mcp install — it deploys to every detected agent host in that host's own native config. Installing @ken-jo/agent-connector globally is not the branded MCP lifecycle path; reserve the global CLI guidance for connector-free token usage reports. Framework artifact tooling stays developer-facing and normally runs through npx @ken-jo/agent-connector ... --connector. The linear path is: get a server → declare it → install through your branded package.

0. You need an MCP server file first. The config below points at ./my-mcp-server.mjs, so that file must exist before you install. Don't have an MCP server yet? Copy examples/acme-db/acme-db-mcp-server.mjs (a self-contained ~35-line stub) as ./my-mcp-server.mjs, or follow the official MCP SDK quickstart.

# 1. add agent-connector as a DEPENDENCY of your connector package
npm install @ken-jo/agent-connector
// 2. package.json — this is the user-facing package identity
{
  "name": "@acme/acme-db-mcp",
  "mcpName": "io.github.acme/acme-db",
  "bin": { "acme-db": "./bin.mjs" },
  "dependencies": { "@ken-jo/agent-connector": "^0.8.0" }
}
// 3. agent-connector.config.mjs — declare your server + hooks once
import { fileURLToPath } from "node:url";
import { defineConnector } from "@ken-jo/agent-connector/sdk";

const serverPath = fileURLToPath(new URL("./my-mcp-server.mjs", import.meta.url));

export default defineConnector({
  // package.json / npm metadata is the source of truth. The host alias/runtime
  // id and connector version are derived from name/mcpName/bin/version unless
  // you need a legacy or multi-instance alias. Host-native ids are generated
  // during install, so do not copy them back into defineConnector({ id }).
  server: {
    transport: "stdio",
    command: "node",
    args: [serverPath],
  },
  // hooks, telemetry, and more surfaces — see "What you define once" below
});

The wiring contract is:

  1. package.json defines the public product identity (name, mcpName, bin, version).
  2. bin.mjs calls createConnectorCli({ packageJson, connector }) so every install/doctor/upgrade/uninstall command runs under the developer's brand.
  3. agent-connector.config.* uses defineConnector({ server, ...surfaces }) to describe the real MCP launch shape or remote endpoint.
  4. install renders that single declaration into each detected host's native MCP config. For stdio processes, the host points at the stable agent-connector home binary, which launches the real command and can measure per-tool traffic. For remote HTTP servers, the host receives the URL where supported; there is no stdio process to wrap.

Command boundary

Keep the two command layers separate:

| Layer | Who runs it | Examples | Purpose | | --- | --- | --- | --- | | Branded MCP lifecycle | Users of your MCP package | npx @acme/acme-db-mcp install, acme-db doctor --probe, acme-db upgrade, acme-db uninstall, acme-db telemetry report --by tool | Install, verify, update, remove, and inspect telemetry for your MCP. | | Framework tooling | MCP package developers | npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs | Emit host plugin bundles and MCP distribution artifacts from a connector config. | | Connector-free user telemetry | Agent-CLI users with no MCP package | npx @ken-jo/agent-connector usage report --by platform | Read host CLI logs read-only for whole-conversation token totals. |

If a command operates the MCP after it is authored, prefer the branded package/bin. If a command builds framework distribution artifacts, use the framework CLI.

MCP server launch examples

Pick the server shape that matches the MCP you are building. The wrapper package identity still comes from package.json; these examples only describe how to start or connect to the actual MCP server.

| Shape | Use when | Minimal launch | | --- | --- | --- | | Package-runner MCP | The MCP is published as a package. | npx -y @acme/acme-db-mcp | | Local server-process MCP | The MCP server ships inside your package. | node ./my-mcp-server.mjs | | Python MCP | The MCP server is Python and should resolve runtime deps at launch. | uv run --with mcp ./my_mcp_server.py | | CLI-based MCP | An existing executable exposes an MCP serving mode. | local-tools mcp serve | | Remote server MCP | The MCP is hosted behind an HTTP endpoint. | https://mcp.example.com/mcp |

Each snippet below is the server field for defineConnector({ ... }); only the local server-process example needs the serverPath helper shown inline.

Package-runner MCP — a published package that should be launched with npx:

server: {
  transport: "stdio",
  command: "npx",
  args: ["-y", "@acme/acme-db-mcp"],
}

Local server-process MCP — a bundled server file or binary:

import { fileURLToPath } from "node:url";

const serverPath = fileURLToPath(new URL("./my-mcp-server.mjs", import.meta.url));

server: {
  transport: "stdio",
  command: "node",
  args: [serverPath],
}

Python MCP — usually run through uv so dependencies are resolved with the server:

server: {
  transport: "stdio",
  command: "uv",
  args: ["run", "--with", "mcp", "./my_mcp_server.py"],
}

Use direct python ./my_mcp_server.py only when the runtime environment is already managed by your package or deployment wrapper.

CLI-based MCP — an existing executable exposes an MCP mode:

server: {
  transport: "stdio",
  command: "local-tools",
  args: ["mcp", "serve"],
}

Remote server MCP — a hosted MCP endpoint:

server: {
  transport: "http",
  url: "https://mcp.example.com/mcp",
}
# 4. deploy under your branded MCP package/bin
npx @acme/acme-db-mcp detect            # which platforms are installed here?
npx @acme/acme-db-mcp audit             # catch package/bin/connector identity drift
npx @acme/acme-db-mcp install --dry-run # preview every change first
npx @acme/acme-db-mcp install           # write native config in each host

install targets only the hosts actually detected on this machine (or an explicit --targets / connector.targets list), intersected with the current adapter registry shown on /coverage — there is no "install to every host unconditionally" path. @ken-jo/agent-connector is the framework dependency underneath; use it directly for framework packaging/debugging or connector-free token telemetry, not as the foreground install brand for your users.

Ship it: direct install or a marketplace plugin

Same one definition, your choice of distribution.

Direct install — your branded command (acme-db install, npx @acme/acme-db-mcp install) writes each host's native MCP + hook + content-surface config in place, with no per-platform marketplace submission or review. This is the Quick start path above.

Framework fallback can also install a connector source directly when you are testing distribution intake: github:owner/repo, npm:@scope/package@version, or an archive: / direct .tgz / .zip source. Every fetched source is cached under ~/.agent-connector/sources/ and must contain agent-connector.config.*.

Marketplace plugin — the framework package command turns the connector into a real plugin/extension bundle (manifest + bundled commands, agents, skills, hooks, MCP) from one definition. This is framework tooling, so run it with npx @ken-jo/agent-connector package --connector .... If you already keep the framework CLI installed globally, agent-connector package --connector ... is only the shorter equivalent. Hooks + MCP keep the telemetry serve-wrapper, so a marketplace-installed connector still reports per-tool tokens for its stdio server. --format all emits 9 host formats — the portable, vendor-neutral Agent Plugins 1.0.0 package (the default) plus eight host-native bundles for hosts that do not speak the spec:

| Format | Hosts | |---|---| | agent-plugin | Agent Plugins 1.0.0 (default — one bundle, single source of truth): Codex · GitHub Copilot CLI · VS Code / JetBrains Copilot · Kiro · Hermes · Cursor · OpenClaw · … | | claude-plugin | Claude Code · OpenClaw · OMP | | factory-plugin | Droid | | gemini-extension | Gemini CLI | | qwen-extension | Qwen Code | | agy-plugin | Antigravity (CLI + IDE) | | cursor-plugin | Cursor | | kimi-plugin | Kimi CLI | | npm-plugin | OpenCode · Kilo CLI · Pi |

agent-plugin is the single source of truth for every spec-speaking host. It follows the open Agent Plugins spec (Vercel-led, co-maintained with AWS, Cursor, GitHub, Microsoft and OpenAI): a root plugin.json, mcp.json (stdio and remote streamable-http/sse servers) and skills/ that any conforming client installs. Hooks, slash commands and subagents are not portable v1 components, so the one bundle carries them per client-extension namespace — com.github.copilot/ (Copilot CLI, VS Code, JetBrains) and com.openai/ (Codex, wired through the manifest's extensions block); clients ignore namespaces they do not own. The install --method marketplace drivers for Codex and GitHub Copilot CLI stage exactly this bundle (live-verified on codex 0.149 + Copilot CLI 1.0.80), and VS Code auto-discovers what the CLI installed. The spec forbids absolute command paths, so the bundle ships a small bin/agent-connector.mjs launcher that resolves the runtime at run time (home binary → PATH → npx); no global install is required for telemetry to carry through. The retired codex-plugin / copilot-plugin names still parse and emit this bundle.

Two official MCP standard artifacts are opt-in (they need a publish block, so they're excluded from --format all) — mcp-server-json (an MCP Registry server.json) and mcpb (a one-click MCPB bundle); see Publish to the MCP ecosystem.

# emit every host format (mcp-server-json + mcpb are opt-in by name)
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs --format all --out ./dist-plugin
npx @ken-jo/agent-connector package --connector ./agent-connector.config.mjs --format gemini-extension --out ./ext   # or just one

# if you already keep the framework CLI globally installed, the same command is:
agent-connector package --connector ./agent-connector.config.mjs --format all --out ./dist-plugin

# e.g. Claude Code:  /plugin marketplace add ./dist-plugin/claude-plugin
#                    /plugin install <connector-id>@agent-connector
# e.g. Gemini CLI:   gemini extensions install ./dist-plugin/gemini-extension/<id>

Embedded-path caveat. Most host bundles bake in the absolute home-bin launcher path of the machine that ran package, so they're valid for a local install on that same machine/home. For shared distribution use agent-plugin, npm-plugin or the MCP standard artifacts, or re-run package per machine.

Let your branded MCP package drive the host's own install flow with install --method marketplace:

acme-db install --method marketplace

# framework fallback for local framework development/debugging only
npx @ken-jo/agent-connector install --method marketplace --connector ./agent-connector.config.mjs
  • What it does — stages the bundle, registers a local marketplace where the host has one, then runs the host's plugin-install verb (or, for npm-plugin hosts, writes a local file:// entry); headless and idempotent. Other marketplace-format hosts print the exact manual commands.
  • Host coverage — live-verified for Claude Code, Codex, OpenCode, Kilo (CLI + ext), and Antigravity (CLI + IDE) on Linux, Windows, and macOS; Droid and Qwen Code have the driver shipped but pending a live host; Gemini CLI is legacy (sunsetting toward Antigravity — driver kept for existing installs).
  • Safety + reversal — a guard refuses installing the same connector by BOTH methods, uninstall --method auto reverses whichever method is installed, and doctor checks registration drift.

Ship a branded CLI

A connector developer can ship their own bin instead of having users type agent-connector. createConnectorCli({ packageJson, connector }) (from the @ken-jo/agent-connector/cli export) derives the bin name/version from package.json and exposes every subcommand under your brand, fully delegated and auto-scoped to your connector — so your users do not need a framework global install or --connector for branded MCP install/doctor/uninstall. See examples/branded-cli for the full, runnable package.

#!/usr/bin/env node
// bin.mjs — every agent-connector subcommand, branded as `acme-db`
import { createConnectorCli } from "@ken-jo/agent-connector/cli";

// run() resolves to the exit code and never calls process.exit
process.exitCode = await 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();

After a consumer installs your package, the acme-db bin is on their PATH and every command is scoped to your connector (acme-db installagent-connector install --connector ./agent-connector.config.mjs). Auto-scoping is pure argument injection over the SAME single home binary; serve and hook still route through the one ~/.agent-connector home binary every host config points back to. An explicit --connector / --connector-id always overrides the injected default.

What you define once

A single defineConnector({...}) declares your MCP server + lifecycle hooks, and optionally the additional surfaces — commands, skills, subagents, memory, statusline, actions, plus host-native escape hatches. agent-connector renders each surface into every detected host's native format, or skip-warns (never silently drops) where a host can't support it.

import { fileURLToPath } from "node:url";
import { defineConnector } from "@ken-jo/agent-connector/sdk";

// Resolve your server to an absolute path — host CLIs spawn it from their own CWD.
const serverPath = fileURLToPath(new URL("./my-mcp-server.mjs", import.meta.url));

export default defineConnector({
  server: {
    transport: "stdio",
    command: "node",           // or "npx", "python", etc. — whatever starts your server
    args: [serverPath],        // replace with your real server entrypoint
    env: { ACME_DB_DSN: "${env:ACME_DB_DSN}" },
  },
  hooks: {
    PreToolUse: {
      matcher: "acme_write",
      async handler(evt) {
        return evt.toolName === "acme_write"
          ? { decision: "ask", reason: "Confirm write" }
          : { decision: "allow" };
      },
    },
  },
  // telemetry is on by default
});

npx @acme/acme-db-mcp install turns that into, e.g.:

| Host | What gets written | |---|---| | Claude Code | ~/.claude.jsonmcpServers.acme-db (+ hooks in ~/.claude/settings.json) | | Codex CLI | ~/.codex/config.toml[mcp_servers.acme-db] (+ ~/.codex/hooks.json) | | Cursor | ~/.cursor/mcp.jsonmcpServers.acme-db (+ ~/.cursor/hooks.json) |

…each pointing hooks at a single stable home binary, so one update propagates everywhere.

Secret env-refs (${env:VAR}). Write "${env:VAR}" (or "${env:VAR:-default}") anywhere in command / args / env / url / headers to reference an environment variable.

On hosts with native interpolation (Claude Code, Cursor, VS Code Copilot, amp, codebuff) the token is written through to the host config and resolved at runtime. Every other host has no native interpolation, so the value is resolved to a literal at install time; an unset variable with no default resolves to an empty string, and install emits a warn for it on a literal-resolving host.

OS keystore secrets (${secret:NAME}). Write "${secret:NAME}" as a value in a stdio server's env (base server.env or a platforms[<id>].server.env override) to reference a secret the user stores once with secrets set NAME. The value never reaches a host config: hosts see only the serve wrapper's --secret-env NAME={secret:NAME} placeholder (a form no host expands), and at launch the wrapper reads the OS keystore and injects the value into the server's own environment. A referenced name that is not set (or reads back empty) aborts the launch with a secrets set hint rather than starting the server with an empty value; ${env:VAR} in the text around a reference is expanded by the wrapper at launch, a secret value never is; install warns per missing name and doctor reports a <id>: secrets check. ${secret:…} is rejected anywhere else (command / args / url / headers, remote servers); names match [A-Za-z0-9][A-Za-z0-9._-]{0,63}, values are non-empty strings of at most 8192 characters, and there is no :-default form.

| Backend | OS | Where the value lives | Notes | |---|---|---|---| | keychain | macOS | login keychain via /usr/bin/security: service agent-connector/<connector-id>, account <NAME> | A locked keychain needs a GUI session (Keychain Access or a desktop login); agent-connector never runs unlock-keychain for you. | | secret-service | Linux | freedesktop Secret Service (GNOME Keyring and compatible daemons) via secret-tool | Needs D-Bus and a running daemon; when either is missing, secrets check says so and points at --backend file. | | credential-manager | Windows | Credential Manager via PowerShell: target agent-connector/<connector-id>/<NAME> | Values are capped at 2560 bytes (1280 UTF-16 characters), the OS blob limit. | | file | any | ~/.agent-connector/secrets/file-store.json (directory 0700, file 0600) | Opt-in and plaintext, not encrypted (--backend file or AGENT_CONNECTOR_SECRETS_BACKEND=file): for CI boxes and headless hosts without a keystore. |

secrets set writes to the OS-native backend unless --backend or AGENT_CONNECTOR_SECRETS_BACKEND (keychain|secret-service|credential-manager|file|auto) says otherwise; the backend holding each name is recorded (names only, never values) in ~/.agent-connector/secrets/<connector-id>.index.json, and reads follow it. Values come from a hidden prompt or --stdin (there is no --value flag, so nothing lands in shell history or the process list) and no command ever prints a value.

OAuth logins (oauth.<key>). A server that talks to an OAuth 2.0 API declares the provider under oauth.<key>oauth: { google: { provider: "google", clientId: "1234-abcd.apps.googleusercontent.com", clientSecret: "${secret:google-client-secret}", scopes: ["https://www.googleapis.com/auth/webmasters.readonly"] } } — and the user authorizes once with auth login <key>: a browser loopback flow (PKCE S256, a listener on 127.0.0.1 that accepts one request) or, when no browser can be opened, a device-code prompt where the preset supports it. The refresh token is stored in the connector's namespace of the OS keystore as oauth.<key>.refresh-token (the same store secrets set writes to), and the server mints access tokens by calling getAccessToken({ connectorId, key }) from the SDK: it refreshes, caches the token in process memory until 60 s before expiry and stores a rotated refresh token. Lazy login: a call with no stored login runs the login itself when a browser can be opened and otherwise fails closed with the exact auth login command, so a connector installed as a host plugin (without install) still logs in on its first tool call. Access tokens live only in process memory and on auth token <key>'s stdout; nothing else ever prints a token, an authorization code or a client secret, and host configs, package manifests and the non-secret metadata file ~/.agent-connector/oauth/<connector-id>.json never carry one. install warns per missing login and doctor reports a <id>: logins check (no network in either). Presets supply endpoints, parameters and quirks only — agent-connector ships no client ids: the author registers the app with each provider, or has each user register their own. Developer-provided: a literal clientId with no secret (a public client), a literal clientSecret only for google (Google documents an installed app's client secret as not confidential), or tokenExchangeUrl — the developer's own https token exchange service that holds the client secret and forwards every token request to the provider (the connector sends client_id, never a secret; the wire contract is in llms-full.txt §2.2); for every other preset a clientSecret is a ${secret:NAME} reference. User-registered: clientId: "${secret:NAME}" plus clientSecret: "${secret:NAME}" — each user registers their own app and stores both with secrets set, and install, doctor and auth login name the secrets set commands still to run. A ${env:VAR} client id is expanded from the process environment at login and refresh time, which a host-spawned server does not see (it does not inherit shell exports). examples/seo-connector is a runnable three-login server (Google Search Console, Bing Webmaster Tools, PostHog) that demonstrates all three ways and ships a token exchange service sample; the per-provider registration table lives in the Operate guide.

| Preset | Provider | Flow(s) | Notes | |---|---|---|---| | google | Google APIs (OAuth 2.0 / OpenID Connect) | loopback | Adds access_type=offline and prompt=consent so a refresh token is returned. flow: "auto" never falls back to the device grant (Google's limited-input grant excludes API scopes such as Search Console); an explicit flow: "device" still runs it. A literal clientSecret is accepted (Google documents it as not confidential). | | microsoft | Microsoft identity platform (Entra ID and personal accounts) | loopback, device | options.tenant selects the tenant (default common); request the offline_access scope, or no refresh token is returned. Public client — clientId only. | | github | GitHub Apps and OAuth Apps | loopback, device | A refresh token needs expiring user tokens (app settings) or the offline_access scope; otherwise the login reports that none came back. The browser flow needs the secret: ${secret:…} (user-registered) or tokenExchangeUrl; the device flow needs none. | | bing-webmaster | Bing Webmaster Tools | loopback | Redirect URIs are registered exactly (including the port), so set redirectPort to a fixed port, and Bing's registration form rejects a redirect URI with no letters after a dot (127.0.0.1 and localhost both fail its check), so set redirectPath to a path with a dot such as /callback.html; no PKCE, and the documented response carries no state (the login is bound by the fixed redirect URI alone, so redirectPort is required). Secret required: ${secret:…} (user-registered) or tokenExchangeUrl. | | posthog | PostHog | loopback | clientId is the URL of the app's Client ID Metadata Document, which lists http://127.0.0.1/callback (no port) as a redirect URI; PKCE without a client secret. options.region pins us or eu; omitted, the region-agnostic issuer routes to the right cloud. clientId only. | | generic | any RFC 8414 / OpenID Connect provider | loopback, device | issuer for discovery, or explicit authorizationEndpoint + tokenEndpoint; the device flow needs a deviceAuthorizationEndpoint (declared or discovered). Secret as the provider demands: none, ${secret:…} (user-registered) or tokenExchangeUrl. |

Native hooks escape hatch. The normalized hooks API covers the 13 cross-platform events; for host-only events (Claude Code alone ships 30) declare platforms: { "claude-code": { nativeHooks: { TaskCompleted: { handler } } } }.

The handler receives the host's raw payload and whatever it returns is the verbatim JSON reply (exit 0 only — exit-2 blocking isn't modeled). Hosts supporting host-native passthrough: amp, claude-code, continue, copilot-cli, cursor, gemini-cli, hermes, jetbrains-copilot, kimi, nemoclaw, omp, openclaw, opencode, qwen-code. Others skip-warn.

Host-config key patches. For host-exclusive settings keys no other surface reaches, declare platforms: { "claude-code": { configPatch: [{ key, value, reason }] } } (Claude Code only for now; other hosts skip-warn with the exact manual edit).

Semantics are fixed: set-if-absent + skip-warn on any conflict — never overwrite, never deep-merge. Ownership is refcounted in a persisted ledger; security-relevant keys and keys agent-connector models as first-class surfaces are hard-refused.

Memory, statusline, actions, and the SDK

  • memory (aligned with the AGENTS.md standard) — ship standing guidance into the memory/rules file each host actually reads. AGENTS.md adopters get the standard file; host-specific exceptions such as Claude Code → CLAUDE.md and Gemini CLI → GEMINI.md are wired per their own official docs. Writes are surgical marker-fenced, hash-stamped managed blocks — multiple connectors coexist, bytes outside your markers are never touched, and uninstall excises exactly your blocks.
  • statusline (defineStatusline) — a live HUD render function the host calls on every status refresh. The SDK supports top-level and per-host render(ctx) handlers plus options such as refreshInterval, respectUserColors, hideContextIndicator, and framework-enforced maxLines. Today it registers command-driven statuslines for Claude Code, Qwen Code, and Antigravity CLI (set-if-absent, refcounted, reversible); hosts whose statusline is only a built-in preset rather than a connector-owned command still skip-warn. The runtime is fail-safe: any error exits 0 with empty stdout so a HUD never wedges the host.
  • actions (defineAction) — named, user-invocable operations dispatched by the universal verb agent-connector action <platform> <id> --connector <id>. Actions can declare label, icon, placement, confirm, and per-host overrides for user-facing metadata or run(ctx). install emits host-side affordances on droid, hermes, kiro, nemoclaw, omp, openclaw, pi, warp, and zed; adapter capabilities expose whether that affordance is an exec command, exec-file, manual hook panel, paste workflow, plugin command, or task. Other hosts skip-warn. Error semantics are user-triggered (unknown id or throw exits 1).
  • The Connector SDK (@ken-jo/agent-connector/sdk, /sdk/test) — the consolidated authoring surface re-exports defineConnector, the full define* family (defineHook, defineCommand, defineSkill, defineSubagent, defineMemory, defineStatusline, defineAction, defineConfigPatch, defineNativeHook), introspection helpers (hostsSupporting, capabilitiesOf, surfaceSupport), and an offline harness (simulate, explain, explainHooks) that runs the real adapter parse→handler→format chain to answer "does my handler actually work on host X?" before you touch a real host. Agent-facing guidance is intentionally split into a small router skill plus focused references under skills/agent-connector/references. See docs/ARCHITECTURE.md.

How it works

  • Home-dir, single binary. The runtime installs once under ~/.agent-connector (override AGENT_CONNECTOR_DATA_DIR). Every host config we write is a thin pointer back to that one binary. Updates are explicit/managed (agent-connector upgrade), never silent auto-update.
  • Per-project data, kept. Telemetry/state is keyed by a stable project identity (git remote or normalized path), partitioned per project, stored under the home data-root — surviving git clean, shared across hosts opening the same project.
  • Native config stays native. We never relocate a host's own settings files; only framework-owned state lives under the data-root.
  • Windows-first correctness. No symlinks, no POSIX-only assumptions.

Three hook paradigms, all install-verified across the registered platform set (see /coverage and docs/ARCHITECTURE.md):

| Paradigm | Platforms | |---|---| | json-stdio (full hook dispatch) | CodeBuddy · Claude Code · Codex CLI · Cursor · VS Code Copilot · JetBrains Copilot · GitHub Copilot CLI · Gemini CLI · Qwen CLI · Kiro · Kimi CLI · Crush · Goose · Hermes · Droid (Factory) · OpenHands · Antigravity · Antigravity CLI · Continue · Amazon Q · Grok Build · Grok CLI · Devin CLI · Open Interpreter | | mcp-only (MCP registration only) | Warp · Cline · Trae · Zed · Freebuff · Xum · Pi · Windsurf · Junie · Mistral Vibe | | ts-plugin (generated bridge module) | OpenCode · MiMoCode · Kilo CLI · Kilo · OMP · NemoClaw · OpenClaw · Amp |

Adding a platform = one registry entry + one adapter.

CLI

| Command | Purpose | |---|---| | detect | List installed platforms, scopes, capabilities, hook paradigm. | | install [<source>] [--scope …] [--targets …] [--method …] [--dry-run] [--force] | Render + write MCP + hooks + content surfaces. <source> may be local, GitHub/git, npm:<package>[@version], or .tgz/.zip/archive:. | | uninstall [--targets …] [--purge] [--method …] | Full inverse — removes everything we wrote; --purge also clears framework state. | | upgrade [--channel …] | Re-render host config + heal stale pointers + refresh the home binary (alias: update, sync); clears every doctor version warning; never a silent self-update. | | doctor [--probe] [--heal] [--explain] [--json] [--dry-run] | Per-platform health checks with fixes, plus version checks: the home binary's target install and every connector's rendering framework version are compared with the running CLI (drift → warn + upgrade). --probe runs a live MCP handshake, --heal re-syncs every fixable finding, --explain prints the per-(host, event) hook honor matrix. | | status | Light install-state: which connectors are present on which hosts (always exits 0). | | secrets set\|delete\|list\|check [<name>] [--backend keychain\|secret-service\|credential-manager\|file] [--stdin] [--json] | Store the secrets a connector references as ${secret:NAME} in the OS keystore: macOS Keychain (keychain), Linux Secret Service (secret-service), Windows Credential Manager (credential-manager), or the opt-in plaintext file store. set reads the value from a hidden prompt or --stdin (no --value flag), list shows name / backend / presence, check runs a round-trip self-test; nothing ever prints a value. | | auth login\|status\|logout\|token [<key>] [--device\|--loopback] [--port <n>] [--json] | Log in to the OAuth 2.0 providers a connector declares under oauth.<key> (presets: google, microsoft, github, bing-webmaster, posthog, generic). login runs the browser loopback or device-code flow and stores the refresh token in the OS keystore, status shows key / provider / presence (a missing login never fails it), logout revokes at the provider when it can and forgets the token, token prints a fresh access token to stdout — the only command that ever prints one. | | package [--format <fmt>\|all] | Emit a host plugin bundle, or an OFFICIAL standard artifact: mcp-server-json (registry) · mcpb (one-click bundle). | | audit [--strict] | Pre-install package identity lint: package name/version/bin, runtime dependency, connector id/version drift, and publish files coverage. | | action <platform> <id> [--connector <id>] | Run a declared action from the shell. | | telemetry report [--by …] [--since …] [--connector <id>] | Per-tool token footprint of your connector's own wrapped server. Stdio servers only. | | telemetry export [--format …] [--connector <id>] | Raw aggregate records for your wrapped server. | | usage report\|export\|leaderboard [--by …] | No connector needed. Host-native whole-conversation token totals parsed read-only from each agent CLI's own logs. Does NOT break down by individual MCP or tool. | | leaderboard [--since …] [--connector <id>] [--scope …] | Three origin-labeled boards with different prerequisites (🔌 MCP/plugin · 🛰️ host-native turns · 🖥️ host/user); counts are never summed across them. |

hook and serve also exist — internal entrypoints the written host configs point at; you never run them by hand. Full flag-level reference: the docs site /docs/dev/cli · llms-full.txt §3 (canonical, drift-guarded by tests).

Something off? The docs site /docs/dev/troubleshooting page walks through reading doctor output and its exit codes, the common ConnectorConfigError messages, "hooks unavailable here" on MCP-only hosts, and why telemetry report can show nothing.

Token telemetry & usage

Two independent, never-summed views of token cost:

  • Per-tool telemetry for your own server (the MCP-developer path). No host reports per-tool usage back to an MCP server, so agent-connector measures your server's own bytes (args in, results out, tool schemas) and tokenizes them locally — aggregate counts only, stored locally, zero egress by default. Per-tool telemetry is automatic for stdio servers; remote (http/sse/ws) servers are registered but not wrapped (the proxy can't intercept remote transports). Read it with agent-connector telemetry report --by tool.

  • Connector-free usage (agent-connector usage). Already run Claude Code / Codex / Cursor and just want totals? usage reads your local agent-CLI session logs read-only and never writes any host config — no connector, no install:

    npx @ken-jo/agent-connector usage report --by platform        # CLI/model/project/session/day
    npx @ken-jo/agent-connector usage leaderboard --by platform   # or --by model
    npx @ken-jo/agent-connector usage export --format csv --out usage.csv

    It reports whole-conversation totals per agent host / model / project / session / day. It does not itemize cost by individual MCP server or tool — agent hosts don't log per-tool attribution.

Privacy & tokenizer. Default tokenizer is gpt-tokenizer (pure-JS, no native build) — o200k_base for OpenAI/Codex-family, a documented approximation for Anthropic; falls back to a chars/4 heuristic if it can't load. Every record carries a confidence tag. Raw tool arguments and results are never stored or transmitted. Off switch: AGENT_CONNECTOR_TELEMETRY=0, or telemetry: { enabled: false }.

Publish to the MCP ecosystem

Where the MCP standard already covers your server's functionality, agent-connector emits the standard exactly so your already-standard work is portable:

  • package --format mcp-server-json → an official MCP Registry server.json (schema 2025-12-11). It describes your real upstream server (what a registry installer runs), not our telemetry wrapper. Publish it with the official mcp-publisher CLI.
  • package --format mcpb → an official MCPB (.mcpb, formerly DXT) bundle manifest.json (manifest_version 0.3) for one-click local install in Claude Desktop and any MCPB host, with secrets routed through the host keychain (user_config).

Both read a publish block on your connector (the namespace you own + your published package + author):

defineConnector({
  server: { transport: "stdio", command: "npx", args: ["-y", "@acme/acme-db-mcp"] },
  publish: {
    registryNamespace: "io.github.acme", // a namespace YOU proved ownership of
    packageName: "@acme/acme-db-mcp",     // your REAL published package
    author: { name: "Acme Inc" },
  },
});

Config we write is the standard. install writes each host's native MCP config in the de-facto canonical mcpServers shape — { command, args, env } for stdio, { url, headers } for remote. The spec transport slug for streamable HTTP is streamable-http (registry server.json); host configs canonically use http. WebSocket (ws) is not an MCP spec transport and the standard artifacts reject it.

Forward-compatible by transport. The serve proxy is byte-transparent: it forwards every JSON-RPC message verbatim and only tees a copy to count tools/call round-trips. So newer MCP features ride through untouched — MCP Apps (the official io.modelcontextprotocol/ui extension) and any reverse-DNS extension negotiated at initialize. A connector whose server already speaks these deploys across every host and keeps its telemetry today, no agent-connector change required.

Verification

The full single-API contract is install-verified across the current platform registry by a committed registry-driven install-roundtrip harness that, for every adapter, drives the real install → uninstall into an isolated HOME and asserts on-disk placement + zero residue. A separate committed scripts/verify-host.mjs driver installs real host CLIs from the verification matrix and checks install → placement → clean-uninstall; live hook dispatch + telemetry are proven end-to-end where the host can run headlessly, against a real model or the committed local mock model provider (scripts/README.md). IDE extensions / GUI editors with no headless CLI stay covered by the install-roundtrip harness.

Measured footprint. The runnable example (examples/acme-db, a 135-line defineConnector() declaring an MCP server, PreToolUse + SessionStart hooks, a status line and an action) is installed through its own bin into an isolated HOME, once per registered host, and the files that appear on that disk are counted — the numbers in By the numbers above. Every host takes the example at user scope or project scope. tests/docs/readme-footprint.test.ts measures it on every run and fails if the README quotes anything else; npm run measure:footprint prints the same JSON.

Development

npm install
npm run typecheck
npm run build
npm run dev -- detect     # run the CLI from source via tsx

# Tests: scope + single-fork (useful on low-RAM machines).
npm run test:single -- tests/adapters/<host>.test.ts

Contributing

PRs welcome — especially new host adapters and fixes verified against a host's primary source. See CONTRIBUTING.md for the dev workflow, the single-fork test discipline, the verify-first rule for adapters, and the new-host checklist. Want a new agent host supported? Open a host adapter request.

Security reports: see SECURITY.md.

License

Apache-2.0 © 2026 KenJo