Skip to content

Connectors

A connector is the registry unit that ties one external capability bundle into Z.E.N.: tools, auth ownership, schemas, state bindings, install/health/freshness commands, and optionally a hosted-provider reference (Composio toolkit, mcporter server).

Connectors don't do anything themselves — they describe what's available. When you run a tool: node, Z.E.N. looks up the tool, finds its connector, asks the connector how to invoke it, and runs.

This page covers the registry concept, the manifest shape, the source kinds, and how to add your own without touching source.

If you just want to use what's installed, open Settings → Connectors in the web UI. If you want to add a specific kind, see Integrations.

What a connector knows

yaml
id: my-service
displayName: My Service
description: Internal HTTP API our team uses for…

source:
  kind: local_cli
  path: /usr/local/bin/myservice

auth:
  owner: env
  env: [MYSERVICE_TOKEN]

tools:
  - id: myservice.list
    kind: cli
    displayName: List items
    bin: myservice
    args: [list, --json]
    outputFormat: json
  - id: myservice.get
    kind: cli
    bin: myservice
    args: [get]

state:
  - id: cache
    kind: cache_dir
    path: ~/.cache/myservice
    access: opaque

health:
  bin: myservice
  args: [status, --json]
  outputFormat: json
  timeoutMs: 5000

freshness:
  bin: myservice
  args: [last-sync]
  outputFormat: json
  defaultPolicy: refresh_if_stale

That YAML is a connector manifest. Drop it at ~/.zen/connectors/my-service.yaml (or .zen/connectors/my-service.yaml for repo-local), restart the daemon, and my-service shows up in Settings → Connectors with two tools registered.

Source kinds

A connector's source.kind tells Z.E.N. how the underlying capability is reached:

kindWhat it meansExample
local_cliA binary already installed on the user's machinegws, your own CLI
repoA repo with code to build/runInternal tooling
composioA Composio toolkit (or hosted MCP URL)gmail, slack toolkits via Composio
mcpAn MCP server registered with mcportermcp.notion, mcp.cadence
httpPlain HTTP endpointsPublic APIs
inlineTools defined entirely inline (no external dependency)Test fixtures, in-process tools

The source kind drives Settings → Connectors' color-coded chips and the per-source integration page you read for setup.

Where Z.E.N. looks for manifests

In order, deduped by id (later wins):

  1. Bundled — shipped with the daemon. Just GWS today; mcporter and Printing Press are bundled but populate dynamically from their own discovery.
  2. Global~/.zen/connectors/*.{yaml,yml}
  3. Project<repo>/.zen/connectors/*.{yaml,yml} when the daemon is scoped to a workspace

Drop a manifest in either path and it's picked up at the next daemon boot. No code change, no rebuild.

Tools live with their connector

A connector's tools: array declares everything the connector exposes. Each entry becomes an endpoint in the @zen/tools registry at boot, keyed as <connectorId>.<toolId> (the leading segment is automatic; you write id: list and it becomes myservice.list).

That's the only path for tools to register today: through a connector. There's no standalone tool manifest. If you want a one-off tool, you make a connector with kind: inline and one tool entry.

Tool kinds:

kindWhat it is
cliSpawn a binary with args, capture stdout
mcpCall a method on an MCP server (via mcporter broker)
httpHTTP request to a URL
composioComposio tool call (Phase 6B)
in_processFunction registered programmatically before workflow starts

Tool details live in the tools reference.

Auth ownership

auth.owner names the subsystem that owns the credentials — env, gws, composio, mcporter, or your own. auth.env lists the env var names the connector reads (never values; Z.E.N. won't carry secrets). auth.refs is for credential-store refs that resolve at invocation.

What gets exposed on /api/connectors: just hasAuth: true|false. Z.E.N. never echoes which env vars or refs you declared back to clients.

State bindings

State bindings let Z.E.N. introspect or query local data the connector owns — its SQLite DB, FTS index, cache dir, cloned repos.

access controls what Z.E.N. can do:

  • opaque (default) — Z.E.N. must NOT touch the resource directly; the connector binary/API is the only safe path. Use this when the live writer holds locks or writes WAL.
  • read_only — Z.E.N. may open for introspection/querying. Safe for read-mostly stores where the connector tolerates concurrent readers.
  • read_write — Z.E.N. may write directly. Use sparingly. Breaks every store with a live writer.

The settings UI surfaces the binding count; the executor can resolve a binding when a workflow node references it.

Health and freshness

health is a self-test command Z.E.N. runs to ask the connector "are you usable right now?" — typically <bin> status. Output is healthy | degraded | unhealthy | unknown | not_configured. Surfaces in Settings → Connectors as a status dot.

freshness is the cache-staleness check — "is the data you cached locally still good?" — returns fresh | stale | unknown | not_supported. Surfaces in Settings → Local data freshness with a "Check now" button per connector.

Both are optional. Connectors that fetch live (no cache) skip freshness; connectors with no self-test skip health.

Lifecycle commands

install, build, update — long-running commands Z.E.N. can run on the user's behalf. They show up in the UI as buttons (install when first added, update on demand). Implementation is mostly stubbed today — the manifest carries the spec, the executor hooks land later.

Programmatic API

typescript
import { registerConnector, listConnectors } from '@zen/connectors';

registerConnector({
  manifest: { /* ConnectorManifest */ },
  builtIn: false,
  fromDisk: false,
});

Built-in adapters (packages/connectors/src/builtins/*.ts) register at bootstrap. Disk-loaded connectors register via the discovery pass. Both end up in the same registry; lookups don't care about source.

What's user-extensible

Everything above is drop-a-file. To add a connector — and through it, new tools/CLIs/MCP servers/HTTP integrations — you write YAML, you don't write TypeScript.

The one thing that requires source is a new source kind itself — e.g. a hypothetical grpc or graphql source kind that the adapter layer doesn't know about yet. Today there are six; adding a seventh means adding an adapter in packages/connectors/src/builtins/ and a converter in @zen/tools.

If your tool fits an existing source kind (almost everything does — local_cli covers any binary, mcp covers any MCP server via mcporter, http covers any HTTP API), you're entirely in YAML territory.

  • Tools — what gets registered from a connector's tools: array
  • Harnesses — agent runtimes that consume tools
  • Integrations — per-source-kind setup
  • Issue #27 — the .zenpkg path that bundles connectors for binary distribution

AI that follows a recipe, not a conversation.