Workflows MCP tools reference
Every operation @sanity/workflow-mcp gives an agent host, with its input, result, and errors, plus the embedding API.
This page lists every operation that @sanity/workflow-mcp gives an agent host, with its input, result, and errors. It also lists the exports a host uses to build engines, record telemetry, and report errors. For how a host wraps the operations in tools, see Connect an agent over MCP.
How to call an operation
Most operations are async functions you call as operation({engine}, input). The engine serves one workflow environment, and its client’s token is the acting identity. Three operations differ:
listWorkflowDefinitions({engine})takes no input.listWorkflowPartitions({client})takes a client for one resource instead of an engine.validateWorkflowDefinitions({definitions})is synchronous and takes no engine.
Inputs use camelCase, such as instanceId and includeCompleted. The operations don’t validate their input at runtime, so check each tool’s input against your own schema first. The one exception is the list cursor, which listWorkflowInstances decodes and checks itself. Each operation returns plain data, and each result has an exported type, such as StartWorkflowResult or DeployDefinitionsResult.
Each entry lists a suggested tool name. The authoring guide and some error messages use those names, so a host that uses different names or input spellings should check that this text still makes sense to its agent.
Workflow environment address
There’s no default environment. Build each engine for the address of one deployment: the workflowResource and partition of a deployment in your sanity.workflow.ts. A deployment that omits partition uses default. The address type is WorkflowEnvironmentAddress:
RequiredworkflowResource
WorkflowResource, required
The resource that holds the workflow documents, as
{type, id}.typeis'dataset','canvas','media-library', or'dashboard'. A datasetidis<projectId>.<dataset>. To parse a resource string such asdataset:PROJECT_ID.DATASET_NAMEfrom tool input, callparseResourceGdrfrom@sanity/workflow-engine. A malformed string throwsInvalid resource GDR "<value>": expected "<type>:<id>" with no document part.Requiredpartition
string, required
The deployment partition within that resource: lowercase letters, digits, and dashes, starting with a letter or digit.
listWorkflowPartitionslists the partitions with deployed definitions. An address with atagkey throws[contract-violation] "tag" is not supported. Replace tag: "dev" with partition: "dev". Keep the existing partition value to retain access to stored workflows..
import type {WorkflowEnvironmentAddress} from '@sanity/workflow-mcp'
export const address: WorkflowEnvironmentAddress = {
workflowResource: {type: 'dataset', id: 'PROJECT_ID.DATASET_NAME'},
partition: 'dev',
}Results and errors
An operation returns plain data that your host can serialize as the tool result. The guide’s example sends it as indented JSON in one text content item.
An operation throws on failure. Render the error for the agent with workflowErrorText:
- An engine error starts with its stable
[kind], for example[instance-not-found],[contract-violation], or[reader-model-acknowledgement]. - Any other error shows its message.
Some results carry authored text, such as titles, descriptions, conditions, or subject titles. Tell your agent to treat that text as data, not instructions, for example in your tool description.
Discovery and operations
listWorkflowPartitions({client})(): Promise<ListWorkflowPartitionsResult>
Lists the partitions that have deployed definitions in one resource, sorted ascending. A partition with nothing deployed doesn’t appear. An empty list means the resource holds no deployed workflows.
Takes a client for the workflow resource instead of an engine, and no input. Build the client with
workflowClientConfig. Read-only. Suggested tool name:workflows_list_partitions. Result:{partitions: string[]}.listWorkflowDefinitions({engine})(): Promise<ListWorkflowDefinitionsResult>
The catalog of deployed workflow types, latest version per name. Each entry reports
startable(false for child workflows that only run under a parent) andstartKind:interactive(a person starts runs from a picker) orautonomous(a system starts runs in reaction to a document).startKindis a classification, not a restriction.Takes no input. Read-only. Suggested tool name:
workflows_list_definitions. Result:{definitions: {name, title, description?, version, startable, startKind}[]}.getWorkflowDefinition({engine}, input)(definition, version): Promise<ProjectedDefinition>
One deployed definition’s full content, by
name, defaulting to the latest version. The returneddefinitionis the stored form without the document envelope. You can pass it tovalidateWorkflowDefinitionsanddeployWorkflowDefinitionsas is. To edit a workflow, read it, change it, validate it, and deploy the next version.Read-only. Suggested tool name:
workflows_get_definition. Result:{name, version, definition}.Error, unknown definition:
no deployed definition "nope" in this workflow environment — workflows_list_definitions shows what is deployedParameters
definition
string
The definition’s
name, aslistWorkflowDefinitionslists it.version
number
A specific deployed version to read. Defaults to the latest.
listWorkflowInstances({engine}, input?)(definition, document, includeCompleted, assignment, limit, cursor): Promise<ListWorkflowInstancesResult>
Lists instances in the engine’s partition, most recently changed first. Every input field is optional.
assignmentfilters to one person. It also adds counts of active activities in the current stage for each instance:held: assigned directly to the person.routed: offered through one of the person’s roles, with no person assigned.unrouted: no person or role selected.
The counts don’t promise that an action is available. A page can hold fewer instances than
limit, because thedocumentfilter drops instances that don’t watch that document. Whenhas_moreistrue, call again with the same filters and passnext_cursorascursor.Read-only. Suggested tool name:
workflows_list_instances. Result:{instances: {instanceId, definition, workflowTitle?, currentStage, currentStageTitle?, lastChangedAt, done, subject?: {ref, title?}, assignment?: {unrouted, routed, held}}[], has_more, next_cursor?}.Error, malformed cursor:
workflows_list_instances: invalid cursorError, cursor from other filters or another environment:
workflows_list_instances: cursor does not match the current filtersParameters
definition
string
Restrict to instances of this workflow definition, by its
name(for example 'article-review').document
GdrUri
Only instances that reference this document, as the subject or from any field. Pass a global document reference (GDR), for example
dataset:PROJECT_ID:DATASET_NAME:article-1.includeCompleted
boolean
trueincludes completed and aborted instances. Defaults tofalse.assignment
{userId, roles?, states?}
Filter to one person’s assignments.
userIdis the person’s Sanity user ID.roleslists their project role names, matched exactly.stateslists the states to include:'unrouted','routed', or'held'. Defaults to all three.limit
number
Results per page. Defaults to
DEFAULT_LIST_LIMIT, 25. The operation doesn’t cap it, so set a maximum in your tool’s schema.cursor
string
The
next_cursorfrom the previous page. Pass the same filters and use the same environment.
getWorkflowState({engine}, input)(instanceId): Promise<ProjectedInstanceState>
Returns everything actionable about one instance: the current stage, the subject, a summary of what the workflow waits on, recent history, and each in-scope activity on the current stage. For each activity, it reports:
- Its classification: interactive, autonomous, off-system, or hybrid.
- A
completesWithoutCallerverdict: yes, no, or conditional. When it isn’t yes,waitsOnlines explain what it waits for. - The actions a caller can fire, each with an allowed or disabled verdict and its declared params.
- The automations the engine fires on its own.
Activities and actions that don’t apply to this stage visit or caller are left out.
Read-only. Suggested tool name:
workflows_get_state. Result:{instanceId, definition, workflowTitle?, currentStage, currentStageTitle?, done, subject?, activities, autonomy, recentHistory}.Error, unknown instance:
[instance-not-found] Workflow instance dev.wf-instance.missing not foundParameters
instanceId
string
The workflow instance ID.
diagnoseWorkflow({engine}, input)(instanceId): Promise<ProjectedDiagnosis>
Explains why an instance is or isn’t progressing.
stateisprogressing,waiting,blocked,completed,aborted, orstuck. A stuck instance adds acause.remediationsare advisory, and no operation runs them.Read-only. Suggested tool name:
workflows_diagnose. Result:{instanceId, state, summary, cause?, explanations?, remediations}.Parameters
instanceId
string
The workflow instance ID.
startWorkflow({engine}, input)(definition, version, initialFields, instanceId): Promise<StartWorkflowResult>
Starts a deployed definition. Only definitions with
startable: truestart here, because child workflows only start from a parent. Every start requirement must pass. The engine runs the cascade before the result returns, so the instance can already be past its first stage.Pass your own
instanceIdto make a start safe to retry. If a start fails partway, retry it with the same ID. If the instance was created but its first automatic step failed, the result still holds the instance, with astartNotSettlednote.Writes. Suggested tool name:
workflows_start. Result: thegetWorkflowStateshape, plusstartNotSettled?.Error, unknown definition:
no deployed definition "nope" in this workflow environment — workflows_list_definitions shows what is deployedError, undeclared field:
workflow declares no field "bogus" — input fields: subjectParameters
definition
string
The definition’s
name, aslistWorkflowDefinitionslists it.version
number
The deployed definition version to start from. Defaults to the highest.
initialFields
Record<string, unknown>
Values for the workflow’s input-sourced fields, keyed by field name. A
doc.refvalue is an object with a GDRidand a documenttype, for example{subject: {id: 'dataset:PROJECT_ID:DATASET_NAME:article-1', type: 'article'}}.instanceId
string
The ID to start the instance under. Retrying with the same ID resumes that start instead of creating a second instance. Leave it out to generate a new ID.
fireWorkflowAction({engine}, input)(instanceId, activity, action, params): Promise<ProjectedInstanceState>
Fires an action on an activity in the current stage, as an editor would, with values for the action’s declared
params. Pick the activity and action fromgetWorkflowState. Actions that the engine fires on its own are listed as automations, and this operation rejects them. Transitions that become eligible run in the same call. A second call with the same input can fire again from the new stage.Writes. Suggested tool name:
workflows_fire_action. Result: thegetWorkflowStateshape.Error, activity not in the current stage:
[contract-violation] Activity "write" not found in current stage "review" of article-reviewParameters
instanceId
string
The workflow instance ID.
activity
string
An activity in the current stage, as
getWorkflowStatereturns it.action
string
An action on that activity that
getWorkflowStatelists as allowed.params
Record<string, unknown>
Values for the action’s declared params, keyed by param name. Required when the action declares a required param. The engine checks the values against the declared params and rejects the call if a required one is missing.
Authoring
WORKFLOW_AUTHORING_GUIDE(): string
The guide an agent reads before it writes a definition, as one Markdown string. It covers the definition shape, GROQ condition built-ins, shorthand forms, the rules the validator enforces, and worked JSON examples, and it refers to the suggested tool names. Suggested tool name:
workflows_get_authoring_guide.validateWorkflowDefinitions({definitions})(definitions): ValidateDefinitionsResult
Checks structure, cross-field rules, and GROQ syntax without reading the Content Lake. It’s synchronous and takes no engine. Validate a parent and its child workflows together. Each result is the desugared definition that would deploy, or a path-prefixed list of every problem.
Deploy also checks duplicate names, spawn dependencies, resource bindings, project roles, and the reader-model acknowledgement. So a valid result doesn’t guarantee that a deploy succeeds.
sanity-workflows definitions validate <file...> --jsonprints the same report.Read-only. Suggested tool name:
workflows_validate_definition. Result:{valid, results: ({valid: true, definition} | {valid: false, error})[]}.Parameters
definitions
readonly Record<string, unknown>[]
Workflow definitions as JSON objects in authoring shape. A single workflow is a one-element array.
deployWorkflowDefinitions({engine}, input)(definitions, expectedMinReaderModel): Promise<DeployDefinitionsResult>
Deploys definitions into the engine’s partition. Pass a parent and the children it spawns in one call, and the children deploy first. Deploys are create-only. Content identical to the latest version returns
unchanged. Any change returnscreatedwith the next version, and a new name starts at version 1. Running instances keep the version they started under.It runs the same checks as
validateWorkflowDefinitions, but throws one error that lists every definition that fails.Writes. Repeating a deploy with the same content changes nothing. Suggested tool name:
workflows_deploy_definition. Result:{results: {name, version, status}[], deployId}.Error, invalid definition:
definitions[0] ("x"): defineWorkflow("x") failed validation (3 issues):Error, acknowledgement below the floor:
[reader-model-acknowledgement] Deployment acknowledges reader model 4; the submitted definitions require at least 10, and this writer supports acknowledgements through 11.Parameters
definitions
readonly Record<string, unknown>[]
Workflow definitions as JSON objects in authoring shape, the same values
validateWorkflowDefinitionstakes.expectedMinReaderModel
number
The highest reader model that every runtime sharing this workflow resource already supports. Defaults to 4. Definitions with role-constrained assignment fields need 8, and singular assignment fields need 9. Required
subject,doc.ref, ordoc.refsfields need 10, and anabortblock needs 11. Raise it only after you upgrade every Studio, CLI, agent host, Function, and other runtime. See Reader-model acknowledgement and Upgrade Workflows packages.
Embedding API
Agent hosts use these exports to build engines and clients, record telemetry, and render errors. The package root is the only entry point.
createWorkflowEngine({address, client, executionContext?, telemetry?})(): Engine
Creates an engine bound to one resource and partition, without caching. A long-lived host can cache engines itself. The client’s token identifies the actor, so keep its authentication unchanged for the engine’s lifetime.
executionContextadds an advisory host label to history entries, andtelemetrytakes theloggerfromcreateMcpSessionTelemetry.The engine accepts document references into any resource the client’s token can reach, and reads a subject’s title from the subject’s own resource. Scope that token to only the resources the agent should reach. For a narrower engine, build one with
createEnginefrom@sanity/workflow-engineand your ownresourceClientsresolver, and pass it to the operations.workflowClientConfig({resource, token?, base})(): Base & WorkflowClientPolicy & WorkflowResourceAddressing
Builds the client config for engine traffic on top of your host’s own config,
base. Pass{}if you have none. It keeps the requester, headers, andapiHostfrombase.It then sets the engine API version,
useCdn: false, thepublishedperspective, and the request-tag prefixsanity.workflows-mcp. For a dataset, it setsprojectIdanddataset. For any other resource, it setsresource. It removes the other addressing style, so a leftover value frombasecan’t point the client at another environment.createMcpSessionTelemetry({clientFor, denied, nodeEnv?, sessionId?})(): McpSessionTelemetry
Opt-in telemetry for an embedding host. Embedding the package doesn’t turn it on.
clientFor(resource)returns an authenticated client. Create one per MCP session, and pass itsloggertocreateWorkflowEngine. Callbind(resource)with each call’s workflow resource before the engine runs. The first dataset resource owns the session, and later calls don’t change it. Awaitflush()beforeend()when the session closes.Nothing is sent when
deniedistrue, or whenCIorDO_NOT_TRACKis set to a true value such as1ortrue. Otherwise, the account’s telemetry consent decides.workflowErrorText(error)(): string
The text a failed tool call should show:
[<kind>] <message>for aWorkflowError, otherwise the message.
Each operation also exports its input type, such as StartWorkflowInput and ListWorkflowInstancesInput. Result types are ListWorkflowDefinitionsResult, ListWorkflowInstancesResult, ListWorkflowPartitionsResult, StartWorkflowResult, DeployDefinitionsResult, ProjectedDefinitionSummary, ProjectedDefinition, ProjectedInstanceSummary, ProjectedInstanceState, ProjectedActivity, ProjectedActionVerdict, ProjectedAutomation, ProjectedHistoryEntry, ProjectedSubject, ProjectedDiagnosis, ValidateDefinitionResult, and ValidateDefinitionsResult. See TypeDoc for their fields.
The package has one peer dependency, @sanity/workflow-engine, at the same version. It doesn’t depend on zod or @modelcontextprotocol/sdk, so your host chooses its own schema library and MCP SDK.