MCP server
rflow mcp serves the Model Context Protocol
over stdio, so AI agents and editors β Claude Code, Cursor, or anything else
that speaks MCP β can operate an rflow project: validate config, inspect
the run journal and indexer cursors, simulate contract calls, and perform the
same journaled, safe mutations the CLI exposes. No other workflow engine ships
this: your agent debugs a dead-lettered run, simulates the fix, and retries it
without ever touching a key.
rflow mcp [--path <dir>]Long-running; reads JSON-RPC from stdin and writes responses to stdout (logs go
to stderr). Exits cleanly when the client closes stdin. The server reads
rflow.yaml fresh on every call and connects to the project's
config.db_connection lazily β config-only tools work before Postgres is even
up.
Claude Code / Cursor setup
Add rflow to your MCP config (.mcp.json for Claude Code, .cursor/mcp.json
for Cursor):
{ "mcpServers": { "rflow": { "command": "rflow", "args": ["mcp", "--path", "/my/project"] } } }Or one-shot with the Claude Code CLI:
claude mcp add rflow -- rflow mcp --path /my/projectThe safety model
The tool surface is ops-level, not wallet-level:
- Read-heavy by design β config, journal, cursors and health are all
read-only queries against
rflow.yamland the Postgres journal. simulate_sendnever broadcasts β it is aneth_callthrough the same read providers the engine uses for pre-flight simulation. It never signs, never queues, never touches the relayer.- Mutations are the journaled safe ops only β
trigger_workflowclaims a manual run through the exactly-once gate (identical torflow trigger),pause_workflow/resume_workflowflip the Postgres kill-switch, andretry_runrequeues a settled run. Every one of them lands in the durable journal and is executed (or not) by the runningrflow startinstance under the workflow's own permissions, simulation gates and relayer policies. - There is deliberately NO raw send/broadcast tool. Transactions only ever originate from workflow definitions β an agent cannot construct and send an arbitrary transaction through rflow's relayers.
- There is deliberately NO approve/reject tool either. An agent can see pending approval gates in the journal, but deciding a money gate stays with humans and the CLI.
Tools
| Tool | Arguments | What it does |
|---|---|---|
validate_config | β | Strict collect-all validation of rflow.yaml; every error and warning. |
get_config_summary | β | Networks (name, chain id), workflows (trigger, paused), relayers (networks, reconciled onchain address). |
list_runs | limit?, failed_only? | Recent runs from the journal, newest first. |
get_run | run_id | One run plus its full step journal: statuses, attempts, tx ids/hashes, outputs, errors. |
list_triggers | limit? | Trigger keys fired per workflow β which exact events/ticks claimed runs. |
get_cursors | β | Indexer progress per workflow Γ network (anchor block, last processed block). |
trigger_workflow | workflow, inputs? | Claims a journaled manual run (manual:<uuid>), picked up by rflow start. |
pause_workflow | workflow | Kill-switch: triggers keep matching, no new runs start. |
resume_workflow | workflow | Resume a paused workflow. |
retry_run | run_id | Requeue a settled (failed | dead_letter | succeeded) run; resumes after its last completed step. |
get_health | β | The /health payload: run counts per status, relayer count. |
simulate_send | network, contract_or_to, function?, args?, value?, data?, from? | Read-only eth_call with decoded output β or the decoded revert reason. |
Every result is compact JSON. Tool failures (unknown workflow, bad uuid,
unreachable RPC) come back as in-band isError results with a precise message,
so agents can read the reason and correct course.
simulate_send in practice
contract_or_to is either a name from your contracts: registry (the ABI and
per-network address resolve automatically, function can be a bare name) or a
raw 0x address (pass the full solidity signature, e.g.
transfer(address,uint256)). value accepts 1 ether / 10 gwei / wei
strings. A revert is a successful simulation with "success": false and the
decoded reason β exactly what the engine's pre-flight simulation would have
caught before a real send.
{ "network": "ethereum", "contract_or_to": "USDC", "function": "transfer", "args": ["0xβ¦", "1000000"], "from": "0xβ¦relayer" }β
{ "success": false, "reverted": true, "reason": "execution reverted: ERC20: transfer amount exceeds balance" }