Skip to main content

MCP tools and limits

Every tool the TaskJuice MCP server exposes, its arguments, its errors, and the per-plan call budgets.

The server exposes at most 17 tools per connection, all of them listed below. A workflow is never its own tool: an assistant finds the workflows you have exposed with find, reads one with describe, and runs it with run_workflow, passing the workflow's slug.

Each tool needs one permission — the Permission column below. A connection is only offered the tools whose permission it holds, on top of your plan, your role, and the connection URL (see Choose what a connection can do). Every connection holds Read.

Always-available tools

Available to sign-in connections and API keys, on every plan.

ToolPermissionArgumentsReturns
whoamiReadnoneWho the connection is and what it can do here — see The whoami result.
findReadtype, query?, cursor?, limit? (≤ 50); for runs also status?, workflowSlug?, workflowId?; for nodes also kind?; for drafts, nodes, apps, tables and app connections also workspace?Searches one type of item a page at a time. type is workflow (ranked by how well the name and descriptions match query), draft, run (newest first, filtered by status, workflowSlug or, on a connection that can author, workflowId; runs take no query), skill, node (the catalog's app actions and triggers, built-in steps and AI sub-nodes, matched by meaning as well as words, so "send a text" finds SMS actions; narrow with kind: app-action, app-trigger, system or capability), app (catalog apps, each with needsConnection), form, table, template or connection (see Read forms, tables, templates and app connection health). Omit query to list everything. Each page returns items, each with the id to pass to describe (a workflow item also carries its workflowId, which create_workflow_draft takes to open a draft of it), plus total (matches across all pages; null for runs), nextCursor (pass it back as cursor for the next page; null on the last one) and truncated (true when there are more items than one search reads, so the rest weren't searched). Drafts, nodes, apps, forms, tables, templates and app connections need a connection that can author (see Authoring tools); skills need a plan with managed skills. On the whole-account URL, nodes, apps, tables and app connections are searched in the workspace workspace names (its slug), unless the account has only one. Each draft names its workspace's slug in workspace, and workspace narrows the draft list to one workspace.
describeReadid, type?; for nodes also host?; for drafts, deployments, nodes, apps, tables and app connections also workspace?One item in full, by the id find returned (workflow:send_invoice, run:<run id>, skill:<slug>, node:<canonical>, app:<slug>, form:<id>, table:<id>, template:<id>, connection:<id>) or by a workflow's id (deployment:<workflow id>): a workflow's input schema with its source, one example input and its workflowId, a draft's graph and validation, a run's status and output, a skill's current instructions, a catalog node's config schema, every step an app offers and whether it still needs a connection, a workflow's deployment status (see Check whether a workflow is live), a form's fields, a table's columns, a template's steps, or an app connection's health. Find catalog nodes and apps with find. Drafts, nodes, apps, deployments, forms, tables, templates and app connections need a connection that can author; skills need a plan with managed skills. An id from another tool, such as a workflow slug or a run id, works when you also pass type. On the whole-account URL, a node, an app or an app connection is read in the workspace workspace names, unless the account has only one; a draft, a deployment or a table decides its own workspace, and a workspace that does not match it answers not-found.
run_workflowRunworkflowSlug, input?, idempotencyKey?Waits up to 25 seconds. A finished run returns its output; a longer run returns {status: "running", runId} — follow up with describe({ id: "run:<runId>" }). An input that doesn't match the workflow's schema returns invalid-input naming the field to fix (extra fields always pass through; nothing is dispatched and the idempotency key is not consumed).
control_runRunrunId, actionStops or resumes one run, by the runId run_workflow or find({ type: "run" }) returned. action is { kind: "cancel", reason? } (ends the run for good: steps that have not started never run; the reason, up to 500 characters, shows on the run page) or { kind: "resume" } (continues a run a person paused). A run already in the requested state answers noOp: true and nothing changes. A run that already finished, and resuming a run waiting for an approval, are refused with run-control-refused; an approver decides an approval in TaskJuice, and cancelling such a run still works. Cancel does not stop a step that is already running (it finishes), AI agent work already under way, or a phone call in progress. Not available to a client's own sign-in.

