@sanity/workflow-mcp 0.5.1 → 0.7.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 +435 -0
- package/README.md +114 -45
- package/bin/run.js +0 -5
- package/dist/_chunks-es/index.js +862 -0
- package/dist/index.cjs +815 -483
- package/dist/index.d.cts +199 -99
- package/dist/index.d.ts +199 -99
- package/dist/index.js +2 -519
- package/dist/stdio.d.ts +14 -10
- package/dist/stdio.js +119 -68
- package/package.json +9 -6
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/stdio.js.map +0 -1
package/README.md
CHANGED
|
@@ -11,23 +11,30 @@ the tools the evals drive, plus a stdio MCP entry point for real MCP clients.
|
|
|
11
11
|
|
|
12
12
|
Operate a running instance:
|
|
13
13
|
|
|
14
|
-
| Tool
|
|
15
|
-
|
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `
|
|
19
|
-
| `
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
14
|
+
| Tool | Read or write | What it's for |
|
|
15
|
+
| --------------------------- | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
16
|
+
| `list_workflow_definitions` | read | The catalogue: which workflow types are deployed (latest version each), and whether they're startable. |
|
|
17
|
+
| `get_workflow_definition` | read | Read one deployed definition's content, envelope-stripped — redeployable as-is, so an agent can iterate on a deployed workflow (deploys are create-only). |
|
|
18
|
+
| `list_workflow_instances` | read | Discover what's running. Filterable (`definition`, `document`, `include_completed` — in-flight by default), capped. |
|
|
19
|
+
| `get_workflow_state` | read | Project one instance into a flat shape: current stage, activities, available actions (with each action's declared params), recent history. |
|
|
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
|
+
| `start_workflow` | write | The lifecycle entry point: start a startable deployed definition, seeding its input-sourced fields (e.g. the subject document). |
|
|
22
|
+
| `fire_action` | write | Advance state. The instance write. Mirrors the engine's universal "something happened" entry point. |
|
|
23
|
+
|
|
24
|
+
Author a definition (guide → validate → deploy; the first two are pure and
|
|
25
|
+
engine-independent):
|
|
26
|
+
|
|
27
|
+
| Tool | Read or write | What it's for |
|
|
28
|
+
| ------------------------------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `get_workflow_authoring_guide` | read | Return the DSL guide an agent reads before authoring — shape, GROQ built-ins, sugars, modeling defaults, and two worked JSON examples. |
|
|
30
|
+
| `validate_workflow_definition` | read | Check authored definitions (a `definitions` batch) the way deploy does (structure + invariants + GROQ). Returns `{valid, results}` — per definition, the desugared form or a path-prefixed error list. |
|
|
31
|
+
| `deploy_workflow_definition` | write | Publish validated definitions into an addressed environment — one `definitions` batch per call (children deploy before the parents that spawn them). Create-only and content-addressed: identical content is an `unchanged` no-op, any change mints the next version. |
|
|
32
|
+
|
|
33
|
+
Each tool def's `description` is written for an LLM consumer — it says
|
|
34
|
+
what the tool does, when to use it, and what _not_ to use it for. Each
|
|
35
|
+
tool lives in its own module under `src/tools/` (description, zod input
|
|
36
|
+
schema, annotations, handler together); `src/tools.ts` assembles them
|
|
37
|
+
into the exported `WORKFLOW_TOOLS` surface.
|
|
31
38
|
|
|
32
39
|
## Why this surface, why no more
|
|
33
40
|
|
|
@@ -36,9 +43,17 @@ the verbs. An MCP is consumed by an LLM that figures out which tool
|
|
|
36
43
|
to use from prose alone. Adding tools beyond the minimum increases
|
|
37
44
|
the search space the LLM has to navigate. We stay narrow until the
|
|
38
45
|
evals tell us the LLM is reaching for something we don't expose. The
|
|
39
|
-
authoring
|
|
40
|
-
eval-backed; the agent generates the
|
|
41
|
-
`validate_workflow_definition
|
|
46
|
+
authoring trio earns its place because authoring-from-a-description is
|
|
47
|
+
eval-backed; the agent generates the definitions, self-corrects against
|
|
48
|
+
`validate_workflow_definition`, then publishes with
|
|
49
|
+
`deploy_workflow_definition` — safe to hand an agent because deploy is
|
|
50
|
+
create-only: it can never patch or clobber what's already deployed, and
|
|
51
|
+
running instances keep the definition version they started under.
|
|
52
|
+
`get_workflow_definition` closes the loop that create-only would
|
|
53
|
+
otherwise leave open — iterating on a deployed workflow means reading
|
|
54
|
+
it back, modifying, and deploying the next version — and
|
|
55
|
+
`start_workflow` is the lifecycle entry point the catalogue's
|
|
56
|
+
`startable` flag advertises.
|
|
42
57
|
|
|
43
58
|
## Run the stdio server
|
|
44
59
|
|
|
@@ -56,44 +71,98 @@ it's `bin/run.js`, and once the package is published this is what
|
|
|
56
71
|
node packages/workflow-mcp/bin/run.js
|
|
57
72
|
```
|
|
58
73
|
|
|
59
|
-
Both share the one boot path in `src/stdio.ts
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
Both share the one boot path in `src/stdio.ts`. The server is org-authed,
|
|
75
|
+
so boot config is just the org-level `SANITY_AUTH_TOKEN` (required) and
|
|
76
|
+
`SANITY_API_HOST` (optional) — no project, dataset, resource, or tag.
|
|
77
|
+
Every instance-operating tool call names its own workflow environment
|
|
78
|
+
instead: a `workflow_resource` parameter (a resource-shaped GDR,
|
|
79
|
+
`<type>:<id>` — e.g. `dataset:abc123.production`, `media-library:mlXyz`)
|
|
80
|
+
plus a `tag`. There is no default environment — a call without an address
|
|
81
|
+
is a validation error, never a guess — and the host builds one engine per
|
|
82
|
+
distinct `(resource, tag)`, cached for the life of the process. The `dev`
|
|
83
|
+
script loads the env from the repo-root `.env`, so copy `.env.example` to
|
|
84
|
+
`.env` to get started.
|
|
85
|
+
|
|
86
|
+
### Telemetry
|
|
87
|
+
|
|
88
|
+
The stdio server reports adoption telemetry to Sanity: one
|
|
89
|
+
`Editorial Workflows MCP Tool Called` event per tool invocation carrying the
|
|
90
|
+
tool name and a success
|
|
91
|
+
flag — never tool arguments or results — plus the engine's own adoption
|
|
92
|
+
events. Nothing is sent unless the account's telemetry consent resolves to
|
|
93
|
+
granted (the account-wide status `sanity telemetry status` shows). Set
|
|
94
|
+
`DO_NOT_TRACK=1` to opt a process out; CI environments (`CI` set) never
|
|
95
|
+
send. Separately from consent, every Content Lake request the server issues
|
|
96
|
+
carries a request tag under the `sanity.workflows-mcp` prefix (composing as
|
|
97
|
+
`sanity.workflows-mcp.<op>`) for request-log attribution — deliberately outside
|
|
98
|
+
the zero-rated `sanity.workflows.*` family, so agent-driven traffic bills.
|
|
66
99
|
|
|
67
100
|
## API
|
|
68
101
|
|
|
102
|
+
The package is a host-neutral library: it never builds an engine, reads
|
|
103
|
+
the environment, or handles auth. A host supplies those per call through
|
|
104
|
+
a context thunk — that's what lets one core serve both a boot-time stdio
|
|
105
|
+
process and a hosted per-request server.
|
|
106
|
+
|
|
107
|
+
For an MCP host, register everything with the bundled glue. The
|
|
108
|
+
`getContext` seam receives the request `extra` (a hosted server derives
|
|
109
|
+
auth from it) and the parsed tool input, which carries the per-call
|
|
110
|
+
workflow environment address — `workflowAddressFromInput` extracts it so
|
|
111
|
+
the host can build (or reuse) the engine for that environment:
|
|
112
|
+
|
|
69
113
|
```ts
|
|
70
|
-
import {
|
|
71
|
-
import {
|
|
114
|
+
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
115
|
+
import {createEngine, resourceGdr, type Engine} from '@sanity/workflow-engine'
|
|
116
|
+
import {registerWorkflowTools, workflowAddressFromInput} from '@sanity/workflow-mcp'
|
|
117
|
+
|
|
118
|
+
const server = new McpServer({name: 'my-host', version: '1.0.0'})
|
|
119
|
+
|
|
120
|
+
// One engine per addressed environment, reused across calls.
|
|
121
|
+
// clientFor(resource): your host's @sanity/client for that resource —
|
|
122
|
+
// the bundled stdio host's version lives in `src/engine-cache.ts`.
|
|
123
|
+
const engines = new Map<string, Engine>()
|
|
124
|
+
|
|
125
|
+
registerWorkflowTools(server, (_extra, input) => {
|
|
126
|
+
const {workflowResource, tag} = workflowAddressFromInput(input)
|
|
127
|
+
const key = `${resourceGdr(workflowResource)} ${tag}`
|
|
128
|
+
const engine =
|
|
129
|
+
engines.get(key) ?? createEngine({client: clientFor(workflowResource), workflowResource, tag})
|
|
130
|
+
engines.set(key, engine)
|
|
131
|
+
return {engine}
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Hosts with their own wrappers (error handling, telemetry, response
|
|
136
|
+
envelopes) iterate the raw defs instead — and non-MCP consumers derive
|
|
137
|
+
the JSON-schema descriptor from each def's zod shape:
|
|
72
138
|
|
|
73
|
-
|
|
74
|
-
|
|
139
|
+
```ts
|
|
140
|
+
import {toolInputJsonSchema, WORKFLOW_TOOLS} from '@sanity/workflow-mcp'
|
|
75
141
|
|
|
76
|
-
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
142
|
+
for (const def of WORKFLOW_TOOLS) {
|
|
143
|
+
// def.name, def.description, def.inputSchema (zod), def.annotations
|
|
144
|
+
// def.handler(contextThunk, input) → plain projected data
|
|
145
|
+
// toolInputJsonSchema(def) → `input_schema` for the Anthropic Messages API
|
|
146
|
+
}
|
|
80
147
|
```
|
|
81
148
|
|
|
149
|
+
A handler's context is `{engine, access?}` — `access` overrides the
|
|
150
|
+
engine's per-call actor resolution for hosts whose token can't resolve a
|
|
151
|
+
real user (the stdio host pins a system actor this way). The thunk is
|
|
152
|
+
lazy: the guide and validate tools never invoke it, so a per-request host
|
|
153
|
+
never builds an engine for them.
|
|
154
|
+
|
|
82
155
|
## Open gaps
|
|
83
156
|
|
|
157
|
+
- **No environment discovery.** Every operational call names its workflow
|
|
158
|
+
environment, but there is no tool to enumerate the environments that
|
|
159
|
+
exist across the org — the agent (or its operator prompt) must know the
|
|
160
|
+
resource + tag. Stays out until the evals demand it.
|
|
84
161
|
- **No `set_stage` tool.** Admin override; intentionally omitted from
|
|
85
162
|
the LLM surface until we have an eval case that needs it (and a
|
|
86
163
|
story for the actor identity that would authorise the override).
|
|
87
|
-
- **No
|
|
88
|
-
|
|
89
|
-
initial field values); operators start instances with the workflow
|
|
90
|
-
CLI's `start` command.
|
|
91
|
-
- **No effect completion.** Same logic — effects are queued by the
|
|
92
|
-
engine and drained by the runtime, not by a human or LLM.
|
|
164
|
+
- **No effect completion.** Effects are queued by the engine and
|
|
165
|
+
drained by the runtime, not by a human or LLM.
|
|
93
166
|
- **No subscription / streaming.** MCP supports it; we haven't needed
|
|
94
167
|
it yet. Pull-based polling via `get_workflow_state` covers the eval
|
|
95
168
|
cases.
|
|
96
|
-
- **No `deploy_workflow_definition`.** `validate_workflow_definition` is
|
|
97
|
-
the authoring tool; a human deploys the validated definition via the
|
|
98
|
-
CLI. Deploy is a Content Lake write that inherits the actor-identity
|
|
99
|
-
story, so it stays out of the LLM surface for now.
|
package/bin/run.js
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Production entry for the published package: loads the compiled boot
|
|
3
|
-
// path from ./dist/stdio.js (built by pkg-utils via the `./stdio`
|
|
4
|
-
// export). The dev counterpart, bin/workflow-mcp.ts, loads the TS
|
|
5
|
-
// source through tsx instead.
|
|
6
|
-
|
|
7
2
|
import {runStdioServer} from '../dist/stdio.js'
|
|
8
3
|
|
|
9
4
|
await runStdioServer()
|