MCP
Workflows
Source: orchestration/mcp-gateway/tools.ts (ExecuteWorkflowSchema, executeWorkflow, ListWorkflowsSchema, listWorkflows, WorkflowSchema, plungeaiWorkflow, BuildWorkflowSchema), orchestration/mcp-gateway/workflow-store.ts, orchestration/mcp-gateway/chat.ts (buildWorkflow), orchestration/mcp-gateway/extras.ts (plungeaiTemplates), orchestration/mcp-gateway/execution.ts (runExecution, executeViaStream), orchestration/mcp-gateway/execution-do.ts, orchestration/mcp-gateway/leg-state.ts, orchestration/mcp-gateway/tool-outcome.ts, orchestration/mcp-gateway/server.ts, orchestration/cnl-engine/cnl-engine.ts (loadWorkflow, request input / inputs), agents/agents/one-agent/call-config.ts, agents/agents/one-agent/call-mode.ts, core/core-base/provider-ids.ts, apps/ocean-skills/skills/plungeai-workflows/references/cnl-spec.md.
Your workflows are saved CNL workflows. CNL is the platform's YAML workflow language. Users call their workflows "my agents", and most day-to-day work is here: find a workflow, run it, inspect it. There is no separate "MCP workspace". A workflow you create or run here is the same workflow you see in Studio and the apps, and every change syncs live.
| Job | Tool |
|---|---|
| Find your workflows and folders | plungeai_list_workflows (8.1) |
| Run a saved workflow, or ad-hoc YAML | plungeai_execute_workflow (8.2) |
| Create, read, edit, delete, version | plungeai_workflow (8.6) |
| Generate or refine one from plain language | plungeai_build_workflow (8.8) |
| Copy a ready-made template into your account | plungeai_templates use (8.9) |
Base URL https://mcp.plungeai.com/v1. https://api.plungeai.com/v1/mcp is the same server; the REST twin is https://api.plungeai.com/docs#10-workflows-plane (opens in a new tab). Always pass user_request: for "show me all my agents in the finance folder", the arguments alone are just {folder: "Finance"} and lose the intent. Parameter types and limits are in the Tool reference.
8.1 Find your workflows — plungeai_list_workflows
| You send | What comes back |
|---|---|
| nothing | # Your Workflows (N most recent), then **Folders:** Finance (15) · Personal (23) · … (only folders that hold something, in your sidebar order), then a table of your 50 most recently updated workflows |
folder | Workflows whose folder name contains this text (case-insensitive) |
search | Workflows whose name contains this text (case-insensitive). Descriptions are not searched |
kind | Only that kind: workflow, agent, bot or campaign (exact) |
folder + search + kind | All given must match |
limit | 1 to 100 (default 50) |
format: "json" | {"workflows":[{"id","name","description","kind","folder"}]} |
The table columns are Name, Kind (workflow, agent or bot), Folder, ID, Description, newest first. The ID is what plungeai_execute_workflow and plungeai_workflow take. With no match you get "No workflows matched. List without filters to see your folders." An empty account answers "No workflows found. Create one with plungeai_workflow or plungeai_build_workflow." The tool is read-only.
Try asking
- "Show me all the workflows in my Finance folder."
- "What folders do I have and how many workflows in each?" For example: Finance 15 · Personal 23 · Technology 11 · Business Strategy 14 · Blockchain 8.
- "Find my workflow about due diligence." This searches your workflow names, so a workflow called "DD Memo" is not found by "due diligence". List the folder instead if the name is different.
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_list_workflows","arguments":{"user_request":"What folders do I have and how many workflows in each?"}}}'"Category" and "folder" are different things. Your folders (Finance, Personal, …) hold your workflows. Categories are the registry's agent taxonomy (Discovery), where the finance one is called
financials.
8.2 Run a workflow — plungeai_execute_workflow
| Argument | Meaning |
|---|---|
workflow_id | A saved workflow you own (the ID from 8.1) |
workflow_yaml | Ad-hoc CNL, up to 256 KiB (8.3) |
input | The primary input, available as {input} |
inputs | Named inputs {key: value}, available as {key} |
mode | sync (default) or async (8.4) |
format | markdown (default) or json |
Send workflow_id or workflow_yaml. With neither you get needs_input ("Provide either workflow_yaml (ad-hoc CNL) or workflow_id (a saved workflow)."). If you send both, workflow_id is used and the YAML is ignored.
Saved workflows. The id must be a workflow on your account. An unknown id, or one owned by someone else, returns needs_input ("No saved workflow with id <id> on this account.") with a pointer to plungeai_list_workflows, and no run. The run is named after the saved workflow. Its title in Studio's sidebar is your input (or the inputs values, or your user_request), the same as a run started in Studio.
Try asking
- "Run the Acquisition Scanner with Adobe as the input." This runs a saved workflow. Most finish in seconds, because the engine fans out over agents in parallel.
- "Show me my workflows in the Finance folder, then run the Acquisition Scanner with Tesla as the input." One request, two steps. Your client lists the folder to find the workflow you meant, then runs it with your input.
- "Run my Company Snapshot Research Brief with Apple as the input." The same pattern: swap in whichever workflow and company you mean.
This runs your workflow's agents (it costs credits and writes an execution; not run by the docs verifier):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_execute_workflow","arguments":{"user_request":"Run the Acquisition Scanner with Adobe as the input.","workflow_id":"<workflow id from plungeai_list_workflows>","input":"Adobe"}}}'A saved workflow's agents are not re-checked by the gateway at run time. Only ad-hoc YAML goes through the active-only fence (8.3). If a saved workflow names an agent that is no longer deployed, the run fails in the engine and comes back unavailable ("registered but not deployed").
8.3 Ad-hoc CNL YAML
workflow_yaml runs CNL without saving it. The workflow: wrapper is optional: bare top-level name + tasks is accepted and wrapped for you (Form B in the CNL spec). This example needs jq to build the JSON. It runs three agents (it costs credits; not run by the docs verifier):
YAML=$(cat <<'CNL'
name: Quick brief
tasks:
- type: parallel
id: research
subtasks:
- id: web
agent: brave-agent
prompt: "{input} — latest news"
- id: deep
agent: tavily-agent
prompt: "{input} — cited sources from the last 12 months"
- id: brief
agent: one-agent
prompt: |
Write a five-bullet brief on {input}.
Brave: {data:web}
Tavily: {data:deep}
CNL
)
jq -n --arg yaml "$YAML" '{jsonrpc:"2.0",id:1,method:"tools/call",params:{name:"plungeai_execute_workflow",arguments:{user_request:"Quick brief on Cloudflare Workers",workflow_yaml:$yaml,input:"Cloudflare Workers"}}}' |
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d @-What the gateway does before the engine sees it (executeWorkflow):
- Parses the YAML. If it cannot be parsed, you get
needs_input("Your workflow_yaml could not be parsed. <parser message>"), and the message points only at your text. Valid YAML that is a bare scalar (a string, a number) or empty returnsneeds_input("workflow_yaml must be a CNL mapping with aworkflow:(or top-levelname/tasks), not a bare value."). Nothing runs in either case. A top-level list is not caught here: it is wrapped and dispatched without the agent fence below, fails in the engine, and leaves a failed execution in your history. Always send a mapping withworkflow:or top-levelname/tasks. - Fences the agents. The
agent:ids of your tasks are checked against the registry, including insidesubtasks, batchtasksandexecutors, plus a harness task'smission_ref(collectAgentIds). Unknown or not-status:activeids returnunavailable, naming each offender ("No such agent: …", "Not permitted: … notstatus:active"). Nothing runs. Agents named elsewhere (adynamicgenerator, debaters, judges, validators) are resolved by the engine when the run starts. - Wraps and runs. Your
inputandinputstravel with the workflow and fill{input}and{key}. The run is named<YAML name> · <first three agents>(+Nfor more). After the run, the name is replaced by the agents that actually ran. Every run also carriesuser_context(your time zone and approximate place, taken from the request's network location), which the agent uses for local time and as the default time zone for a schedule.
The gateway does not run the CNL schema validator on ad-hoc YAML, and the engine records schema findings without blocking the run (its validation mode is record-only; cnl-engine.ts). A malformed document is dispatched and fails however its tasks fail, and that leaves a failed execution in your history. To check a document first, save it with plungeai_workflow create: that call runs the validator (yamlToCNL) and rejects errors such as Workflow name is required and must be a string or Type 'harness' requires a 'goal' string before anything is stored. Or let plungeai_build_workflow write the YAML.
Writing YAML by hand
Quote your colons.
prompt: DD: memobreaks YAML. Writeprompt: "DD: memo", or put long text in a block scalar (prompt: |). Never hard-wrap a value across lines. Aninvalid yamlfailure comes back with exactly this tip.Structured tool-agents take their card's fields as task fields, next to
agent::- id: wx agent: weather-agent operation: forecast city: "Sun Valley,ID,US" units: imperialWatch for card fields that share a name with a CNL task field.
locationis one: on a task it meanscloudorlocal(where the task runs). A weather task withlocation: "Sun Valley,ID,US"runs as ad-hoc YAML, because the field passes through to the agent, but the validator used byplungeai_workflowandplungeai_build_workflowrejects it (Invalid location: Sun Valley,ID,US. Must be 'cloud' or 'local'). In a saved workflow, use the weather card's aliascity(as above) orlat/lon.On a
one-agenttask, preferprovider: geminior omitprovider. Omitting it gives the default (gemini, modelgemini-3.1-flash-lite,agents/agents/one-agent/call-config.ts).googleis accepted and mapped togeminiby the provider resolver (core/core-base/provider-ids.ts), but one-agent then does not apply its default model (it does so only for an omitted or literalgeminiprovider), so passmodelexplicitly if you writegoogle. Any other unknown provider is refused (Unsupported provider: <x>, Troubleshooting).Take agent ids from a live search, never from memory: it is
brave-agent, notbrave-search(Discovery).Outward operations are fenced here too. A task whose
operationoractionissend,pay,buy,transfer, … is refused by the engine asneeds_approval(6.6). Nothing runs, andplungeai_continuecannot resume it: such a task runs only from Ocean Studio.There is no
schedule:field. Schedule a workflow withplungeai_schedule(Scheduling).
Easier still: describe the goal and let "build me a workflow that…" write the YAML for you (8.8).
8.4 Sync and async
Sync (the default) runs the workflow and streams live status while it works. The lines come from the engine's own task events, the same ones Studio's live view renders (executeViaStream):
⚙️ Agents working — 5/18 agents complete · ✓ brave-agent · 13 active (tavily-agent, exa-agent, serper-agent…)
✍️ Generating answer — 16/18 agents complete · 2 active (one-agent)
✍️ Writing (3516 chars) — …the answer as it is produced
⚙️ one-agent still working — 45s elapsed…
✅ Complete — 18 agents in 24.6sA keepalive (⏳ Working — Ns elapsed) is sent every 15 seconds so the stream never goes idle. The feed goes out on notifications/progress when your client sent a progress token, otherwise on notifications/message. Whether it is displayed is up to your client; the work and the result are the same either way (4.6). A quiet client is not a stalled run: the run is visible in Studio's sidebar the whole time.
Async (mode: "async") returns at once with the execution id. The tool description suggests it for runs longer than about 3 minutes, and it is the way around Claude Desktop's roughly 4-minute cap on a single call:
Started execution `<execution id>` (async). Poll plungeai_get_workflow_status with this id, then fetch output with plungeai_get_result once completed. If the status reports ⏸ AWAITING USER APPROVAL or AWAITING USER, relay it to the user and continue with plungeai_continue.
---
execution: `<execution id>` — use this id for plungeai_get_result / plungeai_followup on this run.An async run has no live feed. It is held by its own Durable Object in resumable ten-minute legs, so it survives your client disconnecting (7.9). Poll plungeai_get_workflow_status, then fetch plungeai_get_result (Runs, results & follow-ups). A running execution that stops heartbeating for 16 minutes is marked failed, with Interrupted (run did not complete). Rerun it, in the foreground if it is short.
8.5 What a run returns
| Outcome | When |
|---|---|
ok | The final task's output, then the execution footer (summary: "Workflow completed."). If the final task stored nothing, the stored outputs of the individual tasks are joined under ## <task id> headings instead. An async start is also ok |
needs_approval / needs_input | A task paused for approval or a question (⏸ AWAITING USER APPROVAL: … / ⏸ AWAITING USER: …). Continue with plungeai_continue after the user decides |
needs_approval (no actions) | A task's operation/action is on the engine floor list. Nothing ran, and it cannot be continued over MCP (6.6) |
needs_input | No workflow given, unknown saved id, unparseable YAML, or an agent rejected its inputs |
unavailable | Ad-hoc YAML names an unknown or inactive agent, or an agent is not deployed |
needs_connection / needs_api_key | A task hit a missing connected account or key at run time. Workflows have no pre-flight, so the answer names "the required service" (6.7) |
error | A genuine failure with the real reason. A timeout suggests mode: "async" |
Every outcome follows the vocabulary in 6.4.
Two failure shapes are specific to runs. If the engine reports success but nothing was stored, the run is failed: "Engine reported success but no result was stored (final_task: …). The agent likely failed in-band without storing output — check its own logs." If the final answer starts with a refusal marker (# ❌, Error: ), the run is failed with that text, never shown as a success.
A run whose final harness task ran out of turns is stored as incomplete and reported that way by plungeai_get_workflow_status, but only for async runs. A sync run's answer is ok, and the sync path stamps the outcome ok afterwards, replacing the incomplete mark (7.7).
Every run is visible everywhere. It appears in Studio's results sidebar and the ops dashboard. The title is your question, and the subtitle is the workflow plus the agents that actually ran, the same as a run started in Studio. The result is final Markdown (tables, headings, links). Ask for it "in full" if your client tends to summarize. plungeai_get_result adds a "Steps in this run" index for multi-task runs, so any step's full output is one ask away (Runs, results & follow-ups).
8.6 Manage saved workflows — plungeai_workflow
Direct edits to your saved workflows. The store is byte-compatible with Studio's (D1 yaml_workflows + KV workflow:{user}:{id}), and every write is ownership-checked. Create, update and delete are broadcast and sync live to Studio, iMini and the other apps. save_version and restore_version are not broadcast, so reload an open Studio editor after a restore (saving from a stale editor would overwrite the restored YAML).
action | Needs | Optional | Does | Answers |
|---|---|---|---|---|
create | name, yaml | description, folder, kind | Validates the CNL, saves, files into the folder | "Created workflow <name> (<id>). Filed under <folder>. Synced to all apps." plus any validation warnings |
get | workflow_id | — | Reads the saved definition | # <name>, the description, and the YAML in a code block |
update | workflow_id | name, description, yaml, folder, kind | Changes only what you send. New YAML is validated first. Moving kind away from bot or campaign runs the same clean-up as delete for the old kind (below) | "Updated workflow <id>. Synced to all apps." plus " Moved to folder <folder>." / " Kind set to <kind>." |
delete | workflow_id | — | Deletes the workflow (the D1 row and its live content). A bot also loses its memory namespace <you>:bot:<id> and its scheduler jobs; a campaign loses its scheduler jobs and its ledger is tombstoned | "Deleted workflow <id>. Synced to all apps." |
save_version | workflow_id | version_description | Snapshots the current YAML as the next version (8.7) | "Saved version V<n> (<version id>)." |
list_versions | workflow_id | — | Lists the snapshots, newest first | A Version / ID / When / Note table, or "No versions yet. Use action "save_version"." |
get_version | workflow_id, version_id | — | Reads one snapshot | # V<n> and its YAML |
restore_version | workflow_id, version_id | — | Makes that snapshot live again | "Restored — new version V<n>." |
permissions_get | workflow_id (a saved bot) | — | Reads its permission classes, locks and spend caps, and effective: the preset each of the four classes (send, write, pay, delete) runs under, the stored one or else allow | classes, effective, locks, budget_usd_run, budget_usd_month, and the same as JSON |
permissions_set | workflow_id | classes, locks, budget_usd_run, budget_usd_month | Each field present replaces the stored one; locks only for a team admin over the workflow (otherwise Error: … (lock_admin_required) and nothing is written) | "Updated." and the new policy |
files | workflow_id | — | Lists the bot's persistent workspace | a Path / Size / Type / Updated table |
file | workflow_id, path | — | Reads one workspace file, at most 5 MB | the bytes as a base64 block (larger: use the One API download route) |
inbox | workflow_id | — | Reads the bot-to-bot messages queued for its next run | a From / When / Message table |
bindings | workflow_id | — | Lists the paired chats bound to the bot | a Channel / Sender id table, or a note that bindings are not enabled yet |
bind, unbind | workflow_id, channel, sender_id | — | Binds a chat you already paired to the bot, or releases it | a confirmation, or Error: … (not_paired) |
groups | — | — | Lists your bot groups | a Name / Members / ID table |
group_set | group, members (2–6 bot ids) | — | Creates the group, or replaces the members of the one with that name | "Created" / "Updated group …" |
group_delete, group_thread | group | — | Deletes the group, or reads its shared thread | a confirmation, or the thread table |
Validation. On create, and on update with yaml, the YAML goes through the engine's CNL validator (yamlToCNL) before anything is stored. An error rejects the call: "Workflow YAML failed CNL validation: <field>: <message>, …" (isError), and nothing is saved. Warnings (near-miss keys such as input on a task where prompt was meant) are appended as "⚠️ Validation warnings:" and do not block. The same check runs the save-time kind lint: memory_owner in the YAML is an error for every kind (memory tenancy comes from the executing user), and an agent or bot must be exactly one type: harness task whose allowed_tools (if set) include task_complete. The kind linted is the kind you pass, else the stored one on update. Hardcoded emails or UUIDs only warn. The validator checks the schema, not the agents. A saved workflow can name an inactive agent and fail only when it runs, so take ids from Discovery.
Folders. folder is matched against your folder names: an exact case-insensitive match, otherwise a unique partial match. A name that matches nothing creates that folder (it appears in Studio at once) and files the workflow into it, on create, update and templates use alike. A name that partially matches two or more folders is an error naming them ("…is ambiguous — it matched 2 folders: Sales East, Sales West. Use the full name."), and nothing is changed. folder: "none" (or "") on update moves the workflow out of its folder.
Kind. kind picks the sidebar section: workflow (Flows, the default), agent (Agents), bot (Bot agents) or campaign (a list worked through on a cycle). A campaign create takes campaign_config ({list, cycle, max_attempts, schedule?, deliver?, …}): the config is checked before anything is saved, then the ledger and the scheduler job are set up and the campaign id is written into the YAML. Without campaign_config the campaign is saved with a warning and no ledger.
Bot control. The bot actions work on a saved bot you own and use the same stores as Studio's bot settings and the One API's /v1/workflows/{id}/permissions, …/workspace-files, …/inbox, …/bindings and /v1/bot-groups; a refusal reads Error: <message> (<code>) with the One API's code. Bindings need the message gateway and groups need their table: until then they answer a plain "not enabled yet".
Errors. A missing required field answers "Error: <field> is required for action "<action>"" (isError). An id that does not exist or is not yours answers "Workflow not found." or "Version not found." (isError), except get, which returns a needs_input outcome ("Workflow <id> not found.") pointing at plungeai_list_workflows. The bot actions (permissions_get, files, inbox, bindings, …) answer Error: no saved workflow with that id for this account (workflow_not_found) instead.
Two things to know. update does not save a version first (the same as Studio), so run save_version before an edit you may want to undo. delete is a hard delete of the workflow. Your past executions of it stay in your history. The clean-up that follows is best effort and never fails the call: for a bot it wipes only the memory namespace <you>:bot:<id> (older bots; a bot saved without memory_owner uses the memory of whoever runs it, which a delete never touches) and deletes the bot's scheduler jobs; for a campaign it deletes the jobs and tombstones the ledger. Changing a saved row's kind away from bot or campaign with update runs the same clean-up for the old kind.
Try asking
- "Show me my Acquisition Scanner workflow — what does it actually do?" The client finds its id with
plungeai_list_workflowsand fetches the definition withget, so you can see exactly what it runs (which agents, in what order) before you trust it with real input. - "Rename my Acquisition Scanner, move it to the Finance folder, and save a version first." The client runs
save_version, thenupdatewithnameandfolder. - "Create a workflow from this YAML and put it in Finance."
createfiles it straight into the folder; no separate move is needed. - "What is my Outreach bot allowed to do without asking?" The client runs
permissions_get; "Make it ask before sending anything" ispermissions_setwithclasses: {"send": "ask"}. - "Group my Outreach and Research bots as Sales."
group_setwithgroup: "Sales"and the two bot ids;group_threadreads what they said to each other.
Reading a definition (read-only; it needs a real id, so it has no verifier marker):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"Show me my Acquisition Scanner workflow","action":"get","workflow_id":"<workflow id>"}}}'Creating one into a folder changes your account (not run by the docs verifier):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"Save this as News Scan in my Finance folder","action":"create","name":"News Scan","folder":"Finance","description":"Latest news on a company","yaml":"name: News Scan\ntasks:\n - id: news\n agent: brave-agent\n prompt: \"{input} latest news\"\n"}}}'Reading a bot's policy is read-only (it needs a real id, so it has no verifier marker); permissions_set takes the same arguments plus the fields to change and writes them, and bindings, files, inbox and groups read the bot's paired chats, workspace, queued messages and groups the same way:
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"What is my Outreach bot allowed to do","action":"permissions_get","workflow_id":"<bot id>"}}}'Creating a bot group changes your account (not run by the docs verifier):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_workflow","arguments":{"user_request":"Group my Outreach and Research bots as Sales","action":"group_set","group":"Sales","members":["<bot id>","<bot id>"]}}}'The same workflow is also readable as the resource plungeai://workflows/{id} (Prompts & resources).
A2A doors
Every bot, agentic agent and workflow can get an Agent2Agent (A2A 1.0) door: a URL under https://a2a.plungeai.com/<handle> with its own bearer token, through which any A2A client — another vendor's agent, the official a2a-sdk clients, or one of your own bots — sends it work. plungeai_workflow action: "a2a_door" manages it: door_action: "mint" creates the door and shows the token once (minting again rotates it), get shows the door, pause / resume close and reopen it, revoke deletes it. The door's agent card is at <door url>/.well-known/agent-card.json; a SendMessage runs the workflow as you and returns the result as a text artifact (bots and agentic agents continue the conversation when the caller reuses the contextId; a session that stops with a question answers TASK_STATE_INPUT_REQUIRED). Long work: send with returnImmediately or stream, then poll GetTask. A bot reaches another A2A agent with message_bot and a peers entry a2a:<url>; its bearer for that agent is the Connections credential a2a:<host> (or a2a:<host>/<path> for one door on a host).
8.7 Versions
Versions are explicit snapshots (workflow-store.ts):
save_versioncopies the current live YAML into a new version numbered one above the highest so far (V1, V2, …), with an optionalversion_descriptionnote and a checksum. It becomes the workflow's current version.list_versionsshows every snapshot: Version, ID (theversion_id), When (ISO time), Note.get_versionreturns one snapshot's YAML without changing anything.restore_versionwrites that snapshot's YAML back as the live definition, then saves it as a new version with the note "Restored from V<n>". History is never rewritten, and you can always roll forward again.
update and plungeai_build_workflow's refine both overwrite the live YAML without a snapshot. Save a version first so you can roll back.
8.8 Generate a workflow — plungeai_build_workflow
plungeai_build_workflow turns a plain-language goal into a real multi-agent workflow and saves it. It uses the platform's workflow builder, which picks actual registry agents, validates the YAML and repairs it itself.
| You send | Does |
|---|---|
goal | Generates a new workflow and saves it. It is named from the generated YAML's own name: line (up to 120 characters), or from the first 60 characters of your goal when the YAML has no plain name: value line (a name containing a quote or apostrophe does not count as one), with the description "Generated via MCP builder", no folder, kind workflow |
workflow_id + instruction | Refines an existing workflow: sends its current YAML plus your change to the builder, which regenerates the whole workflow, then overwrites it in place. The name stays; no version is saved |
| anything else | "Provide either goal (new) or workflow_id + instruction (refine)." (isError) |
If you send goal together with workflow_id but no instruction, a new workflow is generated and the id is ignored.
It takes 30 to 60 seconds, and it narrates while it works:
📖 Loading the existing workflow… (refine only)
🧠 Generating CNL YAML — the builder picks real agents, validates and self-repairs (30-60s)…
🔍 Validating the generated workflow (2,431 chars)…
💾 Saving "Acquisition News Briefing" and syncing to all apps…
✅ CompleteThe answer names the workflow and its id, says "Synced to all apps.", and, for a new one, "Run it with plungeai_execute_workflow.", followed by the full YAML in a code block. It fails with isError and saves nothing when:
- the workflow id is unknown ("Workflow not found: <id>");
- the builder returns no workflow ("Workflow builder did not return a workflow section.");
- the generated YAML fails CNL validation ("Generated workflow failed CNL validation: …", with the YAML attached so you can fix it by hand).
The tool has no format argument.
Try asking: "Build me a workflow that scans a company for acquisition news and writes a briefing." To file it, follow up with "…and move it to Finance" (plungeai_workflow update with folder). To design a workflow in a conversation first, "Let's design a due-diligence workflow together" goes to plungeai_chat (Chat, memory & skills).
This runs the builder model and saves a workflow (it costs credits; not run by the docs verifier):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_build_workflow","arguments":{"user_request":"Build me a workflow that scans a company for acquisition news and writes a briefing.","goal":"Scan a company (given as input) for recent acquisition news from several web sources in parallel, then write a one-page briefing with sources."}}}'To refine: {"workflow_id":"<id>","instruction":"Add a step that checks SEC filings for the same company."}. Run save_version first.
8.9 Start from a template — plungeai_templates use
use copies a template (5.5) into your account as a new workflow you can edit:
- The name is your
name, otherwise "<template name> (copy)". Abottemplate keeps its own name. - The template's kind (
agentorbot) and its description carry over. folderfiles it with the same matching asplungeai_workflowcreate: no match means the workflow is created without a folder, with a ⚠️ note listing your folders.- The template's usage count goes up by one, the same as in Studio.
- The YAML is copied as it is. It is not re-validated.
The answer: "Created workflow <name> (<id>) from template <template>. Filed under <folder>. Run it with plungeai_execute_workflow."
Try asking: "What workflow templates are available? Start one from the due-diligence template."
This creates a workflow in your account (not run by the docs verifier):
curl -s https://mcp.plungeai.com/v1 \
-H "Authorization: Bearer $PLUNGE_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"plungeai_templates","arguments":{"user_request":"Start one from the due-diligence template","action":"use","template_id":"<template id from list>","name":"Due Diligence — Acme","folder":"Finance"}}}'8.10 CNL essentials for ad-hoc YAML
The full language reference is the plungeai-workflows skill (https://skills.plungeai.com (opens in a new tab)). The same guide is served to your client as the plungeai://docs/workflow-authoring resource (Prompts & resources). It is also summarised at https://api.plungeai.com/docs#101-cnl-in-one-page (opens in a new tab). The short version (cnl-spec.md, engine v6):
- Shape.
workflow: {name, tasks, inputs?}(Form A) or barename+tasksat the top level (Form B). Both are accepted. The spec's Form B top-levelinput:is not read over MCP: the gateway nests the whole Form B document underworkflow:, where nothing readsinput. Pass the value as the tool'sinputargument instead, or declareinputs:inside a Form Aworkflow:block. - Ten task types.
task(one agent:agent+promptorquery),parallelandsequential(subtasks),dynamic(generator+executors),batch(tasksoveritems/items_from),output,debate,validate,agent_loop(legacy),harness(a mission:goal+missionormission_ref). - Interpolation.
{input},{key}frominputs,{data:task_id}for a prior task's result,{item}/{item.field}inside a batch. - Hard rules. Agent ids exactly as in the registry. No
$variable,depends_on,outputs:orparallel: true. Container blocks never carryagent:.type: taskreadsprompt:, notinputorinstructions. Uniqueids.
A mission as YAML: a type: harness task with the full mission fields, including experts, plugins, MCP servers, effort, spend cap and permissions, which plungeai_run_mission does not expose (7.7):
name: Vendor scan
tasks:
- type: harness
id: market_scan
goal: "Map the top 5 vendors for {input}, with pricing and one differentiator each."
mission:
mission: "You are a market analyst. Research thoroughly, cite sources, stay on topic."
effort: standard
max_turns: 12
allowed_tools: [web_search, web_fetch, task_complete]
success_criteria:
- "5 vendors named with pricing"
- "every claim has a source URL"Run it with plungeai_execute_workflow, passing the YAML as workflow_yaml and the value for {input} as the tool's input argument (for example "input":"workflow orchestration platforms").