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#
| Field | Type | Default | Notes |
|---|---|---|---|
id | string | — | Optional explicit install/runtime alias. Defaults from package identity metadata (`name`, `mcpName`, `bin`); use only for legacy configs or multi-instance aliases. |
mcp | McpPackageIdentity | — | Optional package identity override when package.json is absent or intentionally different. |
displayName | string | derived id | Optional host-facing label override. Omit unless the host should show a label different from the package-derived id. |
version | string | package.json version, else "0.0.0" | Optional connector version override. Prefer package.json version for packaged connectors. |
server | ServerDef | — | The MCP server to deploy. Omit for a hooks-only / content-only connector. |
hooks | HooksConfig | {} | Lifecycle hooks. Omit for a server-only connector. |
telemetry | TelemetryConfig | (defaults) | Telemetry is ON even if omitted. |
commands | CommandDef[] | [] | Slash commands → native content files. |
skills | SkillDef[] | [] | Agent Skills → native content files. |
subagents | SubagentDef[] | [] | Named subagents → native content files. |
memory | MemoryDef[] | [] | Standing guidance → marker-fenced managed blocks in each host's memory/rules file (AGENTS.md-first; CLAUDE.md / GEMINI.md exceptions). |
statusline | StatuslineDef | — | The connector's status line / HUD — a single render(ctx) handler with options and per-host render/options overrides; SINGULAR. Omit when none. |
actions | ActionDef[] | — | 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. |
platforms | Partial<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. |
publish | PublishConfig | — | 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. |
oauth | Record<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#
configmust be an object; if supplied,idmust 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-platformnativeHooks/configPatchdeclaration) — else it throws. - If
serveris present: stdio transport requires a stringcommand; any remote transport (http/sse/ws) requires a stringurl. - Every present hook entry's
handlermust 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.
| Field | Type | Default | Notes |
|---|---|---|---|
idrequired | string | — | The validated kebab-case id, passed through unchanged. |
displayNamerequired | string | — | Resolved to id when not supplied. |
versionrequired | string | — | Resolved to "0.0.0" when not supplied. |
server | ServerDef | — | Normalized ServerDef; omitted entirely for a hooks/content-only connector. |
hooksrequired | HooksConfig | — | Always present ({} when none declared). |
hookEventsrequired | HookEventName[] | — | Derived list of the events that have a function handler — what adapters install. |
telemetryrequired | Required<TelemetryConfig> | — | Fully-resolved: { enabled, modelFamilyHint, measureToolDefs, hostNativeUsage, store, calibration: { anthropicCountTokens } }. |
commandsrequired | CommandDef[] | — | Normalized; [] when none. |
skillsrequired | SkillDef[] | — | Normalized; [] when none. |
subagentsrequired | SubagentDef[] | — | Normalized; [] when none. |
memoryrequired | MemoryDef[] | — | Normalized; names defaulted ("memory"); [] when none. |
statusline | StatuslineDef | — | Normalized; name defaulted ("statusline"); omitted when none. Carries the live render handler plus statusline options. |
actionsrequired | ActionDef[] | — | Normalized; ids defaulted/validated kebab-case + unique; [] when none. Carries live run handlers plus action affordance metadata. |
platformsrequired | Partial<Record<PlatformId, PlatformOverride>> | — | Always present ({} when none declared). |
targetsrequired | "auto" | PlatformId[] | — | Resolved to "auto" when not supplied. |
publish | PublishConfig | — | Passed through verbatim when supplied (omitted otherwise) — distribution metadata consumed by package --format mcp-server-json | mcpb. |
oauthrequired | 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.
| Field | Type | Default | Notes |
|---|---|---|---|
hooks | boolean | Partial<HooksConfig> | — | false → no NORMALIZED hooks here; object → merge / replace. |
nativeHooks | Record<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. |
configPatch | ConfigPatchDef[] | — | 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. |
server | Partial<ServerDef> | false | — | false → don't register server here; object → shallow-merge. |
scope | InstallScope | — | Force a scope for this platform. |
commands | boolean | — | false → skip command files here. |
skills | boolean | — | false → skip skill files here. |
subagents | boolean | — | false → skip subagent files here. |
statusline | boolean | — | false → do not wire the status line on this platform (no object form in v1). |
actions | boolean | — | false → do not emit the action affordance(s) on this platform (no object form in v1). |
memory | boolean | 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) }. |
extra | Record<string, unknown> | — | Verbatim fields merged into the native config. |
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.
| Field | Type | Default | Notes |
|---|---|---|---|
keyrequired | 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. |
valuerequired | 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. |
reasonrequired | string | — | Human-readable why — printed in the install diff, every ChangeRecord, and every skip-warn (one declaration doubles as its own documented manual edit). |
docsUrl | string | — | Docs link appended to the manual-edit fallback printed on skip / conflict / unsupported host. |
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.