@statelyai/mcp 0.1.0 → 0.1.1
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/LICENSE +10 -0
- package/README.md +189 -40
- package/package.json +7 -7
- package/dist/index.cjs +0 -483
- package/dist/index.d.cts +0 -72
- package/dist/index.d.mts +0 -72
- package/dist/index.mjs +0 -472
package/LICENSE
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Copyright (c) 2025 Stately Software, Inc.
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This repository and the Stately Viz software are proprietary to Stately
|
|
6
|
+
Software, Inc. No rights are granted to use, copy, modify, distribute, host, or
|
|
7
|
+
sublicense this software except under a separate written agreement with Stately
|
|
8
|
+
Software, Inc.
|
|
9
|
+
|
|
10
|
+
For licensing inquiries, contact team@stately.ai.
|
package/README.md
CHANGED
|
@@ -1,58 +1,207 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Stately MCP
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Use Stately's machine tools from an MCP-compatible agent.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Connect
|
|
6
6
|
|
|
7
|
-
<!--
|
|
7
|
+
<!-- public Stately MCP URL -->
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
import { createStatelyMcpServer } from '@statelyai/mcp';
|
|
9
|
+
Add this remote MCP server URL to your client:
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
apiBaseUrl: 'https://editor.stately.ai/api',
|
|
15
|
-
credentialProvider: async () => ({
|
|
16
|
-
type: 'oauth',
|
|
17
|
-
accessToken,
|
|
18
|
-
}),
|
|
19
|
-
anonymousGraphPathLimit: 5,
|
|
20
|
-
});
|
|
11
|
+
```text
|
|
12
|
+
https://stately.ai/mcp
|
|
21
13
|
```
|
|
22
14
|
|
|
23
|
-
The
|
|
24
|
-
package owns MCP tool schemas, handlers, annotations, and calls to the Stately
|
|
25
|
-
HTTP API. Use `authMode: 'none'` only when the API is protected elsewhere.
|
|
15
|
+
The client will open Stately sign-in when authentication is required.
|
|
26
16
|
|
|
27
|
-
##
|
|
17
|
+
## Machine input
|
|
28
18
|
|
|
29
|
-
<!--
|
|
19
|
+
<!-- shared machine input schemas from src/server.ts -->
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
Most tools accept either `machine` plus `format`, or a `source` object with
|
|
22
|
+
`format` and `content`. Conversion-based tools support `xstate`, `scxml`,
|
|
23
|
+
`xgraph`, and `d2`. Validation also supports `json`, `yaml`, and `mermaid`.
|
|
24
|
+
|
|
25
|
+
The examples below use `<machine source>` as a placeholder for the complete
|
|
26
|
+
machine document supplied by the agent.
|
|
27
|
+
|
|
28
|
+
## `validate_machine`
|
|
29
|
+
|
|
30
|
+
<!-- validate_machine registration from src/server.ts and validate response contract -->
|
|
31
|
+
|
|
32
|
+
Checks whether a machine can be parsed and reports structural issues such as
|
|
33
|
+
unreachable states. A successful response contains `ok` and an `issues` array.
|
|
34
|
+
|
|
35
|
+
Example prompt: "Check this machine for logic problems."
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"machine": "<machine source>",
|
|
40
|
+
"format": "xstate"
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## `convert_machine`
|
|
45
|
+
|
|
46
|
+
<!-- convert_machine registration from src/server.ts and convert response contract -->
|
|
47
|
+
|
|
48
|
+
Converts a machine between XState, SCXML, XGraph, and D2. The response contains
|
|
49
|
+
the target `format`, converted `content`, and any `warnings`.
|
|
50
|
+
|
|
51
|
+
Example prompt: "Convert this machine to SCXML."
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"machine": "<machine source>",
|
|
56
|
+
"format": "xstate",
|
|
57
|
+
"to": "scxml"
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## `diff_machines`
|
|
62
|
+
|
|
63
|
+
<!-- diff_machines registration from src/server.ts and diff response contract -->
|
|
64
|
+
|
|
65
|
+
Compares two machine documents structurally instead of comparing their source
|
|
66
|
+
text. It returns a summary and semantic diff; set `includePatches` to request
|
|
67
|
+
applicable patches.
|
|
68
|
+
|
|
69
|
+
Example prompt: "What changed between these two machines?"
|
|
70
|
+
|
|
71
|
+
```json
|
|
72
|
+
{
|
|
73
|
+
"base": { "format": "xstate", "content": "<old machine source>" },
|
|
74
|
+
"compare": { "format": "xstate", "content": "<new machine source>" },
|
|
75
|
+
"includePatches": true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## `apply_machine_patches`
|
|
80
|
+
|
|
81
|
+
<!-- apply_machine_patches registration from src/server.ts and apply-patches response contract -->
|
|
82
|
+
|
|
83
|
+
Applies semantic patches to a machine and returns the updated document. Use
|
|
84
|
+
`to` to choose the returned format.
|
|
85
|
+
|
|
86
|
+
Example prompt: "Apply these suggested changes and return XState code."
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"machine": "<machine source>",
|
|
91
|
+
"format": "xstate",
|
|
92
|
+
"patches": [
|
|
93
|
+
{ "op": "updateState", "stateId": "active", "key": "complete" }
|
|
94
|
+
],
|
|
95
|
+
"to": "xstate"
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## `generate_graph_paths`
|
|
100
|
+
|
|
101
|
+
<!-- generate_graph_paths registration from src/server.ts and graph-paths response contract -->
|
|
102
|
+
|
|
103
|
+
Generates shortest or simple paths through a machine. The response includes
|
|
104
|
+
the event steps for each path plus state and transition coverage. Stately MCP
|
|
105
|
+
returns at most five paths per call.
|
|
106
|
+
|
|
107
|
+
Example prompt: "Find the shortest paths through this machine."
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{
|
|
111
|
+
"machine": "<machine source>",
|
|
112
|
+
"format": "xstate",
|
|
113
|
+
"kind": "shortest",
|
|
114
|
+
"max": 5
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## `simulate_machine`
|
|
119
|
+
|
|
120
|
+
<!-- simulate_machine registration from src/server.ts and simulate response contract -->
|
|
121
|
+
|
|
122
|
+
Runs up to 100 events through a machine and returns a step-by-step trace of
|
|
123
|
+
states, context, and executed action names. Guards are stubbed during this
|
|
124
|
+
deterministic simulation.
|
|
125
|
+
|
|
126
|
+
Example prompt: "Simulate PLAY followed by STOP."
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"machine": "<machine source>",
|
|
131
|
+
"format": "xstate",
|
|
132
|
+
"events": [{ "type": "PLAY" }, { "type": "STOP" }]
|
|
133
|
+
}
|
|
33
134
|
```
|
|
34
135
|
|
|
35
|
-
|
|
136
|
+
## `visualize_machine`
|
|
137
|
+
|
|
138
|
+
<!-- visualize_machine registration from src/server.ts and visualize response contract -->
|
|
139
|
+
|
|
140
|
+
Renders a machine as an SVG diagram. The response contains the SVG string,
|
|
141
|
+
its width and height, and any warnings.
|
|
142
|
+
|
|
143
|
+
Example prompt: "Draw this machine from left to right."
|
|
144
|
+
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"machine": "<machine source>",
|
|
148
|
+
"format": "xstate",
|
|
149
|
+
"direction": "right"
|
|
150
|
+
}
|
|
151
|
+
```
|
|
36
152
|
|
|
37
|
-
|
|
38
|
-
- `get_machine`
|
|
153
|
+
## `analyze_machine`
|
|
39
154
|
|
|
40
|
-
|
|
41
|
-
`STATELY_API_KEY`. Use `STATELY_AUTH_MODE=none` for no-auth self-hosted APIs.
|
|
155
|
+
<!-- analyze_machine registration from src/server.ts and analyze response contract -->
|
|
42
156
|
|
|
43
|
-
|
|
157
|
+
Combines deterministic checks with AI review. Provide optional intent to assess
|
|
158
|
+
the machine against its expected behavior. The response contains issues and may
|
|
159
|
+
include suggested fixes. Use `validate_machine` when only deterministic checks
|
|
160
|
+
are needed.
|
|
44
161
|
|
|
45
|
-
|
|
162
|
+
Example prompt: "Check that STOP always ends playback permanently."
|
|
46
163
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
164
|
+
```json
|
|
165
|
+
{
|
|
166
|
+
"machine": "<machine source>",
|
|
167
|
+
"format": "xstate",
|
|
168
|
+
"intent": "STOP must reach a final state, and playback must not restart"
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## `generate_test_code`
|
|
173
|
+
|
|
174
|
+
<!-- generate_test_code registration and generateTestCode implementation from src/server.ts -->
|
|
175
|
+
|
|
176
|
+
Generates a runnable Vitest file with one test per generated path. Each test
|
|
177
|
+
sends the path's events and asserts its final state. Guards and actions are
|
|
178
|
+
stubbed.
|
|
179
|
+
|
|
180
|
+
Example prompt: "Generate tests for the shortest paths through this machine."
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"machine": "<machine source>",
|
|
185
|
+
"format": "xstate",
|
|
186
|
+
"kind": "shortest",
|
|
187
|
+
"max": 5
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## `extract_machines_from_code`
|
|
192
|
+
|
|
193
|
+
<!-- extract_machines_from_code registration from src/server.ts -->
|
|
194
|
+
|
|
195
|
+
Parses XState source code and returns the extracted machine as an XGraph
|
|
196
|
+
document.
|
|
197
|
+
|
|
198
|
+
Example prompt: "Extract the XState machine from this TypeScript file."
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"code": "import { createMachine } from 'xstate';\nexport const player = createMachine({ /* complete config */ });"
|
|
203
|
+
}
|
|
204
|
+
```
|
|
57
205
|
|
|
58
|
-
All tools are
|
|
206
|
+
All Stately MCP tools are registered as read-only, non-destructive, idempotent,
|
|
207
|
+
and closed-world.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@statelyai/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
"require": "./dist/index.cjs"
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
|
-
"scripts": {
|
|
23
|
-
"build": "tsdown",
|
|
24
|
-
"test": "vitest run",
|
|
25
|
-
"test:watch": "vitest"
|
|
26
|
-
},
|
|
27
22
|
"dependencies": {
|
|
28
23
|
"@modelcontextprotocol/sdk": "1.26.0",
|
|
29
24
|
"zod": "^4.4.1"
|
|
@@ -32,5 +27,10 @@
|
|
|
32
27
|
"@types/node": "25.2.1",
|
|
33
28
|
"tsdown": "0.21.0-beta.2",
|
|
34
29
|
"vitest": "^3.2.4"
|
|
30
|
+
},
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsdown",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest"
|
|
35
35
|
}
|
|
36
|
-
}
|
|
36
|
+
}
|
package/dist/index.cjs
DELETED
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
3
|
-
let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
4
|
-
let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
5
|
-
let zod = require("zod");
|
|
6
|
-
|
|
7
|
-
//#region src/server.ts
|
|
8
|
-
const readOnlyAnnotations = {
|
|
9
|
-
readOnlyHint: true,
|
|
10
|
-
destructiveHint: false,
|
|
11
|
-
idempotentHint: true,
|
|
12
|
-
openWorldHint: false
|
|
13
|
-
};
|
|
14
|
-
const apiFormats = [
|
|
15
|
-
"d2",
|
|
16
|
-
"scxml",
|
|
17
|
-
"xgraph",
|
|
18
|
-
"xstate"
|
|
19
|
-
];
|
|
20
|
-
const validationFormats = [
|
|
21
|
-
...apiFormats,
|
|
22
|
-
"json",
|
|
23
|
-
"yaml",
|
|
24
|
-
"mermaid"
|
|
25
|
-
];
|
|
26
|
-
const apiFormatSchema = zod.z.enum(apiFormats);
|
|
27
|
-
const validationFormatSchema = zod.z.enum(validationFormats);
|
|
28
|
-
const machineSchema = zod.z.union([zod.z.record(zod.z.string(), zod.z.unknown()), zod.z.string()]);
|
|
29
|
-
const sourceSchema = zod.z.object({
|
|
30
|
-
format: validationFormatSchema.optional(),
|
|
31
|
-
content: machineSchema
|
|
32
|
-
});
|
|
33
|
-
const patchSchema = zod.z.object({
|
|
34
|
-
op: zod.z.string(),
|
|
35
|
-
path: zod.z.string(),
|
|
36
|
-
value: zod.z.unknown().optional()
|
|
37
|
-
});
|
|
38
|
-
function generateTestCode(machineCode, paths) {
|
|
39
|
-
return [
|
|
40
|
-
[
|
|
41
|
-
"// Generated by generate_test_code (Stately MCP).",
|
|
42
|
-
"// Guards and actions are stubbed; assertions use state paths.",
|
|
43
|
-
"import { createActor } from 'xstate';",
|
|
44
|
-
"import { describe, expect, it } from 'vitest';",
|
|
45
|
-
"",
|
|
46
|
-
machineCode.trim(),
|
|
47
|
-
""
|
|
48
|
-
].join("\n"),
|
|
49
|
-
"describe('generated paths', () => {",
|
|
50
|
-
paths.map((path) => {
|
|
51
|
-
const events = path.steps.filter((step) => step.eventType !== null && step.eventType !== "" && !step.eventType.startsWith("xstate.")).map((step) => ` actor.send({ type: ${JSON.stringify(step.eventType)} });`);
|
|
52
|
-
return [
|
|
53
|
-
` it(${JSON.stringify(path.name)}, () => {`,
|
|
54
|
-
" const actor = createActor(machine).start();",
|
|
55
|
-
...events,
|
|
56
|
-
` expect(actor.getSnapshot().matches(${JSON.stringify(path.finalStatePath)})).toBe(true);`,
|
|
57
|
-
" });"
|
|
58
|
-
].join("\n");
|
|
59
|
-
}).join("\n\n"),
|
|
60
|
-
"});",
|
|
61
|
-
""
|
|
62
|
-
].join("\n");
|
|
63
|
-
}
|
|
64
|
-
function toolText$1(body) {
|
|
65
|
-
return JSON.stringify(body, null, 2);
|
|
66
|
-
}
|
|
67
|
-
function toolResult(body) {
|
|
68
|
-
return {
|
|
69
|
-
structuredContent: body,
|
|
70
|
-
content: [{
|
|
71
|
-
type: "text",
|
|
72
|
-
text: toolText$1(body)
|
|
73
|
-
}]
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
function toolError(body) {
|
|
77
|
-
return {
|
|
78
|
-
isError: true,
|
|
79
|
-
...toolResult(body)
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
function normalizeApiBaseUrl(apiBaseUrl) {
|
|
83
|
-
return new URL(String(apiBaseUrl ?? "https://editor.stately.ai/api")).toString().replace(/\/$/, "");
|
|
84
|
-
}
|
|
85
|
-
function normalizeSource(input) {
|
|
86
|
-
const content = input.source?.content ?? input.machine;
|
|
87
|
-
if (content === void 0) throw new Error("Expected machine or source.content.");
|
|
88
|
-
return {
|
|
89
|
-
format: input.source?.format ?? input.format ?? (typeof content === "string" ? "xstate" : "json"),
|
|
90
|
-
content
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
function toConversionSource(source) {
|
|
94
|
-
if (!apiFormats.includes(source.format)) {
|
|
95
|
-
if (source.format === "json" && typeof source.content === "object") return {
|
|
96
|
-
format: "xstate",
|
|
97
|
-
content: `createMachine(${JSON.stringify(source.content, null, 2)})`
|
|
98
|
-
};
|
|
99
|
-
if (source.format === "json" && typeof source.content === "string") return {
|
|
100
|
-
format: "xstate",
|
|
101
|
-
content: source.content
|
|
102
|
-
};
|
|
103
|
-
throw new Error(`Format ${source.format} is only supported by validate_machine.`);
|
|
104
|
-
}
|
|
105
|
-
return source;
|
|
106
|
-
}
|
|
107
|
-
async function callApi(options, pathname, body) {
|
|
108
|
-
const configuredHeaders = typeof options.apiHeaders === "function" ? await options.apiHeaders() : options.apiHeaders;
|
|
109
|
-
const headers = new Headers(configuredHeaders);
|
|
110
|
-
const authMode = options.authMode ?? "auto";
|
|
111
|
-
if (authMode !== "none" && !headers.has("authorization")) {
|
|
112
|
-
const credential = await options.credentialProvider?.();
|
|
113
|
-
const token = credential?.type === "oauth" ? credential.accessToken : credential?.token;
|
|
114
|
-
if (token) headers.set("authorization", `Bearer ${token}`);
|
|
115
|
-
else if (authMode === "bearer") throw new Error("No Stately credential configured. Run `statelyai login` or set STATELY_ACCESS_TOKEN.");
|
|
116
|
-
}
|
|
117
|
-
const response = await fetch(`${normalizeApiBaseUrl(options.apiBaseUrl)}${pathname}`, {
|
|
118
|
-
method: "POST",
|
|
119
|
-
headers: {
|
|
120
|
-
"content-type": "application/json",
|
|
121
|
-
...Object.fromEntries(headers.entries())
|
|
122
|
-
},
|
|
123
|
-
body: JSON.stringify(body)
|
|
124
|
-
});
|
|
125
|
-
const result = await response.json().catch(() => ({ error: `Invalid JSON response from ${pathname}.` }));
|
|
126
|
-
if (!response.ok) throw new Error(typeof result.error === "string" ? result.error : `${pathname} failed with HTTP ${response.status}`);
|
|
127
|
-
return result;
|
|
128
|
-
}
|
|
129
|
-
function registerTool(server, name, config, handler) {
|
|
130
|
-
server.registerTool(name, {
|
|
131
|
-
...config,
|
|
132
|
-
annotations: readOnlyAnnotations
|
|
133
|
-
}, async (args) => {
|
|
134
|
-
try {
|
|
135
|
-
return toolResult(await handler(args));
|
|
136
|
-
} catch (error) {
|
|
137
|
-
return toolError({ error: error instanceof Error ? error.message : String(error) });
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
function registerStatelyMcpTools(server, options = {}) {
|
|
142
|
-
registerTool(server, "validate_machine", {
|
|
143
|
-
title: "Validate machine",
|
|
144
|
-
description: "Validate an XState machine config or source string through the Stately API.",
|
|
145
|
-
inputSchema: {
|
|
146
|
-
machine: machineSchema.optional(),
|
|
147
|
-
format: validationFormatSchema.optional(),
|
|
148
|
-
source: sourceSchema.optional()
|
|
149
|
-
}
|
|
150
|
-
}, (input) => callApi(options, "/v1/validate", { source: normalizeSource(input) }));
|
|
151
|
-
registerTool(server, "convert_machine", {
|
|
152
|
-
title: "Convert machine",
|
|
153
|
-
description: "Convert between XState, SCXML, XGraph, and D2 through the Stately API.",
|
|
154
|
-
inputSchema: {
|
|
155
|
-
machine: machineSchema.optional(),
|
|
156
|
-
format: apiFormatSchema.optional(),
|
|
157
|
-
source: sourceSchema.optional(),
|
|
158
|
-
to: apiFormatSchema
|
|
159
|
-
}
|
|
160
|
-
}, (input) => callApi(options, "/v1/convert", {
|
|
161
|
-
source: toConversionSource(normalizeSource(input)),
|
|
162
|
-
target: { format: input.to }
|
|
163
|
-
}));
|
|
164
|
-
registerTool(server, "diff_machines", {
|
|
165
|
-
title: "Diff machines",
|
|
166
|
-
description: "Compute a semantic diff between two machine documents through the Stately API.",
|
|
167
|
-
inputSchema: {
|
|
168
|
-
base: sourceSchema,
|
|
169
|
-
compare: sourceSchema,
|
|
170
|
-
includePatches: zod.z.boolean().optional()
|
|
171
|
-
}
|
|
172
|
-
}, (input) => callApi(options, "/v1/diff", {
|
|
173
|
-
base: toConversionSource(normalizeSource({ source: input.base })),
|
|
174
|
-
compare: toConversionSource(normalizeSource({ source: input.compare })),
|
|
175
|
-
includePatches: input.includePatches
|
|
176
|
-
}));
|
|
177
|
-
registerTool(server, "apply_machine_patches", {
|
|
178
|
-
title: "Apply machine patches",
|
|
179
|
-
description: "Apply semantic machine patches through the Stately API.",
|
|
180
|
-
inputSchema: {
|
|
181
|
-
machine: machineSchema.optional(),
|
|
182
|
-
format: apiFormatSchema.optional(),
|
|
183
|
-
source: sourceSchema.optional(),
|
|
184
|
-
patches: zod.z.array(patchSchema),
|
|
185
|
-
to: apiFormatSchema.optional()
|
|
186
|
-
}
|
|
187
|
-
}, (input) => callApi(options, "/v1/apply-patches", {
|
|
188
|
-
source: toConversionSource(normalizeSource(input)),
|
|
189
|
-
patches: input.patches,
|
|
190
|
-
target: input.to ? { format: input.to } : void 0
|
|
191
|
-
}));
|
|
192
|
-
registerTool(server, "generate_graph_paths", {
|
|
193
|
-
title: "Generate graph paths",
|
|
194
|
-
description: "Generate shortest or simple paths through the Stately API.",
|
|
195
|
-
inputSchema: {
|
|
196
|
-
machine: machineSchema.optional(),
|
|
197
|
-
format: apiFormatSchema.optional(),
|
|
198
|
-
source: sourceSchema.optional(),
|
|
199
|
-
graph: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
|
|
200
|
-
kind: zod.z.enum(["shortest", "simple"]).default("shortest"),
|
|
201
|
-
max: zod.z.number().int().positive().optional()
|
|
202
|
-
}
|
|
203
|
-
}, async (input) => {
|
|
204
|
-
return callApi(options, "/v1/graph-paths", {
|
|
205
|
-
graph: input.graph ?? (await callApi(options, "/v1/convert", {
|
|
206
|
-
source: toConversionSource(normalizeSource(input)),
|
|
207
|
-
target: { format: "xgraph" }
|
|
208
|
-
})).content,
|
|
209
|
-
options: {
|
|
210
|
-
strategy: input.kind,
|
|
211
|
-
limit: Math.min(input.max ?? 5, options.anonymousGraphPathLimit ?? 5)
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
registerTool(server, "simulate_machine", {
|
|
216
|
-
title: "Simulate machine",
|
|
217
|
-
description: "Simulate an XState machine through a sequence of events; returns per-step state, context, and executed actions. Deterministic; guards are stubbed.",
|
|
218
|
-
inputSchema: {
|
|
219
|
-
machine: machineSchema.optional(),
|
|
220
|
-
format: apiFormatSchema.optional(),
|
|
221
|
-
source: sourceSchema.optional(),
|
|
222
|
-
events: zod.z.array(zod.z.object({ type: zod.z.string() }).passthrough()).min(1).max(100)
|
|
223
|
-
}
|
|
224
|
-
}, (input) => callApi(options, "/v1/simulate", {
|
|
225
|
-
source: toConversionSource(normalizeSource(input)),
|
|
226
|
-
events: input.events
|
|
227
|
-
}));
|
|
228
|
-
registerTool(server, "visualize_machine", {
|
|
229
|
-
title: "Visualize machine",
|
|
230
|
-
description: "Render the machine as an SVG diagram (returns svg string + dimensions).",
|
|
231
|
-
inputSchema: {
|
|
232
|
-
machine: machineSchema.optional(),
|
|
233
|
-
format: apiFormatSchema.optional(),
|
|
234
|
-
source: sourceSchema.optional(),
|
|
235
|
-
direction: zod.z.enum(["right", "down"]).optional()
|
|
236
|
-
}
|
|
237
|
-
}, (input) => callApi(options, "/v1/visualize", {
|
|
238
|
-
source: toConversionSource(normalizeSource(input)),
|
|
239
|
-
options: input.direction ? { direction: input.direction } : void 0
|
|
240
|
-
}));
|
|
241
|
-
registerTool(server, "analyze_machine", {
|
|
242
|
-
title: "Analyze machine",
|
|
243
|
-
description: "Deep analysis of machine logic (merges deterministic graph checks with AI review); may require an API key — configure via server options; for purely deterministic checks use validate_machine.",
|
|
244
|
-
inputSchema: {
|
|
245
|
-
machine: machineSchema.optional(),
|
|
246
|
-
format: apiFormatSchema.optional(),
|
|
247
|
-
source: sourceSchema.optional(),
|
|
248
|
-
intent: zod.z.string().optional()
|
|
249
|
-
}
|
|
250
|
-
}, (input) => callApi(options, "/v1/analyze", {
|
|
251
|
-
source: toConversionSource(normalizeSource(input)),
|
|
252
|
-
intent: input.intent
|
|
253
|
-
}));
|
|
254
|
-
registerTool(server, "generate_test_code", {
|
|
255
|
-
title: "Generate test code",
|
|
256
|
-
description: "Generate a runnable vitest test file from the machine, one test per generated path. Guards and actions are stubbed; assertions use state paths.",
|
|
257
|
-
inputSchema: {
|
|
258
|
-
machine: machineSchema.optional(),
|
|
259
|
-
format: apiFormatSchema.optional(),
|
|
260
|
-
source: sourceSchema.optional(),
|
|
261
|
-
kind: zod.z.enum(["shortest", "simple"]).default("shortest"),
|
|
262
|
-
max: zod.z.number().int().positive().optional()
|
|
263
|
-
}
|
|
264
|
-
}, async (input) => {
|
|
265
|
-
const source = toConversionSource(normalizeSource(input));
|
|
266
|
-
const graph = (await callApi(options, "/v1/convert", {
|
|
267
|
-
source,
|
|
268
|
-
target: { format: "xgraph" }
|
|
269
|
-
})).content;
|
|
270
|
-
const pathsResponse = await callApi(options, "/v1/graph-paths", {
|
|
271
|
-
graph,
|
|
272
|
-
options: {
|
|
273
|
-
strategy: input.kind,
|
|
274
|
-
limit: Math.min(input.max ?? 5, options.anonymousGraphPathLimit ?? 5)
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
return {
|
|
278
|
-
code: generateTestCode(String((await callApi(options, "/v1/convert", {
|
|
279
|
-
source,
|
|
280
|
-
target: { format: "xstate" }
|
|
281
|
-
})).content), pathsResponse.paths),
|
|
282
|
-
paths: pathsResponse.paths,
|
|
283
|
-
coverage: pathsResponse.coverage
|
|
284
|
-
};
|
|
285
|
-
});
|
|
286
|
-
registerTool(server, "extract_machines_from_code", {
|
|
287
|
-
title: "Extract machines from code",
|
|
288
|
-
description: "Parse XState source code into XGraph through the Stately API.",
|
|
289
|
-
inputSchema: { code: zod.z.string().min(1) }
|
|
290
|
-
}, async (input) => {
|
|
291
|
-
return { machines: [{
|
|
292
|
-
format: "xgraph",
|
|
293
|
-
graph: (await callApi(options, "/v1/convert", {
|
|
294
|
-
source: {
|
|
295
|
-
format: "xstate",
|
|
296
|
-
content: input.code
|
|
297
|
-
},
|
|
298
|
-
target: { format: "xgraph" }
|
|
299
|
-
})).content
|
|
300
|
-
}] };
|
|
301
|
-
});
|
|
302
|
-
return server;
|
|
303
|
-
}
|
|
304
|
-
function createStatelyMcpServer(options = {}) {
|
|
305
|
-
const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
|
|
306
|
-
name: "stately",
|
|
307
|
-
version: "0.1.0"
|
|
308
|
-
});
|
|
309
|
-
registerStatelyMcpTools(server, options);
|
|
310
|
-
return server;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
//#endregion
|
|
314
|
-
//#region src/index.ts
|
|
315
|
-
async function loadNode() {
|
|
316
|
-
const [fs, os, path] = await Promise.all([
|
|
317
|
-
import("node:fs/promises"),
|
|
318
|
-
import("node:os"),
|
|
319
|
-
import("node:path")
|
|
320
|
-
]);
|
|
321
|
-
return {
|
|
322
|
-
fs,
|
|
323
|
-
os,
|
|
324
|
-
path
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
function workspaceFromArgv() {
|
|
328
|
-
const index = process.argv.indexOf("--workspace");
|
|
329
|
-
return index >= 0 ? process.argv[index + 1] : process.cwd();
|
|
330
|
-
}
|
|
331
|
-
function isCandidate(pathname) {
|
|
332
|
-
return /\.(?:[cm]?[jt]sx?|json|ya?ml|scxml|mmd|mermaid)$/i.test(pathname);
|
|
333
|
-
}
|
|
334
|
-
function formatForPath(pathname, text) {
|
|
335
|
-
if (/\.(?:mmd|mermaid)$/i.test(pathname)) return "mermaid";
|
|
336
|
-
if (/\.scxml$/i.test(pathname)) return "scxml";
|
|
337
|
-
if (/\.ya?ml$/i.test(pathname)) return "yaml";
|
|
338
|
-
if (/\.json$/i.test(pathname)) return "json";
|
|
339
|
-
if (/^\s*</.test(text)) return "scxml";
|
|
340
|
-
return "xstate";
|
|
341
|
-
}
|
|
342
|
-
async function walkWorkspace(root, glob) {
|
|
343
|
-
const { fs, path } = await loadNode();
|
|
344
|
-
const results = [];
|
|
345
|
-
async function walk(dir) {
|
|
346
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
347
|
-
for (const entry of entries) {
|
|
348
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
|
|
349
|
-
const full = path.join(dir, entry.name);
|
|
350
|
-
if (entry.isDirectory()) await walk(full);
|
|
351
|
-
else if (isCandidate(full)) {
|
|
352
|
-
const relative = path.relative(root, full);
|
|
353
|
-
if (!glob || relative.includes(glob)) results.push(relative);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
await walk(root);
|
|
358
|
-
return results;
|
|
359
|
-
}
|
|
360
|
-
async function readWorkspaceMachine(input) {
|
|
361
|
-
const { fs, path } = await loadNode();
|
|
362
|
-
const root = workspaceFromArgv();
|
|
363
|
-
const fullPath = path.resolve(root, input.path);
|
|
364
|
-
const relative = path.relative(root, fullPath);
|
|
365
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error("Path must stay inside workspace.");
|
|
366
|
-
const sourceText = await fs.readFile(fullPath, "utf8");
|
|
367
|
-
return {
|
|
368
|
-
machine: sourceText,
|
|
369
|
-
format: formatForPath(fullPath, sourceText),
|
|
370
|
-
source: { path: relative },
|
|
371
|
-
...input.includeSourceText ? { sourceText } : {}
|
|
372
|
-
};
|
|
373
|
-
}
|
|
374
|
-
async function readStoredCliCredential() {
|
|
375
|
-
const { fs, os, path } = await loadNode();
|
|
376
|
-
const configDir = process.env.STATELYAI_CONFIG_DIR ? path.resolve(process.env.STATELYAI_CONFIG_DIR) : process.platform === "darwin" ? path.join(os.homedir(), "Library", "Application Support", "statelyai") : process.platform === "win32" ? path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "statelyai") : path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "statelyai");
|
|
377
|
-
try {
|
|
378
|
-
const raw = await fs.readFile(path.join(configDir, "credentials.json"), "utf8");
|
|
379
|
-
const parsed = JSON.parse(raw);
|
|
380
|
-
if (typeof parsed.apiKey === "string" && parsed.apiKey) return {
|
|
381
|
-
type: "api_key",
|
|
382
|
-
token: parsed.apiKey
|
|
383
|
-
};
|
|
384
|
-
if (typeof parsed.credential === "object" && parsed.credential !== null && "type" in parsed.credential) {
|
|
385
|
-
const credential = parsed.credential;
|
|
386
|
-
if (credential.type === "api_key" && typeof credential.token === "string") return {
|
|
387
|
-
type: "api_key",
|
|
388
|
-
token: credential.token
|
|
389
|
-
};
|
|
390
|
-
if (credential.type === "oauth" && typeof credential.accessToken === "string") return {
|
|
391
|
-
type: "oauth",
|
|
392
|
-
accessToken: credential.accessToken,
|
|
393
|
-
...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
|
|
394
|
-
...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {}
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
} catch {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
async function getDefaultCredential() {
|
|
402
|
-
if (process.env.STATELY_ACCESS_TOKEN) return {
|
|
403
|
-
type: "oauth",
|
|
404
|
-
accessToken: process.env.STATELY_ACCESS_TOKEN
|
|
405
|
-
};
|
|
406
|
-
if (process.env.STATELY_API_KEY) return {
|
|
407
|
-
type: "api_key",
|
|
408
|
-
token: process.env.STATELY_API_KEY
|
|
409
|
-
};
|
|
410
|
-
return readStoredCliCredential();
|
|
411
|
-
}
|
|
412
|
-
function toolText(body) {
|
|
413
|
-
return JSON.stringify(body, null, 2);
|
|
414
|
-
}
|
|
415
|
-
function createStatelyMcpServer$1(options = {}) {
|
|
416
|
-
const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
|
|
417
|
-
name: "stately",
|
|
418
|
-
version: "0.1.0"
|
|
419
|
-
});
|
|
420
|
-
if (options.includeWorkspaceTools !== false) {
|
|
421
|
-
server.registerTool("list_machines", {
|
|
422
|
-
title: "list_machines",
|
|
423
|
-
description: "List candidate machine sources in the workspace.",
|
|
424
|
-
inputSchema: { glob: zod.z.string().optional() },
|
|
425
|
-
annotations: readOnlyAnnotations
|
|
426
|
-
}, async (request) => {
|
|
427
|
-
const machines = (await walkWorkspace(workspaceFromArgv(), request.glob)).map((pathname) => ({
|
|
428
|
-
source: { path: pathname },
|
|
429
|
-
format: "xstate"
|
|
430
|
-
}));
|
|
431
|
-
return {
|
|
432
|
-
structuredContent: { machines },
|
|
433
|
-
content: [{
|
|
434
|
-
type: "text",
|
|
435
|
-
text: toolText({ machines })
|
|
436
|
-
}]
|
|
437
|
-
};
|
|
438
|
-
});
|
|
439
|
-
server.registerTool("get_machine", {
|
|
440
|
-
title: "get_machine",
|
|
441
|
-
description: "Read a machine source from the workspace and return it.",
|
|
442
|
-
inputSchema: {
|
|
443
|
-
path: zod.z.string(),
|
|
444
|
-
includeSourceText: zod.z.boolean().optional()
|
|
445
|
-
},
|
|
446
|
-
annotations: readOnlyAnnotations
|
|
447
|
-
}, async (request) => {
|
|
448
|
-
const result = await readWorkspaceMachine(request);
|
|
449
|
-
return {
|
|
450
|
-
structuredContent: result,
|
|
451
|
-
content: [{
|
|
452
|
-
type: "text",
|
|
453
|
-
text: toolText(result)
|
|
454
|
-
}]
|
|
455
|
-
};
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
registerStatelyMcpTools(server, {
|
|
459
|
-
...options,
|
|
460
|
-
authMode: options.authMode ?? (process.env.STATELY_AUTH_MODE === "none" ? "none" : void 0),
|
|
461
|
-
credentialProvider: options.credentialProvider ?? getDefaultCredential
|
|
462
|
-
});
|
|
463
|
-
return server;
|
|
464
|
-
}
|
|
465
|
-
async function main() {
|
|
466
|
-
const server = createStatelyMcpServer$1();
|
|
467
|
-
const transport = new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport();
|
|
468
|
-
await server.connect(transport);
|
|
469
|
-
}
|
|
470
|
-
if (require("url").pathToFileURL(__filename).href === `file://${process.argv[1]}`) main();
|
|
471
|
-
|
|
472
|
-
//#endregion
|
|
473
|
-
exports.createHostedStatelyMcpServer = createStatelyMcpServer;
|
|
474
|
-
exports.createStatelyMcpServer = createStatelyMcpServer$1;
|
|
475
|
-
exports.formatForPath = formatForPath;
|
|
476
|
-
exports.generateTestCode = generateTestCode;
|
|
477
|
-
exports.getDefaultCredential = getDefaultCredential;
|
|
478
|
-
exports.isCandidate = isCandidate;
|
|
479
|
-
exports.readOnlyAnnotations = readOnlyAnnotations;
|
|
480
|
-
exports.readWorkspaceMachine = readWorkspaceMachine;
|
|
481
|
-
exports.registerStatelyMcpTools = registerStatelyMcpTools;
|
|
482
|
-
exports.walkWorkspace = walkWorkspace;
|
|
483
|
-
exports.workspaceFromArgv = workspaceFromArgv;
|
package/dist/index.d.cts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
|
|
3
|
-
//#region src/server.d.ts
|
|
4
|
-
declare const readOnlyAnnotations: {
|
|
5
|
-
readOnlyHint: boolean;
|
|
6
|
-
destructiveHint: boolean;
|
|
7
|
-
idempotentHint: boolean;
|
|
8
|
-
openWorldHint: boolean;
|
|
9
|
-
};
|
|
10
|
-
type StatelyMcpServerOptions = {
|
|
11
|
-
includeWorkspaceTools?: boolean;
|
|
12
|
-
apiBaseUrl?: string | URL;
|
|
13
|
-
apiHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
14
|
-
credentialProvider?: () => StatelyCredential | undefined | Promise<StatelyCredential | undefined>;
|
|
15
|
-
authMode?: 'auto' | 'none' | 'bearer';
|
|
16
|
-
anonymousGraphPathLimit?: number;
|
|
17
|
-
};
|
|
18
|
-
type StatelyCredential = {
|
|
19
|
-
type: 'api_key';
|
|
20
|
-
token: string;
|
|
21
|
-
} | {
|
|
22
|
-
type: 'oauth';
|
|
23
|
-
accessToken: string;
|
|
24
|
-
refreshToken?: string;
|
|
25
|
-
expiresAt?: number;
|
|
26
|
-
};
|
|
27
|
-
type GraphPathStep = {
|
|
28
|
-
stateIds: string[];
|
|
29
|
-
statePath: string;
|
|
30
|
-
eventType: string | null;
|
|
31
|
-
edgeId: string | null;
|
|
32
|
-
};
|
|
33
|
-
type GraphPath = {
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
viaLabel: string | null;
|
|
37
|
-
steps: GraphPathStep[];
|
|
38
|
-
finalStatePath: string;
|
|
39
|
-
finalStateIds: string[];
|
|
40
|
-
};
|
|
41
|
-
declare function generateTestCode(machineCode: string, paths: GraphPath[]): string;
|
|
42
|
-
declare function registerStatelyMcpTools(server: McpServer, options?: StatelyMcpServerOptions): McpServer;
|
|
43
|
-
declare function createStatelyMcpServer(options?: StatelyMcpServerOptions): McpServer;
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/index.d.ts
|
|
46
|
-
declare function workspaceFromArgv(): string;
|
|
47
|
-
declare function isCandidate(pathname: string): boolean;
|
|
48
|
-
declare function formatForPath(pathname: string, text: string): "mermaid" | "scxml" | "yaml" | "json" | "xstate";
|
|
49
|
-
declare function walkWorkspace(root: string, glob?: string): Promise<string[]>;
|
|
50
|
-
declare function readWorkspaceMachine(input: {
|
|
51
|
-
path: string;
|
|
52
|
-
includeSourceText?: boolean;
|
|
53
|
-
}): Promise<{
|
|
54
|
-
sourceText?: string | undefined;
|
|
55
|
-
machine: string;
|
|
56
|
-
format: "mermaid" | "scxml" | "yaml" | "json" | "xstate";
|
|
57
|
-
source: {
|
|
58
|
-
path: string;
|
|
59
|
-
};
|
|
60
|
-
}>;
|
|
61
|
-
declare function getDefaultCredential(): Promise<{
|
|
62
|
-
type: "oauth";
|
|
63
|
-
accessToken: string;
|
|
64
|
-
refreshToken?: string;
|
|
65
|
-
expiresAt?: number;
|
|
66
|
-
} | {
|
|
67
|
-
type: "api_key";
|
|
68
|
-
token: string;
|
|
69
|
-
} | undefined>;
|
|
70
|
-
declare function createStatelyMcpServer$1(options?: StatelyMcpServerOptions): McpServer;
|
|
71
|
-
//#endregion
|
|
72
|
-
export { type StatelyMcpServerOptions, createStatelyMcpServer as createHostedStatelyMcpServer, createStatelyMcpServer$1 as createStatelyMcpServer, formatForPath, generateTestCode, getDefaultCredential, isCandidate, readOnlyAnnotations, readWorkspaceMachine, registerStatelyMcpTools, walkWorkspace, workspaceFromArgv };
|
package/dist/index.d.mts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
|
|
3
|
-
//#region src/server.d.ts
|
|
4
|
-
declare const readOnlyAnnotations: {
|
|
5
|
-
readOnlyHint: boolean;
|
|
6
|
-
destructiveHint: boolean;
|
|
7
|
-
idempotentHint: boolean;
|
|
8
|
-
openWorldHint: boolean;
|
|
9
|
-
};
|
|
10
|
-
type StatelyMcpServerOptions = {
|
|
11
|
-
includeWorkspaceTools?: boolean;
|
|
12
|
-
apiBaseUrl?: string | URL;
|
|
13
|
-
apiHeaders?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
14
|
-
credentialProvider?: () => StatelyCredential | undefined | Promise<StatelyCredential | undefined>;
|
|
15
|
-
authMode?: 'auto' | 'none' | 'bearer';
|
|
16
|
-
anonymousGraphPathLimit?: number;
|
|
17
|
-
};
|
|
18
|
-
type StatelyCredential = {
|
|
19
|
-
type: 'api_key';
|
|
20
|
-
token: string;
|
|
21
|
-
} | {
|
|
22
|
-
type: 'oauth';
|
|
23
|
-
accessToken: string;
|
|
24
|
-
refreshToken?: string;
|
|
25
|
-
expiresAt?: number;
|
|
26
|
-
};
|
|
27
|
-
type GraphPathStep = {
|
|
28
|
-
stateIds: string[];
|
|
29
|
-
statePath: string;
|
|
30
|
-
eventType: string | null;
|
|
31
|
-
edgeId: string | null;
|
|
32
|
-
};
|
|
33
|
-
type GraphPath = {
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
viaLabel: string | null;
|
|
37
|
-
steps: GraphPathStep[];
|
|
38
|
-
finalStatePath: string;
|
|
39
|
-
finalStateIds: string[];
|
|
40
|
-
};
|
|
41
|
-
declare function generateTestCode(machineCode: string, paths: GraphPath[]): string;
|
|
42
|
-
declare function registerStatelyMcpTools(server: McpServer, options?: StatelyMcpServerOptions): McpServer;
|
|
43
|
-
declare function createStatelyMcpServer(options?: StatelyMcpServerOptions): McpServer;
|
|
44
|
-
//#endregion
|
|
45
|
-
//#region src/index.d.ts
|
|
46
|
-
declare function workspaceFromArgv(): string;
|
|
47
|
-
declare function isCandidate(pathname: string): boolean;
|
|
48
|
-
declare function formatForPath(pathname: string, text: string): "mermaid" | "scxml" | "yaml" | "json" | "xstate";
|
|
49
|
-
declare function walkWorkspace(root: string, glob?: string): Promise<string[]>;
|
|
50
|
-
declare function readWorkspaceMachine(input: {
|
|
51
|
-
path: string;
|
|
52
|
-
includeSourceText?: boolean;
|
|
53
|
-
}): Promise<{
|
|
54
|
-
sourceText?: string | undefined;
|
|
55
|
-
machine: string;
|
|
56
|
-
format: "mermaid" | "scxml" | "yaml" | "json" | "xstate";
|
|
57
|
-
source: {
|
|
58
|
-
path: string;
|
|
59
|
-
};
|
|
60
|
-
}>;
|
|
61
|
-
declare function getDefaultCredential(): Promise<{
|
|
62
|
-
type: "oauth";
|
|
63
|
-
accessToken: string;
|
|
64
|
-
refreshToken?: string;
|
|
65
|
-
expiresAt?: number;
|
|
66
|
-
} | {
|
|
67
|
-
type: "api_key";
|
|
68
|
-
token: string;
|
|
69
|
-
} | undefined>;
|
|
70
|
-
declare function createStatelyMcpServer$1(options?: StatelyMcpServerOptions): McpServer;
|
|
71
|
-
//#endregion
|
|
72
|
-
export { type StatelyMcpServerOptions, createStatelyMcpServer as createHostedStatelyMcpServer, createStatelyMcpServer$1 as createStatelyMcpServer, formatForPath, generateTestCode, getDefaultCredential, isCandidate, readOnlyAnnotations, readWorkspaceMachine, registerStatelyMcpTools, walkWorkspace, workspaceFromArgv };
|
package/dist/index.mjs
DELETED
|
@@ -1,472 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
-
import { z } from "zod";
|
|
5
|
-
|
|
6
|
-
//#region src/server.ts
|
|
7
|
-
const readOnlyAnnotations = {
|
|
8
|
-
readOnlyHint: true,
|
|
9
|
-
destructiveHint: false,
|
|
10
|
-
idempotentHint: true,
|
|
11
|
-
openWorldHint: false
|
|
12
|
-
};
|
|
13
|
-
const apiFormats = [
|
|
14
|
-
"d2",
|
|
15
|
-
"scxml",
|
|
16
|
-
"xgraph",
|
|
17
|
-
"xstate"
|
|
18
|
-
];
|
|
19
|
-
const validationFormats = [
|
|
20
|
-
...apiFormats,
|
|
21
|
-
"json",
|
|
22
|
-
"yaml",
|
|
23
|
-
"mermaid"
|
|
24
|
-
];
|
|
25
|
-
const apiFormatSchema = z.enum(apiFormats);
|
|
26
|
-
const validationFormatSchema = z.enum(validationFormats);
|
|
27
|
-
const machineSchema = z.union([z.record(z.string(), z.unknown()), z.string()]);
|
|
28
|
-
const sourceSchema = z.object({
|
|
29
|
-
format: validationFormatSchema.optional(),
|
|
30
|
-
content: machineSchema
|
|
31
|
-
});
|
|
32
|
-
const patchSchema = z.object({
|
|
33
|
-
op: z.string(),
|
|
34
|
-
path: z.string(),
|
|
35
|
-
value: z.unknown().optional()
|
|
36
|
-
});
|
|
37
|
-
function generateTestCode(machineCode, paths) {
|
|
38
|
-
return [
|
|
39
|
-
[
|
|
40
|
-
"// Generated by generate_test_code (Stately MCP).",
|
|
41
|
-
"// Guards and actions are stubbed; assertions use state paths.",
|
|
42
|
-
"import { createActor } from 'xstate';",
|
|
43
|
-
"import { describe, expect, it } from 'vitest';",
|
|
44
|
-
"",
|
|
45
|
-
machineCode.trim(),
|
|
46
|
-
""
|
|
47
|
-
].join("\n"),
|
|
48
|
-
"describe('generated paths', () => {",
|
|
49
|
-
paths.map((path) => {
|
|
50
|
-
const events = path.steps.filter((step) => step.eventType !== null && step.eventType !== "" && !step.eventType.startsWith("xstate.")).map((step) => ` actor.send({ type: ${JSON.stringify(step.eventType)} });`);
|
|
51
|
-
return [
|
|
52
|
-
` it(${JSON.stringify(path.name)}, () => {`,
|
|
53
|
-
" const actor = createActor(machine).start();",
|
|
54
|
-
...events,
|
|
55
|
-
` expect(actor.getSnapshot().matches(${JSON.stringify(path.finalStatePath)})).toBe(true);`,
|
|
56
|
-
" });"
|
|
57
|
-
].join("\n");
|
|
58
|
-
}).join("\n\n"),
|
|
59
|
-
"});",
|
|
60
|
-
""
|
|
61
|
-
].join("\n");
|
|
62
|
-
}
|
|
63
|
-
function toolText$1(body) {
|
|
64
|
-
return JSON.stringify(body, null, 2);
|
|
65
|
-
}
|
|
66
|
-
function toolResult(body) {
|
|
67
|
-
return {
|
|
68
|
-
structuredContent: body,
|
|
69
|
-
content: [{
|
|
70
|
-
type: "text",
|
|
71
|
-
text: toolText$1(body)
|
|
72
|
-
}]
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
function toolError(body) {
|
|
76
|
-
return {
|
|
77
|
-
isError: true,
|
|
78
|
-
...toolResult(body)
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
function normalizeApiBaseUrl(apiBaseUrl) {
|
|
82
|
-
return new URL(String(apiBaseUrl ?? "https://editor.stately.ai/api")).toString().replace(/\/$/, "");
|
|
83
|
-
}
|
|
84
|
-
function normalizeSource(input) {
|
|
85
|
-
const content = input.source?.content ?? input.machine;
|
|
86
|
-
if (content === void 0) throw new Error("Expected machine or source.content.");
|
|
87
|
-
return {
|
|
88
|
-
format: input.source?.format ?? input.format ?? (typeof content === "string" ? "xstate" : "json"),
|
|
89
|
-
content
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function toConversionSource(source) {
|
|
93
|
-
if (!apiFormats.includes(source.format)) {
|
|
94
|
-
if (source.format === "json" && typeof source.content === "object") return {
|
|
95
|
-
format: "xstate",
|
|
96
|
-
content: `createMachine(${JSON.stringify(source.content, null, 2)})`
|
|
97
|
-
};
|
|
98
|
-
if (source.format === "json" && typeof source.content === "string") return {
|
|
99
|
-
format: "xstate",
|
|
100
|
-
content: source.content
|
|
101
|
-
};
|
|
102
|
-
throw new Error(`Format ${source.format} is only supported by validate_machine.`);
|
|
103
|
-
}
|
|
104
|
-
return source;
|
|
105
|
-
}
|
|
106
|
-
async function callApi(options, pathname, body) {
|
|
107
|
-
const configuredHeaders = typeof options.apiHeaders === "function" ? await options.apiHeaders() : options.apiHeaders;
|
|
108
|
-
const headers = new Headers(configuredHeaders);
|
|
109
|
-
const authMode = options.authMode ?? "auto";
|
|
110
|
-
if (authMode !== "none" && !headers.has("authorization")) {
|
|
111
|
-
const credential = await options.credentialProvider?.();
|
|
112
|
-
const token = credential?.type === "oauth" ? credential.accessToken : credential?.token;
|
|
113
|
-
if (token) headers.set("authorization", `Bearer ${token}`);
|
|
114
|
-
else if (authMode === "bearer") throw new Error("No Stately credential configured. Run `statelyai login` or set STATELY_ACCESS_TOKEN.");
|
|
115
|
-
}
|
|
116
|
-
const response = await fetch(`${normalizeApiBaseUrl(options.apiBaseUrl)}${pathname}`, {
|
|
117
|
-
method: "POST",
|
|
118
|
-
headers: {
|
|
119
|
-
"content-type": "application/json",
|
|
120
|
-
...Object.fromEntries(headers.entries())
|
|
121
|
-
},
|
|
122
|
-
body: JSON.stringify(body)
|
|
123
|
-
});
|
|
124
|
-
const result = await response.json().catch(() => ({ error: `Invalid JSON response from ${pathname}.` }));
|
|
125
|
-
if (!response.ok) throw new Error(typeof result.error === "string" ? result.error : `${pathname} failed with HTTP ${response.status}`);
|
|
126
|
-
return result;
|
|
127
|
-
}
|
|
128
|
-
function registerTool(server, name, config, handler) {
|
|
129
|
-
server.registerTool(name, {
|
|
130
|
-
...config,
|
|
131
|
-
annotations: readOnlyAnnotations
|
|
132
|
-
}, async (args) => {
|
|
133
|
-
try {
|
|
134
|
-
return toolResult(await handler(args));
|
|
135
|
-
} catch (error) {
|
|
136
|
-
return toolError({ error: error instanceof Error ? error.message : String(error) });
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
}
|
|
140
|
-
function registerStatelyMcpTools(server, options = {}) {
|
|
141
|
-
registerTool(server, "validate_machine", {
|
|
142
|
-
title: "Validate machine",
|
|
143
|
-
description: "Validate an XState machine config or source string through the Stately API.",
|
|
144
|
-
inputSchema: {
|
|
145
|
-
machine: machineSchema.optional(),
|
|
146
|
-
format: validationFormatSchema.optional(),
|
|
147
|
-
source: sourceSchema.optional()
|
|
148
|
-
}
|
|
149
|
-
}, (input) => callApi(options, "/v1/validate", { source: normalizeSource(input) }));
|
|
150
|
-
registerTool(server, "convert_machine", {
|
|
151
|
-
title: "Convert machine",
|
|
152
|
-
description: "Convert between XState, SCXML, XGraph, and D2 through the Stately API.",
|
|
153
|
-
inputSchema: {
|
|
154
|
-
machine: machineSchema.optional(),
|
|
155
|
-
format: apiFormatSchema.optional(),
|
|
156
|
-
source: sourceSchema.optional(),
|
|
157
|
-
to: apiFormatSchema
|
|
158
|
-
}
|
|
159
|
-
}, (input) => callApi(options, "/v1/convert", {
|
|
160
|
-
source: toConversionSource(normalizeSource(input)),
|
|
161
|
-
target: { format: input.to }
|
|
162
|
-
}));
|
|
163
|
-
registerTool(server, "diff_machines", {
|
|
164
|
-
title: "Diff machines",
|
|
165
|
-
description: "Compute a semantic diff between two machine documents through the Stately API.",
|
|
166
|
-
inputSchema: {
|
|
167
|
-
base: sourceSchema,
|
|
168
|
-
compare: sourceSchema,
|
|
169
|
-
includePatches: z.boolean().optional()
|
|
170
|
-
}
|
|
171
|
-
}, (input) => callApi(options, "/v1/diff", {
|
|
172
|
-
base: toConversionSource(normalizeSource({ source: input.base })),
|
|
173
|
-
compare: toConversionSource(normalizeSource({ source: input.compare })),
|
|
174
|
-
includePatches: input.includePatches
|
|
175
|
-
}));
|
|
176
|
-
registerTool(server, "apply_machine_patches", {
|
|
177
|
-
title: "Apply machine patches",
|
|
178
|
-
description: "Apply semantic machine patches through the Stately API.",
|
|
179
|
-
inputSchema: {
|
|
180
|
-
machine: machineSchema.optional(),
|
|
181
|
-
format: apiFormatSchema.optional(),
|
|
182
|
-
source: sourceSchema.optional(),
|
|
183
|
-
patches: z.array(patchSchema),
|
|
184
|
-
to: apiFormatSchema.optional()
|
|
185
|
-
}
|
|
186
|
-
}, (input) => callApi(options, "/v1/apply-patches", {
|
|
187
|
-
source: toConversionSource(normalizeSource(input)),
|
|
188
|
-
patches: input.patches,
|
|
189
|
-
target: input.to ? { format: input.to } : void 0
|
|
190
|
-
}));
|
|
191
|
-
registerTool(server, "generate_graph_paths", {
|
|
192
|
-
title: "Generate graph paths",
|
|
193
|
-
description: "Generate shortest or simple paths through the Stately API.",
|
|
194
|
-
inputSchema: {
|
|
195
|
-
machine: machineSchema.optional(),
|
|
196
|
-
format: apiFormatSchema.optional(),
|
|
197
|
-
source: sourceSchema.optional(),
|
|
198
|
-
graph: z.record(z.string(), z.unknown()).optional(),
|
|
199
|
-
kind: z.enum(["shortest", "simple"]).default("shortest"),
|
|
200
|
-
max: z.number().int().positive().optional()
|
|
201
|
-
}
|
|
202
|
-
}, async (input) => {
|
|
203
|
-
return callApi(options, "/v1/graph-paths", {
|
|
204
|
-
graph: input.graph ?? (await callApi(options, "/v1/convert", {
|
|
205
|
-
source: toConversionSource(normalizeSource(input)),
|
|
206
|
-
target: { format: "xgraph" }
|
|
207
|
-
})).content,
|
|
208
|
-
options: {
|
|
209
|
-
strategy: input.kind,
|
|
210
|
-
limit: Math.min(input.max ?? 5, options.anonymousGraphPathLimit ?? 5)
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
registerTool(server, "simulate_machine", {
|
|
215
|
-
title: "Simulate machine",
|
|
216
|
-
description: "Simulate an XState machine through a sequence of events; returns per-step state, context, and executed actions. Deterministic; guards are stubbed.",
|
|
217
|
-
inputSchema: {
|
|
218
|
-
machine: machineSchema.optional(),
|
|
219
|
-
format: apiFormatSchema.optional(),
|
|
220
|
-
source: sourceSchema.optional(),
|
|
221
|
-
events: z.array(z.object({ type: z.string() }).passthrough()).min(1).max(100)
|
|
222
|
-
}
|
|
223
|
-
}, (input) => callApi(options, "/v1/simulate", {
|
|
224
|
-
source: toConversionSource(normalizeSource(input)),
|
|
225
|
-
events: input.events
|
|
226
|
-
}));
|
|
227
|
-
registerTool(server, "visualize_machine", {
|
|
228
|
-
title: "Visualize machine",
|
|
229
|
-
description: "Render the machine as an SVG diagram (returns svg string + dimensions).",
|
|
230
|
-
inputSchema: {
|
|
231
|
-
machine: machineSchema.optional(),
|
|
232
|
-
format: apiFormatSchema.optional(),
|
|
233
|
-
source: sourceSchema.optional(),
|
|
234
|
-
direction: z.enum(["right", "down"]).optional()
|
|
235
|
-
}
|
|
236
|
-
}, (input) => callApi(options, "/v1/visualize", {
|
|
237
|
-
source: toConversionSource(normalizeSource(input)),
|
|
238
|
-
options: input.direction ? { direction: input.direction } : void 0
|
|
239
|
-
}));
|
|
240
|
-
registerTool(server, "analyze_machine", {
|
|
241
|
-
title: "Analyze machine",
|
|
242
|
-
description: "Deep analysis of machine logic (merges deterministic graph checks with AI review); may require an API key — configure via server options; for purely deterministic checks use validate_machine.",
|
|
243
|
-
inputSchema: {
|
|
244
|
-
machine: machineSchema.optional(),
|
|
245
|
-
format: apiFormatSchema.optional(),
|
|
246
|
-
source: sourceSchema.optional(),
|
|
247
|
-
intent: z.string().optional()
|
|
248
|
-
}
|
|
249
|
-
}, (input) => callApi(options, "/v1/analyze", {
|
|
250
|
-
source: toConversionSource(normalizeSource(input)),
|
|
251
|
-
intent: input.intent
|
|
252
|
-
}));
|
|
253
|
-
registerTool(server, "generate_test_code", {
|
|
254
|
-
title: "Generate test code",
|
|
255
|
-
description: "Generate a runnable vitest test file from the machine, one test per generated path. Guards and actions are stubbed; assertions use state paths.",
|
|
256
|
-
inputSchema: {
|
|
257
|
-
machine: machineSchema.optional(),
|
|
258
|
-
format: apiFormatSchema.optional(),
|
|
259
|
-
source: sourceSchema.optional(),
|
|
260
|
-
kind: z.enum(["shortest", "simple"]).default("shortest"),
|
|
261
|
-
max: z.number().int().positive().optional()
|
|
262
|
-
}
|
|
263
|
-
}, async (input) => {
|
|
264
|
-
const source = toConversionSource(normalizeSource(input));
|
|
265
|
-
const graph = (await callApi(options, "/v1/convert", {
|
|
266
|
-
source,
|
|
267
|
-
target: { format: "xgraph" }
|
|
268
|
-
})).content;
|
|
269
|
-
const pathsResponse = await callApi(options, "/v1/graph-paths", {
|
|
270
|
-
graph,
|
|
271
|
-
options: {
|
|
272
|
-
strategy: input.kind,
|
|
273
|
-
limit: Math.min(input.max ?? 5, options.anonymousGraphPathLimit ?? 5)
|
|
274
|
-
}
|
|
275
|
-
});
|
|
276
|
-
return {
|
|
277
|
-
code: generateTestCode(String((await callApi(options, "/v1/convert", {
|
|
278
|
-
source,
|
|
279
|
-
target: { format: "xstate" }
|
|
280
|
-
})).content), pathsResponse.paths),
|
|
281
|
-
paths: pathsResponse.paths,
|
|
282
|
-
coverage: pathsResponse.coverage
|
|
283
|
-
};
|
|
284
|
-
});
|
|
285
|
-
registerTool(server, "extract_machines_from_code", {
|
|
286
|
-
title: "Extract machines from code",
|
|
287
|
-
description: "Parse XState source code into XGraph through the Stately API.",
|
|
288
|
-
inputSchema: { code: z.string().min(1) }
|
|
289
|
-
}, async (input) => {
|
|
290
|
-
return { machines: [{
|
|
291
|
-
format: "xgraph",
|
|
292
|
-
graph: (await callApi(options, "/v1/convert", {
|
|
293
|
-
source: {
|
|
294
|
-
format: "xstate",
|
|
295
|
-
content: input.code
|
|
296
|
-
},
|
|
297
|
-
target: { format: "xgraph" }
|
|
298
|
-
})).content
|
|
299
|
-
}] };
|
|
300
|
-
});
|
|
301
|
-
return server;
|
|
302
|
-
}
|
|
303
|
-
function createStatelyMcpServer(options = {}) {
|
|
304
|
-
const server = new McpServer({
|
|
305
|
-
name: "stately",
|
|
306
|
-
version: "0.1.0"
|
|
307
|
-
});
|
|
308
|
-
registerStatelyMcpTools(server, options);
|
|
309
|
-
return server;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
//#endregion
|
|
313
|
-
//#region src/index.ts
|
|
314
|
-
async function loadNode() {
|
|
315
|
-
const [fs, os, path] = await Promise.all([
|
|
316
|
-
import("node:fs/promises"),
|
|
317
|
-
import("node:os"),
|
|
318
|
-
import("node:path")
|
|
319
|
-
]);
|
|
320
|
-
return {
|
|
321
|
-
fs,
|
|
322
|
-
os,
|
|
323
|
-
path
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
function workspaceFromArgv() {
|
|
327
|
-
const index = process.argv.indexOf("--workspace");
|
|
328
|
-
return index >= 0 ? process.argv[index + 1] : process.cwd();
|
|
329
|
-
}
|
|
330
|
-
function isCandidate(pathname) {
|
|
331
|
-
return /\.(?:[cm]?[jt]sx?|json|ya?ml|scxml|mmd|mermaid)$/i.test(pathname);
|
|
332
|
-
}
|
|
333
|
-
function formatForPath(pathname, text) {
|
|
334
|
-
if (/\.(?:mmd|mermaid)$/i.test(pathname)) return "mermaid";
|
|
335
|
-
if (/\.scxml$/i.test(pathname)) return "scxml";
|
|
336
|
-
if (/\.ya?ml$/i.test(pathname)) return "yaml";
|
|
337
|
-
if (/\.json$/i.test(pathname)) return "json";
|
|
338
|
-
if (/^\s*</.test(text)) return "scxml";
|
|
339
|
-
return "xstate";
|
|
340
|
-
}
|
|
341
|
-
async function walkWorkspace(root, glob) {
|
|
342
|
-
const { fs, path } = await loadNode();
|
|
343
|
-
const results = [];
|
|
344
|
-
async function walk(dir) {
|
|
345
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
346
|
-
for (const entry of entries) {
|
|
347
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "dist") continue;
|
|
348
|
-
const full = path.join(dir, entry.name);
|
|
349
|
-
if (entry.isDirectory()) await walk(full);
|
|
350
|
-
else if (isCandidate(full)) {
|
|
351
|
-
const relative = path.relative(root, full);
|
|
352
|
-
if (!glob || relative.includes(glob)) results.push(relative);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
await walk(root);
|
|
357
|
-
return results;
|
|
358
|
-
}
|
|
359
|
-
async function readWorkspaceMachine(input) {
|
|
360
|
-
const { fs, path } = await loadNode();
|
|
361
|
-
const root = workspaceFromArgv();
|
|
362
|
-
const fullPath = path.resolve(root, input.path);
|
|
363
|
-
const relative = path.relative(root, fullPath);
|
|
364
|
-
if (relative.startsWith("..") || path.isAbsolute(relative)) throw new Error("Path must stay inside workspace.");
|
|
365
|
-
const sourceText = await fs.readFile(fullPath, "utf8");
|
|
366
|
-
return {
|
|
367
|
-
machine: sourceText,
|
|
368
|
-
format: formatForPath(fullPath, sourceText),
|
|
369
|
-
source: { path: relative },
|
|
370
|
-
...input.includeSourceText ? { sourceText } : {}
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
async function readStoredCliCredential() {
|
|
374
|
-
const { fs, os, path } = await loadNode();
|
|
375
|
-
const configDir = process.env.STATELYAI_CONFIG_DIR ? path.resolve(process.env.STATELYAI_CONFIG_DIR) : process.platform === "darwin" ? path.join(os.homedir(), "Library", "Application Support", "statelyai") : process.platform === "win32" ? path.join(process.env.APPDATA ?? path.join(os.homedir(), "AppData", "Roaming"), "statelyai") : path.join(process.env.XDG_CONFIG_HOME ?? path.join(os.homedir(), ".config"), "statelyai");
|
|
376
|
-
try {
|
|
377
|
-
const raw = await fs.readFile(path.join(configDir, "credentials.json"), "utf8");
|
|
378
|
-
const parsed = JSON.parse(raw);
|
|
379
|
-
if (typeof parsed.apiKey === "string" && parsed.apiKey) return {
|
|
380
|
-
type: "api_key",
|
|
381
|
-
token: parsed.apiKey
|
|
382
|
-
};
|
|
383
|
-
if (typeof parsed.credential === "object" && parsed.credential !== null && "type" in parsed.credential) {
|
|
384
|
-
const credential = parsed.credential;
|
|
385
|
-
if (credential.type === "api_key" && typeof credential.token === "string") return {
|
|
386
|
-
type: "api_key",
|
|
387
|
-
token: credential.token
|
|
388
|
-
};
|
|
389
|
-
if (credential.type === "oauth" && typeof credential.accessToken === "string") return {
|
|
390
|
-
type: "oauth",
|
|
391
|
-
accessToken: credential.accessToken,
|
|
392
|
-
...typeof credential.refreshToken === "string" ? { refreshToken: credential.refreshToken } : {},
|
|
393
|
-
...typeof credential.expiresAt === "number" ? { expiresAt: credential.expiresAt } : {}
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
} catch {
|
|
397
|
-
return;
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
async function getDefaultCredential() {
|
|
401
|
-
if (process.env.STATELY_ACCESS_TOKEN) return {
|
|
402
|
-
type: "oauth",
|
|
403
|
-
accessToken: process.env.STATELY_ACCESS_TOKEN
|
|
404
|
-
};
|
|
405
|
-
if (process.env.STATELY_API_KEY) return {
|
|
406
|
-
type: "api_key",
|
|
407
|
-
token: process.env.STATELY_API_KEY
|
|
408
|
-
};
|
|
409
|
-
return readStoredCliCredential();
|
|
410
|
-
}
|
|
411
|
-
function toolText(body) {
|
|
412
|
-
return JSON.stringify(body, null, 2);
|
|
413
|
-
}
|
|
414
|
-
function createStatelyMcpServer$1(options = {}) {
|
|
415
|
-
const server = new McpServer({
|
|
416
|
-
name: "stately",
|
|
417
|
-
version: "0.1.0"
|
|
418
|
-
});
|
|
419
|
-
if (options.includeWorkspaceTools !== false) {
|
|
420
|
-
server.registerTool("list_machines", {
|
|
421
|
-
title: "list_machines",
|
|
422
|
-
description: "List candidate machine sources in the workspace.",
|
|
423
|
-
inputSchema: { glob: z.string().optional() },
|
|
424
|
-
annotations: readOnlyAnnotations
|
|
425
|
-
}, async (request) => {
|
|
426
|
-
const machines = (await walkWorkspace(workspaceFromArgv(), request.glob)).map((pathname) => ({
|
|
427
|
-
source: { path: pathname },
|
|
428
|
-
format: "xstate"
|
|
429
|
-
}));
|
|
430
|
-
return {
|
|
431
|
-
structuredContent: { machines },
|
|
432
|
-
content: [{
|
|
433
|
-
type: "text",
|
|
434
|
-
text: toolText({ machines })
|
|
435
|
-
}]
|
|
436
|
-
};
|
|
437
|
-
});
|
|
438
|
-
server.registerTool("get_machine", {
|
|
439
|
-
title: "get_machine",
|
|
440
|
-
description: "Read a machine source from the workspace and return it.",
|
|
441
|
-
inputSchema: {
|
|
442
|
-
path: z.string(),
|
|
443
|
-
includeSourceText: z.boolean().optional()
|
|
444
|
-
},
|
|
445
|
-
annotations: readOnlyAnnotations
|
|
446
|
-
}, async (request) => {
|
|
447
|
-
const result = await readWorkspaceMachine(request);
|
|
448
|
-
return {
|
|
449
|
-
structuredContent: result,
|
|
450
|
-
content: [{
|
|
451
|
-
type: "text",
|
|
452
|
-
text: toolText(result)
|
|
453
|
-
}]
|
|
454
|
-
};
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
registerStatelyMcpTools(server, {
|
|
458
|
-
...options,
|
|
459
|
-
authMode: options.authMode ?? (process.env.STATELY_AUTH_MODE === "none" ? "none" : void 0),
|
|
460
|
-
credentialProvider: options.credentialProvider ?? getDefaultCredential
|
|
461
|
-
});
|
|
462
|
-
return server;
|
|
463
|
-
}
|
|
464
|
-
async function main() {
|
|
465
|
-
const server = createStatelyMcpServer$1();
|
|
466
|
-
const transport = new StdioServerTransport();
|
|
467
|
-
await server.connect(transport);
|
|
468
|
-
}
|
|
469
|
-
if (import.meta.url === `file://${process.argv[1]}`) main();
|
|
470
|
-
|
|
471
|
-
//#endregion
|
|
472
|
-
export { createStatelyMcpServer as createHostedStatelyMcpServer, createStatelyMcpServer$1 as createStatelyMcpServer, formatForPath, generateTestCode, getDefaultCredential, isCandidate, readOnlyAnnotations, readWorkspaceMachine, registerStatelyMcpTools, walkWorkspace, workspaceFromArgv };
|