# AI Agents (MCP)

Turn the workflows you built into tools an AI assistant can call. Each flow you expose becomes its own named tool — the assistant runs it and reads the result.

Your workflow

The flow you already built

↓ expose
Named MCP tool

One tool per exposed flow

↓ called by
AI assistant

Calls it, reads the result

## What It Is

An AI assistant decides what to do; Watchflows decides what it's allowed to do. It can see, run, and read results from the flows you share, and nothing else. This works over the Model Context Protocol (MCP) — a small `watchflows-mcp` helper in the app bundle talks to a local-only server inside the app (it listens on 127.0.0.1 / loopback, so nothing off this Mac can reach it).

It is off by default: no server listens and no live token is in effect until you flip the master toggle. When on, it stays local to this Mac:

Property | Detail
--- | ---
Binds | `127.0.0.1` only — no network exposure
Auth | Private bearer token required on every call
Token | 32 random bytes (43 base64url chars), regenerated every time the server starts
Stored in | A `0600` file only your account can read
Never in | The Keychain or your shell history

## Setup at a Glance

Four steps, once. The next three sections walk through 1–3.

1 Enable

AI Agents master toggle

2 Connect

Wire in your assistant

3 Expose

Per-flow toggle

4 Call

Assistant runs the tool

## 1. Turn It On

Open Settings → AI Agents and flip Enable AI Agents. That starts the loopback server, generates a fresh access token, and writes the port to disk so your assistant's helper can find it. The token is regenerated every time the server starts — a restart or off/on cycle invalidates the old one. Turning the toggle off stops the server.

## 2. Connect Your Assistant

The AI Agents pane has a connector for each major client. You never paste the token — every snippet carries only the path to the bundled helper, and the helper reads the token off its `0600` file itself.

Client | How to connect
--- | ---
Claude Desktop | One-click Connect: merges a `watchflows` entry into your config, leaves other servers untouched. You restart Claude. (If it isn't installed, you get a copyable config instead.)
Claude Code | Copy one command, paste into a terminal (below)
Cursor / Windsurf / VS Code | Copy the same `mcpServers` JSON; only the target file differs (`~/.cursor/mcp.json`, `~/.codeium/windsurf/mcp_config.json`, etc.)
Codex | Copy a TOML table into `~/.codex/config.toml`

The Claude Code command looks like this (the helper path comes from wherever the app is actually installed):

    claude mcp add --transport stdio watchflows -- /Applications/Watchflows.app/Contents/MacOS/watchflows-mcp

An Advanced section shows the active port and a masked copy of the token, with Copy and Rotate. Rotating replaces the token file and restarts the server so the new token takes effect immediately — any client still using the old token will need to reconnect.

## 3. Expose a Flow

Enabling AI Agents does not hand over all your flows. Each workflow has its own Expose to AI agents toggle, off by default, in the “Exposed workflows” list or the flow's inspector. A flow is agent-runnable only when both are true:

Enabled

The flow is on

AND

Exposed

Toggle flipped on

↓

Agent-runnable

Becomes a named tool

- Everything else stays visible, just not runnable. `list_workflows` reports all flows with an `agentRunnable` flag, so the assistant sees what exists — but a non-exposed flow has no run tool and the run gate refuses it.
- Destructive flows use the same toggle. A flow that can delete files or run scripts is gated by the very same Expose to AI agents switch — there's no separate one. The only difference: the refusal message adds a heads-up that the flow contains such a node, so you opt in with eyes open.

## How Input Works

Exposing a flow turns it into a tool the assistant can call — and the first node of your flow is the slot where its input goes in. Whatever the assistant passes replaces the placeholder you saved while building (it replaces, never blends). Your saved value is shown to the assistant only as an example of what belongs there — not a fallback it quietly runs.

You get a clean, labeled slot when your flow starts with an Input node: it shows the assistant exactly one box, named after the Input's type.

Entry trigger | Argument key | Type
--- | --- | ---
Input · Text (default) | `text` | string
Input · JSON | `data` | object
Input · File | `filePath` | string
Any other trigger | `payload` | object (free-form)

An Input node isn't strictly required — the assistant's data is fed into whichever trigger your flow starts with. But only Input flows get that tidy labeled box. Every other trigger — File Changed, Schedule, Device Event, and so on — hands the assistant a single blank `payload` box to fill in, with no labels to guide it.

Assistant calls
`podcast_info_fetcher(text: "Acquired")`
Typed expansion

Fills `contentType` + `data` for you

Flow runs → returns

Curated `output` + `output_source` inline

The two run entry points are not interchangeable. Only the flow's own named tool does the typed key→payload expansion shown above (your `text` argument, then `contentType` and `data` filled in). `run_workflow` passes its `payload` object straight through with no remapping, so there you supply the raw trigger keys yourself.

To see a flow's exact input contract, call `describe_workflow` — it returns a `runPayload` block: a `note` plus a `keys` list giving each key's type, description, and example.

Omit a key and the flow runs on its saved default — the same value you tested with in the canvas. A supplied value always wins.

## What You Get Back

A completed run returns the answer inline — a top-level `output` plus an `output_source` telling you where it came from. The rest of the body is lean: per-node status, timings, and errors, but not full payloads. For every node's full payload, the assistant calls `get_run`.

Watchflows picks the output automatically, and `output_source` self-advertises the pick:

`output_source` | Meaning
--- | ---
`explicit:<node>` | You set “Return this to the agent” on that node, and it ran
`auto:<node>` | Single end node — its `result` was returned
`none:side-effect` | The leaf ran but produced no `result`
`none:dead-end` | The run ended without reaching an end node
`ambiguous:multi-leaf:<names>` | Two or more branches ended at once — pick unclear

- “Return this to the agent” override. When the auto-pick is wrong, turn this on in any node's inspector — its output becomes the answer (`explicit:<node>`), as long as it ran and produced a value. If it was skipped or empty, projection quietly falls back to the auto-pick. Never a hard error.
- Verbose agent-trace fields are stripped from the output.
- The inline value is capped at ~16 KB (16,384 bytes); anything larger is truncated inline to a marker.
- A value already spilled to disk surfaces as a `…[spilled blob — use get_run]` marker, never the full bytes.

## Long Runs & Retries

A run that hasn't finished after about a minute doesn't fail — the flow keeps executing in the background and is saved under a `run_id` from the start. Two non-error statuses tell the assistant to wait, not re-call:

Status | Means | Do
--- | --- | ---
`running` | Still executing past ~60s | Poll `get_run` with the `run_id`
`already_running` | You tried to start it while in flight | Poll the in-flight `run_id` — don't start a duplicate

The rule: on `running` or `already_running`, poll the `run_id` — don't re-call.

## The Tools

Alongside each exposed flow's own named tool, the assistant always has these five core tools. They are read/run only — no creating, editing, or enabling/disabling flows in this version (planned for a later phase).

Tool | What it does
--- | ---
`list_workflows` | Lists every workflow, each with an `agentRunnable` flag
`describe_workflow` | A flow's node-by-node shape plus its `runPayload` input contract
`run_workflow` | Runs a flow by id with an optional `payload`, returning curated `output` inline (a flow's named tool is the typed shortcut)
`get_run` | The full, verbose record of one run, including every node's payload
`list_runs` | Pages through a flow's run history

Read tools see every flow; only exposed-and-enabled flows can be run.