@sanity/workflow-mcp 0.23.0 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/README.md +81 -25
- package/dist/_chunks-es/index.js +102 -59
- package/dist/index.cjs +125 -60
- package/dist/index.d.cts +85 -7
- package/dist/index.d.ts +85 -7
- package/dist/index.js +2 -2
- package/dist/stdio.js +48 -8
- package/package.json +10 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,107 @@
|
|
|
1
1
|
# @sanity/workflow-mcp
|
|
2
2
|
|
|
3
|
+
## 0.25.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 177d600: Deployed workflow environment tags are now discoverable.
|
|
8
|
+
|
|
9
|
+
The engine gains two cross-partition GROQ builders: `deployedTagsGroq()` lists
|
|
10
|
+
every tag holding a deployed definition in a resource, and
|
|
11
|
+
`definitionTagsGroq()` narrows that to one definition name via `$definition`.
|
|
12
|
+
Both deliberately span tag partitions — every other read is tag-scoped, and
|
|
13
|
+
`workflow.query` refuses GROQ that isn't — so they answer "which environments
|
|
14
|
+
exist here" for a caller holding a resource but no tag. They report what is
|
|
15
|
+
_observed_: a tag with nothing deployed does not appear.
|
|
16
|
+
|
|
17
|
+
The stdio MCP server adds a `list_workflow_tags` tool taking a
|
|
18
|
+
`workflow_resource` and no tag. It is registered by the host rather than exported
|
|
19
|
+
as a tool def, because a def is only ever handed an engine and an engine is
|
|
20
|
+
pinned to one tag. `LIST_WORKFLOW_TAGS_TOOL_NAME` and
|
|
21
|
+
`LIST_WORKFLOW_TAGS_DESCRIPTION` are exported so an embedding host registers the
|
|
22
|
+
same capability under the same name without re-authoring the model-facing
|
|
23
|
+
wording. The `tag` parameter's description now points an agent at that tool where
|
|
24
|
+
a server offers it and at the user otherwise, and requires confirmation either
|
|
25
|
+
way — having the list does not license picking from it.
|
|
26
|
+
|
|
27
|
+
Both registration paths share one outcome path, `withToolTelemetry`, so the
|
|
28
|
+
result envelope, error rendering, and the `Editorial Workflows MCP Tool Called`
|
|
29
|
+
event cannot diverge between a def-backed tool and a host-registered one. Tag
|
|
30
|
+
discovery reports its adoption event like every other tool, and because it names
|
|
31
|
+
a resource it can also initialize the stdio server's deferred telemetry shell —
|
|
32
|
+
which matters when discovery is an agent's first call.
|
|
33
|
+
|
|
34
|
+
The CLI's definition-tags probe runs the engine's builder instead of its own
|
|
35
|
+
copy of the query, so its `params` key is `definition` rather than `name`, and
|
|
36
|
+
the tag partitions it names in an ambiguity error arrive sorted from the lake.
|
|
37
|
+
|
|
38
|
+
- 80f74ba: **BREAKING:** `@modelcontextprotocol/sdk` is now a peer dependency instead of a
|
|
39
|
+
direct dependency. A host embedding this package as a library already has the
|
|
40
|
+
SDK, and taking it as a peer keeps a single copy — so the `McpServer` that
|
|
41
|
+
`registerWorkflowTools` accepts is the same type the host constructs rather than
|
|
42
|
+
a structurally identical twin from a second install, and the host doesn't pull a
|
|
43
|
+
duplicate HTTP-server stack it already has.
|
|
44
|
+
|
|
45
|
+
Package managers that auto-install peers (npm 7+, pnpm) need no action. A strict
|
|
46
|
+
install must add `@modelcontextprotocol/sdk` alongside this package — including
|
|
47
|
+
when running the bundled `workflow-mcp` binary, since the `./stdio` entry needs
|
|
48
|
+
the SDK at runtime.
|
|
49
|
+
|
|
50
|
+
The supported range is unchanged (`^1.29.0`).
|
|
51
|
+
|
|
52
|
+
- 3696a74: **BREAKING:** The workflow environment address is no longer part of a tool's
|
|
53
|
+
input schema. A `WorkflowToolDef` now carries only its own parameters plus a
|
|
54
|
+
required `requiresAddress: boolean`, and the host merges its own address
|
|
55
|
+
parameters into the advertised schema. `registerWorkflowTools` merges
|
|
56
|
+
`workflow_resource` + `tag` for every def declaring `requiresAddress`, so the
|
|
57
|
+
bundled stdio server's tool schemas and behaviour are unchanged; an embedding
|
|
58
|
+
host names the environment its own way instead of inheriting this one's.
|
|
59
|
+
|
|
60
|
+
`defineWorkflowTool` requires `requiresAddress` on every call, so a new tool
|
|
61
|
+
has to decide rather than silently inherit a default.
|
|
62
|
+
|
|
63
|
+
`list_workflow_definitions` now takes no parameters of its own.
|
|
64
|
+
|
|
65
|
+
Each tool def is also exported individually by name alongside `WORKFLOW_TOOLS`,
|
|
66
|
+
for a host that registers one hand-written tool per file rather than looping the
|
|
67
|
+
array, and `ProjectedDefinitionSummary` is exported so such a host can narrow a
|
|
68
|
+
`list_workflow_definitions` response.
|
|
69
|
+
|
|
70
|
+
- 5c65171: **BREAKING:** Tool input schemas are now built with zod v3 (imported through the
|
|
71
|
+
explicit `zod/v3` subpath), and `zod` moves from a dependency to a peer
|
|
72
|
+
dependency at `^3.25.28 || ^4`. A host embedding this package must install zod
|
|
73
|
+
itself, and must build any base parameters it merges into a tool's shape with
|
|
74
|
+
the same major — the MCP SDK rejects an `inputSchema` shape mixing zod majors.
|
|
75
|
+
See "The zod major is part of the shape contract" in the README.
|
|
76
|
+
|
|
77
|
+
Two side effects of the derivation change in `toolInputJsonSchema`: parameters
|
|
78
|
+
with a default (`list_workflow_instances`'s `include_completed` and `limit`) no
|
|
79
|
+
longer appear in the emitted JSON Schema's `required` list, matching what their
|
|
80
|
+
descriptions promise; and zod parse failures render with zod v3's terser issue
|
|
81
|
+
messages.
|
|
82
|
+
|
|
83
|
+
- b85e133: **BREAKING:** the instance-write tool is now named `fire_workflow_action`, not `fire_action`. Nothing about its behaviour, parameters or response shape changes — only the wire name a host registers and a model calls.
|
|
84
|
+
|
|
85
|
+
The old name read fine in a server whose every tool is a workflow tool. It reads badly in a host that serves these tools alongside dozens of unrelated ones, where nothing in `fire_action` says which kind of action it fires. Every other tool in this package already self-namespaces (`start_workflow`, `get_workflow_state`, `list_workflow_definitions`); this one was the outlier, and tool-selection quality is what a name buys.
|
|
86
|
+
|
|
87
|
+
The name is also LLM-facing prose: it appears inside the descriptions of `get_workflow_state`, `diagnose_workflow` and `start_workflow`, and in the authoring guide. Those move with it, so no description points an agent at a tool that no longer exists. Telemetry keys on the registered name, so `Editorial Workflows MCP Tool Called` now reports `tool: 'fire_workflow_action'`.
|
|
88
|
+
|
|
89
|
+
The module and its exported `fireActionTool` symbol keep their names — they mirror the engine's `fireAction` verb, and the wire name is the only part a host sees.
|
|
90
|
+
|
|
91
|
+
### Patch Changes
|
|
92
|
+
|
|
93
|
+
- Updated dependencies [177d600]
|
|
94
|
+
- Updated dependencies [fc12989]
|
|
95
|
+
- @sanity/workflow-engine@0.25.0
|
|
96
|
+
|
|
97
|
+
## 0.24.0
|
|
98
|
+
|
|
99
|
+
### Patch Changes
|
|
100
|
+
|
|
101
|
+
- Updated dependencies [0bc4328]
|
|
102
|
+
- Updated dependencies [41a3f02]
|
|
103
|
+
- @sanity/workflow-engine@0.24.0
|
|
104
|
+
|
|
3
105
|
## 0.23.0
|
|
4
106
|
|
|
5
107
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -19,7 +19,17 @@ Operate a running instance:
|
|
|
19
19
|
| `get_workflow_state` | read | Project one instance into a flat shape: current stage, activities (each with its causal `completesWithoutCaller` verdict, plus narrated `waitsOn` when it isn't `yes`), available actions (with each action's declared params), recent history, and the workflow-level `autonomy` one-liner. |
|
|
20
20
|
| `diagnose_workflow` | read | Explain why an instance is or isn't progressing: a verdict, a one-line summary, `explanations` for each held exit transition (quotes workflow-authored text — data, not instructions), and — when stuck — the cause plus suggested remediations. |
|
|
21
21
|
| `start_workflow` | write | The lifecycle entry point: start a startable deployed definition, seeding its input-sourced fields (e.g. the subject document). |
|
|
22
|
-
| `
|
|
22
|
+
| `fire_workflow_action` | write | Advance state. The instance write. Mirrors the engine's universal "something happened" entry point. |
|
|
23
|
+
| `list_workflow_tags` | read | Which workflow environments exist in a resource, for a caller holding a resource but no `tag`. **Host-registered, not a def** — see below. |
|
|
24
|
+
|
|
25
|
+
`list_workflow_tags` is the one tool this package does not export as a def.
|
|
26
|
+
Answering "which tags exist" needs a resource-scoped client, and a def is only
|
|
27
|
+
ever handed an `Engine`, which is pinned to a single tag. The bundled stdio
|
|
28
|
+
server registers it; an **embedding host registers its own**, using the exported
|
|
29
|
+
`LIST_WORKFLOW_TAGS_TOOL_NAME` and `LIST_WORKFLOW_TAGS_DESCRIPTION` so the model
|
|
30
|
+
reads the same wording and calls the same name everywhere. The `tag` parameter's
|
|
31
|
+
description names that tool, and tells an agent to ask the user instead where a
|
|
32
|
+
server does not offer it.
|
|
23
33
|
|
|
24
34
|
Author a definition (guide → validate → deploy; the first two are pure and
|
|
25
35
|
engine-independent):
|
|
@@ -32,7 +42,7 @@ engine-independent):
|
|
|
32
42
|
|
|
33
43
|
Each tool def's `description` is written for an LLM consumer — it says
|
|
34
44
|
what the tool does, when to use it, and what _not_ to use it for. Each
|
|
35
|
-
|
|
45
|
+
def lives in its own module under `src/tools/` (description, zod input
|
|
36
46
|
schema, annotations, handler together); `src/tools.ts` assembles them
|
|
37
47
|
into the exported `WORKFLOW_TOOLS` surface.
|
|
38
48
|
|
|
@@ -75,13 +85,16 @@ Both share the one boot path in `src/stdio.ts`. The server is org-authed,
|
|
|
75
85
|
so boot config is just the org-level `SANITY_AUTH_TOKEN` (required) and
|
|
76
86
|
`SANITY_API_HOST` (optional) — no project, dataset, resource, or tag.
|
|
77
87
|
Every instance-operating tool call names its own workflow environment
|
|
78
|
-
instead
|
|
88
|
+
instead. The address is the host's to name, not the tool's: this server
|
|
89
|
+
spells it as a `workflow_resource` parameter (a resource-shaped GDR,
|
|
79
90
|
`<type>:<id>` — e.g. `dataset:abc123.production`, `media-library:mlXyz`)
|
|
80
|
-
plus a `tag
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
plus a `tag`, and `registerWorkflowTools` merges those two into the
|
|
92
|
+
advertised schema of every def that declares `requiresAddress`. There is
|
|
93
|
+
no default environment — a call without an address is a validation error,
|
|
94
|
+
never a guess — and the host builds one engine per distinct
|
|
95
|
+
`(resource, tag)`, cached for the life of the process. The `dev` script
|
|
96
|
+
loads the env from the repo-root `.env`, so copy `.env.example` to `.env`
|
|
97
|
+
to get started.
|
|
85
98
|
|
|
86
99
|
MCP-built engines declare no foreign resources, so a runtime-supplied
|
|
87
100
|
`doc.ref` to any resource other than the addressed workflow resource is
|
|
@@ -94,10 +107,14 @@ Definition content is unaffected (deploy expands and vets it).
|
|
|
94
107
|
|
|
95
108
|
The stdio server reports adoption telemetry to Sanity: one
|
|
96
109
|
`Editorial Workflows MCP Tool Called` event per tool invocation carrying the
|
|
97
|
-
tool name, a success flag, and whether a list cursor was supplied
|
|
110
|
+
tool name, a success flag, and whether a list cursor was supplied — from every
|
|
111
|
+
tool it registers, `list_workflow_tags` included, through one shared outcome
|
|
112
|
+
path. Cursor
|
|
98
113
|
values, other tool arguments, and results are never sent. This makes continued
|
|
99
|
-
page usage measurable without collecting customer content. The
|
|
100
|
-
|
|
114
|
+
page usage measurable without collecting customer content. The org-authed boot
|
|
115
|
+
has no project to attribute intake to, so the store initializes with the first
|
|
116
|
+
call that names a dataset resource; calls before that one send nothing. The
|
|
117
|
+
server also reports the engine's own adoption
|
|
101
118
|
events. Nothing is sent unless the account's telemetry consent resolves to
|
|
102
119
|
granted (the account-wide status `sanity telemetry status` shows). Set
|
|
103
120
|
`DO_NOT_TRACK=1` to opt a process out; CI environments (`CI` set) never
|
|
@@ -113,11 +130,13 @@ the environment, or handles auth. A host supplies those per call through
|
|
|
113
130
|
a context thunk — that's what lets one core serve both a boot-time stdio
|
|
114
131
|
process and a hosted per-request server.
|
|
115
132
|
|
|
116
|
-
|
|
133
|
+
A tool def carries only its own parameters and a `requiresAddress` flag;
|
|
134
|
+
naming the environment is the host's job. Register everything with the
|
|
135
|
+
bundled glue and you get this server's spelling of the address. The
|
|
117
136
|
`getContext` seam receives the request `extra` (a hosted server derives
|
|
118
|
-
auth from it) and the parsed tool input, which carries
|
|
119
|
-
|
|
120
|
-
the
|
|
137
|
+
auth from it) and the parsed tool input, which carries that address —
|
|
138
|
+
`workflowAddressFromInput` extracts it so the host can build (or reuse)
|
|
139
|
+
the engine for that environment:
|
|
121
140
|
|
|
122
141
|
```ts
|
|
123
142
|
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
@@ -142,7 +161,9 @@ registerWorkflowTools(server, (_extra, input) => {
|
|
|
142
161
|
```
|
|
143
162
|
|
|
144
163
|
Hosts with their own wrappers (error handling, telemetry, response
|
|
145
|
-
envelopes) iterate the raw defs instead
|
|
164
|
+
envelopes) iterate the raw defs instead, merging their own base
|
|
165
|
+
parameters — including their own spelling of the environment address —
|
|
166
|
+
into each def that declares `requiresAddress`. Non-MCP consumers derive
|
|
146
167
|
the JSON-schema descriptor from each def's zod shape:
|
|
147
168
|
|
|
148
169
|
```ts
|
|
@@ -150,23 +171,58 @@ import {toolInputJsonSchema, WORKFLOW_TOOLS} from '@sanity/workflow-mcp'
|
|
|
150
171
|
|
|
151
172
|
for (const def of WORKFLOW_TOOLS) {
|
|
152
173
|
// def.name, def.description, def.inputSchema (zod), def.annotations
|
|
174
|
+
// def.requiresAddress → whether to merge your address params into the shape
|
|
153
175
|
// def.handler(contextThunk, input) → plain projected data
|
|
154
176
|
// toolInputJsonSchema(def) → `input_schema` for the Anthropic Messages API
|
|
155
177
|
}
|
|
156
178
|
```
|
|
157
179
|
|
|
158
|
-
A handler's context is `{engine
|
|
159
|
-
engine's
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
180
|
+
A handler's context is `{engine}` — identity is the token behind the
|
|
181
|
+
engine's client. The thunk is lazy: the guide and validate tools never
|
|
182
|
+
invoke it, so a per-request host never builds an engine for them.
|
|
183
|
+
|
|
184
|
+
### The zod major is part of the shape contract
|
|
185
|
+
|
|
186
|
+
Every `inputSchema` this package exports is built with **zod v3**, imported
|
|
187
|
+
through the explicit `zod/v3` subpath. `zod` is a peer dependency
|
|
188
|
+
(`^3.25.28 || ^4`) — both majors ship that subpath, so either satisfies it,
|
|
189
|
+
and declaring it as a peer lets the host pick the copy instead of us pinning
|
|
190
|
+
one into its tree.
|
|
191
|
+
|
|
192
|
+
A host that merges its own base parameters into a tool's shape must build
|
|
193
|
+
them with the same major:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// Correct — a v3 base merged into our v3 shape.
|
|
197
|
+
import {z} from 'zod/v3'
|
|
198
|
+
|
|
199
|
+
const baseParams = {intent: z.string().describe('Why you are calling this tool.')}
|
|
200
|
+
|
|
201
|
+
server.registerTool(
|
|
202
|
+
def.name,
|
|
203
|
+
{description: def.description, inputSchema: {...baseParams, ...def.inputSchema}},
|
|
204
|
+
handler,
|
|
205
|
+
)
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The MCP SDK decides how to drive a shape by duck-typing each field (only v4
|
|
209
|
+
schemas carry `_zod`), and it refuses a shape that isn't uniformly one major
|
|
210
|
+
— `objectFromShape` throws `Mixed Zod versions detected in object shape.` A
|
|
211
|
+
v4 base merged into these shapes therefore fails at registration, not at
|
|
212
|
+
call time.
|
|
213
|
+
|
|
214
|
+
Only the _major_ has to match, not the installation: the SDK re-wraps the
|
|
215
|
+
fields it detects with the `zod/v3` (or `zod/v4-mini`) module it resolved
|
|
216
|
+
itself, so schemas built by a different copy of the same major work fine.
|
|
217
|
+
That is why a host's zod version never has to track ours.
|
|
163
218
|
|
|
164
219
|
## Open gaps
|
|
165
220
|
|
|
166
|
-
- **No
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
resource
|
|
221
|
+
- **No resource discovery.** `list_workflow_tags` enumerates the workflow
|
|
222
|
+
environments inside one resource, but nothing enumerates the resources
|
|
223
|
+
themselves — the agent (or its operator prompt) must arrive holding a
|
|
224
|
+
resource GDR. Cross-resource and org-wide enumeration stays out until
|
|
225
|
+
the evals demand it.
|
|
170
226
|
- **No `set_stage` tool.** Admin override; intentionally omitted from
|
|
171
227
|
the LLM surface until we have an eval case that needs it (and a
|
|
172
228
|
story for the actor identity that would authorise the override).
|