@sanity/workflow-mcp 0.3.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/stdio.js ADDED
@@ -0,0 +1,70 @@
1
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
4
+ import { createClient } from "@sanity/client";
5
+ import { createEngine } from "@sanity/workflow-engine";
6
+ import { buildTools } from "./index.js";
7
+ const RESOURCE_TYPES = ["dataset", "canvas", "media-library", "dashboard"];
8
+ function requireEnv(env, name) {
9
+ const value = env[name];
10
+ if (typeof value != "string" || value === "")
11
+ throw new Error(`Missing required environment variable: ${name}`);
12
+ return value;
13
+ }
14
+ function parseResourceType(raw) {
15
+ const value = raw ?? "dataset";
16
+ if (RESOURCE_TYPES.includes(value))
17
+ return value;
18
+ throw new Error(`Invalid WORKFLOW_RESOURCE_TYPE: ${value}`);
19
+ }
20
+ async function handleToolCall(impls, name, args) {
21
+ const impl = impls[name];
22
+ if (impl === void 0)
23
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: !0 };
24
+ try {
25
+ const result = await impl(args ?? {});
26
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
27
+ } catch (err) {
28
+ return { content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }], isError: !0 };
29
+ }
30
+ }
31
+ async function runStdioServer() {
32
+ const projectId = requireEnv(process.env, "SANITY_PROJECT_ID"), dataset = requireEnv(process.env, "SANITY_DATASET"), token = requireEnv(process.env, "SANITY_AUTH_TOKEN"), tag = requireEnv(process.env, "WORKFLOW_TAG"), apiHost = process.env.SANITY_API_HOST ?? "https://api.sanity.io", client = createClient({
33
+ projectId,
34
+ dataset,
35
+ token,
36
+ apiHost,
37
+ apiVersion: "2026-04-29",
38
+ useCdn: !1
39
+ });
40
+ process.stderr.write(
41
+ `workflow-mcp MCP starting against ${apiHost} (project=${projectId}, dataset=${dataset}, tag=${tag})
42
+ `
43
+ );
44
+ const engine = createEngine({
45
+ client,
46
+ workflowResource: {
47
+ type: parseResourceType(process.env.WORKFLOW_RESOURCE_TYPE),
48
+ id: process.env.WORKFLOW_RESOURCE_ID ?? `${projectId}.${dataset}`
49
+ },
50
+ tag
51
+ }), { descriptors, impls } = buildTools(engine, {
52
+ access: { actor: { kind: "system", id: "workflow-mcp" } }
53
+ }), server = new Server({ name: "workflow-mcp", version: "0.0.0" }, { capabilities: { tools: {} } });
54
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
55
+ tools: descriptors.map((d) => ({
56
+ name: d.name,
57
+ description: d.description,
58
+ inputSchema: d.input_schema
59
+ }))
60
+ })), server.setRequestHandler(
61
+ CallToolRequestSchema,
62
+ async (request) => handleToolCall(impls, request.params.name, request.params.arguments)
63
+ );
64
+ const transport = new StdioServerTransport();
65
+ await server.connect(transport);
66
+ }
67
+ export {
68
+ runStdioServer
69
+ };
70
+ //# sourceMappingURL=stdio.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdio.js","sources":["../src/server.ts","../src/stdio.ts"],"sourcesContent":["/**\n * Server-side helpers for the stdio MCP entry point. Kept separate from\n * the boot path so the env parsing and tool-call dispatch are\n * unit-testable; `src/stdio.ts` wires these to the real\n * `@modelcontextprotocol/sdk` transport.\n */\n\nimport type {CallToolResult} from '@modelcontextprotocol/sdk/types.js'\n\nimport type {ToolImpl} from './tools.ts'\n\nexport type ResourceType = 'dataset' | 'canvas' | 'media-library' | 'dashboard'\n\nconst RESOURCE_TYPES: readonly ResourceType[] = ['dataset', 'canvas', 'media-library', 'dashboard']\n\n/** Read a required env var, throwing a clear error when it's absent/empty. */\nexport function requireEnv(env: NodeJS.ProcessEnv, name: string): string {\n const value = env[name]\n if (typeof value !== 'string' || value === '') {\n throw new Error(`Missing required environment variable: ${name}`)\n }\n return value\n}\n\n/** Parse `WORKFLOW_RESOURCE_TYPE`, defaulting to `dataset`. */\nexport function parseResourceType(raw: string | undefined): ResourceType {\n const value = raw ?? 'dataset'\n if ((RESOURCE_TYPES as readonly string[]).includes(value)) {\n return value as ResourceType\n }\n throw new Error(`Invalid WORKFLOW_RESOURCE_TYPE: ${value}`)\n}\n\n/**\n * Dispatch one tool call against the bound impls. Unknown tools and\n * thrown impls both become `isError` text results so the MCP client\n * sees a recoverable error rather than a transport-level failure.\n */\nexport async function handleToolCall(\n impls: Record<string, ToolImpl>,\n name: string,\n args: unknown,\n): Promise<CallToolResult> {\n const impl = impls[name]\n if (impl === undefined) {\n return {content: [{type: 'text', text: `Unknown tool: ${name}`}], isError: true}\n }\n try {\n const result = await impl(args ?? {})\n return {content: [{type: 'text', text: JSON.stringify(result, null, 2)}]}\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return {content: [{type: 'text', text: message}], isError: true}\n }\n}\n","/**\n * stdio MCP host — boots a {@link Server} from `@modelcontextprotocol/sdk`,\n * binds the workflow tools to an engine built from environment variables,\n * and listens on stdio.\n *\n * This is one of two thin hosts over the shared {@link buildTools} core:\n * the stdio host (here) is the early-access self-host (one env-built\n * engine, BYO scoped token, system-actor attribution); the production\n * host embeds {@link buildTools} with per-request identity elsewhere.\n *\n * Kept as its own package entry (not inline in `bin/`) so it can be\n * compiled to `dist/stdio.js` by pkg-utils and re-used by both the\n * published bin (`bin/run.js`) and the dev entry (`bin/workflow-mcp.ts`)\n * — one boot path, no duplication. Keeping it out of the `.` entry also\n * keeps the MCP transport deps out of the library import graph.\n */\n\nimport {Server} from '@modelcontextprotocol/sdk/server/index.js'\nimport {StdioServerTransport} from '@modelcontextprotocol/sdk/server/stdio.js'\nimport {CallToolRequestSchema, ListToolsRequestSchema} from '@modelcontextprotocol/sdk/types.js'\nimport {createClient} from '@sanity/client'\nimport {createEngine} from '@sanity/workflow-engine'\n\nimport {handleToolCall, parseResourceType, requireEnv} from './server.ts'\nimport {buildTools} from './tools.ts'\n\n/**\n * Read the environment, build the engine, register the tools, and serve\n * on stdio. Resolves when the transport is connected (the process then\n * stays alive on the open stdio streams).\n */\nexport async function runStdioServer(): Promise<void> {\n const projectId = requireEnv(process.env, 'SANITY_PROJECT_ID')\n const dataset = requireEnv(process.env, 'SANITY_DATASET')\n const token = requireEnv(process.env, 'SANITY_AUTH_TOKEN')\n // The environment partition. Required and never defaulted — the engine\n // enforces nothing, so the tag is the only thing stopping a misconfigured\n // server from reading and writing prod. Fail loudly rather than guess.\n const tag = requireEnv(process.env, 'WORKFLOW_TAG')\n const apiHost = process.env.SANITY_API_HOST ?? 'https://api.sanity.io'\n\n const client = createClient({\n projectId,\n dataset,\n token,\n apiHost,\n apiVersion: '2026-04-29',\n useCdn: false,\n })\n\n // Stderr (not stdout — stdout is the MCP JSON-RPC channel). Surfaces in\n // Claude Desktop's developer log; lets you spot wrong-environment\n // mistakes without spelunking through tool errors.\n process.stderr.write(\n `workflow-mcp MCP starting against ${apiHost} (project=${projectId}, dataset=${dataset}, tag=${tag})\\n`,\n )\n\n const engine = createEngine({\n client,\n workflowResource: {\n type: parseResourceType(process.env.WORKFLOW_RESOURCE_TYPE),\n id: process.env.WORKFLOW_RESOURCE_ID ?? `${projectId}.${dataset}`,\n },\n tag,\n })\n\n // The engine's default actor resolution hits `/users/me`, which a\n // project-scoped token can't reach. Mirror the CLI's \"pin a system\n // actor for writes\" pattern — every call from this MCP is attributed\n // to `workflow-mcp` rather than a real user. Production-shaped auth\n // (where the LLM operates on behalf of a known user) needs a different\n // shape; this is the early-access compromise the CLI also makes.\n const {descriptors, impls} = buildTools(engine, {\n access: {actor: {kind: 'system', id: 'workflow-mcp'}},\n })\n\n const server = new Server({name: 'workflow-mcp', version: '0.0.0'}, {capabilities: {tools: {}}})\n\n server.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: descriptors.map((d) => ({\n name: d.name,\n description: d.description,\n inputSchema: d.input_schema,\n })),\n }))\n\n server.setRequestHandler(CallToolRequestSchema, async (request) =>\n handleToolCall(impls, request.params.name, request.params.arguments),\n )\n\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n"],"names":[],"mappings":";;;;;;AAaA,MAAM,iBAA0C,CAAC,WAAW,UAAU,iBAAiB,WAAW;AAG3F,SAAS,WAAW,KAAwB,MAAsB;AACvE,QAAM,QAAQ,IAAI,IAAI;AACtB,MAAI,OAAO,SAAU,YAAY,UAAU;AACzC,UAAM,IAAI,MAAM,0CAA0C,IAAI,EAAE;AAElE,SAAO;AACT;AAGO,SAAS,kBAAkB,KAAuC;AACvE,QAAM,QAAQ,OAAO;AACrB,MAAK,eAAqC,SAAS,KAAK;AACtD,WAAO;AAET,QAAM,IAAI,MAAM,mCAAmC,KAAK,EAAE;AAC5D;AAOA,eAAsB,eACpB,OACA,MACA,MACyB;AACzB,QAAM,OAAO,MAAM,IAAI;AACvB,MAAI,SAAS;AACX,WAAO,EAAC,SAAS,CAAC,EAAC,MAAM,QAAQ,MAAM,iBAAiB,IAAI,GAAA,CAAG,GAAG,SAAS,GAAA;AAE7E,MAAI;AACF,UAAM,SAAS,MAAM,KAAK,QAAQ,CAAA,CAAE;AACpC,WAAO,EAAC,SAAS,CAAC,EAAC,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAA,CAAE,EAAA;AAAA,EACzE,SAAS,KAAK;AAEZ,WAAO,EAAC,SAAS,CAAC,EAAC,MAAM,QAAQ,MADjB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,GAChB,GAAG,SAAS,GAAA;AAAA,EAC7D;AACF;ACvBA,eAAsB,iBAAgC;AACpD,QAAM,YAAY,WAAW,QAAQ,KAAK,mBAAmB,GACvD,UAAU,WAAW,QAAQ,KAAK,gBAAgB,GAClD,QAAQ,WAAW,QAAQ,KAAK,mBAAmB,GAInD,MAAM,WAAW,QAAQ,KAAK,cAAc,GAC5C,UAAU,QAAQ,IAAI,mBAAmB,yBAEzC,SAAS,aAAa;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ,QAAQ;AAAA,EAAA,CACT;AAKD,UAAQ,OAAO;AAAA,IACb,qCAAqC,OAAO,aAAa,SAAS,aAAa,OAAO,SAAS,GAAG;AAAA;AAAA,EAAA;AAGpG,QAAM,SAAS,aAAa;AAAA,IAC1B;AAAA,IACA,kBAAkB;AAAA,MAChB,MAAM,kBAAkB,QAAQ,IAAI,sBAAsB;AAAA,MAC1D,IAAI,QAAQ,IAAI,wBAAwB,GAAG,SAAS,IAAI,OAAO;AAAA,IAAA;AAAA,IAEjE;AAAA,EAAA,CACD,GAQK,EAAC,aAAa,MAAA,IAAS,WAAW,QAAQ;AAAA,IAC9C,QAAQ,EAAC,OAAO,EAAC,MAAM,UAAU,IAAI,iBAAc;AAAA,EAAC,CACrD,GAEK,SAAS,IAAI,OAAO,EAAC,MAAM,gBAAgB,SAAS,QAAA,GAAU,EAAC,cAAc,EAAC,OAAO,CAAA,EAAC,GAAG;AAE/F,SAAO,kBAAkB,wBAAwB,aAAa;AAAA,IAC5D,OAAO,YAAY,IAAI,CAAC,OAAO;AAAA,MAC7B,MAAM,EAAE;AAAA,MACR,aAAa,EAAE;AAAA,MACf,aAAa,EAAE;AAAA,IAAA,EACf;AAAA,EAAA,EACF,GAEF,OAAO;AAAA,IAAkB;AAAA,IAAuB,OAAO,YACrD,eAAe,OAAO,QAAQ,OAAO,MAAM,QAAQ,OAAO,SAAS;AAAA,EAAA;AAGrE,QAAM,YAAY,IAAI,qBAAA;AACtB,QAAM,OAAO,QAAQ,SAAS;AAChC;"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@sanity/workflow-mcp",
3
+ "version": "0.3.0",
4
+ "description": "MCP server exposing Sanity workflow tools to agents — operate running workflow instances and author new definitions.",
5
+ "keywords": [
6
+ "agent",
7
+ "groq",
8
+ "mcp",
9
+ "model-context-protocol",
10
+ "sanity",
11
+ "sanity-io",
12
+ "workflow",
13
+ "workflows"
14
+ ],
15
+ "homepage": "https://github.com/sanity-io/workflows/tree/main/packages/workflow-mcp#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/sanity-io/workflows/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": "Sanity.io <hello@sanity.io>",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/sanity-io/workflows.git",
24
+ "directory": "packages/workflow-mcp"
25
+ },
26
+ "bin": {
27
+ "workflow-mcp": "./bin/run.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "bin/run.js"
32
+ ],
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "main": "./dist/index.cjs",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "import": "./dist/index.js",
41
+ "require": "./dist/index.cjs",
42
+ "default": "./dist/index.js"
43
+ },
44
+ "./stdio": {
45
+ "import": "./dist/stdio.js",
46
+ "default": "./dist/stdio.js"
47
+ },
48
+ "./package.json": "./package.json"
49
+ },
50
+ "publishConfig": {
51
+ "access": "restricted"
52
+ },
53
+ "dependencies": {
54
+ "@modelcontextprotocol/sdk": "^1.29.0",
55
+ "@sanity/client": "^7.22.1",
56
+ "@sanity/workflow-engine": "0.11.0"
57
+ },
58
+ "devDependencies": {
59
+ "@sanity/pkg-utils": "^10.5.2",
60
+ "@types/node": "^24.12.4",
61
+ "vitest": "^4.1.8",
62
+ "@sanity/workflow-examples": "0.1.7",
63
+ "@sanity/workflow-engine-test": "0.6.1"
64
+ },
65
+ "engines": {
66
+ "node": ">=20"
67
+ },
68
+ "scripts": {
69
+ "build": "pkg-utils build --clean",
70
+ "typecheck": "tsc --noEmit -p tsconfig.json",
71
+ "test": "vitest run",
72
+ "test:watch": "vitest",
73
+ "dev": "NODE_OPTIONS='--conditions=development' tsx --env-file-if-exists=../../.env bin/workflow-mcp.ts"
74
+ }
75
+ }