Skip to content

Tools

The tool registry is the lookup table that tool: <id> workflow nodes resolve against. Every tool comes from a connector. Every tool gets one entry. Every entry knows how to invoke itself.

If you just want to use one, browse Settings → Tools (with kind filter + search). If you want to add one, see the connectors reference — tools are added through their connector's manifest.

What a tool entry has

typescript
{
  id: 'gws.gmail.users.messages.list',
  kind: 'cli',
  displayName: 'List Gmail messages',
  description: 'Lists the messages in the user\'s mailbox…',
  connectorId: 'gws',
  outputFormat: 'json',
  builtIn: true,
}

id is what you put in tool: <id> YAML. Always <connectorId>.<localId>. Never invented; always derived from the connector's manifest.

The five kinds

The tool's kind tells Z.E.N. how to invoke it.

cli — spawn a binary

Most common kind. Used by GWS (gws schema, gws gmail.users.messages.list), Printing Press CLIs, any local binary you wrap in a connector manifest.

Z.E.N. spawns the binary with structured args, captures stdout, parses according to outputFormat. Stderr lands in the error message when exit is non-zero.

advancedShell two-gate: Some CLI tools want shell features (pipes, env expansion). The endpoint itself must declare advancedShell: true, AND the workflow node must opt in with tool_advanced_shell: true. Both sides must agree before the executor runs the command in shell mode. This blocks a runaway agent from escalating a deterministic CLI tool to shell execution.

mcp — call an MCP server

When mcporter has an MCP server registered, every method it exposes becomes a mcp.* tool. The mcporter adapter brokers the call — Z.E.N. doesn't speak MCP directly; mcporter is the bridge.

When you mcporter add notion and the server exposes search, query_database, etc., they show up as mcp.notion.search, mcp.notion.query_database. Add a new MCP server, refresh the connector, the tools appear.

http — plain HTTP request

URL, method, headers, body shape. Auth comes from the connector's auth block. Response parsed per outputFormat.

composio — Composio toolkit

Phase 6B. Composio adapter takes a toolkit id, fetches the tool catalog, registers each as a composio.<toolkit>.<tool> entry. The endpoint knows the Composio session id; invocations go through Composio's API.

in_process — registered function

Programmatically registered before workflow start. Used for test fixtures and tools that need closure over runtime objects. Not loaded from manifests; explicitly added via the runtime API.

Output formats

outputFormat tells the tool node how to parse what the tool returns:

FormatWhat it is$nodeId.output shape
jsonSingle JSON documentThe parsed object
ndjsonNewline-delimited JSON (one record per line)Array of parsed records
textRaw textString
binaryRaw bytesBase64-encoded string

Most tools are json. NDJSON is useful for streaming list outputs from CLIs.

How a tool: node invokes one

yaml
- id: list-unread
  tool: gws.gmail.users.messages.list
  tool_args:
    userId: me
    q: is:unread
  tool_timeout_ms: 30000
  tool_advanced_shell: false
  1. Z.E.N. looks up gws.gmail.users.messages.list in the in-memory tool registry.
  2. Finds kind: cli and the spawn details (bin, args, outputFormat).
  3. Dispatches to the CLI invoker with tool_args merged into the command.
  4. Runs the binary with tool_timeout_ms ceiling.
  5. Parses output per outputFormat.
  6. Writes parsed result to $nodeId.output.
  7. Records the call as a workflow_events row.

Errors (non-zero exit, timeout, unknown id) become a state: failed node output with a verbatim message. See tool nodes.

How tools end up in the registry

                              boot


        ┌────────────────────────────────────────────────┐
        │ @zen/connectors discovery + bootstrap          │
        │                                                │
        │  bundled adapters (GWS, mcporter, Printing     │
        │  Press) auto-register their connectors         │
        │                                                │
        │  global manifests (~/.zen/connectors/*.yaml)   │
        │  project manifests (<repo>/.zen/connectors/)   │
        │  parsed + registered                           │
        └─────────────────┬──────────────────────────────┘

                          ▼ for each connector
        ┌────────────────────────────────────────────────┐
        │ @zen/tools converter                           │
        │                                                │
        │  walks manifest.tools[], builds a ToolEndpoint │
        │  for each, registers under <connectorId>.<id>  │
        └─────────────────┬──────────────────────────────┘


                  in-memory tool registry
                  surfaced at /api/tools
                  rendered in Settings → Tools
                  picked from in workflow editor

Every tool you see in Settings → Tools came through this path. No manual registration. Add a connector → tools appear. Remove a connector → tools disappear.

When to use tool: vs agent: vs bash:

You want…Use
One specific known API call with structured argstool:
One shell command you wrotebash:
An AI to decide which tool(s) to callagent: with tools available via its harness
A specific script in a specific runtimescript: (bun or uv)

Tool nodes are deterministic — same args, same call, same result. No AI, no thinking, no plan. When you need that, use tool:. When you need the model to figure it out, use agent:.

Programmatic API

typescript
import { invokeTool, registerToolEndpoint, getToolInfoList } from '@zen/tools';

// register a tool endpoint programmatically (for in_process kind or tests)
registerToolEndpoint({
  id: 'mycompany.echo',
  kind: 'in_process',
  invoke: async (args) => ({ ok: true, args }),
  outputFormat: 'json',
});

// invoke at runtime
const result = await invokeTool({
  toolId: 'mycompany.echo',
  args: { message: 'hi' },
});

Connector-loaded tools register via the converter automatically. You only need the programmatic API for in_process kinds or tests.

AI that follows a recipe, not a conversation.