Appearance
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_staleThat 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:
kind | What it means | Example |
|---|---|---|
local_cli | A binary already installed on the user's machine | gws, your own CLI |
repo | A repo with code to build/run | Internal tooling |
composio | A Composio toolkit (or hosted MCP URL) | gmail, slack toolkits via Composio |
mcp | An MCP server registered with mcporter | mcp.notion, mcp.cadence |
http | Plain HTTP endpoints | Public APIs |
inline | Tools 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):
- Bundled — shipped with the daemon. Just GWS today; mcporter and Printing Press are bundled but populate dynamically from their own discovery.
- Global —
~/.zen/connectors/*.{yaml,yml} - 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:
kind | What it is |
|---|---|
cli | Spawn a binary with args, capture stdout |
mcp | Call a method on an MCP server (via mcporter broker) |
http | HTTP request to a URL |
composio | Composio tool call (Phase 6B) |
in_process | Function 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.
Related
- Tools — what gets registered from a connector's
tools:array - Harnesses — agent runtimes that consume tools
- Integrations — per-source-kind setup
- Issue #27 — the
.zenpkgpath that bundles connectors for binary distribution