@sanity/workflow-mcp 0.5.0 → 0.6.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,42 +1,31 @@
1
1
  import type { Diagnosis } from "@sanity/workflow-engine";
2
- import { Engine } from "@sanity/workflow-engine";
2
+ import type { Engine } from "@sanity/workflow-engine";
3
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
5
+ import type { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
6
+ import type { ServerRequest } from "@modelcontextprotocol/sdk/types.js";
3
7
  import type { StuckCause } from "@sanity/workflow-engine";
4
8
  import type { SuggestedRemediation } from "@sanity/workflow-engine";
5
- import { WorkflowAccessOverride } from "@sanity/workflow-engine";
9
+ import type { TelemetryLogger } from "@sanity/telemetry";
10
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
11
  import type { WorkflowDefinition } from "@sanity/workflow-engine";
12
+ import { WorkflowResource } from "@sanity/workflow-engine";
13
+ import { ZodRawShape } from "zod";
7
14
 
8
15
  /**
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.
16
- */
17
- export declare function buildTools(
18
- engine: Engine,
19
- options?: BuildToolsOptions,
20
- ): BuiltTools;
21
-
22
- /**
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.
16
+ * The host seam: produce the {@link WorkflowToolContext} for one tool
17
+ * call. `extra` carries the request (a hosted server derives auth from
18
+ * it); `input` carries the parsed tool arguments — for the
19
+ * instance-operating tools that includes the per-call workflow
20
+ * environment address, which `workflowAddressFromInput` extracts so the
21
+ * host can build (or reuse) the engine for that environment. The
22
+ * authoring tools never invoke their context thunk, so this is never
23
+ * called without an address to read.
31
24
  */
32
- export declare interface BuildToolsOptions {
33
- access?: WorkflowAccessOverride;
34
- }
35
-
36
- export declare interface BuiltTools {
37
- descriptors: ToolDescriptor[];
38
- impls: Record<string, ToolImpl>;
39
- }
25
+ export declare type GetWorkflowToolContext = (
26
+ extra: WorkflowToolExtra,
27
+ input: unknown,
28
+ ) => WorkflowToolContext | Promise<WorkflowToolContext>;
40
29
 
41
30
  export declare interface ProjectedActionVerdict {
42
31
  /** Action name — what to pass to `fire_action` as `action`. */
@@ -80,6 +69,10 @@ export declare interface ProjectedDiagnosis {
80
69
  summary: string;
81
70
  /** When `state` is "stuck", the structured cause kind. */
82
71
  cause?: StuckCause["kind"];
72
+ /** Per unsatisfied exit transition, its insight summary ("the "Publish"
73
+ * transition: Needs: «Legal approved» must be true") — present when any
74
+ * exit is currently held. */
75
+ explanations?: string[];
83
76
  /**
84
77
  * What would unstick the instance — empty unless stuck. Each names a
85
78
  * surface-neutral verb, whether it's runnable as an engine operation today
@@ -92,7 +85,8 @@ export declare interface ProjectedDiagnosis {
92
85
  export declare interface ProjectedHistoryEntry {
93
86
  /** ISO timestamp. */
94
87
  at: string;
95
- /** Engine history type (e.g. stageEntered). */
88
+ /** Human-readable entry title from the engine's display metadata
89
+ * (e.g. "Stage entered"). */
96
90
  type: string;
97
91
  /** Free-form one-line summary suitable for an LLM to read. */
98
92
  summary: string;
@@ -162,73 +156,35 @@ export declare interface ProjectedSubject {
162
156
  }
163
157
 
164
158
  /**
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.
159
+ * Register every workflow tool onto `server`, wired through `getContext`.
160
+ * A host that owns a telemetry shell passes its logger to log one
161
+ * adoption event per tool call; without it, calls log nothing.
208
162
  */
163
+ export declare function registerWorkflowTools(
164
+ server: McpServer,
165
+ getContext: GetWorkflowToolContext,
166
+ options?: {
167
+ telemetry?: TelemetryLogger<unknown>;
168
+ },
169
+ ): void;
170
+
209
171
  /**
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.
172
+ * JSON-schema descriptor for consumers that don't speak zod e.g. the
173
+ * Anthropic Messages API `input_schema` field. `properties`/`required`
174
+ * are declared (not left to the index signature) so the shape is
175
+ * directly assignable to the Anthropic SDK's `Tool.InputSchema`.
213
176
  */
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
- };
177
+ export declare interface ToolInputJsonSchema {
178
+ type: "object";
179
+ properties?: Record<string, unknown>;
180
+ required?: string[];
181
+ [key: string]: unknown;
224
182
  }
225
183
 
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>;
184
+ /** Derive the JSON-schema descriptor from a def's zod shape. */
185
+ export declare function toolInputJsonSchema(
186
+ def: WorkflowToolDef,
187
+ ): ToolInputJsonSchema;
232
188
 
233
189
  /**
234
190
  * Result of `validate_workflow_definition`. On `valid: true`, `definition` is the
@@ -245,4 +201,78 @@ export declare type ValidateDefinitionResult =
245
201
  error: string;
246
202
  };
247
203
 
204
+ export declare const WORKFLOW_TOOLS: readonly WorkflowToolDef[];
205
+
206
+ /**
207
+ * Parse the {@link WorkflowEnvironmentAddress} out of a tool call's input —
208
+ * the host side of the address contract: a host's `getContext` reads the
209
+ * address from the input this parses and builds (or reuses) the engine for
210
+ * it. Extra input keys (the tool's other parameters) are ignored.
211
+ */
212
+ export declare function workflowAddressFromInput(
213
+ input: unknown,
214
+ ): WorkflowEnvironmentAddress;
215
+
216
+ /** Where one tool call reads/writes workflow data: resource + tag. */
217
+ export declare interface WorkflowEnvironmentAddress {
218
+ workflowResource: WorkflowResource;
219
+ tag: string;
220
+ }
221
+
222
+ /**
223
+ * Everything a tool call needs from its host: the engine to operate on.
224
+ * Identity is the token behind the engine's client (`/users/me`) — a host
225
+ * that should act as someone else supplies an engine over their token, and
226
+ * declares itself via the engine's `executionContext`.
227
+ */
228
+ export declare interface WorkflowToolContext {
229
+ engine: Engine;
230
+ }
231
+
232
+ /**
233
+ * Lazy context supplier. A thunk rather than the context itself so a
234
+ * host never pays for engine construction on tools that don't call it —
235
+ * the guide and validate tools teach and check the DSL without touching
236
+ * the lake, and a per-request host would otherwise build a client for
237
+ * nothing.
238
+ */
239
+ export declare type WorkflowToolContextThunk =
240
+ () => Promise<WorkflowToolContext>;
241
+
242
+ /**
243
+ * One workflow tool, host-neutral. Hosts iterate {@link WORKFLOW_TOOLS}
244
+ * and register each def with their own glue (error wrapping, telemetry,
245
+ * result envelopes); `registerWorkflowTools` is the bundled convenience
246
+ * for hosts without opinions of their own.
247
+ */
248
+ export declare interface WorkflowToolDef {
249
+ readonly name: string;
250
+ readonly description: string;
251
+ /**
252
+ * Raw zod shape — the single schema source of truth: MCP servers
253
+ * register it directly, {@link parseToolInput} wraps it for runtime
254
+ * parsing, and {@link toolInputJsonSchema} derives the JSON-schema
255
+ * descriptor for non-MCP consumers.
256
+ */
257
+ readonly inputSchema: ZodRawShape;
258
+ readonly annotations: ToolAnnotations;
259
+ /**
260
+ * Returns plain projected data (the `Projected*` shapes) — hosts own
261
+ * the serialisation into their transport's result envelope. Takes
262
+ * `unknown` and parses internally: a heterogeneous list of defs with
263
+ * typed handler inputs is unassignable under `strictFunctionTypes`,
264
+ * so the narrowing lives inside each handler instead.
265
+ */
266
+ readonly handler: (
267
+ context: WorkflowToolContextThunk,
268
+ input: unknown,
269
+ ) => Promise<unknown>;
270
+ }
271
+
272
+ /** The per-request extra the MCP SDK hands every registered tool callback. */
273
+ export declare type WorkflowToolExtra = RequestHandlerExtra<
274
+ ServerRequest,
275
+ ServerNotification
276
+ >;
277
+
248
278
  export {};
package/dist/index.d.ts CHANGED
@@ -1,42 +1,31 @@
1
1
  import type { Diagnosis } from "@sanity/workflow-engine";
2
- import { Engine } from "@sanity/workflow-engine";
2
+ import type { Engine } from "@sanity/workflow-engine";
3
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
5
+ import type { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
6
+ import type { ServerRequest } from "@modelcontextprotocol/sdk/types.js";
3
7
  import type { StuckCause } from "@sanity/workflow-engine";
4
8
  import type { SuggestedRemediation } from "@sanity/workflow-engine";
5
- import { WorkflowAccessOverride } from "@sanity/workflow-engine";
9
+ import type { TelemetryLogger } from "@sanity/telemetry";
10
+ import type { ToolAnnotations } from "@modelcontextprotocol/sdk/types.js";
6
11
  import type { WorkflowDefinition } from "@sanity/workflow-engine";
12
+ import { WorkflowResource } from "@sanity/workflow-engine";
13
+ import { ZodRawShape } from "zod";
7
14
 
8
15
  /**
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.
16
- */
17
- export declare function buildTools(
18
- engine: Engine,
19
- options?: BuildToolsOptions,
20
- ): BuiltTools;
21
-
22
- /**
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.
16
+ * The host seam: produce the {@link WorkflowToolContext} for one tool
17
+ * call. `extra` carries the request (a hosted server derives auth from
18
+ * it); `input` carries the parsed tool arguments — for the
19
+ * instance-operating tools that includes the per-call workflow
20
+ * environment address, which `workflowAddressFromInput` extracts so the
21
+ * host can build (or reuse) the engine for that environment. The
22
+ * authoring tools never invoke their context thunk, so this is never
23
+ * called without an address to read.
31
24
  */
32
- export declare interface BuildToolsOptions {
33
- access?: WorkflowAccessOverride;
34
- }
35
-
36
- export declare interface BuiltTools {
37
- descriptors: ToolDescriptor[];
38
- impls: Record<string, ToolImpl>;
39
- }
25
+ export declare type GetWorkflowToolContext = (
26
+ extra: WorkflowToolExtra,
27
+ input: unknown,
28
+ ) => WorkflowToolContext | Promise<WorkflowToolContext>;
40
29
 
41
30
  export declare interface ProjectedActionVerdict {
42
31
  /** Action name — what to pass to `fire_action` as `action`. */
@@ -80,6 +69,10 @@ export declare interface ProjectedDiagnosis {
80
69
  summary: string;
81
70
  /** When `state` is "stuck", the structured cause kind. */
82
71
  cause?: StuckCause["kind"];
72
+ /** Per unsatisfied exit transition, its insight summary ("the "Publish"
73
+ * transition: Needs: «Legal approved» must be true") — present when any
74
+ * exit is currently held. */
75
+ explanations?: string[];
83
76
  /**
84
77
  * What would unstick the instance — empty unless stuck. Each names a
85
78
  * surface-neutral verb, whether it's runnable as an engine operation today
@@ -92,7 +85,8 @@ export declare interface ProjectedDiagnosis {
92
85
  export declare interface ProjectedHistoryEntry {
93
86
  /** ISO timestamp. */
94
87
  at: string;
95
- /** Engine history type (e.g. stageEntered). */
88
+ /** Human-readable entry title from the engine's display metadata
89
+ * (e.g. "Stage entered"). */
96
90
  type: string;
97
91
  /** Free-form one-line summary suitable for an LLM to read. */
98
92
  summary: string;
@@ -162,73 +156,35 @@ export declare interface ProjectedSubject {
162
156
  }
163
157
 
164
158
  /**
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.
159
+ * Register every workflow tool onto `server`, wired through `getContext`.
160
+ * A host that owns a telemetry shell passes its logger to log one
161
+ * adoption event per tool call; without it, calls log nothing.
208
162
  */
163
+ export declare function registerWorkflowTools(
164
+ server: McpServer,
165
+ getContext: GetWorkflowToolContext,
166
+ options?: {
167
+ telemetry?: TelemetryLogger<unknown>;
168
+ },
169
+ ): void;
170
+
209
171
  /**
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.
172
+ * JSON-schema descriptor for consumers that don't speak zod e.g. the
173
+ * Anthropic Messages API `input_schema` field. `properties`/`required`
174
+ * are declared (not left to the index signature) so the shape is
175
+ * directly assignable to the Anthropic SDK's `Tool.InputSchema`.
213
176
  */
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
- };
177
+ export declare interface ToolInputJsonSchema {
178
+ type: "object";
179
+ properties?: Record<string, unknown>;
180
+ required?: string[];
181
+ [key: string]: unknown;
224
182
  }
225
183
 
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>;
184
+ /** Derive the JSON-schema descriptor from a def's zod shape. */
185
+ export declare function toolInputJsonSchema(
186
+ def: WorkflowToolDef,
187
+ ): ToolInputJsonSchema;
232
188
 
233
189
  /**
234
190
  * Result of `validate_workflow_definition`. On `valid: true`, `definition` is the
@@ -245,4 +201,78 @@ export declare type ValidateDefinitionResult =
245
201
  error: string;
246
202
  };
247
203
 
204
+ export declare const WORKFLOW_TOOLS: readonly WorkflowToolDef[];
205
+
206
+ /**
207
+ * Parse the {@link WorkflowEnvironmentAddress} out of a tool call's input —
208
+ * the host side of the address contract: a host's `getContext` reads the
209
+ * address from the input this parses and builds (or reuses) the engine for
210
+ * it. Extra input keys (the tool's other parameters) are ignored.
211
+ */
212
+ export declare function workflowAddressFromInput(
213
+ input: unknown,
214
+ ): WorkflowEnvironmentAddress;
215
+
216
+ /** Where one tool call reads/writes workflow data: resource + tag. */
217
+ export declare interface WorkflowEnvironmentAddress {
218
+ workflowResource: WorkflowResource;
219
+ tag: string;
220
+ }
221
+
222
+ /**
223
+ * Everything a tool call needs from its host: the engine to operate on.
224
+ * Identity is the token behind the engine's client (`/users/me`) — a host
225
+ * that should act as someone else supplies an engine over their token, and
226
+ * declares itself via the engine's `executionContext`.
227
+ */
228
+ export declare interface WorkflowToolContext {
229
+ engine: Engine;
230
+ }
231
+
232
+ /**
233
+ * Lazy context supplier. A thunk rather than the context itself so a
234
+ * host never pays for engine construction on tools that don't call it —
235
+ * the guide and validate tools teach and check the DSL without touching
236
+ * the lake, and a per-request host would otherwise build a client for
237
+ * nothing.
238
+ */
239
+ export declare type WorkflowToolContextThunk =
240
+ () => Promise<WorkflowToolContext>;
241
+
242
+ /**
243
+ * One workflow tool, host-neutral. Hosts iterate {@link WORKFLOW_TOOLS}
244
+ * and register each def with their own glue (error wrapping, telemetry,
245
+ * result envelopes); `registerWorkflowTools` is the bundled convenience
246
+ * for hosts without opinions of their own.
247
+ */
248
+ export declare interface WorkflowToolDef {
249
+ readonly name: string;
250
+ readonly description: string;
251
+ /**
252
+ * Raw zod shape — the single schema source of truth: MCP servers
253
+ * register it directly, {@link parseToolInput} wraps it for runtime
254
+ * parsing, and {@link toolInputJsonSchema} derives the JSON-schema
255
+ * descriptor for non-MCP consumers.
256
+ */
257
+ readonly inputSchema: ZodRawShape;
258
+ readonly annotations: ToolAnnotations;
259
+ /**
260
+ * Returns plain projected data (the `Projected*` shapes) — hosts own
261
+ * the serialisation into their transport's result envelope. Takes
262
+ * `unknown` and parses internally: a heterogeneous list of defs with
263
+ * typed handler inputs is unassignable under `strictFunctionTypes`,
264
+ * so the narrowing lives inside each handler instead.
265
+ */
266
+ readonly handler: (
267
+ context: WorkflowToolContextThunk,
268
+ input: unknown,
269
+ ) => Promise<unknown>;
270
+ }
271
+
272
+ /** The per-request extra the MCP SDK hands every registered tool callback. */
273
+ export declare type WorkflowToolExtra = RequestHandlerExtra<
274
+ ServerRequest,
275
+ ServerNotification
276
+ >;
277
+
248
278
  export {};