Write your MCP server + hooks once. agent-connector detects every AI-agent platform on the machine, renders the right config in each one's native dialect, installs/syncs/uninstalls them, and gives you default, platform-independent per-tool token telemetry — the metric MCP developers actually want.
This document is the authoritative design. It is grounded in the understand-phase
report (docs/research/understand-report.md),
which reverse-engineered context-mode's proven 15-platform adapter layer and the
real installed config formats across Claude Code, Codex, Cursor, OpenCode,
VS Code/JetBrains Copilot, Gemini, Warp, Hermes, and more.
Every AI-agent host re-invents the same two integration surfaces — MCP server
registration and lifecycle hooks — with mutually incompatible config files,
root keys, formats (JSON / JSONC / TOML / YAML / exported TS-or-Python functions),
transports, scopes, and event vocabularies. A developer who wants reach must
hand-author and maintain N dialects, N hook adapters, and N install flows, then
chase each platform's quirks (Cursor silently fails on a wrong root key; VS Code
uses servers not mcpServers; Codex uses TOML [mcp_servers.x]; OpenCode wants
an exported TS plugin function, not a hook table). context-mode paid this cost in
full for a single server. agent-connector generalizes that work into a reusable
framework.
Second, no host reports per-tool token usage back to an MCP server (the MCP
spec has no usage on CallToolResult). So "how much context does installing my
server actually cost, everywhere?" — the question MCP devs care most about — is
unanswerable today. agent-connector answers it by measuring the server's own
bytes and tokenizing them locally, identically across all hosts.
- Single-API multi-platform deployment. One declarative + programmatic
defineConnector({...})→ adapters render it into each platform's native MCP registration and hook config; one CLI installs/syncs/uninstalls everywhere. - Default per-MCP token telemetry. Platform-independent, local-first, privacy-preserving (aggregate counts, never content). On by default, opt-out granular.
This is the baseline the user mandated (oh-my-claudecode style), refined after critical review. Three rules:
The framework installs one runtime in the home root:
~/.agent-connector/ (override: AGENT_CONNECTOR_DATA_DIR)
bin/agent-connector single binary — CLI + universal hook entrypoint + telemetry runtime
connectors/<id>/connector.json each registered connector's resolved definition
telemetry.db (or telemetry.ndjson) shared store, rows keyed by project — see R3
backups/ timestamped settings backups before each mutation
logs/
Every platform config we write is a thin pointer back to this one binary:
- a hook command is
agent-connector hook <platform> <event> --connector <id>(mirrors context-mode's provencontext-mode hook <platform> <name>dispatch); - an MCP server entry runs the connector's server, optionally wrapped by
agent-connector serve --connector <id> -- <real server cmd>so telemetry is captured with zero work from the dev.
Because the pointers reference one stable home path, updating that single binary updates behavior in every platform at once — exactly the requested ergonomic.
Critical adjustment #1 — stable path, managed update (NOT silent auto-update). A forced/silent auto-update of a single binary means one bad release breaks every project × every platform simultaneously (maximum blast radius, lost reproducibility) — the
npx pkg@latestfailure mode. So: pointers reference a stable path (~/.agent-connector/bin/agent-connector), never a versioned cache dir (this also sidesteps the whole class of bug that forces context-mode'scache-healSessionStart hook). Updates are explicit/managed (agent-connector upgrade, channel = stable|latest), with an optional per-project version pin override. One place to update — without global instant breakage.
Cursor, VS Code Copilot, etc. require config files at platform-mandated locations
(.cursor/mcp.json, .vscode/mcp.json, ~/.codex/config.toml, …). So:
Critical adjustment #2. "Home-based" means the binary + shared telemetry store live in home as the single source of truth; the framework still writes the minimal native pointer config wherever each platform mandates it. Those pointers exec the one home binary — which is how "single-binary update" actually propagates to hosts that never read
~/.agent-connector.
Native config files (settings.json, config.toml, mcp.json) are never relocated
by AGENT_CONNECTOR_DATA_DIR — only framework-owned state is. (Generalized from
context-mode's resolveContextModeDataRoot / issue #649.)
Critical adjustment #3. Telemetry/state is keyed by a stable project identity —
gitRemote || normalizedAbsPath, hashed — and stored under the home data-root (default), not by code location. This survivesgit clean, isn't committed, and lets multiple platforms opening the same project share one store (the cross-agent shared-DB mechanism). In-repo storage is opt-in. Concurrent writers (several hosts at once) are handled by append-atomic NDJSON (MVP) or SQLite WAL (upgrade). Data is partitioned byproject_keycolumn → both per-project retention and cross-project rollups.
Single binary must resolve home via the right per-OS dir (%USERPROFILE% /
%APPDATA% / XDG / homedir()), no POSIX-only assumptions, no symlink-based
installs. The Windows-safe spawn/quoting helpers (buildNodeCommand /
parseNodeCommand, fixing context-mode bugs #369/#372/#548/#738) are mandatory.
Distilled from the union of platform behaviors (report §3).
ServerDef— transport-polymorphic server descriptor (stdio | http | sse | ws;command/args/env/cwdorurl/headers/auth;tools,timeoutMs,enabled). Adapters render it into each dialect; the root key is a per-adapter constant (mcpServers|servers|mcp|mcp_servers), as are field-name differences (cwdvsworking_directory,envvsenvironment, scalarcommandvscommand:[]).- Transport capability set — an adapter may reject/downgrade a transport it can't honor and report it (never crash).
- Scope — normalized ordered enum
{ system, user, project, profile, managed }; each adapter maps to a concrete path + knows precedence. Default install scope =user;--scope projectopt-in. - Normalized lifecycle events —
SessionStart,SessionEnd,UserPromptSubmit,PreToolUse,PostToolUse,PreCompact,Stop,Notification,PermissionRequest,PostToolUseFailure,SubagentStart,SubagentStop,PostCompact(13 canonical events — the last five are newer additions; hosts without a native analog mark them unsupported in capabilities and the install reports a skip-warn, never a silent drop). Mapped to each platform's names (PreToolUse↔BeforeTool↔tool.execute.before↔pre_tool_call). Normalized payload{ toolName, toolInput, toolOutput?, isError?, sessionId, projectDir?, raw }; normalized response{ decision: allow|deny|modify|context|ask, reason?, updatedInput?, additionalContext?, updatedOutput? }. The union is a floor, not a ceiling: host-only events (Claude Code alone ships 30) are reachable per platform via theplatforms.<id>.nativeHookspassthrough — raw payload in, verbatim JSON reply out, exit 0 only; adapters that setsupportsNativeHookswire it and the rest skip-warn. An event is promoted into the union once ≥3 hosts ship a native analog (TaskCreated/TaskCompleted first candidates). Full contract:llms-full.txt§2.3. - Hook I/O paradigm taxonomy (the deepest divergence — exactly three):
json-stdio(18) — Claude Code, Codex, Cursor, VS Code Copilot, JetBrains Copilot, Copilot CLI, Gemini CLI, Qwen, Kiro, Kimi, Crush, Goose, Hermes, Droid (Factory), Antigravity, Antigravity CLI, Continue, Amazon Q. One universal hook entrypoint binary reads host JSON, the adapter normalizes it, the dev's handler runs, the adapter formats the reply.ts-plugin(8) — OpenCode, MiMoCode, Kilo CLI, Kilo, OMP, NemoClaw, OpenClaw, Amp. Framework generates an exported plugin module importing the dev's handler.mcp-only(9) — Warp, Roo Code, Cline, Trae, Zed, Codebuff, Mux, Pi, Windsurf. No hook layer; install only the MCP server; detection surfaces "hooks unavailable here."
PlatformCapabilitiesflags (preToolUse,postToolUse,preCompact,sessionStart,canModifyArgs,canModifyOutput,canInjectSessionContext) — the single-API layer queries these and degrades gracefully.- Detection — two layers: install-time platform detection (which hosts are
installed: config-dir + marker files) and runtime host detection (which host is
executing this hook now: env-var markers → config-dir →
clientInfo). Generalized from context-mode'sregistry.ts+detect.ts(incl. fork-before-parent order & foreign-env scrubbing). - Escape hatch — every adapter accepts
platforms.<id>.extrapassthrough so a dev reaches platform-exclusive features the core doesn't model. Thin universal core + fat per-adapter tail. memory(the fourth content surface) & the AGENTS.md standard — standing guidance declared once (memory: [{ name?, description?, content }]) and written by each supporting adapter as a marker-fenced managed block into the memory/rules file that host actually reads. Unlike commands / skills / subagents (files we wholly own), memory edits a SHARED, user-authored file — so every write goes through one dependency-free engine (core/managed-block.ts): markers<!-- agent-connector:begin <connectorId>/<name> hash=<sha256-12> -->…<!-- agent-connector:end <connectorId>/<name> -->plus a one-line do-not-edit notice; the blockId-on-the-marker makes multi-connector coexistence safe, and the hash (sha256-12 over the CRLF→LF-normalized, trimmed inner content) gives O(1) idempotence (unchanged → skip) and edit detection (inner hash ≠ recorded ⇒ user edited ⇒ warn-and-leave; overwrite only underinstall --forceafter a timestamped backup). Replacement is in-place — zero bytes outside the marker pair ever change; the scanner is line-anchored, CRLF-preserving, BOM-safe, and fence-aware. AGENTS.md-first policy (grammar v1): 29/35 hosts read the open AGENTS.md standard, so project scope targets<projectDir>/AGENTS.md— with exclusive/first-match readers PROBED so the block lands in the file the host will actually read (zed's first-match candidate list, warp's WARP.md priority, hermes' .hermes.md, opencode's CLAUDE.md fallback, codex's AGENTS.override.md; openclaw maps both scopes to its agent workspace) — and user scope the host's documented global memory file: AGENTS.md where one exists, else the host's own file (~/.qwen/QWEN.md, goose .goosehints, ~/.copilot/copilot-instructions.md, kilo/roo/kiro rules-dir agent-connector.md), else skip-warn. The two exceptions follow their own official docs: claude-code → CLAUDE.md ("Claude Code reads CLAUDE.md, not AGENTS.md" — HTML-comment markers are stripped from Claude's context, invisible to the model yet parseable by us; opt-inmemory.mode: "agents-import"manages the documented@AGENTS.mdimport as a ref-counted_sharedbridge block instead) and gemini-cli → GEMINI.md (AGENTS.md only whencontext.fileNameopts in). We never edit host settings to make AGENTS.md readable — probe and respect only. Install order: memory last among content surfaces, removed FIRST on uninstall; uninstall excises every block under the<connectorId>/marker prefix (markers in the file are the source of truth; theconnectorDir(id)/memory-state.jsonledger adds created-file deletion rights + doctor diagnostics: block present / hash intact / user-edited / file missing). Full contract:llms-full.txt§2.4.statusline(handler surface) — a HUD / status-line declared once (statusline?: StatuslineDef) and driven by a developer-suppliedrenderfunction, not content. Singular (one per connector, not an array). Interface:interface StatuslineDef { name?: string; // kebab-case id; default "statusline" description?: string; options?: StatuslineOptions; render: (ctx: StatuslineContext) => string | Promise<string>; hosts?: Partial<Record<PlatformId, { render?: (ctx: StatuslineContext) => string | Promise<string>; options?: StatuslineOptions; }>>; }
StatuslineContextcarries{ host, connectorId?, sessionId?, cwd?, model?{id?,displayName?}, cost?{totalUsd?}, context?{usedTokens?,maxTokens?,percent?}, transcriptPath?, raw }— fields the host doesn't provide are undefined;rawis the host's verbatim payload.StatuslineOptionsincludes host-mapped knobs (refreshInterval,respectUserColors,hideContextIndicator) and framework-enforcedmaxLines; adapters write only knobs their host actually supports. SDK helperdefineStatusline({ render })(typed identity) plus typesStatuslineDef/StatuslineContextare exported from the package root and/sdk. Deployment (command-driven v1 = antigravity-cli + claude-code + qwen-code):PlatformCapabilities.supportsStatuslinegates it (read as?? false); every other adapter emits the standard skip-warn, never a silent drop. Capability metadata exposesstatuslineModeand option support so SDK users can see whether a host iscommand-stdinand which options can be written. On claude-code, install registerssettings.json.statusLine = { type: "command", command: "<homeBin> statusline claude-code --connector <id>", refreshInterval? }; on qwen-code it registers the nestedsettings.json.ui.statusLineequivalent and may passrefreshInterval,respectUserColors, andhideContextIndicator; on antigravity-cli it writes theagycommand statusline shape. Hosts whose only status UI is a built-in preset (for example a host-owned compact/verbose mode with no connector-owned command entrypoint) are intentionally not modeled as arender(ctx)statusline yet. All supporting hosts use the refcounted ownership ledger: set-if-absent, never clobber a status-line setting agent-connector doesn't own (skip-warn + manual-edit hint), and uninstall removes only when last-owner ∧ value-unchanged ∧ prior-absent.statusLineremains a reserved key for the modeled statusline surface — rawconfigPatchtargeting it throwsConnectorConfigErrorpointing atstatuslinewhere configPatch is available. Runtime is fail-safe: any error (throwing render, unknown connector, malformed stdin) → exit 0 / empty stdout; a HUD must never wedge the host. Doctor adds a dedicatedstatusline wiredcheck (ok / present-but-not-ours / missing). CLI verb:agent-connector statusline <platform> --connector <id>(internal, likehook/serve).actions(dispatch backbone) — a connector declaresactions?: ActionDef[]. EachActionDef = { id: string; label?: string; description?: string; icon?: string; placement?: ActionPlacement | ActionPlacement[]; confirm?: boolean | { title?: string; message?: string }; run: (ctx: HostCtx) => ActionResult | void | Promise<…>; hosts?: per-host metadata/run override }. The universal verbagent-connector action <platform> <actionId> --connector <id>loads the connector and invokesrun(ctx). Error semantics are USER-TRIGGERED (not fail-safe-silent like hooks/statusline): unknown action id or a throw → exit 1 + stderr.defineAction({ id, run })is the typed authoring helper (exported from root and/sdk). v1 = universal dispatch plus selected host affordance emitters: install emits host-side affordances ondroid,hermes,kiro,nemoclaw,omp,openclaw,pi,warp, andzed; every other host skip-warns. Capability metadata exposesactionInvocationMode(exec,exec-file,manual-hook,paste,plugin-command, ortask) andactionAffordanceKind(slash-command,hook-panel,workflow,extension-command,task, etc.) so SDK introspection can distinguish "supported" from "supported how".actionsis a real member of theSurfaceNameintrospection vocabulary;explain()reports those emitter hosts as native with mode/affordance details and the rest as skip-warn;simulate()does not cover actions (an action takes no host payload and has no host-honor verdict — intentional).configPatch— the third (and smallest) escape hatch besideextraandnativeHooks: a declarative, ownership-tracked patch of ONE host-exclusive config keyextracannot reach (extramerges into the native MCP ENTRY / content frontmatter, not sibling top-level settings keys — e.g. Claude Code'senv.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS). Semantics are FIXED and not configurable: set-if-absent on a single dotted leaf key (segments[A-Za-z0-9_-]+, no array indices, no deep merge, no overwrite, no delete), skip-warn on ANY conflict (present key, drifted value, non-object intermediate — every skip prints the exact manual edit from the requiredreason+ optionaldocsUrl). Ownership lives in a persisted, refcounted ledger (<dataRoot>/state/config-patches.json, atomic writes): co-owners refcount a shared key; uninstall removes a key only when the LAST owner releases it AND the current value still deep-equals what was written AND the prior state was absent — otherwise the key is left in place with a warn. Doctor reports per-patchok / drifted / missing / orphanedand never auto-fixes drift. Safety: keys agent-connector already models (hooks*,mcpServers*,statusLine*) are rejected atdefineConnector(namespace guard), and each supporting adapter hard-refuses a documented sensitive-key denylist (claude-code:permissions*,allowedTools*/disallowedTools*,apiKey*,awsAuthRefresh/awsCredentialExport,forceLoginMethod/forceLoginOrgUUID,otelHeadersHelper,env.ANTHROPIC_*,env.AWS_*,env.*_PROXY,env.*TOKEN*/env.*KEY*/env.*SECRET*). v1 host scope: claude-code only (supportsConfigPatch); every other adapter reports the standard skip-warn (thesupportsNativeHooksprecedent) plus the per-patch manual edit. Promotion rules: (a) a second host gainssupportsConfigPatchonly on demonstrated, genuine connector-facing key demand for that host; (b) a host-exclusive feature graduates fromconfigPatchto a typed cross-host knob only when ≥3 hosts ship an analog — the same bar as hook-event promotion (statuslineis the first graduated example: previously a configPatch key, now a reserved first-class surface). Format-preservation requirements for future hosts: VS Code JSONC must usejsonc-parsermodify/applyEdits and Codexconfig.tomlmust use an anchored section/line edit —core/toml.ts's parse/stringify round-trip destroys comments/ordering and is BANNED for configPatch. Explicitly NOT configPatch targets:statusLine(→ statusline surface; raw configPatch targetingstatusLineorstatusLine.*throwsConnectorConfigError— declare it viastatusline: { render }instead); VS Codeinputsarrays and Zedcontext_servers.<id>.settings— same-file sibling structures coupled to the MCP entry's lifecycle (adapter dialect /extraterritory; VS Codeinputsdoubles as the secret-prompt mechanism). Also out of v1 scope (deferred, documented): TOML hosts (Codex'sexperimental_use_rmcp_clientbecomes codex-adapter-internal behavior when remote-MCP support lands),onConflict/forceoptions, array/index paths, secret sourcing, sidecar/side-state files, prereq checks, and sync-removal of patches dropped between connector versions (uninstall/reinstall covers it).
Borrow (the mechanical spine): the HookAdapter SPI shape, single-source
ADAPTER_REGISTRY + matrix test, runtime detection + disambiguators, Windows
spawn/quoting helpers, BaseAdapter, the data-root override, the 3-paradigm
taxonomy, capability flags, the thunk-based doctor.
Generalize away (context-mode domain logic, must not leak into the framework):
the hardcoded "context-mode" identity → becomes the dev-supplied id/entrypoint
parameter; baked-in hook scripts → synthesized from the dev's handlers; session /
memory / instruction-file / FTS / bytes_avoided logic → deleted from the SPI;
context-mode's analytics schema → replaced by our own minimal telemetry schema; the
4-bytes/token heuristic → demoted from default to fallback (a real tokenizer is
the default).
- Measure the server's own bytes — the only data identical across hosts.
Intercept every
tools/callat the server boundary (viaagent-connector serveproxy or in-proc middleware). input =params.arguments; output =result.content[]+structuredContent. Also tokenizetools/listschemas once → the fixed "cost of merely defining my tools" per-turn overhead. - Default = real tokenizer —
gpt-tokenizer(pure-JS, no native build → Windows/single-binary safe):o200k_basefor OpenAI/Codex-family (labeledtokenizer-exact), and the sameo200k_baseas a documented approximation for every other family (labeledtokenizer-approx; no offline Claude tokenizer ships). Family auto-selected frominitialize.clientInfoormodelFamilyHint. - Fallback = heuristic —
chars/4with content-type multipliers; labeledheuristicso it's never mistaken for exact. Non-text blocks: per-modality formulas, never tokenize base64. - Confidence tag on every record:
tokenizer-exact | tokenizer-calibrated | tokenizer-approx | heuristic | host-native. - Opt-in enrichers (never the hot path): Anthropic
count_tokensas a rate-limited calibration sampler (sends content off-box → opt-in only); host-native usage where it exists (GeminiAfterModel.usageMetadata.totalTokenCount). - Store — local, at data-root, aggregate counts only — never raw
args/results. MVP = append-atomic NDJSON event log + derived rollups behind a
TelemetryStoreinterface (SQLite/WAL is a drop-in upgrade). Rows keyed byconnectorId, toolName, scope(call|tool_defs|model_turn|hook), surfaceKind(server|hook|command|skill|subagent), hostPlatform, sessionId, projectKey, projectDir, inputTokens, outputTokens, confidenceSource, isError, ts. - Surface —
agent-connector telemetry report [--by tool|session|project] [--since 7d] [--json]→ ranked per-tool footprint, tool-def overhead line, input/output split, calls, tokens/call avg, per-session/project rollups, with a visible confidence label; plustelemetry export --format csv|json. - Privacy / opt-out — local-first, zero egress by default; granular kill
switches
AGENT_CONNECTOR_TELEMETRY=0(global) + per-layer (measure / calibrate / upload); hashable aggregation keys; dashboards state numbers are estimates from the server's own I/O, not host-billed usage.
import { defineConnector } from "@ken-jo/agent-connector/sdk";
export default defineConnector({
// package.json name/mcpName/bin/version are the default metadata source.
// Set id or mcp.hostAlias only for legacy configs or multi-instance aliases.
server: { // declared ONCE, transport-polymorphic
transport: "stdio",
command: "npx",
args: ["-y", "@acme/acme-db-mcp"],
env: { ACME_DB_DSN: "${env:ACME_DB_DSN}" }, // universal ${env:VAR} / ${env:VAR:-default}
tools: { include: ["*"] },
},
hooks: { // normalized events; framework synthesizes per paradigm
PreToolUse: {
matcher: "acme_query|acme_write",
async handler(evt) {
if (evt.toolName === "acme_write") return { decision: "ask", reason: "Confirm write" };
return { decision: "allow" };
},
},
},
memory: [{ // standing guidance → managed marker block in each
content: "Use the acme-db MCP tools for schema questions; never hand-edit migrations.",
}], // host's memory file (AGENTS.md-first; CLAUDE.md/GEMINI.md exceptions)
telemetry: { enabled: true, modelFamilyHint: "auto", measureToolDefs: true },
platforms: { // per-platform escape hatch / overrides
warp: { hooks: false }, // mcp-only host: skip hooks gracefully
},
targets: "auto", // or ["claude-code","codex","cursor"]
});The root export (@ken-jo/agent-connector) is unchanged. Two additive subpaths
layer the developer-facing SDK on top of the abstraction model above.
@ken-jo/agent-connector/sdk — consolidated authoring surface. Re-exports
defineConnector, ConnectorConfigError, the full define* typed-identity
helper family, the introspection helpers, and the public types — one import
site for all authoring.
The define* helpers are typed identity functions: they infer the right *Def
type and return the definition unchanged; validation stays inside
defineConnector. The family:
defineConnector(config)— existing; the root definition entry point.defineStatusline({ render })— existing; typed toStatuslineDef.defineAction({ id, run })— typed toActionDef; see the action surface below.defineCommand,defineSkill,defineSubagent,defineMemory,defineConfigPatch,defineNativeHook— each(def) => def, typed to its*Def.defineHook— event-parameterized so the handler payload narrows to the concrete event type rather than the union:The leading event string exists only to drive type inference; the def is returned unchanged.const onPre = defineHook("PreToolUse", { handler(evt) { // evt is typed as PreToolUseEvent, not the full event union return { decision: "deny", reason: "no" }; }, });
Introspection (async — adapters load lazily):
capabilitiesOf(host): Promise<PlatformCapabilities | undefined>— a host's capability flags;undefinedfor an unrecognized id.hostsSupporting(surface): Promise<PlatformId[]>— which registered hosts honor a surface.surfaceis aSurfaceName:server | hooks | commands | skills | subagents | memory | statusline | configPatch | nativeHooks | actions.surfaceSupport(host, surface): Promise<boolean>— per-host / per-surface convenience predicate.SURFACE_PREDICATES— the per-surface capability predicate map, exported for advanced use.
@ken-jo/agent-connector/sdk/test — offline harness. The two exports
answer "does my handler / HUD actually work on host X?" without touching a
real host.
-
explain(connector): Promise<ExplainRow[]>— the per-host × per-declared- surface support matrix. Each row is{ host, surface, support: "native" | "skip-warn" | "disabled", reason }. Only surfaces the connector actually declares are included;configPatch/nativeHooksrows are scoped to the host that declares them;disabled= an explicitplatforms[host].<surface> = false. (Theserverrow is capability-based for v1 — registration may be host-managed on some IDEs; the row reason notes this.) -
simulate(connector, { surface, host, event?, input }): Promise<{ honored, hostReply?, reason }>— runs the real adapter parse → handler → format chain offline and reports whether the host would actually honor the handler's decision.surfaceis"hooks"(requiresevent) or"statusline";inputis the host-shaped raw payload. It mirrors the runtime exactly: tolerant stdin, matcher filtering (a matcher-scoped handler that doesn't match is not run), and a verdict computed by parsing the actual host reply and judging the true(event, decision)contract — not substring guessing. This means it reports real host quirks honestly:- codex drops
contextonUserPromptSubmit(no stdout path) →honored: false. - a
denyonStop/SubagentStopis continuation/persistence, not a block →honored: true, reason says "continues … (persistence)". - a
denyonSubagentStart/PostToolUseFailuredegrades to a context note →honored: false. - a
PermissionRequestaskis honored by the host's native dialog (no stdout) →honored: true.
- codex drops
In short: explain is the whole support matrix; simulate is the behavioral
check that encodes each host's real honor / drop / degrade contract — the
honest per-host reach, offline, before you touch a real host.
agent-connector detect # installed platforms + scope + capabilities + paradigm
agent-connector install [--scope user|project] [--targets a,b] [--connector path] [--dry-run] # framework fallback / CI-debug; branded package/bin is the normal MCP lifecycle path
agent-connector uninstall [--targets ...] [--purge] # full inverse — removes server + hook registrations; --purge also drops the connector's home state record (+ the shared launcher when none remain)
agent-connector upgrade [--channel stable|latest] # bring all current: re-render host config + heal pointer + managed update guidance (alias: update, sync)
agent-connector doctor [--probe] # per-platform health checks; --probe = live MCP handshake (initialize → ping → tools/list)
agent-connector status # light install-state per host (always exits 0)
agent-connector package [--connector path] [--format <fmt>|all] # framework tooling: 10 host bundle formats; mcp-server-json|mcpb = OFFICIAL MCP standard artifacts (opt-in by name)
agent-connector telemetry report|export [...]
agent-connector usage report|export|leaderboard [...] # host-native usage from agent CLI logs (read-only)
agent-connector leaderboard [--since] [--scope] [--connector] # 🔌 mcp-self + 🖥️ host-scan-logs + 🛰️ host-native-live (never summed)
agent-connector hook <platform> <event> --connector <id> # universal hook entrypoint (internal)
agent-connector serve --connector <id> -- <server cmd...> # telemetry-wrapping MCP proxy (internal)
The user-facing install/doctor/uninstall path should normally be the developer's
branded MCP package/bin, for example npx @acme/acme-db-mcp install or
acme-db install. The agent-connector ... commands above are the framework
surface: development fallback, CI/debug entrypoint, marketplace packaging driver,
internal home-bin target, and connector-free usage telemetry utility.
Canonical CLI reference: the docs site
/docs/cliandllms-full.txt§3 (kept current; drift-guarded by tests). This block is design context — when they disagree, the canonical reference wins.
Install per target: backupSettings() → render server config into native file →
if hooks & paradigm≠mcp-only: synthesize entrypoint + write hook config + set exec
bit → register in plugin registry where applicable → return change list. Everything
idempotent, reversible, --dry-run-able.
- Phase 0 — core spine (no platforms): contracts,
defineConnector+ validation, interpolation, registry/detection, spawn helpers, data-root/paths, doctor harness, CLI skeleton. - Phase 1 — MVP: 3 maximally-divergent, high-confidence platforms + telemetry core.
Claude Code (JSON,
mcpServers, richest hooks), Codex CLI (TOML, env split, trust gates), Cursor (JSON +${env:}interpolation +hooks.jsonv1). This trio alone exercises JSON+TOML, three hook dialects, env-var detection, and the wholejson-stdiopath end-to-end. Ship telemetry core here (it's platform- independent by design). - Phase 2 — breadth on json-stdio + first host-native telemetry + first mcp-only:
VS Code Copilot, Copilot CLI, Gemini (wire
AfterModelhost-native enricher), Warp. - Phase 3 —
ts-pluginparadigm: OpenCode, Kilo, Hermes (the hard adapters). - Phase 4 — verification-gated long tail: JetBrains, Pi, OpenClaw, zed/antigravity/ kiro/qwen/kimi/omp.
src/
index.ts public API surface (defineConnector + types)
core/
types.ts all shared contracts (ServerDef, events, config, scope, …)
define-connector.ts defineConnector() + validation + normalization
interpolate.ts ${env:VAR} / ${env:VAR:-default}
managed-block.ts marker-fenced managed blocks + memory ledger (the memory-surface engine)
paths.ts home data-root, project key/identity, per-OS dirs
spawn.ts Windows-safe build/parseNodeCommand, runtime resolution
spawn-child.ts Windows-safe child_process.spawn wrapper
mcp-standard.ts pinned official-MCP literals + guards (server.json / MCPB / wire)
package-formats/ per-host marketplace bundle renderers (claude-family, gemini, …)
logger.ts
adapters/
spi.ts Adapter interface (generalized HookAdapter + MCP render)
registry.ts ADAPTER_REGISTRY single source of truth (+ matrix test)
detect.ts install-time + runtime detection
base.ts BaseAdapter shared impl
claude-code/ codex/ cursor/ (Phase 1)
telemetry/
types.ts tokenizer.ts measure.ts store.ts report.ts proxy.ts
runtime/
index.ts hook-entrypoint.ts serve.ts probe.ts
cli/
index.ts app.ts sdk.ts (sdk.ts = createConnectorCli for branded CLIs)
commands/{detect,install,uninstall,upgrade,package,doctor,status,telemetry,
usage,leaderboard,hook,serve,usage-event}.ts