Details worth knowing:

  • input is the trigger payload. Whatever the agent passes arrives at the workflow exactly as a webhook body would. describe({ id: "workflow:<slug>" }) shows the expected shape and one example input.
  • idempotencyKey prevents double runs. The same key from the same connection within 24 hours returns the original run instead of dispatching again. Use it whenever an agent might retry.
  • Outputs are capped at 256 KB. Larger outputs come back truncated with outputTruncated: true; the full result stays in the run detail view.
  • Two workflows with the same name get distinct slugs suffixed with the workflow id's first 8 characters, so slugs never collide. Exposing a second workflow with the same name changes the first one's slug too.

The whoami result

FieldWhat it holds
accountThe account: id, name, slug.
workspaceThe workspace the URL names, the same shape; null on a whole-account URL.
connectionUrlThe URL this connection uses.
credentialkind: "oauth" with the signed-in email, or kind: "api-key" with the key's label and expiresAt.
permissions.grantedThe permissions this connection holds, as read, run, build, admin.
permissions.sourcegranted (chosen when it connected), legacy-default (made before permissions existed), or unreadable-claim (the grant couldn't be read, so only Read applies).
permissions.statusesOne entry per permission: granted, usable here, and blockedBy when it isn't — not-granted, api-key (keys hold Read and Run only), role (authoring needs an account role), or plan.
dailyBudgetThe account's tool calls today (tenant), the key's own cap if it has one (credential), and the tighter of the two as remaining. null for a sign-in user with only workspace access (no account role). control_run calls are not counted.
workspacesOn a whole-account URL: up to 50 workspaces this credential can connect to, each with its mcpUrl, and truncated if there are more. null on a workspace URL, and for a key with a workflow allowlist.

Skills tools

Available when your plan includes managed skills. List the skills licensed to a connection with find({ type: "skill" }).

ToolPermissionArgumentsReturns
get_skillReadskillSlugOne skill's current instructions, with the slug of each workflow it runs through run_workflow on this connection.

Authoring tools

Available only to sign-in connections, on a workspace URL or the whole-account URL, on plans that include authoring. A call about an existing workflow, draft or table works in that item's own workspace; making something new names its workspace with workspace (see One connection for every client).

ToolPermissionArgumentsReturns
create_workflow_draftBuildname or workflowId, description?, type?, workspace?A new empty draft: ids, the draft's graph hash, an editor link, and the workspace it was created in (draft.workspace). The workflow goes in the workspace workspace names (its slug), or the only one; on the whole-account URL of an account with more than one, leaving it out creates nothing and answers invalid-arguments listing the workspaces. With type: "form" (forms belong to the whole account, so no workspace): a draft form with one starter field, returned as describe shows a form plus editorUrl (the form builder). Nothing is public until it is published. See Build a form. With workflowId instead of name: opens a draft of a workflow that is already published — see Change a published workflow.
propose_changesBuildworkflowId, changeSet, baseGraphHash?; for a form type: "form", formId, changeSet, baseFormHash?Applies a change-set all-or-nothing. Rejections list every violation; success persists and returns the new graph hash. For a form, applies a form change set to a draft form and returns the saved form with its new formHash.
validate_draftReadworkflowIdJudges the draft against the same gate the in-app drafter uses. Red names the blocker. Green lists what a person must still finish before publishing — app connections on the workflow's Setup page, the rest in the editor; none of it blocks the draft: unconnected apps (needsConnection), unset selections such as a phone number or form (needsResources), a Voice Agent whose Model sub-node needs an LLM key (needsModelKeys), and credentials finished on the node, such as a provider's public key (needsCredentialSetup). Green may also list warnings: things that do not stop the draft or publish but that a person should see, such as a Branch whose no-match path leads nowhere. Each names its step (stepName).
preview_mappingReadexpression, sampleOutput, bindings?Evaluates a JSONata mapping against sample data and returns the output plus warnings.
test_stepBuildworkflowId, nodeId, input?, workspace?Tests one step of the newest draft, the same as the editor's Test button, and returns its output, its error and Data In. The step runs on its sample data: input if you pass it, else the step before it's pinned output, else its own pinned input; $trigger and $steps come from the draft's pinned samples, and inputsUsed says which were used. The step runs for real against the workspace's connections (an email step sends), and nothing is saved to the draft. Triggers, notes and sub-nodes can't be tested this way.
edit_tableBuildcreate or tableId, changes, previewHash?, workspace?Creates a table with its columns, or changes an existing table's columns: add-column, rename-column, retype-column, remove-column. Live as soon as it saves. Returns the table as describe does, with every column id. Removing a column takes the confirm step — see Build and fill tables. A new workspace table goes in the workspace workspace names, as for create_workflow_draft; an account table and a change by tableId take no workspace.
table_rowsBuildtableId, action, previewHash?One table's rows, one action per call: query, insert, update or delete, up to 50 rows. Deleting takes the confirm step — see Build and fill tables.

The authoring tools read through describe and find:

  • A draft: describe({ id: "draft:<workflowId>" }) returns its graph, its hash (the base for propose_changes), validation state (with any warnings, as validate_draft returns them), what a person still has to finish — app connections on the workflow's Setup page, the rest in the editor (see validate_draft), and the editor link.
  • A node: describe({ id: "node:<canonical>" }) returns one node's configuration schema. An unknown canonical returns found: false with a hint — not an error. For the capability sub-nodes (capability-model, capability-tool, capability-memory) it returns their contract: which agent nodes accept them, the exact edge to add, and their config schema. capability-model also lists eligibleModels; pass host: "taskjuice.system.voice_agent" to get the realtime models a Voice Agent can run.
  • The catalog: describe({ id: "app:<slug>" }) lists every step one app offers and whether it still needs a connection; find({ type: "node", query }) searches every app's steps, the built-in system nodes and the capability sub-nodes.

Agents propose, the platform validates — an invalid change never persists. Connecting accounts, attaching keys, and anything else that touches a credential always happens in the app, from the links the tools return.

Change a published workflow

Once a workflow is published it usually has no draft, so propose_changes and describe({ id: "draft:<workflowId>" }) answer not-found for it. Open one first:

  1. create_workflow_draft({ workflowId }) opens a draft of the workflow. It is a copy of the live version, or, when the workflow is switched off, of its newest switched-off version. Leave name, description and workspace out: the workflow already has them. Each item find({ type: "workflow" }) returns carries the workflow's workflowId, and so does describe({ id: "workflow:<slug>" }). It is also the one create_workflow_draft returned when the workflow was first drafted, and once the workflow has run, its runs (find({ type: "run" })) carry it too.
  2. The result has the same shape as a new draft's: created is true, sourceVersion is the version the draft was copied from, and version is the draft's own number. If the workflow already has a draft, you get that draft back with created: false and sourceVersion: null, and nothing new is made.
  3. Read it with describe({ id: "draft:<workflowId>" }), change it with propose_changes, then preview_publish and publish. The live version keeps running until the publish replaces it.

A workflow with no draft and no version that ever went live has nothing to copy, so the call answers not-found and points to the editor. While a publish of the workflow is still running (compiling, or waiting on its evaluations), the call opens nothing and answers deploy-refused with refusalCode: "version-in-progress"; try again once describe({ id: "deployment:<workflowId>" }) shows it has finished. Opening a draft needs the Build permission and, like creating a workflow, a plan that allows it.

Publish and state tools

Available to the same connections as the authoring tools.

ToolPermissionArgumentsReturns
preview_publishReadworkflowId, or type: "form" and formIdWhat publishing would do, without doing it: every blockers entry, the sideEffects going live has (each marked reversible and externallyVisible), the setupRequired steps a person must take, canPublish, and a previewHash. Non-blocking warnings (for example a Branch or Switch whose no-match path leads nowhere) are listed too; they never change canPublish or the previewHash. For a form: its blockers, the public link (publicUrl), the fields added, removed or changed since it was last published (changes, with plain warnings), canPublish and a previewHash.
publishAdminworkflowId (or type: "form" and formId), previewHashPublishes and turns the workflow on — but only if nothing changed since the preview with that hash. Returns outcome (active, already_active, or pending_eval while evaluations run), the after-publish steps, and a next sentence saying what to do. For a form: its public page starts taking submissions; returns publicUrl and a next sentence on starting a workflow from it.
set_workflow_stateAdminworkflowId, change, dryRun?, previewHash?Makes one change: { "kind": "activate" }, { "kind": "deactivate" }, { "kind": "expose", "description"?: "…" } (make it findable with find and runnable with run_workflow), or { "kind": "unexpose" }. dryRun: true returns the effects and changes nothing.
delete_workflowBuildworkflowId, previewHash?Deletes a workflow that is turned off, for good, with its versions; its run history will no longer be viewable. The first call deletes nothing and returns preview-required — see Deleting takes the confirm step below. Once deleted, returns the workflow's name, versionsDeleted and a next sentence.

How the loop works:

  • Preview, then publish with the hash. publish refuses with preview-stale if the workflow, your plan's headroom, or its evaluations changed after the preview — call preview_publish again and show the person what changed. Retrying a publish that already succeeded returns already_active, so a dropped connection never publishes twice.
  • Setup steps are data, never secrets. Each setupRequired step has a stable id, plain-language instructions, and a humanLink that opens where a person finishes it: the workflow's Setup page for connecting an app, otherwise the exact node (or page) — attaching an LLM key to a Voice Agent's Model sub-node, picking a phone number or form, pasting a provider's key, or entering a webhook signing secret. Steps marked before_publish must be done first; publish refuses with refusalCode: "setup-required" until they are. Steps marked after_publish — such as registering the webhook URL with the sending system (the step carries the URL) — are for the person to do once it is live.
  • Apps are connected on the Setup page. Each workflow has a Setup page that lists every app its draft still needs, and a person reaches it from a connection step's humanLink after signing in. Connecting an app there, or asking the client to connect it, fills every step of that app in the draft at once, so the person never opens the editor once per step. A connection the Setup page can't add, such as one for a tool attached to an AI Agent, keeps its link to the node in the editor.
  • In-chat setup card. In assistants that support MCP Apps, validate_draft and preview_publish also show a small setup card in the chat, drawn from the setupStatus field of their result. It lists each app still to connect, with a Set up button that opens the workflow's Setup page for that app, the number of other items the assistant will walk the person through, and the apps waiting on your client, which never hold publishing back. While it is on screen and setup is not finished, the card checks again by calling validate_draft, first every 5 seconds and then less often, for at most 40 checks (about 17 minutes); these checks count against the usual limits. When the last item is done, it posts one message into the chat so the assistant can carry on and publish. Assistants without MCP Apps ignore the card, and setupStatus is in the result for any assistant to read.
  • Irreversible effects need agreement. Registering a webhook with a provider, or emailing queued connection requests to your clients, cannot be undone by turning the workflow off. set_workflow_state asks for a previewHash before an activation with such an effect, or an expose or unexpose that changes another workflow's slug: without one it returns preview-required with details.mustConfirm, one sentence per effect to show the person. Turning a workflow off never needs one.
  • Turning on runs the version that was last live. set_workflow_state with activate turns a workflow back on with the version that was running when it was turned off, not the newest saved one. If newer saved changes exist, the result's unpublishedChanges names both versions and carries a message to show the person, on both the dry run and the apply. Those changes stay unpublished until you call preview_publish, then publish.
  • Deleting takes the confirm step. delete_workflow without previewHash deletes nothing: it returns preview-required with details.mustConfirm — the workflow's name, how many versions go with it, that its run history will no longer be viewable, and, if it was ever published, that its triggers (schedules and webhooks) are removed for good — and details.freshHash. Show the person those sentences; if they agree, call delete_workflow again with previewHash set to details.freshHash. If the workflow or any of its versions changed in between, the second call returns preview-stale with the new hash and deletes nothing. A workflow that is turned on, or still being published, is refused with deploy-refused and refusalCode: "workflow-live": turn it off first with set_workflow_state and { "kind": "deactivate" }, which needs Admin, then delete it.
  • Who may do what. Publishing a workflow follows the editor's rule: the person who created the version (or a platform admin). Deleting a workflow follows the app's rule too: only the person who created it (or a platform admin) can delete it; anyone else gets refusalCode: "not-workflow-creator". Turning workflows on and off, and exposing them, needs a workspace admin. Creating, editing and publishing a form need a workspace admin somewhere in the account. These role rules apply on top of the tool's permission (Build to create and edit, Admin to publish): a connection needs both.

Build a form

An assistant can make a form, fill in its fields, and publish it, with the same four tools it uses for workflows, each called with type: "form":

  1. create_workflow_draft({ type: "form", name: "Lead Intake" }) makes a draft form with one starter field, exactly like New form in the app. It returns the form's formId, its fields, its formHash and the form builder link.
  2. propose_changes({ type: "form", formId, baseFormHash, changeSet }) edits the draft. Every change set is checked against the same rules the form builder uses, all or nothing: if anything is wrong, nothing is saved. A malformed change set comes back as invalid-arguments naming the field; an op that can't apply is reported on its own, so fix it and send again; problems with the finished form (its rules and its size) are listed together.
  3. preview_publish({ type: "form", formId }) shows what going live means: anything still blocking it, the public link, and the fields added, removed or changed since it was last published.
  4. publish({ type: "form", formId, previewHash }) makes the public page start taking submissions and returns outcome: "published". It is refused with preview-stale if the form, or anything blocking it, changed since that preview.

A workflow then starts from the form with a Form Submission trigger whose formId is the new form. The workflow can't be published until the form is.

A form change set is a summary and a list of ops, applied in order. Fields are addressed by their key and pages by their number, as describe returns them; each op sees the form as the ops before it left it, so page numbers and positions shift as pages and fields are added or removed:

OpWhat it does
add-fieldAdds a field on a page, before position index (the end when left out).
update-fieldChanges a field's settings: set replaces each named setting whole, by the names add-field uses (to change one choice, resend the whole options list, built from the choices describe returns), and set.key renames the field; unset removes optional settings. A field's type can't change; remove it and add a new one.
remove-fieldRemoves a field.
move-fieldMoves a field to another position on its own page.
add-pageAdds an empty last page, with an optional title and description; the page that was last stops being the last step.
update-pageChanges a page's title, description, whether it is the last step (terminal), and its branchRules: where to go next, by the number of another page. unset removes the title, the description or the rules.
remove-pageRemoves a page and its fields. A form always keeps one page.
update-formChanges the form's title, description or submitButtonLabel; unset removes the description.
  • Field types: short_text, long_text, email, number, date, single_select, multi_select, checkbox, radio, file, signature, rating, address, phone and hidden, each with the settings the form builder offers for it. A new or renamed field's key is snake_case: a lowercase letter, then lowercase letters, digits or underscores, at most 64 characters. A key built in the app in another shape still works for addressing the field.
  • Conditions: a field's showIf and a page's branch rules are one comparison, such as { "==": [{ "var": "budget" }, "over_20k"] }, or an and / or list of up to 20 comparisons — the shape the form builder can show, so a person can still edit every rule. A comparison's value is text (up to 200 characters), a number, true, false or null. A field's showIf can't use the field itself, and a branch rule may also be true (always go there).
  • Size: a change set can leave a form with at most 200 fields and 50 pages, so describe can always show every field. A larger form built in the app can still be edited, as long as the change doesn't raise a count that is already over its limit.
  • Drafts only. An assistant edits draft forms only. A published form's page shows its saved fields straight away, so a person changes a live form in the form builder (or unpublishes it first). On a published form, propose_changes answers form-status-refused with a link to the form builder.
  • Reload an open form builder. The form builder saves the whole form. If you have the form open while an assistant edits it, reload the page before you change anything, or your save replaces the assistant's changes.

Check whether a workflow is live

describe({ id: "deployment:<workflowId>" }) answers for any workflow the connection reaches — published or not, exposed or not. It returns the active and last-active version ids, the newest version's status (for example pending-eval), whether it is exposed and under which slug, the setup steps still outstanding, and one entry per trigger with its phase:

PhaseMeaning
offThe workflow is not turned on.
provisioningStill being set up — a provider webhook registering, a phone number being pointed at the workflow, a schedule being created.
needs_setupLive, but waiting on a person — usually the webhook URL has not been registered with the sending system yet. The trigger's detail and the setupRequired steps say what to do.
liveReady to start runs.
failedStopped and will not recover by itself — for example the connection was revoked. The detail names the fix.

settled is true once no trigger is provisioning. While it is false, recheckAfterSeconds says when checking again is useful — poll until settled is true.

Read forms, tables, templates and app connection health

A connection that can author also reads the things a person builds with, so an assistant can wire a workflow to them without asking for every field key or column id:

  • Forms: find({ type: "form", query? }) lists the account's forms (draft and published) with each one's slug and status. describe({ id: "form:<id>" }) returns its fields: key (what a Form Submission trigger emits), label, type, required, page, a field's condition as showIf, and for a dropdown, multi-select or radio field its option values as options and each choice's label and value as choices. It also returns pages (each page's number, title, field count, whether it is the last step, and its branch rules) and formHash, the base for editing it with propose_changes (see Build a form). Up to 200 fields come back; omittedFieldCount says how many more there are.
  • Tables: find({ type: "table" }) lists the tables this workspace can use: its own, and the account's. describe({ id: "table:<id>" }) returns the columns (id, label, type, required, unique, indexed, and options on select columns), the primary key column and the time-to-live setting.
  • Templates: find({ type: "template" }) lists the account Library's templates: the agency's own (source: "account") and the built-in ones (source: "platform"). Templates other agencies share on the public gallery are not included. describe({ id: "template:<id>" }) returns the latest revision's steps, the apps and app connections it needs, and its variables.
  • App connections: find({ type: "connection" }) lists every app connection this workspace can reach, broken ones first, each with status, healthy and a plain reason. An account connection appears only once it is shared with this workspace, and only to account admins, the same as on the Apps page. describe({ id: "connection:<id>" }) adds token-refresh timing and humanLink, the connection's page in the app, where a person reconnects it or finds where to re-enter the credential. It never returns a secret or a token.

reason says what happened, then what a person does next:

statushealthyWhat happened
activetrueNothing: reason is null.
pendingfalseSetup was started but never finished.
errorfalseThe last attempt to use or refresh this connection failed.
reauth-requiredfalseFor a sign-in (OAuth) connection: the sign-in expired. For any other kind: the saved credential stopped working.
revokedfalseIt was disconnected.
Credential kindWhat a person does
Sign-in (OAuth)Reconnect it on its connection page.
API key, basic, custom or DSNRe-enter it from a node that uses it in the workflow editor.

When healthy is false, show the person the reason and the humanLink.

Build and fill tables

With Build, an assistant can create a table, change its columns, and read and change its rows. Tables have no draft: every change is live as soon as it saves, and the table editor's own save rules decide what a change may do.

A typical exchange:

  1. edit_table({ create: { name: "Leads" }, changes: [...] }) adds Email (the primary key), Name (required) and Status (a dropdown of new, contacted and won, indexed). The answer is the table with each column's id.
  2. table_rows({ tableId, action: { kind: "insert", rows: [{ values: { "<Email column id>": "ada@example.com", ... } }] } }) adds rows. Each row comes back with its rowId — the Email, because Email is the primary key.
  3. table_rows({ tableId, action: { kind: "query", where: { columnId: "<Status column id>", equals: "new" } } }) finds the rows still marked new.
  4. table_rows({ tableId, action: { kind: "update", rows: [{ rowId: "ada@example.com", values: { "<Status column id>": "contacted" } }] } }) changes one.
  5. table_rows({ tableId, action: { kind: "delete", rowIds: ["bob@example.com"] } }) answers preview-required and lists the row. The assistant shows the person, then calls again with previewHash set to details.freshHash, and the row is deleted.

Values are always keyed by column id, never by label, so renaming a column breaks nothing.

ChangeUndoWhat it needs
Create a tableDelete it in the app (Delete table on the tables list)Build
Add a column (optional)Remove it (confirm step)Build
Rename a columnRename it back; ids and workflow references never changeBuild
Retype a columnRetype it back: stored values are never rewritten, and the save rules refuse any change a stored value cannot surviveBuild
Remove a columnNone. Its values are never shown again, adding the column back does not restore them, and workflow steps that write it fail until changedBuild, then the confirm step
Insert rowsDelete them (confirm step)Build
Update rowsNone for the overwritten values, the same as a workflow's Update row stepBuild
Delete rowsNoneBuild, then the confirm step

How it behaves:

  • The confirm step. Deleting rows or removing a column first answers preview-required with details.mustConfirm, one sentence per row or column that will be lost, and changes nothing. Only a second call carrying previewHash set to details.freshHash goes ahead. If a row or the table changed in between, the second call answers preview-stale with the new hash and changes nothing.
  • Filter on indexed columns only. where matches one indexed column's exact value. A column that is not indexed answers invalid-arguments naming the indexed columns you can use; without where, query pages through every row (limit up to 50, default 20, and cursor from the previous page). A column can be indexed only while the table has no rows — with edit_table, or in the table editor.
  • Writes stop at the first failure. Rows are written one at a time, in order. The answer lists the rows written, the one that failed (its index, code and message), and how many were notAttempted. Nothing before the failure is undone, so fix the failed row and send it and the rest again.
  • Create-only settings. required and primaryKey can be set only when creating a table. An indexed or unique column can be added or removed only while the table has no rows.
  • Account tables. create: { scope: "account" } makes a table every workspace of the account can use. Creating or changing one, or writing its rows, needs an account admin or owner.
  • Archived tables can still be read with query, but not changed.

Retired tools

These tools were removed. A client that still lists one can call it, and gets entitlement-denied (not retryable) with a message saying the tool was retired and a hint naming its replacement. A retired call is not counted against your budget. If the replacement is not in the assistant's tool list either, reconnect the connector to refresh it.

Retired toolUse instead
list_workflowsfind({ type: "workflow" }) (type draft for drafts)
describe_workflowdescribe({ id: "workflow:<slug>" }) for inputs, describe({ id: "deployment:<workflowId>" }) for deployment status
get_rundescribe({ id: "run:<runId>" })
list_runsfind({ type: "run" })
list_skillsfind({ type: "skill" })
get_draftdescribe({ id: "draft:<workflowId>" })
describe_nodedescribe({ id: "node:<canonical>" })
ground_appsdescribe({ id: "app:<app slug>" }) for one app's nodes, find({ type: "node", query }) to search the catalog
<prefix><workflow-slug>run_workflow({ workflowSlug: "<workflow-slug>", input }) — the hint carries the slug

A per-workflow tool name was your account's prefix (taskjuice_, or your own with a connector domain) followed by the workflow's slug, so taskjuice_send_invoice is answered with run_workflow({ workflowSlug: "send_invoice", … }).

Errors

Every tool failure is a structured result with a code, a message, a retryable flag, and usually a hint naming the fix.

CodeMeaningRetry?
invalid-argumentsAn argument failed validation; the message names the field.Yes, corrected
not-foundNo such workflow, run, draft, skill, catalog app, form, table, template or app connection in this connection's scope. For a template, the hint adds that community templates from the public gallery cannot be read here; for an app connection, that an account connection appears only after it is shared with this workspace, and only to account admins.No
entitlement-deniedThe tool isn't available to this connection (plan, API key, URL, or, for control_run, a client's own sign-in), or the connection lacks the tool's permission — then details.requiredPermission names it — or the tool was retired, and the hint names its replacement (see Retired tools).No. If details.requiredPermission is set, reconnect (or use a new key) with that permission
rate-limitedAn hourly bound or the daily budget was hit.Yes, after the hint's backoff
run-refusedThe platform declined to start the run (limits, safety, disabled workflow).Not this session
proposal-rejectedThe change-set violated the workflow schema (every violation is listed), or a form change set broke the form builder's rules or the size limit (see Build a form).Yes, corrected
stale-draftThe draft or form changed since the hash you proposed against; details.freshHash is the current one.Yes — re-read with describe and rebase
compile-refusedvalidate_draft found a blocker, or propose_changes could not set up a trigger step (nothing was saved); the message names it.Yes, after repairing the draft
payload-too-largeTool arguments exceeded 256 KB.Yes, smaller
invalid-inputrun_workflow input didn't match the workflow's schema; the message names the field.Yes, corrected
preview-staleThe workflow, form, table or rows changed after the preview; details.freshHash is the current hash.Yes — preview again, then retry
preview-requiredThe change needs the person's agreement; details.mustConfirm lists what to show them, details.freshHash the hash to send.Yes, with the hash once they agree
internal-errorThe tool failed on our side; the cause is never returned.Yes, shortly
deploy-refusedPublishing, the state change or the delete was refused; details.refusalCode says why and the hint says what to do.Depends on the code — see below
form-status-refusedThe form is published (an assistant edits drafts only; the hint links the form builder) or archived (it can't be changed or published).No
run-control-refusedcontrol_run could not act: details.refusalCode is terminal, awaiting-approval, state-changed or not-ready.state-changed and not-ready yes; the others no
step-test-refusedtest_step could not test the step: details.refusalCode is not-testable, invalid-step, model-not-allowed, billing-plan-required, too-large, timed-out or execution-failed, and the hint says what to do. After timed-out the step may still finish, so check the target system before testing again.
table-refusededit_table or table_rows was refused; details.refusalCode says why and the hint says what to do — see below.Depends on the code — see below

A missing permission comes back as entitlement-denied with a details object, so an assistant can explain it without parsing the message. Permission names in details are lowercase: read, run, build, admin.

{
  "ok": false,
  "code": "entitlement-denied",
  "message": "publish needs the Admin permission, which this connection was not granted.",
  "hint": "Ask the person to reconnect this assistant and tick Admin on the Authorize access screen. Call whoami to see what this connection can do.",
  "retryable": false,
  "details": { "requiredPermission": "admin", "grantedPermissions": ["read", "run", "build"] }
}

For an API key the hint says instead that a key's permissions are fixed when it is created, and to ask for a new key. A tool the connection doesn't have at all answers Tool "<name>" is not available. with no details, the same as a name that doesn't exist (a retired name answers that it was retired instead). That covers a tool your plan, role, or credential doesn't include (API keys never reach Build or Admin tools). whoami says which of those applies.

deploy-refused codes an assistant can clear in the conversation (by changing the draft, waiting, or finishing setup steps) come back with retryable: true. Codes that need a person with another role, a plan change, or a reconnect come back false. One of those is mfa-step-up-required: your account requires two-factor authentication, and the assistant's connection was made before you verified it. Remove the TaskJuice connector in the assistant (Claude: Settings → Connectors; ChatGPT: Settings → Apps & Connectors), add it again, and complete the two-factor check when sign-in asks — then publish again.

table-refused codes:

refusalCodeWhenWhat to doRetry?
archivedThe table is archived, so it can't be changed. Its rows can still be read with query.Tell the person the table is archived. Use another table, or create one with edit_table.No
not-permittedThe person's account role can't make this change (an account table needs an account admin or owner).Tell the person; an account admin or owner has to make this change. Reconnecting won't help.No
plan-limitCreating the table would pass the plan's table limit.See details.planLimit. An account admin can upgrade the plan, or the person can delete a table they no longer use.No
schema-invalidA change can't be applied, or the table editor's save rules refuse it; the message names the change.Fix the change the message names and call again. describe the table to see its columns.Yes
cell-invalidA value doesn't fit its column, names a column the table doesn't have, a required column is missing, or an update changes the row's primary key.Fix the value to fit its column (describe the table for types and options) and call again.Yes
row-not-foundA row to delete doesn't exist (the message names up to five).Query the table for its current row ids, then call again.No
page-too-largeA query page is over 256 KB.Call again with a smaller limit. A row too large to return on its own can be seen in the app.Yes
duplicate-keyAn inserted row's primary key already exists.A row with that key exists. Query it and update it instead.Yes
unique-conflictA value another row already holds in a unique column.Another row holds that value. Use another value.Yes
row-limitAn insert would pass the plan's row limit.Tell the person; they can delete rows or upgrade the plan.No
storage-frozenThe account is on the Free plan and out of storage, so new rows can't be saved (an insert or update).Tell the person; the account owner can delete tables or agent memory they no longer need, or upgrade the plan.No

row-limit, storage-frozen, duplicate-key and unique-conflict only ever arrive as a write answer's failed.code, never as a refused call, and so can cell-invalid (one row of an insert or update) and row-not-found (one row of an update): the rows before the failed one are saved.

Limits

Tool calls are counted per account per day, shared across every connection and key — adding credentials never adds budget. Stopping or resuming a run with control_run is never counted against it, so an account that has used up its day can still stop runs.

PlanTool calls per day
Free50
Solo250
Starter1,000
Growth5,000
Scale20,000

On top of the daily budget, hourly bounds protect against runaways: 10,000 calls per account, and per-credential class caps (3,000 reads, 600 runs and step tests, 600 authoring calls including edit_table and table_rows, 300 mapping previews, and 60 publish calls — preview_publish, publish, set_workflow_state and delete_workflow share that one budget). control_run has no class cap and is not counted against the daily budget or a key's daily call cap; only the 10,000-per-account bound, which every call shares, applies to it. A call refused for a missing permission still counts, against the daily budget and these hourly bounds in the refused tool's class (for control_run, the 10,000-per-account bound only), so an assistant retrying a refused tool is capped like any other. Form calls count in their tool's own class: creating and editing a form as authoring calls, previewing and publishing one as publish calls. find and describe are counted in the class of the type they read: drafts, nodes, apps, forms, tables, templates and app connections as authoring calls, everything else as reads. A find or describe call for a type this connection can't read is refused and still counts, in that type's class. A call to a tool that isn't available at all is not counted. An API key's optional daily call cap bounds that key's share of the account budget — it lowers what one key can spend, never raises the total.

Authoring availability by plan: Starter includes the always-available tools only; Growth and Scale include authoring.

A table_rows call reads or writes at most 50 rows, and a query page returns at most 256 KB.

Workflow runs started through MCP are normal runs — they appear in run history and count toward your plan's usage like any other run.

Next steps

Was this helpful?