_API_KEY=...` |
| Rate limit | Too many requests | Wait and retry automatically (built-in retry) |
The node retries transient failures automatically (3 attempts, 1 second wait).
# MCP tools
Source: https://docs.pflow.run/reference/nodes/mcp
Use tools from MCP servers
**Agent commands.** Your AI agent uses this node in workflows. You manage the MCP servers that provide these tools with [`pflow mcp`](/reference/cli/mcp) commands.
MCP (Model Context Protocol) lets you extend pflow with external tools. When you add an MCP server, its tools become pflow nodes — same validation, same error handling, same template access as built-in nodes.
## How it works
1. **Add an MCP server** with `pflow mcp add`
2. **Sync tools** with `pflow mcp sync` (or let auto-sync handle it)
3. **Tools become nodes** named `mcp-{server}-{tool}`
4. **Agent uses them** like any other node
```bash theme={null}
# Add a GitHub MCP server
pflow mcp add github.mcp.json
# Tools are now available
pflow mcp list github
# mcp-github-create_issue
# mcp-github-list_repos
# mcp-github-search_code
```
## Node naming
MCP tools become nodes with the pattern `mcp-{server}-{tool}`:
| Server | Tool | Node name |
| ---------- | ------------- | -------------------------- |
| github | create\_issue | `mcp-github-create_issue` |
| slack | send\_message | `mcp-slack-send_message` |
| filesystem | read\_file | `mcp-filesystem-read_file` |
## Parameters
MCP node parameters come directly from the tool's input schema. Each MCP server defines its own tools with their own parameters.
To see a tool's parameters:
```bash theme={null}
pflow mcp describe mcp-github-create_issue
```
## Output
All MCP nodes write to:
| Key | Type | Description |
| -------- | ---- | ------------------------------- |
| `result` | any | Tool execution result |
| `error` | str | Error message (only on failure) |
For dict results, read nested fields through `result`:
If a tool returns `{"issue_url": "...", "issue_number": 123}`, use:
```markdown theme={null}
${github-issue.result.issue_url}
${github-issue.result.issue_number}
```
MCP nodes do not extract top-level result fields into `${node.field}` and do not
write a `{server}_{tool}_result` alias. Each workflow node already has its own
namespace, so `${node.result}` is the canonical success output.
## Example workflow
```markdown theme={null}
### create_issue
Create a GitHub issue from the error summary.
- type: mcp-github-create_issue
- repository: myorg/myrepo
- title: "Bug: ${error_summary}"
- body: ${error_details}
### notify
Send a Slack notification with the issue link.
- type: mcp-slack-send_message
- channel: "#alerts"
- text: "Created issue: ${create_issue.result.issue_url}"
```
## Setup
To use MCP tools, first add MCP servers to pflow. See [Adding MCP servers](/guides/adding-mcp-servers) for configuration format, authentication, and examples.
pflow supports both local (stdio) and remote (HTTP) MCP servers. Both work identically in workflows - same naming, parameters, and output structure.
## Error handling
MCP nodes return `error` action on:
* **Connection failure**: Server not reachable
* **Tool error**: Tool reported an error (via `isError` flag)
* **Timeout**: Request took too long (default 30 seconds)
The node does **not** retry automatically - MCP calls start a server subprocess, and retries would cause resource conflicts.
## Auto-sync
When you run a workflow, pflow automatically syncs MCP tools if the server configuration has changed. You don't need to manually run `pflow mcp sync` after adding servers.
Manual sync is useful for:
* Testing connection immediately after adding a server
* Debugging connection issues
* Forcing re-discovery without running a workflow
# Shell
Source: https://docs.pflow.run/reference/nodes/shell
Execute shell commands
**Agent commands.** Your AI agent uses this node in workflows. You don't configure it directly.
The shell node runs commands with pipes, redirects, and standard shell features. It blocks dangerous patterns (fork bombs, recursive deletes) before execution, but otherwise commands run with full system access.
## Parameters
| Parameter | Type | Required | Default | Description |
| --------------- | ---- | -------- | ----------- | ------------------------------------------------------------------------------ |
| `command` | str | Yes | - | Shell command, or path to an external script file (e.g., `./scripts/build.sh`) |
| `stdin` | any | No | - | Input data for the command |
| `cwd` | str | No | Current dir | Working directory |
| `env` | dict | No | `{}` | Additional environment variables |
| `timeout` | int | No | `30` | Maximum execution time in seconds |
| `ignore_errors` | bool | No | `false` | Continue workflow on non-zero exit |
## Output
| Key | Type | Description |
| ------------------ | ---- | ------------------------------------------------------- |
| `stdout` | str | Command output (UTF-8 or base64 if binary) |
| `stdout_is_binary` | bool | `true` if stdout is base64-encoded |
| `stderr` | str | Error output (UTF-8 or base64 if binary) |
| `stderr_is_binary` | bool | `true` if stderr is base64-encoded |
| `exit_code` | int | Exit code (0=success, -1=timeout, -2=execution failure) |
| `error` | str | Error message (only on timeout/failure) |
## Using stdin for data
**Always use `stdin` for data, not command interpolation.** Shell escaping breaks on special characters in JSON, and `stdin` handles any data type safely.
**Correct** - data via stdin:
````markdown theme={null}
### process
Process the API response with jq.
- type: shell
- stdin: ${api.response}
```shell command
jq -r '.data.name'
```
````
**Wrong** - data in command (will break on special characters):
````markdown theme={null}
### process
Process the API response with jq.
- type: shell
```shell command
echo '${api.response}' | jq
```
````
### stdin type handling
| Input type | Conversion |
| ---------- | --------------------------------- |
| str | Used as-is |
| dict/list | Serialized to JSON |
| int/float | Converted to string |
| bool | Lowercase string (`true`/`false`) |
| bytes | Decoded UTF-8 (fallback: latin-1) |
## Validation and error handling
**Your agent handles this.** pflow validates commands when the workflow is created, not at runtime. If structured data ends up in a command string, the error message tells your agent exactly what to move to `stdin` and why.
The shell node validates that dicts and lists aren't embedded directly in command strings (which would break shell parsing). Data should go through `stdin` instead:
````markdown theme={null}
### process
Process the API response with jq.
- type: shell
- stdin: ${api.response}
```shell command
jq -r '.data.name'
```
````
When a workflow is created, pflow checks if command templates contain dict or list variables. If found, you'll see an error like:
````
Shell node 'process': cannot use ${api.response} (type: object) in command parameter.
PROBLEM: Object data embedded in shell commands breaks shell parsing. Dict/list
data contains special characters (quotes, braces, spaces) that break command syntax.
FIX: Move data to stdin, keep command simple:
- stdin: ${api.response}
```shell command
jq '.field'
```
````
This validation happens at workflow creation time (compile-time), not during execution, so you get immediate feedback.
**Why this matters:** Shell parsers expect text, and JSON data contains special characters (`{`, `}`, `"`, spaces) that have meaning to the shell. Even with careful quoting, it's fragile. The `stdin` approach is safer and more reliable.
## Security
Commands run directly on your system without sandboxing. While dangerous patterns are blocked, the shell node has full access to your filesystem and network. Sandboxed execution is planned for a future release.
**You're in control.** Your agent asks before running workflows, and you can inspect the workflow to see exactly what commands will execute. If you need stronger restrictions, you can disable shell entirely with `pflow settings deny shell` - but this significantly limits pflow's capabilities since shell is often used for data processing and filtering.
### Blocked patterns
These commands are rejected immediately with an error:
* `rm -rf /` and variants (recursive system deletion)
* `dd if=/dev/zero of=/dev/sda` (device operations)
* `:(){:|:&};:` (fork bombs)
* `chmod -R 777 /` (dangerous permissions)
* `sudo rm -rf /` (privileged dangerous commands)
### Warning patterns
These trigger warnings but execute unless `PFLOW_SHELL_STRICT=true`:
* `sudo`, `su -`
* `shutdown`, `reboot`, `halt`
* `systemctl poweroff`
## Smart error handling
Some commands return non-zero exit codes for valid "not found" results. The shell node treats these as success:
| Pattern | Exit code | Reason |
| ----------------------- | --------- | -------------------------- |
| `ls *.txt` (no matches) | 1 | Empty glob is valid |
| `grep pattern file` | 1 | Pattern not found is valid |
| `which nonexistent` | 1 | Command check |
| `command -v foo` | 1 | Existence check |
## Examples
### Basic command
````markdown theme={null}
### list
List files in the temp directory.
- type: shell
```shell command
ls -la /tmp
```
````
### Process JSON with jq
````markdown theme={null}
### fetch
Fetch data from the API.
- type: http
- url: https://api.example.com/data
### extract
Extract item names from the response.
- type: shell
- stdin: ${fetch.response}
```shell command
jq -r '.items[].name'
```
````
### With environment variables
````markdown theme={null}
### deploy
Run the deployment script.
- type: shell
- env:
ENV: production
DEBUG: "false"
- timeout: 120
```shell command
deploy.sh
```
````
### Working directory
````markdown theme={null}
### build
Build the project.
- type: shell
- cwd: /path/to/project
```shell command
npm run build
```
````
### Ignoring errors
````markdown theme={null}
### cleanup
Remove temporary log files.
- type: shell
- ignore_errors: true
```shell command
rm -f temp/*.log
```
````
### External script file
For multi-line scripts, reference an external file. The file content is read and used as the command. Path is relative to the workflow file.
```markdown theme={null}
### deploy
Run the deployment script.
- type: shell
- command: ./scripts/deploy.sh
```
## Error handling
| Exit code | Meaning |
| --------- | ---------------------- |
| 0 | Success |
| 1+ | Command-specific error |
| -1 | Timeout |
| -2 | Execution failure |
The node returns `error` action on non-zero exit (unless `ignore_errors` is `true` or it's an auto-handled pattern like grep).
## Recommended tools
pflow's template variables handle most data access - you can use `${api.response.items[0].name}` to access nested fields and array elements directly without shell commands.
Shell is needed when you need to:
* **Iterate over arrays** - `jq '.items[].name'` (templates can't do wildcards)
* **Filter or transform** - `jq 'select(.active)'`, `sort`, `uniq`
* **Compute values** - `wc -l`, arithmetic
Common Unix tools are built into macOS: `grep`, `awk`, `cut`, `sort`, `head`, `tail`, `curl`.
For JSON processing, install [jq](https://jqlang.github.io/jq/):
```bash theme={null}
brew install jq
```
# Roadmap
Source: https://docs.pflow.run/roadmap
pflow's direction and priorities
## Current status
Where pflow is today (v0.15.1):
* **Standalone orchestration engine** with compile-once batch optimization (\~7x speedup for large parallel workflows)
* **Markdown workflows** — `.pflow.md` files that agents read and write naturally
* **Node system** — file, llm, http, shell, code (Python), agent (Claude or Codex), and MCP bridge
* **Conditional branching** — `on-error`, static routing, and data-driven routing via code nodes with branch convergence (`??` coalesce operator)
* **Nested workflows** — saved or file-based workflows as nodes inside other workflows, with automatic input/output mapping
* **Memoization cache** — unchanged nodes serve cached results across re-runs, with `--only`, `--no-cache`, and per-node `cache: false` controls
* **Execution reports** — `--report` generates navigable markdown directories with rendered prompts, responses, token breakdowns, and cost data per node
* **Dry-run execution plans** — `--dry-run` previews cost, duration, and cache boundaries before execution
* **Workflow visualization** — `pflow mermaid` generates Mermaid flowcharts of workflow topology, and `pflow ui` serves an interactive React Flow canvas with a live execution overlay, run controls, agent-directed focus, and voice narration
* **Human-in-the-loop approval gates** — pause a workflow before a step with `approval: required`, answer paused gates from the CLI or browser canvas, and handle agent-raised escalations with auditable decisions
* **Resume and durable gate pause** — `pflow resume` continues a failed, interrupted, or gate-paused run from where it stopped without re-running completed steps; a gate hit in a non-interactive run (CI, MCP, pipe, browser) pauses durably — exit 4 with a resume token — and resumes hours later with `--approve`/`--choose`, with `pflow resume list` showing pending pauses
* **Stateful loops** — the `loop:` modifier runs a node repeatedly with `while:`/`until:` conditions and a `carry:` block to thread state across iterations
* **Streamable traces** — execution traces stream to disk as JSONL for crash-tail resilience, with per-node retry/backoff via the `retry:` block
* **Batch processing** — process arrays through nodes, sequential or parallel, with per-item parameter overrides and error handling modes
* **External file references** — `prompt: ./prompts/system.md` keeps long content out of workflow files
* **Workflow bundling** — `pflow save` packages workflows with all file dependencies as self-contained folders
* **Unified diagnostics** — one error format across CLI text and JSON output, with structured suggestions and source file provenance
* **Recursive sub-workflow validation** — structural errors caught before any node executes
* **Template variables** — `${var}` syntax with nested path access, automatic JSON parsing, and structured output schemas
* **AI agent integration** via CLI and MCP server
* **Discovery** — find nodes and workflows by describing what you need
* **Unix-first piping** — stdin/stdout, works with any Unix tool
* **Skills publishing** — save workflows as Claude Code skills, cross-platform
* **Settings management** — API keys, node filtering
* **Unified model support** — 100+ providers (OpenAI, Anthropic, Google, OpenRouter, Ollama, ...) via LiteLLM
* **Published on PyPI** — `uv tool install pflow-cli`
* **Cross-platform execution** — macOS, Linux, and Windows support
## Now
**Iteration speed and workflow quality**
* **Function-based code node syntax** — write Python functions instead of top-level scripts, with automatic input/output wiring from type annotations
* **Workflow export** — export a workflow to standalone Python with zero pflow dependency. Build and iterate with structure, ship plain code.
* **Workflow testing** — mock nodes, assert outputs, `pflow test`. Modify a saved workflow and know it still works before re-publishing.
* **Code and shell linting** — catch syntax errors in code blocks during validation, not at runtime
* **Batch limits** — cap iteration count for development and cost control
## Later
**Security and sandboxing**
* **Sandboxed execution runtime** — isolated execution for shell and code nodes. Needed before running agent-generated workflows you haven't reviewed.
* **Export as MCP server packages** — distribute workflows as standalone MCP servers that work without pflow installed
**MCP ecosystem**
* **MCP gateway integration** — route to remote MCP servers
* **Dynamic MCP discovery** — search and install MCP servers on demand instead of manual configuration
* **OAuth for remote MCP servers** — authenticate with HTTP-based MCP servers
## Vision
pflow is infrastructure, not a destination. It provides building blocks and a runtime — agents do the assembly. The better the building blocks get, the more capable the agents become.
The longer-term direction:
* **Code node dependency management** — install packages on demand for code nodes
* **TypeScript code node** — for teams that think in TypeScript
* **Reduce/fold for batch** — aggregate batch results incrementally instead of collecting all at once
pflow improves through a direct loop: agents build workflows, we find where they struggle, we fix it. Because the surface area is finite — node types, template syntax, error messages — each fix is targeted and compounds. A year from now pflow will be meaningfully better at helping agents build workflows, not because of some grand vision but because each friction point gets filed down one at a time.
## Get involved
Ideas and feature requests
Bug reports
Guides and reference
***
Built by a developer who got tired of watching agents re-think the same tasks.
Questions or ideas? Reach out — [andreas@pflow.run](mailto:andreas@pflow.run)