Core API

defineConnector#

defineConnector(config: ConnectorConfig): ResolvedConnector — the public, write-once surface. It validates eagerly and throws ConnectorConfigError on any violation, returning a fully-defaulted ResolvedConnector that adapters and the CLI consume.

The typed per-surface identity helpers and host-capability introspection live at the consolidated @ken-jo/agent-connector/sdk subpath: defineStatusline, defineAction, defineHook, defineCommand, defineSkill, defineSubagent, defineMemory, defineConfigPatch, defineNativeHook, plus hostsSupporting / capabilitiesOf / surfaceSupport. The root @ken-jo/agent-connector export is unchanged for backward compatibility — these helpers are also re-exported from root.

ConnectorConfig#

FieldTypeDefaultNotes
idstring—Optional explicit install/runtime alias. Defaults from package identity metadata (`name`, `mcpName`, `bin`); use only for legacy configs or multi-instance aliases.
mcpMcpPackageIdentity—Optional package identity override when package.json is absent or intentionally different.
displayNamestringderived idOptional host-facing label override. Omit unless the host should show a label different from the package-derived id.
versionstringpackage.json version, else "0.0.0"Optional connector version override. Prefer package.json version for packaged connectors.
serverServerDef—The MCP server to deploy. Omit for a hooks-only / content-only connector.
hooksHooksConfig{}Lifecycle hooks. Omit for a server-only connector.
telemetryTelemetryConfig(defaults)Telemetry is ON even if omitted.
commandsCommandDef[][]Slash commands → native content files.
skillsSkillDef[][]Agent Skills → native content files.
subagentsSubagentDef[][]Named subagents → native content files.
memoryMemoryDef[][]Standing guidance → marker-fenced managed blocks in each host's memory/rules file (AGENTS.md-first; CLAUDE.md / GEMINI.md exceptions).
statuslineStatuslineDef—The connector's status line / HUD — a single render(ctx) handler with options and per-host render/options overrides; SINGULAR. Omit when none.
actionsActionDef[]—User-invokable actions dispatched by `agent-connector action`; supports label/icon/placement/confirm metadata and per-host overrides. Supporting adapters emit native host affordances, unsupported hosts skip-warn. Omit when none.
platformsPartial<Record<PlatformId, PlatformOverride>>{}Per-platform overrides / escape hatch (extra, nativeHooks, configPatch, memory target/mode tuning, disable a surface (incl. statusline), force scope).
targets"auto" | PlatformId[]"auto""auto" = all detected; or an explicit allow-list.
publishPublishConfig—Distribution metadata for the official MCP standard artifacts (package --format mcp-server-json | mcpb): { registryNamespace? (reverse-DNS namespace you own, e.g. "io.github.acme"; server.json name = <namespace>/<id>), packageName? (your REAL published package, e.g. "@acme/acme-db-mcp"), registryBaseUrl? (default https://registry.npmjs.org), author? ({ name, email?, url? } — MCPB requires author.name) }. Describes your real upstream server, NOT the serve wrapper; optional — each format errors only when its required field is missing.
oauthRecord<string, OAuthLoginDef>{}OAuth 2.0 providers the server logs in to, keyed by login key (^[a-z0-9][a-z0-9-]{0,31}$): { provider: "google" | "microsoft" | "github" | "bing-webmaster" | "posthog" | "generic", clientId (a literal, ${env:VAR} — expanded at login and refresh time, which a host-spawned server does not see — or exactly one ${secret:NAME} reference when each user registers their own app), clientSecret? (exactly one ${secret:NAME} reference: a literal secret is never written into a connector config, except for google, whose provider documents an installed app's client secret as not confidential; exclusive with tokenExchangeUrl), tokenExchangeUrl? (https; the developer's own token exchange service that holds the client secret — the authorization-code exchange, every refresh and device-code polling go there with client_id and never a secret; exclusive with clientSecret), scopes (at least one), flow? ("auto" | "loopback" | "device", default "auto"), redirectPort? (1024..65535; default ephemeral), redirectPath? (default "/callback"), issuer? / authorizationEndpoint? / tokenEndpoint? / deviceAuthorizationEndpoint? / revocationEndpoint? (https only; generic needs issuer or both endpoints), tokenEndpointAuth?, pkce?, extraAuthorizationParams?, options? (posthog region, microsoft tenant), storeAs? (default oauth.<key>.refresh-token) }. The user runs `auth login <key>` once; the server calls getAccessToken({ connectorId, key }) from the SDK. agent-connector ships no client ids — the developer registers the app and ships it (developer-provided, or through a token exchange service), or each user registers their own (${secret:NAME} id and secret, stored with secrets set).

Top-level validation rules#

  • config must be an object; if supplied, id must match the kebab-case regex ^[a-z0-9][a-z0-9-]*$. Otherwise it is derived from package identity metadata.
  • A connector must declare at least one of server, hooks, commands, skills, subagents, memory, statusline, actions (or a per-platform nativeHooks / configPatch declaration) — else it throws.
  • If server is present: stdio transport requires a string command; any remote transport (http/sse/ws) requires a string url.
  • Every present hook entry's handler must be a function.

ResolvedConnector#

What defineConnector returns: every optional ConnectorConfig field is resolved to a concrete value. hookEvents lists the events that have a function handler (what adapters install), and telemetry is fully defaulted. commands / skills / subagents / memory are normalized to [] when none.

FieldTypeDefaultNotes
id
required
string—The validated kebab-case id, passed through unchanged.
displayName
required
string—Resolved to id when not supplied.
version
required
string—Resolved to "0.0.0" when not supplied.
serverServerDef—Normalized ServerDef; omitted entirely for a hooks/content-only connector.
hooks
required
HooksConfig—Always present ({} when none declared).
hookEvents
required
HookEventName[]—Derived list of the events that have a function handler — what adapters install.
telemetry
required
Required<TelemetryConfig>—Fully-resolved: { enabled, modelFamilyHint, measureToolDefs, hostNativeUsage, store, calibration: { anthropicCountTokens } }.
commands
required
CommandDef[]—Normalized; [] when none.
skills
required
SkillDef[]—Normalized; [] when none.
subagents
required
SubagentDef[]—Normalized; [] when none.
memory
required
MemoryDef[]—Normalized; names defaulted ("memory"); [] when none.
statuslineStatuslineDef—Normalized; name defaulted ("statusline"); omitted when none. Carries the live render handler plus statusline options.
actions
required
ActionDef[]—Normalized; ids defaulted/validated kebab-case + unique; [] when none. Carries live run handlers plus action affordance metadata.
platforms
required
Partial<Record<PlatformId, PlatformOverride>>—Always present ({} when none declared).
targets
required
"auto" | PlatformId[]—Resolved to "auto" when not supplied.
publishPublishConfig—Passed through verbatim when supplied (omitted otherwise) — distribution metadata consumed by package --format mcp-server-json | mcpb.
oauth
required
Record<string, ResolvedOAuthLoginDef>—Always present ({} when none declared). Every login validated and defaulted: key, flow ("auto"), redirectPath ("/callback"), storeAs (oauth.<key>.refresh-token); nothing is resolved against the network at define time.

PlatformOverride (escape hatch)#

Per-platform overrides keep the universal core thin. Use extra to reach platform-exclusive features the core doesn't model — a thin universal core with a fat per-adapter tail.

FieldTypeDefaultNotes
hooksboolean | Partial<HooksConfig>—false → no NORMALIZED hooks here; object → merge / replace.
nativeHooksRecord<string, NativeHookDef>—Native passthrough — wire ANY host hook event outside the 13 normalized ones, keyed by the host's event name verbatim. Raw payload in, verbatim JSON reply out (exit 0 only). Honored by the 16 adapters that set supportsNativeHooks; the rest skip-warn.
configPatchConfigPatchDef[]—Declarative host-config key patches for host-exclusive settings keys extra can't reach (e.g. Claude Code env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS). Fixed semantics: set-if-absent on a dotted leaf key, skip-warn on ANY conflict, refcounted ownership ledger, reversible uninstall. statusLine is reserved for the statusline surface. claude-code only today; other adapters skip-warn with the manual edit.
serverPartial<ServerDef> | false—false → don't register server here; object → shallow-merge.
scopeInstallScope—Force a scope for this platform.
commandsboolean—false → skip command files here.
skillsboolean—false → skip skill files here.
subagentsboolean—false → skip subagent files here.
statuslineboolean—false → do not wire the status line on this platform (no object form in v1).
actionsboolean—false → do not emit the action affordance(s) on this platform (no object form in v1).
memoryboolean | PlatformMemoryOverride—false → no memory block on this host; object → { path? (override the target file), mode? ("block" | "agents-import" — claude-code only, ignored elsewhere with a warn) }.
extraRecord<string, unknown>—Verbatim fields merged into the native config.
agent-connector.config.mjs
ts
platforms: {
  warp:   { hooks: false },                 // mcp-only host: skip hooks
  cursor: { scope: "project" },             // force project scope here
  codex:  { server: { timeoutMs: 60_000 } },// shallow-merge into ServerDef
  "claude-code": {
    extra: { /* verbatim native fields the core doesn't model */ },
  },
}

Host-config key patches (configPatch)#

extra merges into the native MCP server entry — it cannot reach a sibling top-level settings key like Claude Code's env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS. platforms.<id>.configPatch declares those as ownership-tracked patches: you name a platform + key, never a file path — the adapter owns the key→file mapping (claude-code: settings.json at the install scope).statusLine is reserved for the first-class statusline surface instead of raw configPatch.

FieldTypeDefaultNotes
key
required
string—Dotted LEAF path into the adapter's one patchable file, e.g. "env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS". Segments match [A-Za-z0-9_-]+ — no dots-in-key, no array indices. Keys agent-connector already models (hooks*, mcpServers*, statusLine*) are rejected at defineConnector; the claude-code sensitive-key denylist (permissions*, allowedTools*, apiKey*, env.ANTHROPIC_*, token/key/secret/proxy env vars, auth/login keys) is hard-refused.
value
required
JsonValue—Written ONLY when the key is absent; ${env:VAR} refs resolve at install time. May be an object/array but is written atomically as the leaf — never merged into.
reason
required
string—Human-readable why — printed in the install diff, every ChangeRecord, and every skip-warn (one declaration doubles as its own documented manual edit).
docsUrlstring—Docs link appended to the manual-edit fallback printed on skip / conflict / unsupported host.
agent-connector.config.mjs
ts
platforms: {
  "claude-code": {
    configPatch: [
      {
        // dotted LEAF path into settings.json at the install scope
        // (segments [A-Za-z0-9_-]+ — no array indices)
        key: "env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS",
        // written ONLY if the key is absent; any conflict → skip-warn
        value: "1",
        // REQUIRED — printed in the install diff and every skip-warn
        reason: "Enable Claude Code agent teams for this connector's workflow",
        docsUrl: "https://docs.anthropic.com/en/docs/claude-code/settings",
      },
    ],
  },
}

Fixed semantics — set-if-absent, skip-warn, refcounted ownership

The value is written only when the key is absent; ANY conflict (key already present, drifted value, non-object intermediate) is a skip-warn that prints current vs desired plus the exact manual edit — never an overwrite, delete, or deep merge. Ownership is refcounted in a persisted ledger (<dataRoot>/state/config-patches.json): co-owners share a key, and uninstall (run first, before other surfaces) deletes a key only when the last owner releases it AND the current value still equals what was written AND the key was absent before install — after backing up the file. doctor reports each patch as ok / drifted / missing / orphaned and never auto-fixes drift. Only claude-code sets supportsConfigPatch today; other adapters skip-warn with the per-patch manual edit. VS Code inputs arrays and Zed context_servers.<id>.settings are deliberately NOT configPatch targets (entry-coupled adapter dialect), and TOML hosts are out of v1 (comment-destroying round-trips are banned). A patch graduates to a typed cross-host knob only when ≥3 hosts ship an analog.