docs(workflows): MCP triggering — tools, flow and trigger settings (PRD-742) - #21
docs(workflows): MCP triggering — tools, flow and trigger settings (PRD-742)#21christophebrun-forest wants to merge 15 commits into
Conversation
…RD-742) Document the MCP channel added by PRD-49, written from the implementation in the three feature branches rather than from the spec. MCP server page: the three workflow tools (listWorkflows, triggerWorkflow, getWorkflowRun) with their arg/return contracts, the discover -> trigger -> poll flow, and the v1 limitations — no dedicated rate limiter, human-gated runs are report-only. Triggers page: goes from two triggers to three, states that gating is channel-aware, and documents two prerequisites that were missing for the webhook trigger too (Forest Runtime installed, fallback inbox selected). The section is named Triggers, not Process. Screenshot refreshed with the MCP row. Deviations from the spec, followed as implemented: listWorkflows returns no description; getWorkflowRun only exposes MCP-triggered runs; result is the terminal step, not a business payload. Also fixes an unescaped pipe that broke a table in the webhook reference. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
PMerlet
left a comment
There was a problem hiding this comment.
Adversarial review with cross-checking against the two implementation branches (forestadmin-server 6b9c421, agent-nodejs a4d3949). A lot verified exact: tool names and gating, listWorkflows shape (no description), the runState table is exhaustive vs the server enum, the error-table semantics (404 indistinguishable on purpose, 409 leaves the in-flight run untouched), the audit label, the three revocation levers, all links/anchors/images, and the pipe fix. One blocker — the getWorkflowRun section documents a contract the system does not produce — plus four quick text fixes. Details inline.
|
|
||
| ### `getWorkflowRun` | ||
|
|
||
| Reads the normalized status of a run, given the `runId` returned by `triggerWorkflow`. |
There was a problem hiding this comment.
Blocker — this section documents a contract the system does not produce.
On forestadmin-server#8418 (head 6b9c421) the normalized status mapper was removed after review: GET /mcp-workflows/runs/:runId deliberately returns the full hydrated run (service jsdoc: "the whole run is exposed to give the MCP client maximum context"), and git grep waitingForHumanInput finds nothing server-side. agent-nodejs#1792 only casts the response type — no runtime mapping — so the assistant will actually receive { runState, triggerType, workflowId, collectionId, workflowHistory: [...] }.
The field table, both JSON examples, and every waitingForHumanInput mention (here, l.392, and triggers.mdx) describe fields that will never appear. Hold this section until the contract is settled on the code side (mapper re-landed vs hydrated run embraced), then document the real shape.
Also, even against the normalized types: currentStep is { name, type } — no taskType — and result/error are optional/absent, not null as in the examples.
There was a problem hiding this comment.
Fixed in 17b6774 — the page now documents the hydrated-run contract as shipped: the field table matches HydratedWorkflowRun (the runState enum is exhaustive vs the server model), waitingForHumanInput is gone everywhere, and human-gating is described via workflowHistory (a step with done: false and executionType: manual). Cross-checked against both implementation heads.
| | `recordId` | The record to run on. Composite primary keys use the packed form, values joined by `\|` (e.g. `"123\|456"`). | | ||
|
|
||
| ```json Returns | ||
| { "runId": 1234, "runState": "loading" } |
There was a problem hiding this comment.
runId is a string in what the tool returns: the orchestrator responds with a number, but forestadmin-client normalizes it (runId: String(result.runId)) and the getWorkflowRun tool requires runId: z.string(). The example should read { "runId": "1234", "runState": "loading" }.
There was a problem hiding this comment.
Fixed in 17b6774 — the trigger example now returns { "runId": "1234", "runState": "loading" }, and the getWorkflowRun section explains the asymmetry (id is the numeric form of the runId string).
| The record is **not** checked when the run is created — the orchestrator has no data access at that point. An id that does not exist, or that the user cannot read, produces a run that fails at its first data step; the assistant sees it through `getWorkflowRun`'s `error`, not as a trigger-time failure. | ||
| </Note> | ||
|
|
||
| Only **one run per record** can be active at a time. Triggering a record that already has an ongoing run fails and does **not** resume it — the run in flight is left untouched. |
There was a problem hiding this comment.
The real constraint is one active run per (workflow, record), not per record: the active-run guard and the partial unique index are on (workflowId, collectionId, selectedRecordId). Two different workflows can run on the same record simultaneously. Same fix needed in the error table (l.401).
There was a problem hiding this comment.
Fixed in 17b6774 — now "Only one run of a given workflow can be active on a given record… A different workflow can still start on that record", and the error table says "A run of the same workflow".
|
|
||
| ### `listWorkflows` | ||
|
|
||
| Lists workflows with the MCP trigger enabled, scoped to the connected user's rendering and permissions. |
There was a problem hiding this comment.
"and permissions" overstates it: listMcpEnabledWorkflows filters by renderingId + presence of the mcp trigger only — no role/user-level filtering (the only upstream check is project membership). "scoped to the connected user's rendering" is accurate on its own; triggers.mdx's "can reach in the rendering" is the right phrasing. Same nuance at l.323 ("MCP-enabled for that user" → "in that rendering") and in the error table l.402.
There was a problem hiding this comment.
Fixed in 17b6774 — "and permissions" dropped; the page now says "scoped to the connected user's rendering" and the error table "outside the user's rendering".
| | The workflow's MCP trigger is off, is unknown, or the user cannot reach it | Not found — with a hint to call `listWorkflows` | | ||
| | A run is already ongoing on that record | Conflict; no run is started and none is resumed | | ||
| | The `runId` is unknown, belongs to another user's rendering, or was not started through MCP | Not found | | ||
| | The project has no [Forest Runtime](/product/process/workflows/forest-runtime) installed | Conflict — automated triggering needs server-side execution | |
There was a problem hiding this comment.
The 409 is keyed on the workflow engine, not on Runtime presence — and per environment, not per project: ensureWorkflowEngineOrchestrator returns Conflict when the environment's workflowEngine === 'browser'. An orchestrator-engine environment with no executor deployed accepts the trigger (202) and the run just stays pending. Suggest: "The environment's workflows still run in the browser engine → Conflict".
There was a problem hiding this comment.
Fixed in 17b6774 — now "The environment's workflows still run in the browser engine → Conflict", matching ensureWorkflowEngineOrchestrator.
| Three tools let an assistant drive a [Workflow](/product/process/workflows/overview) end-to-end from its own context: pick a workflow that fits the record at hand, start it, then watch the run. | ||
|
|
||
| <Info> | ||
| A workflow is only reachable through MCP once an editor enables its **MCP** trigger in the workflow's [trigger settings](/product/process/workflows/triggers#the-mcp-trigger). Nothing is exposed by default. |
There was a problem hiding this comment.
Minor: "an editor" risks confusion with the Editor permission level — "a workflow editor" or "anyone with access to the workflow's trigger settings" avoids implying a role requirement. Same at l.442.
There was a problem hiding this comment.
Fixed in 17b6774 for the intro (now "a workflow editor") and completed in 4361f16 for the Security bullet that still said "an editor".
|
|
||
| ### Runs that need a human | ||
|
|
||
| In this first version the assistant can *observe* a parked run but not answer it. When `waitingForHumanInput` is `true`, the run is waiting in the workflow's **fallback inbox**, and someone finishes it from the Forest UI. Relaying the step's question into the chat and submitting the answer through MCP is planned, not available yet. |
There was a problem hiding this comment.
Nit: "is planned, not available yet" is a roadmap commitment in product docs; safer as "is not available in this version".
There was a problem hiding this comment.
Fixed in 17b6774 — now "is not available in this version."
…ract (PRD-742) The orchestrator endpoint now returns the whole HydratedWorkflowRun instead of the dropped normalized status. Replace the currentStep/waitingForHumanInput/ result field table and JSON examples with runState + triggerType + the workflowHistory shape (each step's resolved definition and per-step context), and reword the poll step, the trigger-time error note and the human-gated section accordingly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PMerlet
left a comment
There was a problem hiding this comment.
Adversarial review against the two implementation branches (orchestrator #8418, mcp-server #1792). The prose is clear, but a few technical claims and JSON examples don't match the code as shipped. One is a contradiction with the server (fallback inbox), a couple are factual errors an integrator would code against. Details inline.
|
|
||
| ### Runs that need a human | ||
|
|
||
| In this first version the assistant can *observe* a parked run but not answer it. When the run sits on a human step — an incomplete step (`done: false`) whose definition marks it manual — it is waiting in the workflow's **fallback inbox**, and someone finishes it from the Forest UI. Relaying the step's question into the chat and submitting the answer through MCP is planned, not available yet. |
There was a problem hiding this comment.
This contradicts the server — MCP runs are NOT routed to the fallback inbox.
On the orchestrator branch (#8418), tryFallbackToInbox early-returns for any triggerType !== Webhook, so a run started via MCP that parks on a human step stays in started with no inbox assignment — it never lands in the fallback inbox. The same claim appears in triggers.mdx and execute/workflows.mdx.
Either the server needs to route MCP runs to the fallback inbox too (I've raised this on #8418 as the likely fix), or these three pages need to be corrected. As written, the docs promise behavior the code doesn't implement, and a parked MCP run is effectively unactionable. Please align with the #8418 outcome before merging.
There was a problem hiding this comment.
Resolved on the server side rather than here: 945eae136 on #8418 widened the tryFallbackToInbox gate to both automated triggers (Webhook and Mcp), with a mirrored test — so this claim now matches the shipped behavior, in all three pages. (Remaining nuance, deliberately left open: the sentence is unconditional while the assignment only happens when a fallback inbox is configured — candidate wording "when a fallback inbox is configured".)
| The record is **not** checked when the run is created — the orchestrator has no data access at that point. An id that does not exist, or that the user cannot read, produces a run that fails at its first data step; the assistant sees it through the failing step's `context.error` in `getWorkflowRun`'s history, not as a trigger-time failure. | ||
| </Note> | ||
|
|
||
| Only **one run per record** can be active at a time. Triggering a record that already has an ongoing run fails and does **not** resume it — the run in flight is left untouched. |
There was a problem hiding this comment.
Inaccurate, and contradicts another page. Uniqueness is per (workflow, record), not per record — findActiveRunForWorkflowAndRecord filters on workflowId + collectionId + selectedRecordId. Two different workflows can each have an active run on the same record. The API reference page states it correctly: "Only one run of a given workflow can be active on a given record" (trigger-workflow-webhook.mdx). Suggest matching that wording.
There was a problem hiding this comment.
Fixed in 17b6774 — aligned with the webhook reference wording: "Only one run of a given workflow can be active on a given record… A different workflow can still start on that record".
| | `recordId` | The record to run on. Composite primary keys use the packed form, values joined by `\|` (e.g. `"123\|456"`). | | ||
|
|
||
| ```json Returns | ||
| { "runId": 1234, "runState": "loading" } |
There was a problem hiding this comment.
runId is a string in the actual response, not a number. The forestadmin-client HTTP layer normalizes it explicitly: return { ...result, runId: String(result.runId) } (forest-http-api.ts), and triggerWorkflow serializes that. So the assistant sees { "runId": "1234", "runState": "loading" }. (Note the real asymmetry: getWorkflowRun returns "id": 1234 as a number — so the example at L362 is right, but this one should be "1234".)
There was a problem hiding this comment.
Fixed in 17b6774 — "runId": "1234" in the trigger example; the numeric "id": 1234 in the getWorkflowRun examples stays as-is per your note.
|
|
||
| ```json Parked on a human step | ||
| { | ||
| "id": 1234, |
There was a problem hiding this comment.
These JSON examples drop required fields with no ellipsis, so an integrator coding a strict parser against them will break. getWorkflowRun returns the whole hydrated run with no serializer (response: run); the HydratedWorkflowRun type carries userId, renderingId, collectionId, bpmnVersion, engine, lockedAt, createdAt, updatedAt (all omitted here), and every step always has isCardStep (set unconditionally by hydrateStep) plus automaticCompletion, none of which appear. The prose at L356 even references collectionId and the timestamps the examples don't show. Either add an ellipsis/"trimmed for brevity" note or include the required fields.
Separately, at L404 the Finished example shows a guideline task step with "outgoing": [] — a task step always has exactly one transition (WorkflowTaskBase.outgoing: [WorkflowTransition]); only end steps have an empty outgoing.
There was a problem hiding this comment.
Fixed in 17b6774 — the run-level fields are now enumerated with a "trimmed to the load-bearing fields" note, and both task-step examples show exactly one outgoing entry (the empty array remains only on the End step, matching WorkflowEnd.outgoing: []). The end step's executionType was additionally corrected to manual in 4361f16.
…tract (PRD-742) - runId is a string; one active run per (workflow, record); Conflict is keyed on the browser engine, not Runtime presence - getWorkflowRun: document executionType/stepName/stepIndex, the trimmed examples note, the id/runId asymmetry, and no dedicated failed state - fix the finished example (static stepDefinition keeps prompt/outgoing) - enabledTools levers rewritten with allowlist semantics + third revocation lever; rate-limiting rationale corrected (no anonymous surface claim) - new from the impl fixes: polling cadence, segments not enforced at trigger time, 4xx row for out-of-bounds ids - onboarding page: workflows bullet in the MCP capability list Fallback-inbox claims stay as written: forestadmin-server 945eae136 now routes parked MCP runs to the fallback inbox. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Two one-line corrections landed in
Deliberately left open (non-blocking wording-softening candidates): the unconditional fallback-inbox phrasing ( |
PMerlet
left a comment
There was a problem hiding this comment.
Adversarial review — docs (PRD-742)
Checked every claim in this PR against the two implementation branches (ForestAdmin/forestadmin-server#8418, ForestAdmin/agent-nodejs#1792).
This is the strongest of the three PRs. The Deviations section, the <Note> on the unvalidated record, the "no dedicated rate limiter" statement, the runState table (matches the server enum exactly, and correctly says there is no failed state), the one-run-per-workflow-per-record rule, the 400/404/409 mapping — all verified accurate against the code. It reads like it was written from the implementation, as claimed.
Four claims are stronger than what the code enforces, and one error case is missing. All inline. The permissions one (triggers.mdx) is the one I would not ship as-is: it promises a guarantee the server does not implement.
Note this PR is already approved, so these are post-approval findings.
| - Browse your collections and their schemas | ||
| - Query and filter records across collections | ||
| - Execute actions on records, including approval workflows | ||
| - Start [workflows](/product/process/workflows/triggers#the-mcp-trigger) on a record and follow their progress |
There was a problem hiding this comment.
This bullet lands directly above "Every operation runs with the permissions of the authenticated Forest user and is logged just like a UI action" — and for the two read-only workflow tools, the second half is not true.
listWorkflows and getWorkflowRun write no activity log (unlike list, listRelated, describeCollection, which all do). Only triggerWorkflow is audited. So of the three tools this bullet introduces, one is logged and two are not.
Either the tools gain audit logging (raised on ForestAdmin/agent-nodejs#1792) or this page needs a qualifier — "every state-changing operation" would already be accurate.
There was a problem hiding this comment.
Fixed in ec3b24d with your qualifier — the sentence now reads "every state-changing operation is logged just like a UI action", which is exactly what the code enforces. Auditing the two read tools needs server-side support (the MCP activity-log route silently drops resource-less logs), tracked in PRD-967; when that lands this sentence can be strengthened back.
| G -. "not finished yet" .-> G | ||
| ``` | ||
|
|
||
| 1. **Discover** — `listWorkflows` returns the MCP-enabled workflows the connected user can reach, with the collection each one operates on. Pass `collectionName` to narrow it to the collection of the record in context. |
There was a problem hiding this comment.
"the MCP-enabled workflows the connected user can reach" — the endpoint does not filter by anything the user can or cannot reach beyond the rendering itself.
listMcpEnabledWorkflows returns every workflow in the rendering whose mcp trigger is on. There is no collection-permission filter, and isVisible is deliberately ignored (so a workflow hidden from the UI still appears here). A user with no read access to customers sees the customers workflow — and can trigger it.
Suggest: "the MCP-enabled workflows in the connected user's rendering". That is exactly what the code does, and it keeps the sentence honest without weakening the page.
There was a problem hiding this comment.
Fixed in ec3b24d with your wording — "the MCP-enabled workflows in the connected user's rendering", here and in triggers.mdx's Enabling section ("only the MCP-enabled workflows in the connected user's rendering"). No reach/permission claim left on the listing.
|
|
||
| ### `getWorkflowRun` | ||
|
|
||
| Reads the full run, given the `runId` returned by `triggerWorkflow`. The orchestrator holds no customer record data — that lives in the executor — so the whole run is returned, giving the assistant maximum context about where it is and what each step does. |
There was a problem hiding this comment.
"The orchestrator holds no customer record data" is a slightly stronger guarantee than the payload gives, and it is doing load-bearing work here — it is the stated reason the whole run is returned to a third-party LLM.
Two counter-examples in the very examples below: selectedRecordId is a customer record identifier, and context.error is free-form text reported by the executor — a failing get-data/update-data step can embed values from the customer's database in it. The Runs that need a human section and the runState table both tell readers to read context.error precisely because it carries execution detail.
Suggest: "holds no record payload — only identifiers and the executor's step outcomes", so a reader assessing what leaves their perimeter is not misled.
There was a problem hiding this comment.
Fixed in ec3b24d — now "The run carries no record payload — records live in the executor — … Identifiers (selectedRecordId) and step error messages (context.error) may still contain customer data." The same hedge landed on the server jsdoc (forestadmin-server 50f2b89c2) and the client docstring (agent-nodejs 1ce50dce), so the three copies agree.
|
|
||
| | Situation | What the assistant gets | | ||
| |---|---| | ||
| | The workflow is unknown, its MCP trigger is off, or it is outside the user's rendering | Not found — with a hint to call `listWorkflows` | |
There was a problem hiding this comment.
One error case is missing from this table: the tool also rejects a workflow whose collection is unavailable (renamed or deleted, so collectionName resolves to null) — before any run and before any audit log, with a distinct message pointing at the workflow's configuration.
Worth a row, because the symptom is confusing: the workflow appears in listWorkflows (with collectionName: null) yet cannot be triggered.
There was a problem hiding this comment.
Added in ec3b24d — new row right after the unknown/disabled one: "The workflow's collection is unavailable (renamed or deleted — it may still appear in listWorkflows with a null collectionName) → Rejected up front, pointing at the workflow's configuration; nothing starts." The listing symptom you called out is in the row so the confusing pairing is explained where readers will hit it.
| ### Identity, auditing, and limits | ||
|
|
||
| - **Identity** — the run executes as the Forest user of the MCP session, established by the OAuth login. Its permissions bound everything the run can read, write, or trigger. | ||
| - **Auditing** — each trigger is recorded in the run history and in your **Activity Logs**, attributed to that user and labelled *via MCP*, so MCP-started runs are distinguishable from manual and webhook ones. |
There was a problem hiding this comment.
"labelled via MCP" — only one of the two rows written per trigger carries that label.
A single MCP trigger produces two Activity Log entries: the MCP server writes one before calling the orchestrator (fail-closed: the audit lands before any side effect), labelled triggered the workflow "X" with no channel; the orchestrator then writes its own, labelled triggered the workflow "X" via MCP and attached to the run. Webhook has no such duplicate.
So a reader auditing Activity Logs sees each MCP trigger twice, and the first row looks like a manual start. I have asked both code PRs to align the labels; if that lands, this sentence becomes true and the duplication is still worth a line here (the two rows are complementary — the early one has no runId).
There was a problem hiding this comment.
The label fix landed (agent-nodejs ecb3125f: the MCP-server row now also says via MCP), so the sentence is true as written — and ec3b24d adds the line you suggested: "An MCP trigger writes two complementary log entries: one before the run starts (the audit that blocks the trigger if it cannot be written), and one attached to the run." Mirrored in triggers.mdx's Auditing section.
| ### What an assistant can and cannot do | ||
|
|
||
| - It **can** start the workflow on a record and poll the run's state, current step, and outcome. | ||
| - It **cannot** exceed the connected user's permissions — data and actions are gated by [Roles & permissions](/get-started/control/roles-permissions) exactly as in the interface. |
There was a problem hiding this comment.
This is the claim I would not ship as written. "It cannot exceed the connected user's permissions — data and actions are gated by Roles & permissions exactly as in the interface."
The trigger path enforces two things and no more: the caller has access to the rendering, and the workflow's mcp toggle is on. There is no collection-permission check and no scoping of the recordId — the server's own test suite pins this (should start a segment-restricted workflow on an out-of-segment record (segments not enforced)).
"Exactly as in the interface" is what makes it misleading: in the interface a user can only start a workflow from a record they can already see, so the visible surface is the permission boundary. Over MCP the assistant supplies the id, so that boundary disappears — and the assistant may be choosing that id from record content it just read (prompt injection).
The <Note> on mcp-server.mdx is much better calibrated about the record not being checked; this bullet contradicts it. Suggest aligning the two: what an assistant cannot do is exceed the user's permissions on the data the run reads and writes — while the trigger itself is bounded only by the rendering and the toggle.
Related: "permission scopes still bound everything the run reads and writes" on mcp-server.mdx rests on the executor minting a per-user token and the agent applying scopes on a by-primary-key read. Worth confirming that end of the chain before publishing it as a guarantee — a get-by-id that skips scope filtering would make this a scope bypass, and it is the same class of bug as PRD-898/916/922.
There was a problem hiding this comment.
Reworded in ec3b24d along the line you proposed — the bullet now scopes the guarantee to execution and states the trigger boundary explicitly: "It cannot exceed the connected user's permissions on the data the run reads and writes — every data step is gated by Roles & permissions. The trigger itself is bounded by the user's rendering and the MCP toggle only: the record id the assistant supplies is not checked at trigger time (see the note on unvalidated records)." No "exactly as in the interface" left, and it now agrees with the <Note> on mcp-server.mdx instead of contradicting it.
On the related ask — the scope-enforcement chain was traced end-to-end during the #8418 segments thread: the executor calls the agent per data step with a JWT minted from the triggering user, and the agent applies getScope() on the by-primary-key get/update/action routes too, so "permission scopes still bound everything the run reads and writes" holds. The trigger-time gap itself (collection permission + unchecked record id, incl. the prompt-injection angle) is now consolidated with segments in PRD-919 for one decision.
| ## Auditing | ||
|
|
||
| Every webhook-triggered run is recorded in the workflow run history and in your **Activity Logs**, attributed to the token's user and marked as **webhook-triggered** so you can distinguish automated runs from manual ones. | ||
| Every automated run is recorded in the workflow run history and in your **Activity Logs**, attributed to the user the run acted as, and labelled by channel — *via webhook* or *via MCP* — so you can tell automated runs from manual ones, and from each other. |
There was a problem hiding this comment.
Same point as the Auditing bullet on mcp-server.mdx: "labelled by channel — via webhook or via MCP" holds for the webhook (one row, labelled) but an MCP trigger writes two rows and only the orchestrator's carries the channel. Worth waiting for the label fix on ForestAdmin/agent-nodejs#1792 before this sentence is accurate.
There was a problem hiding this comment.
Aligned in ec3b24d now that the label fix landed on agent-nodejs (ecb3125f): the channel claim is accurate, and the Auditing paragraph gains the same two-complementary-entries sentence as mcp-server.mdx so an auditor expects two via MCP rows per trigger.
| Webhook triggers are available on **all environments**, with no production-only restriction. | ||
| ### Before you can enable an automated trigger | ||
|
|
||
| Both automated triggers (Webhook and MCP) share two prerequisites. Until both are met, their toggles stay locked, with a tooltip explaining which one is missing. |
There was a problem hiding this comment.
Flagging as unverifiable rather than wrong: this whole section (locked toggles, the tooltip wording "Ask your tech team to install the Workflow Executor", the Automated group layout, and the "stays actionable once enabled" behavior below) describes front-end behavior that comes from ForestAdmin/forestadmin#9870, not from either back-end branch — so I could not confirm any of it from the code this PR cites.
Since the PR is already approved, worth a second pass against the front branch before merge — tooltip strings in particular tend to drift late, and the "re-locks once disabled" rule is precise enough to be wrong.
There was a problem hiding this comment.
Second pass done against the front branch (ForestAdmin/forestadmin#9870, current head) — all four claims are confirmed in code:
- Layout —
update/template.hbs: "Manual" row with its own toggle, then<h3>Automated</h3>holdingFallbackInbox,WebhookTrigger,McpTriggerin that order; labels match the page word-for-word. - Lock conditions — both
webhook-triggerandmcp-triggercomponents computeisLockedfrom the same two prerequisites and bind@disabled; the tooltip names the missing one. - Tooltip wording —
'Ask your tech team to install the Workflow Executor.'is byte-identical (hardcoded, no i18n) in both components, trailing period included. - Stays actionable / re-locks — the inbox branch is guarded by
&& !hasMcpTrigger(webhook:&& !hasWebhookTrigger), so an enabled trigger stays toggleable with a cleared inbox and re-locks once off; pinned bycomponent-test.ts("no fallback inbox is set and the mcp trigger is enabled" → toggle enabled, no tooltip).
One nuance for the record: the executor prerequisite is gated on the environment's engine (workflowEngine === 'orchestrator'), not on a reported executor version — functionally what the page describes, so no text change needed.
…nforces (PRD-742)
Post-approval review findings: the listing is rendering-scoped (no
permission filter), the trigger is bounded by rendering + toggle only,
runs carry no record payload but ids/error strings may hold customer
data, an MCP trigger writes two via-MCP audit entries, revocation does
not abort in-flight runs, fallback-inbox routing needs one configured,
and the unavailable-collection rejection joins the error table.
Read-tool auditing is deferred to PRD-967 ("every state-changing
operation is logged").
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…claim (PRD-742) - A parked run covers automated-with-confirmation steps too, not only manual ones - List the always-present isCardStep / automaticCompletion fields the examples omit - Make the triggers-page fallback-inbox claim conditional, matching the other pages Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PMerlet
left a comment
There was a problem hiding this comment.
Adversarial pass, read against the two implementation branches rather than against the spec. The pages are accurate on the hard parts (segments not enforced, the record not validated at trigger time, the two audit rows) — these five are where the text promises more than the code does, or less.
| - Start [workflows](/product/process/workflows/triggers#the-mcp-trigger) on a record and follow their progress | ||
|
|
||
| Every operation runs with the permissions of the authenticated Forest user and is logged just like a UI action. | ||
| Every operation runs with the permissions of the authenticated Forest user, and every state-changing operation is logged just like a UI action. |
There was a problem hiding this comment.
Important — this sentence was weakened globally to fit a local gap.
"Every operation […] is logged" → "every state-changing operation is logged".
The change is needed only because the two new read tools (listWorkflows, getWorkflowRun) skip withActivityLog, unlike every other read tool on the server — list, describeCollection and listRelatedData all write a type: 'read' entry. So the new wording now under-describes the pre-existing behaviour: it tells readers their reads aren't logged, when most of them are.
It also quietly drops the audit guarantee on the one tool whose payload we document as potentially carrying customer data (getWorkflowRun — selectedRecordId, context.error).
I've raised the code side on agent-nodejs#1792. If the tools get wrapped, this line should go back to the original sentence. If they deliberately stay unaudited, the honest form isn't to weaken the global claim — it's to keep it and carve out the exception where it applies, in mcp-server.mdx under Identity, auditing, and limits.
There was a problem hiding this comment.
Agreed on the conclusion, and fixed in 19fe126 — but the premise needs a correction that changes the fix.
These aren't the only unaudited reads. getActionForm doesn't import withActivityLog either, and it predates this epic. So "Every operation […] is logged" was already inaccurate before PRD-49 — the weakening wasn't introducing a falsehood, it was replacing one imprecision with another, and under-describing three-quarters of the reads in the process, exactly as you said.
And my own justification on the code side was wrong, which I've corrected on agent-nodejs#1792: the audit route doesn't silently drop a resource-less log — collectionModelName is Joi.string().required() → 400, and an unresolvable modelName → 404. The conclusion holds (no persisted row, an extra round trip per poll) but the mechanism is a rejection — which was precisely the uncovered path in with-activity-log.ts you flagged in the other thread. Fixed there in e97b498.
So I took your preferred shape: the global sentence is restored here, and the exception is carved out where it applies, naming all three tools plus the fail policy in one line under Identity, auditing, and limits:
Three reads are not audited —
listWorkflows,getWorkflowRunandgetActionFormleave no Activity Logs entry, because the log route needs a resource these tools have no way to name. Every other tool, read or write, writes one. A write whose log cannot be created is blocked; a read proceeds with a warning, so an audit-store outage never takes the read surface down.
Two other global claims on mcp-server.mdx said the same thing and would have contradicted the carve-out — the intro (:19, "logging every activity") and the security bullet list ("Logs all operations") — both now point at the same section. PRD-967 is updated to cover the third tool and to record that restoring the unqualified sentence is part of closing it.
|
|
||
| There is nothing to copy or rotate here: **the toggle is the whole configuration**. The assistant is already authenticated against the MCP server through OAuth, and that session's Forest user is the identity the run executes under — the same model as a manual start, not the webhook's separately-provisioned URL and token. | ||
|
|
||
| Enabling MCP triggering is what makes the workflow visible to assistants at all: |
There was a problem hiding this comment.
Important — this reads as parity with UI visibility, and there is none.
"Enabling MCP triggering is what makes the workflow visible to assistants at all" — true as a necessary condition, misleading as a sufficient description. The store query deliberately does not filter isVisible (layout-workflows-store.ts, the wf CTE carries a comment saying so), so a workflow hidden in the interface stays fully listable and triggerable through MCP.
That matters because hiding a workflow is the gesture people use to retire one. Flip isVisible off, it disappears from every list view, and assistants keep starting runs on it — the retirement that works everywhere else silently doesn't here.
The page already does the honest thing for segments ("Workflow segments are not enforced either" in mcp-server.mdx); this deserves the same treatment. A line under What an assistant can and cannot do would cover it: hiding a workflow does not withdraw it from MCP — disable the MCP toggle.
Flagged the code side on forestadmin-server#8418 too, since the behaviour was left pending an explicit product sign-off that I can't find recorded anywhere in the three PRs.
There was a problem hiding this comment.
Fixed in 19fe126, with your wording as the basis.
The sentence now says enabling MCP is what makes the workflow reachable and is the only thing that does, followed by an explicit warning where a reader will hit it:
Hiding a workflow from the interface does not hide it from MCP. Assistant exposure is driven solely by the MCP toggle, not by the workflow's visibility — so a workflow you retired by hiding it stays listable and triggerable until you also turn its MCP toggle off.
I verified the code claim rather than taking it from the CTE comment, and there's a nuance worth carrying: the server filters isVisible nowhere — not in the listing, not in the by-id lookup, not on the manual /start. So a hidden workflow was already triggerable by API before this epic; what MCP adds is a discovery surface that hands it to an assistant. That's what I've put to product on the server PR, since it's a narrower and more answerable question than the one the thread implies.
Your point about the segments sentence being the right precedent is what shaped the fix — I checked that one too and it's accurate: segmentIds is read nowhere in the orchestrator service.
The sign-off is still outstanding and I'm not treating the docs change as a substitute for it. If product decides hidden = unreachable, the CTE filter is one line and this warning comes back out.
| |---|---| | ||
| | **Disable the MCP toggle** | The workflow disappears from `listWorkflows` and can no longer be triggered through MCP. Manual and webhook starts are unaffected. | | ||
| | **Restrict the MCP server's tools** | Set an `enabledTools` allowlist that leaves `triggerWorkflow` out to remove MCP triggering across *all* workflows at once — when `enabledTools` is unset, every tool is enabled. See [Restrict tools](/product/embed/mcp-server#restrict-tools). | | ||
| | **Revoke the user's access** | The assistant acts as its connected Forest user; removing that user's access to the rendering or the workflow halts its runs — runs in flight are not aborted, but their next data step fails once the permissions are gone. | |
There was a problem hiding this comment.
This row makes a behavioural guarantee that nothing in the three branches demonstrates.
"runs in flight are not aborted, but their next data step fails once the permissions are gone"
It's presented as one of three revocation levers, in a security table — so a reader will act on it as the emergency stop for a run already moving. But it's an assertion about how the executor re-evaluates permissions mid-run, and no test in the server, MCP or executor branches covers it.
The other two rows are verifiable from this epic's code (the toggle gates the store query; enabledTools gates registration). This one isn't. Either confirm it against the executor and say so, or soften it to what we know — revoking access stops new triggers, and a run in flight is not aborted — so nobody plans an incident response around the second half of the sentence.
There was a problem hiding this comment.
You're right that nothing demonstrated it, and softened in 19fe126 — but there is a mechanism, just not the one the row described, so the new wording says what the code actually does.
What I found tracing it:
- The user profile is re-resolved on every hand-off to the executor (
workflow-history-hydrater.ts:134-174). If the user is no longer in the project, the run doesn't "fail its next data step" — it getsWorkflowUserNotFoundInternalError, the error is written onto the current step, and the run goes back tostarted. It stalls, which is a different observable outcome from the one an incident responder would plan around. - If the user keeps project access but loses collection permissions, the check does fall on the agent side — with a 15-minute permission cache by default (
build-application-services.ts:48). So "once the permissions are gone" was missing the delay that matters most in an incident. getPendingRunsperforms no access check at all, andhasRunAccessis only wired to an executor HTTP route, never to step execution.- And
grep -rn "permissions are gone\|revok" packages/private-api/test/jest/domain/workflow-orchestrator/→ nothing. Confirmed untested, in all three repos.
The row now reads:
Removing that user's access to the rendering stops new triggers immediately. A run already in flight is not aborted: it keeps going until it next touches data, then fails on the permission check and stalls with the error recorded on the current step — allow up to 15 minutes for the agent's permission cache to expire. Abort a run explicitly if you need it stopped now.
I also dropped "or the workflow" from the same row: there is no per-workflow user access to revoke — permissions-by-action.ts:88 only defines manageWorkflow, which is the editing right, and the trigger path checks getRendering only. Same fix applied to the mirrored sentence in mcp-server.mdx.
|
|
||
| - **Identity** — the run executes as the Forest user of the MCP session, established by the OAuth login. Its permissions bound everything the run can read, write, or trigger. | ||
| - **Auditing** — each trigger is recorded in the run history and in your **Activity Logs**, attributed to that user and labelled *via MCP*, so MCP-started runs are distinguishable from manual and webhook ones. An MCP trigger writes two complementary log entries: one before the run starts (the audit that blocks the trigger if it cannot be written), and one attached to the run. | ||
| - **Rate limiting** — the workflow tools have no dedicated limiter. They inherit the MCP server's authentication and limits; unlike the [webhook trigger](/reference/api/endpoints/trigger-workflow-webhook#rate-limiting), there is no separately exposed HTTP endpoint to protect — every call happens inside an authenticated MCP session. The one-run-per-workflow-per-record rule also absorbs repeated triggers on the same record. |
There was a problem hiding this comment.
Important — this bullet reassures about the wrong risk.
"The one-run-per-workflow-per-record rule also absorbs repeated triggers on the same record."
True, and beside the point. The rule bounds repetition on one record; it bounds nothing across records. An assistant looping over a list view can open one run per record, and each run consumes executor capacity — there's no limiter anywhere on the path (the bullet says so itself, one sentence earlier).
So the paragraph names the absence of a limiter and then offers a mitigation that doesn't cover the actual fan-out vector. triggerWorkflow is the only write tool we ship with destructiveHint: true and no volume guard; a reader deciding whether to enable it deserves that stated plainly rather than softened.
Suggest keeping the first two sentences, and replacing the third with what's actually true: the one-run-per-record rule prevents duplicate runs on the same record, but nothing bounds the number of records an assistant can trigger on — size the exposure with the mcp toggle, per workflow.
There was a problem hiding this comment.
Fixed in 19fe126, essentially your suggested replacement.
You were right that the paragraph named the absence of a limiter and then offered a mitigation for a different risk. Verified before rewriting: the unique index is on (workflowId, collectionId, selectedRecordId) — per record, bounding nothing across records; the only rate limiter in the orchestrator routes is rateLimitPendingRun, mounted on the executor's /pending-run and not on the MCP start; and triggerWorkflow is indeed the only tool shipping destructiveHint: true.
The bullet now ends:
The one-run-per-workflow-per-record rule prevents duplicate runs on the same record, but nothing bounds how many records an assistant can trigger on — walking a list view opens one run per record, each consuming Forest Runtime capacity.
triggerWorkflowis annotated as destructive so MCP clients ask the user to confirm each call; keep that confirmation on, and size the exposure with themcptoggle, per workflow.
I kept the destructive-annotation sentence because it's the only actual mitigation on that path, and it's worth a reader knowing it exists — but it's framed as a client-side confirmation prompt, not as a volume guard, so it can't be mistaken for one.
Adjacent and now bounded on the server side: the workflow listing had no LIMIT at all (fixed on #8418, capped at 200). Different axis from the fan-out you're describing, but the same "nothing bounds this" family.
| ] | ||
| ``` | ||
|
|
||
| An empty array means no workflow is MCP-enabled in that rendering — most often because nobody has turned the toggle on yet. |
There was a problem hiding this comment.
Minor — "An empty array means no workflow is MCP-enabled in that rendering" is only true when no filter was passed. With collectionName set, an empty array much more often means "none for this collection", and the suggested cause ("nobody has turned the toggle on yet") sends the reader debugging the wrong thing.
Worth one clause: with collectionName set, an empty array means no MCP-enabled workflow operates on that collection.
Same section, one more: the error table below ("An empty or out-of-bounds recordId […] rejected up front, nothing starts") is accurate for the empty case (zod catches it) but not for the >255-char one — that only fails at the server, after the pending audit row has been written. Adding .max(255) to the tool's zod schema would make the sentence true rather than needing a caveat; raised it on agent-nodejs#1792.
There was a problem hiding this comment.
Both fixed in 19fe126, and the second one is now true rather than caveated.
Empty array — reworded as you suggested, with the debugging misdirection removed:
An empty array means nothing matched. Without
collectionName, that means no workflow is MCP-enabled in that rendering — most often because nobody has turned the toggle on yet. WithcollectionNameset, it usually just means no MCP-enabled workflow operates on that collection: calllistWorkflowsagain without the filter before concluding anything.
I added a second line there, because the listing changed under this review: workflows whose collection was renamed or removed are now filtered out. They were being listed while triggerWorkflow rejects them up front, so an assistant could list, pick, get rejected, and list again — the same loop shape as the rollback case, but reachable today. Fixed on agent-nodejs#1792 (9ec10b78).
The recordId row — rather than caveat it, .max(255) was added to the tool's zod schema (2be4504), so "rejected up front, nothing starts" is now accurate for the over-long case too. Worth noting what it buys: I checked the server, and Joi already capped recordId at 255, so a long id was a clean 400 before any INSERT — never a 500. The gain is avoiding the round trip and the pending audit row marked failed, which is exactly the gap between the sentence and reality that you pointed at.
While in that table: Macroscope found a related precision bug on the server, now fixed (6205c9e0f) — a numeric recordId past MAX_SAFE_INTEGER was rounded by JSON.parse before being persisted, starting the run on a different record. The MCP tool always sends strings so this path was never exposed, but it's the same row's territory if we ever document the numeric form.
… (PRD-742) The finished example marked the terminal end step done: true, which the code never does - startStepExecution creates every entry with done: false and the End branch only sets runState. Combined with the parked rule (done: false plus a manual executionType) and end steps being pinned to manual server-side, an assistant following this page reported a finished run as awaiting a human. The rule now matches buildHumanInterventionFallback: runState started with no context.error on the last entry, covering both an unanswered step and one awaiting confirmation, and saying not to read executionType. Also drops a selectedOption from a task step - it is only written for condition steps, and carries the branch answer, not the button text. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… (PRD-742) The lock on the automated toggles depends on the environment's workflow engine, not on whether a runtime is installed: a missing runtime replaces the whole settings page with a "Forest Runtime can't be reached" screen, and the install-executor tooltip only appears on the legacy browser engine. Both pages claimed the tooltip covered the missing runtime. Adds the upgrade-before-enabling warning: a runtime older than the MCP trigger release rejects those runs at validation, so the run is created, never picked up, and nothing warns the operator. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s (PRD-742) Reworked after verifying each claim against the implementation heads. - The global "every operation is logged" sentence is restored rather than weakened: it had been narrowed to state-changing operations only because two new read tools skip the audit wrapper, which under-described the reads that do write one. The exception is now named where it applies, and it covers three tools - getActionForm was already unaudited before this epic. - The Identity bullet no longer implies permissions bound triggering. They bound what the run reads and writes; the trigger is gated by the rendering and the MCP toggle alone. - Hiding a workflow does not withdraw it from MCP. The listing is driven solely by the toggle, and hiding is how people retire a workflow. - The one-run-per-record rule bounds repetition on one record, not the number of records an assistant can fan out over - nothing limits that. - The revocation lever no longer promises that in-flight runs fail on their next data step: nothing in these branches demonstrates it, and the agent's permission cache holds for up to 15 minutes. - An empty listing means something different once collectionName is set, and the locked-toggle tooltip is quoted as the frontend now ships it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…742) The image had been replaced to show the whole Triggers section but kept its webhook-only filename, so the next person editing the page would go looking for a webhook screenshot. Also adds the one trigger-time failure missing from the errors table: a workflow whose MCP Task step targets an OAuth2-protected connector is refused at start, and a reader had no way to know that from this page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…RD-742) Checked against the three implementation heads. The error table promised a split that never landed, and was wrong twice over: "rejected up front" holds for an empty argument and an over-long recordId, but an out-of-range runId is only a string on this side, so it round-trips and comes back a server 400. The OAuth2 row said the step type is "not yet supported" when the gate is version-based at 1.14.0 — which forest-runtime.mdx, in this same PR, documents as a supported feature. The unaudited-tool list counted three; there are four. The fourth, requestActionFileUpload, is not a read: it mints a pre-authorized upload, and it is on by default whenever file uploads are configured. An enumerated compliance claim that omits the one write-ish tool is worse than a general statement. The stated rationale also did not hold for getActionForm, which does receive a collection. Three more: the two Activity Log entries were presented as equally reliable when the run-attached one is best-effort; the opt-in Note implied the toggle governs reading a run already in flight; and the Security section still carried flat guarantees the page contradicts elsewhere. Adds the 200-workflow cap and its invisible truncation, and replaces a runState example that cannot occur at trigger time. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
An executor predating MCP triggering does not leave the run pending forever. It picks it up, classifies it as malformed and reports an error outcome, so the run fails immediately with a validation error on its first step and getWorkflowRun shows it — that reporting path predates the executor's MCP support, so every affected runtime has it. The prerequisite is unchanged, but the operator symptom is "every MCP trigger fails loudly with a confusing error", not "runs pile up unnoticed", and the rollback runbook should read that way. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Resolved the Security bullet list in product/embed/mcp-server.mdx: kept this branch's three corrected claims (four unaudited tools, permissions scoped to what a run reads and writes rather than to the trigger, and the hedge that identifiers and step error messages can still carry customer data) and kept main's new bullet on restricting which AI clients may connect, which is new information rather than a claim this branch corrected. Both anchors referenced from the list exist on the merged page. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e code does (PRD-742)
Four claims the implementation contradicts:
- The Security summary still read "logs every write, and every read except four
tools", contradicting the enumerated bullet on the same page. Ten tools are
audited and four are not — getActionForm, requestActionFileUpload,
listWorkflows and getWorkflowRun — so it is three reads out of six plus one
non-read. Naming the four removes the read/write split that produced two wrong
versions in a row.
- The entry-point page promised "every operation runs with the permissions of
the authenticated Forest user" one line after adding workflow triggering to
what that sentence covers. Triggering checks rendering membership and the MCP
toggle only — no collection permission, no record, no segment. The diagram alt
text repeated the audit half and is aligned too.
- getWorkflowRun was described as user-scoped ("another user's rendering"). The
store filters on renderingId with no userId, so any MCP session on the
rendering can read any MCP-started run in it — including its selectedRecordId
and step error strings, which the same page flags as possibly sensitive.
- requestActionFileUpload was called historical: it landed on main two days ago.
The "the log route needs a resource" rationale applies to it, not to
getActionForm, which does receive a collection.
Also documents the non-UUID workflowId case now that it is reported as terminal
rather than retryable, notes that a parked or errored run keeps blocking new
triggers on its record, and drops the internal tracker id, the only one in the
repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ew symptom (PRD-742) The permission gate that landed on the front branch added a third prerequisite, and the page still said two. Worse, it named "a workflow editor" as the person who enables MCP triggering — Editor is precisely the level the interface now excludes. The levels are named, and the fact that the gate is client-side only is stated rather than implied, since the layout permission behind the PATCH is broader today. The runtime-skew warning said the run is "marked failed", while the sibling page states there is no failed state. The run stays in `started` with an error on its first step. Two consequences were missing and are the actual recovery cost: it is routed to the fallback inbox, which fills with runs that never began, and `started` counts as active, so each affected record is blocked from being triggered again on that workflow until its run is aborted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Documents the MCP trigger channel shipped by PRD-49, closing the docs half of PRD-742.
Written from the implementation in the three feature branches (front #9870, mcp-server #1792, orchestrator #8418), not from the spec — see Deviations below.
What changed
product/embed/mcp-server.mdx— the bulk of itrunStatetable, JSON examples for a parked and a finished run, an error table, and Identity, auditing, and limits covering the two v1 limitations PRD-742 asks for — no dedicated rate limiter and report-only human-gated runsproduct/process/workflows/triggers.mdx— two triggers → threeAligned neighbours —
overview.mdx,execute/workflows.mdx(automated runs land in the fallback inbox),forest-runtime.mdx(MCP triggering needs the Runtime too). Plus an unescaped pipe that broke a table in the webhook API reference.Deviations from the spec, followed as implemented
listWorkflowsreturns nodescription— onlyworkflowId,name,collectionName. The docs don't promise one.getWorkflowRunis scoped to MCP-triggered runs (triggerType='mcp'in the store query): a manual or webhook run 404s, even for the same user. Not in the spec; documented as a<Warning>.getWorkflowRunreturns the full hydrated run (runState,triggerType, and the completeworkflowHistory— each step's resolved definition and per-step context), not a normalized status. The run carries no record payload, so it is surfaced whole for LLM context (identifiers and step error strings may still hold customer data).runStatereturned bytriggerWorkflowdepends on the workflow's first step, so the docs tell readers not to treat it as an outcome.No-regression verification (2nd AC of PRD-742)
Confirmed by reading the code, not by running it: MCP gating lives in
startWorkflowRunFromMcp, webhook gating inworkflow-webhook-route.ts:106, and the manual/startpath has neither —startWorkflowRunis only touched by an audit-label refactor. Covered byshould not gate the manual /start path (no regression) on an mcp-enabled workflow(workflow-orchestrator-endpoint.test.ts:1161). No symmetric webhook test exists, but the webhook channel never reads themcptoggle.Checks
mint broken-linkspasses.🤖 Generated with Claude Code
Note
Document MCP trigger for workflows including tools, flow, and trigger settings
listWorkflows,triggerWorkflow, andgetWorkflowRun, plus a full guide covering the discover → trigger → poll flow, run state model, constraints, and human-in-the-loop behavior.Changes since #21 opened
getWorkflowRunresponse model from current-state fields to history-based model [488139f]stepDefinitionconfigurationexecutionTypeproperty from"fully-automated"to"manual"[4361f16]getWorkflowRundocumentation to clarify data privacy boundaries: runs carry no record payload, but identifiers likeselectedRecordIdand step error messages incontext.errormay contain customer data [ec3b24d]listWorkflowsdescription to specify it returns only MCP-enabled workflows in the connected user's rendering [ec3b24d]listWorkflowswith a nullcollectionName[ec3b24d]recordIdnot validated at trigger time [ec3b24d]workflowHistorytool behavior to checkrunStatefirst, specified that terminalendsteps are never markeddone: true, explained that the last entry withdone: falseindicates current position in live runs, and redefined human-waiting criteria based onrunStateandcontext.errorpresence rather thanstepDefinition.executionType[72b63a2]pendingstate without visible warnings, and specifying that until runtime reports in the settings page shows unreachable screen and automated triggers cannot be enabled [72b63a2]listWorkflows,getWorkflowRun,getActionForm) are not logged, while all write operations and other reads are audited, with additional details about audit-store outage behavior [19fe126]listWorkflowsmay return an empty array, including behavior differences with and withoutcollectionNamefiltering, omission of workflows whose collection was renamed or removed, and recommendation to call without filter before concluding no workflows exist [19fe126]requestActionFileUploadto the list of tools not logged in audit logs alongsidelistWorkflows,getWorkflowRun, andgetActionForm, with rationale thatrequestActionFileUploadmints upload URLs and is enabled by default when file uploads are configured [712e294]triggerWorkflowresponse runState from 'loading' to 'pending' and expanded runState semantics documentation to explain that 'pending' is typical, 'started' or 'finished' may occur occasionally, and 'loading' cannot occur at trigger time [712e294]getWorkflowRun, replacing previous behavior description of runs stuck in 'pending' forever [712e294]listWorkflowsandtriggerWorkflowso they only show workflows with MCP trigger enabled, whilegetWorkflowRunremains readable for already-started runs even if the toggle is later turned off [712e294]listWorkflowswith no pagination support and guidance to usecollectionNamefilter to stay within the cap [712e294]listWorkflowshides collection-unavailable workflows so rejection only occurs if collection changed after listing, and added explicit invalid argument cases including emptyworkflowId/recordId/runIdrejection,recordIdover 255 characters rejection, andrunIdout of range rejection by Forest [712e294]getWorkflowRunresponse is projected onto a declared contract with only specific field categories returned, and noted that examples are trimmed [712e294]getWorkflowRuntool in MCP server [d48861a]requestActionFileUploadbehavior [d48861a]📊 Macroscope summarized cef6f8d. 3 files reviewed, 0 issues evaluated, 0 issues filtered, 0 comments posted
🗂️ Filtered Issues
No issues evaluated.