@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/dist/index.d.cts CHANGED
@@ -1,43 +1,41 @@
1
+ import type { ActionParam } from "@sanity/workflow-engine";
1
2
  import type { Diagnosis } from "@sanity/workflow-engine";
2
- import { Engine } from "@sanity/workflow-engine";
3
+ import type { Engine } from "@sanity/workflow-engine";
4
+ import type { ExecutorClassification } from "@sanity/workflow-engine";
5
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
7
+ import type { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
8
+ import type { ServerRequest } from "@modelcontextprotocol/sdk/types.js";
3
9
  import type { StuckCause } from "@sanity/workflow-engine";
4
10
  import type { SuggestedRemediation } from "@sanity/workflow-engine";
5
- import { WorkflowAccessOverride } from "@sanity/workflow-engine";
11
+ import type { TelemetryLogger } from "@sanity/telemetry";
12
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
13
  import type { WorkflowDefinition } from "@sanity/workflow-engine";
14
+ import { WorkflowResource } from "@sanity/workflow-engine";
15
+ import { ZodRawShape } from "zod";
7
16
 
8
17
  /**
9
- * Build the MCP tool descriptors and bound implementations against a
10
- * single engine. Pass the engine the tools should operate on; the
11
- * returned descriptors are static (safe to cache), the implementations
12
- * close over the engine.
13
- *
14
- * In production this is called once at server boot. In tests it's
15
- * called per-bench so each eval case has an isolated engine.
18
+ * The host seam: produce the {@link WorkflowToolContext} for one tool
19
+ * call. `extra` carries the request (a hosted server derives auth from
20
+ * it); `input` carries the parsed tool arguments — for the
21
+ * instance-operating tools that includes the per-call workflow
22
+ * environment address, which `workflowAddressFromInput` extracts so the
23
+ * host can build (or reuse) the engine for that environment. The
24
+ * authoring tools never invoke their context thunk, so this is never
25
+ * called without an address to read.
16
26
  */
17
- export declare function buildTools(
18
- engine: Engine,
19
- options?: BuildToolsOptions,
20
- ): BuiltTools;
27
+ export declare type GetWorkflowToolContext = (
28
+ extra: WorkflowToolExtra,
29
+ input: unknown,
30
+ ) => WorkflowToolContext | Promise<WorkflowToolContext>;
21
31
 
22
32
  /**
23
- * Optional knobs for `buildTools`.
24
- *
25
- * `access`override the engine's actor + grants resolution for every
26
- * call the tools make. In production, leave this unset: the engine
27
- * resolves the actor from the supplied client's token via `/users/me`.
28
- * In tests, pass `ALL_ACCESS` (from `@sanity/workflow-engine-test`)
29
- * because the TestClient has no token and would otherwise throw on
30
- * the resolution path.
33
+ * One invocable (fireAction-fired) action. Two whole classes of action never
34
+ * appear here: an action whose `filter` scoped it out for this actor is
35
+ * ABSENT (filter is existence it might as well not exist, never "disabled"),
36
+ * and a cascade-fired (`when`) action is projected as a
37
+ * {@link ProjectedAutomation} instead the engine fires it, no caller can.
31
38
  */
32
- export declare interface BuildToolsOptions {
33
- access?: WorkflowAccessOverride;
34
- }
35
-
36
- export declare interface BuiltTools {
37
- descriptors: ToolDescriptor[];
38
- impls: Record<string, ToolImpl>;
39
- }
40
-
41
39
  export declare interface ProjectedActionVerdict {
42
40
  /** Action name — what to pass to `fire_action` as `action`. */
43
41
  action: string;
@@ -47,6 +45,10 @@ export declare interface ProjectedActionVerdict {
47
45
  allowed: boolean;
48
46
  /** When `allowed` is false, a short reason describing why. */
49
47
  disabledReason?: string;
48
+ /** The action's declared params — what `fire_action`'s `params` object must
49
+ * satisfy (each entry names the param and whether it is required). Absent
50
+ * when the action declares none. */
51
+ params?: ActionParam[];
50
52
  }
51
53
 
52
54
  export declare interface ProjectedActivity {
@@ -56,10 +58,57 @@ export declare interface ProjectedActivity {
56
58
  title?: string;
57
59
  /** Longer prose description, if provided. */
58
60
  description?: string;
59
- /** Current activity status. */
60
- status: "pending" | "active" | "done" | "skipped" | "failed";
61
- /** Available actions on this activity, each with its allowed/disabled verdict. */
61
+ /** Current activity status. Activities are active from stage entry; an
62
+ * activity whose `filter` scoped it out of this visit is absent from the
63
+ * projection, so `skipped` here always means an action resolved it so. */
64
+ status: "active" | "done" | "skipped" | "failed";
65
+ /**
66
+ * Who, if anyone, the activity waits on — `interactive` (only invocable
67
+ * actions), `autonomous` (only cascade-fired automation), `off-system`
68
+ * (work performed outside the system), or `hybrid` (both kinds).
69
+ */
70
+ classification: ExecutorClassification;
71
+ /** Invocable actions on this activity, each with its allowed/disabled
72
+ * verdict. See {@link ProjectedActionVerdict} for what never appears. */
62
73
  actions: ProjectedActionVerdict[];
74
+ /** Cascade-fired actions, narrated as automation ("fires when …").
75
+ * Present only when the activity declares any. */
76
+ automations?: ProjectedAutomation[];
77
+ }
78
+
79
+ /**
80
+ * One cascade-fired (`when`) action, narrated as automation: the engine fires
81
+ * it on its own the moment the trigger holds — it is never invocable via
82
+ * `fire_action`, so it must not read as a button.
83
+ */
84
+ export declare interface ProjectedAutomation {
85
+ /** The cascade-fired action's name. Not accepted by `fire_action`. */
86
+ action: string;
87
+ /** Human label, if provided. */
88
+ title?: string;
89
+ /** The trigger — the GROQ condition whose truth makes the engine fire it. */
90
+ firesWhen: string;
91
+ /** What still stands between the current state and the trigger, as one
92
+ * English line ("Needs: …"). Absent when the trigger is already satisfied
93
+ * or already fired this stage visit. */
94
+ pending?: string;
95
+ }
96
+
97
+ /**
98
+ * Result of `get_workflow_definition` — one deployed version's content.
99
+ * `definition` is the stored (desugared) form with the document envelope
100
+ * stripped — valid input for the validate/deploy tools as-is (their parse
101
+ * accepts stored form; parts of it, e.g. resolved guard `idRefs`, are NOT
102
+ * valid authoring shape): modify it and redeploy via
103
+ * `deploy_workflow_definition` to mint the next version.
104
+ */
105
+ export declare interface ProjectedDefinition {
106
+ /** Definition `name`. */
107
+ name: string;
108
+ /** The deployed version this content came from. */
109
+ version: number;
110
+ /** The envelope-stripped definition content. */
111
+ definition: WorkflowDefinition;
63
112
  }
64
113
 
65
114
  /**
@@ -80,6 +129,10 @@ export declare interface ProjectedDiagnosis {
80
129
  summary: string;
81
130
  /** When `state` is "stuck", the structured cause kind. */
82
131
  cause?: StuckCause["kind"];
132
+ /** Per unsatisfied exit transition, its insight summary ("the "Publish"
133
+ * transition: Needs: «Legal approved» must be true") — present when any
134
+ * exit is currently held. */
135
+ explanations?: string[];
83
136
  /**
84
137
  * What would unstick the instance — empty unless stuck. Each names a
85
138
  * surface-neutral verb, whether it's runnable as an engine operation today
@@ -92,7 +145,8 @@ export declare interface ProjectedDiagnosis {
92
145
  export declare interface ProjectedHistoryEntry {
93
146
  /** ISO timestamp. */
94
147
  at: string;
95
- /** Engine history type (e.g. stageEntered). */
148
+ /** Human-readable entry title from the engine's display metadata
149
+ * (e.g. "Stage entered"). */
96
150
  type: string;
97
151
  /** Free-form one-line summary suitable for an LLM to read. */
98
152
  summary: string;
@@ -162,78 +216,41 @@ export declare interface ProjectedSubject {
162
216
  }
163
217
 
164
218
  /**
165
- * The MCP tool surface for workflow operations the static descriptors an
166
- * agent reads to decide what to call. The implementations live in `tools.ts`;
167
- * these are pure data (descriptions + JSON schemas) and safe to cache.
168
- *
169
- * Two jobs: operate running instances, and author new definitions.
170
- *
171
- * Operate an instance:
172
- *
173
- * - `list_workflow_instances` is the only way to *discover* what's
174
- * running without already knowing an id. Filterable, capped.
175
- * - `get_workflow_state` is the "tell me everything actionable about
176
- * one instance" call. It composes the engine's `evaluate` projection
177
- * into a flat shape an LLM can reason over without piecing together
178
- * raw `WorkflowInstance` documents.
179
- * - `diagnose_workflow` answers "*why* isn't this moving?" — it
180
- * classifies an instance as healthy (waiting/progressing) or genuinely
181
- * stuck (a failed effect/activity, a dead-end transition) and names the
182
- * remediations that would unstick it. A pure read; the remediations are
183
- * advisory (none execute through this server).
184
- * - `fire_action` is the only write. Mirrors the engine's universal
185
- * "something happened" entry point — everything other than admin
186
- * override flows through it.
187
- *
188
- * Author a definition (both pure, engine-independent, neither deploys):
189
- *
190
- * - `get_workflow_authoring_guide` returns the DSL guide an agent reads
191
- * before writing a definition — shape, GROQ built-ins, sugars, examples.
192
- * - `validate_workflow_definition` runs the deploy-time checks (structure +
193
- * invariants + GROQ) and returns the desugared definition or a
194
- * path-prefixed error list. A human deploys it via the CLI.
195
- *
196
- * Surface choices that matter for an LLM consumer:
197
- *
198
- * - Descriptions are written for an agent, not a human reader.
199
- * Each one says (a) what the tool does, (b) when to use it,
200
- * (c) what *not* to use it for.
201
- * - Parameter names use snake_case, matching MCP convention.
202
- * - The write tool's description spells out that this is the way
203
- * to advance workflow state — not a side door. Without that line,
204
- * an LLM tends to look for a `complete_activity` or `set_stage` tool.
205
- * - Read tools cap their output. A 50-page `recent_history` would
206
- * burn context and is rarely informative; 8 is enough to spot
207
- * loops or stuck-in-stage patterns.
219
+ * Register every workflow tool onto `server`, wired through `getContext`.
220
+ * A host that owns a telemetry shell passes its logger to log one
221
+ * adoption event per tool call; without it, calls log nothing.
208
222
  */
223
+ export declare function registerWorkflowTools(
224
+ server: McpServer,
225
+ getContext: GetWorkflowToolContext,
226
+ options?: {
227
+ telemetry?: TelemetryLogger<unknown>;
228
+ },
229
+ ): void;
230
+
209
231
  /**
210
- * The descriptor shape the Anthropic Messages API + the MCP SDK both
211
- * accept. Kept here as a small, transport-neutral interface so we can
212
- * feed the same descriptors to either consumer.
232
+ * JSON-schema descriptor for consumers that don't speak zod e.g. the
233
+ * Anthropic Messages API `input_schema` field. `properties`/`required`
234
+ * are declared (not left to the index signature) so the shape is
235
+ * directly assignable to the Anthropic SDK's `Tool.InputSchema`.
213
236
  */
214
- export declare interface ToolDescriptor {
215
- name: string;
216
- description: string;
217
- /** JSON Schema describing the tool's parameters. */
218
- input_schema: {
219
- type: "object";
220
- properties: Record<string, unknown>;
221
- required?: string[];
222
- additionalProperties?: boolean;
223
- };
237
+ export declare interface ToolInputJsonSchema {
238
+ type: "object";
239
+ properties?: Record<string, unknown>;
240
+ required?: string[];
241
+ [key: string]: unknown;
224
242
  }
225
243
 
226
- /**
227
- * A tool implementation is a function from validated input → JSON-able
228
- * output. The runner is responsible for serialising the output and for
229
- * surfacing thrown errors as `is_error` tool results.
230
- */
231
- export declare type ToolImpl = (input: unknown) => Promise<unknown>;
244
+ /** Derive the JSON-schema descriptor from a def's zod shape. */
245
+ export declare function toolInputJsonSchema(
246
+ def: WorkflowToolDef,
247
+ ): ToolInputJsonSchema;
232
248
 
233
249
  /**
234
- * Result of `validate_workflow_definition`. On `valid: true`, `definition` is the
235
- * desugared form that would deploy (defaults filled, sugar expanded); on
236
- * `valid: false`, `error` is the aggregated, path-prefixed list of problems.
250
+ * Per-definition result inside {@link ValidateDefinitionsResult}. On
251
+ * `valid: true`, `definition` is the desugared form that would deploy
252
+ * (defaults filled, sugar expanded); on `valid: false`, `error` is the
253
+ * aggregated, path-prefixed list of problems.
237
254
  */
238
255
  export declare type ValidateDefinitionResult =
239
256
  | {
@@ -245,4 +262,87 @@ export declare type ValidateDefinitionResult =
245
262
  error: string;
246
263
  };
247
264
 
265
+ /**
266
+ * Result of `validate_workflow_definition`. `results` pairs positionally with
267
+ * the input `definitions`; `valid` is true only when every definition passed.
268
+ */
269
+ export declare interface ValidateDefinitionsResult {
270
+ valid: boolean;
271
+ results: ValidateDefinitionResult[];
272
+ }
273
+
274
+ export declare const WORKFLOW_TOOLS: readonly WorkflowToolDef[];
275
+
276
+ /**
277
+ * Parse the {@link WorkflowEnvironmentAddress} out of a tool call's input —
278
+ * the host side of the address contract: a host's `getContext` reads the
279
+ * address from the input this parses and builds (or reuses) the engine for
280
+ * it. Extra input keys (the tool's other parameters) are ignored.
281
+ */
282
+ export declare function workflowAddressFromInput(
283
+ input: unknown,
284
+ ): WorkflowEnvironmentAddress;
285
+
286
+ /** Where one tool call reads/writes workflow data: resource + tag. */
287
+ export declare interface WorkflowEnvironmentAddress {
288
+ workflowResource: WorkflowResource;
289
+ tag: string;
290
+ }
291
+
292
+ /**
293
+ * Everything a tool call needs from its host: the engine to operate on.
294
+ * Identity is the token behind the engine's client (`/users/me`) — a host
295
+ * that should act as someone else supplies an engine over their token, and
296
+ * declares itself via the engine's `executionContext`.
297
+ */
298
+ export declare interface WorkflowToolContext {
299
+ engine: Engine;
300
+ }
301
+
302
+ /**
303
+ * Lazy context supplier. A thunk rather than the context itself so a
304
+ * host never pays for engine construction on tools that don't call it —
305
+ * the guide and validate tools teach and check the DSL without touching
306
+ * the lake, and a per-request host would otherwise build a client for
307
+ * nothing.
308
+ */
309
+ export declare type WorkflowToolContextThunk =
310
+ () => Promise<WorkflowToolContext>;
311
+
312
+ /**
313
+ * One workflow tool, host-neutral. Hosts iterate {@link WORKFLOW_TOOLS}
314
+ * and register each def with their own glue (error wrapping, telemetry,
315
+ * result envelopes); `registerWorkflowTools` is the bundled convenience
316
+ * for hosts without opinions of their own.
317
+ */
318
+ export declare interface WorkflowToolDef {
319
+ readonly name: string;
320
+ readonly description: string;
321
+ /**
322
+ * Raw zod shape — the single schema source of truth: MCP servers
323
+ * register it directly, {@link parseToolInput} wraps it for runtime
324
+ * parsing, and {@link toolInputJsonSchema} derives the JSON-schema
325
+ * descriptor for non-MCP consumers.
326
+ */
327
+ readonly inputSchema: ZodRawShape;
328
+ readonly annotations: ToolAnnotations;
329
+ /**
330
+ * Returns plain projected data (the `Projected*` shapes) — hosts own
331
+ * the serialisation into their transport's result envelope. Takes
332
+ * `unknown` and parses internally: a heterogeneous list of defs with
333
+ * typed handler inputs is unassignable under `strictFunctionTypes`,
334
+ * so the narrowing lives inside each handler instead.
335
+ */
336
+ readonly handler: (
337
+ context: WorkflowToolContextThunk,
338
+ input: unknown,
339
+ ) => Promise<unknown>;
340
+ }
341
+
342
+ /** The per-request extra the MCP SDK hands every registered tool callback. */
343
+ export declare type WorkflowToolExtra = RequestHandlerExtra<
344
+ ServerRequest,
345
+ ServerNotification
346
+ >;
347
+
248
348
  export {};