@miadi/stateloom-mcp 0.1.2 → 0.1.3
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 +21 -0
- package/README.md +91 -0
- package/dist/server.js +2 -2
- package/package.json +34 -5
- package/dist/tests/projectSwitch.test.d.ts +0 -1
- package/dist/tests/projectSwitch.test.js +0 -41
- package/src/projectSwitch.ts +0 -32
- package/src/server.ts +0 -1182
- package/src/tests/projectSwitch.test.ts +0 -47
- package/tsconfig.json +0 -15
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Guillaume Isabelle (jgwill)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# @miadi/stateloom-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@miadi/stateloom-mcp)
|
|
4
|
+
|
|
5
|
+
MCP server for stateloom — an LLM agent designs a hierarchical state machine through conversation, and the diagram on your screen changes while it talks.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @miadi/stateloom-mcp
|
|
11
|
+
# or let your MCP client fetch it:
|
|
12
|
+
npx -y @miadi/stateloom-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## What it is
|
|
16
|
+
|
|
17
|
+
A [Model Context Protocol](https://modelcontextprotocol.io) server over stdio that gives an agent fifteen tools for building a state machine: create it, add states and events and transitions, validate it, generate code or a RISE rispec from it, and draw it. Every mutation persists to a `.smdf.json` file on disk and — when a bridge URL is configured — emits to the live hub, so a human watching the web canvas or a terminal running `smcx watch` sees each edit as the agent makes it.
|
|
18
|
+
|
|
19
|
+
It is not the state machine engine (`smcraft` runs the machines) and not the hub (`@miadi/stateloom` sequences the live document). This is the agent-facing surface, sitting beside the human-facing ones on the same file.
|
|
20
|
+
|
|
21
|
+
## Configure your MCP client
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
{
|
|
25
|
+
"mcpServers": {
|
|
26
|
+
"stateloom": {
|
|
27
|
+
"command": "npx",
|
|
28
|
+
"args": ["-y", "@miadi/stateloom-mcp"],
|
|
29
|
+
"env": {
|
|
30
|
+
"STATELOOM_PROJECT_FILE": "/abs/path/to/statemachine.smdf.json",
|
|
31
|
+
"STATELOOM_BRIDGE_URL": "http://127.0.0.1:4599"
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
| Environment variable | Default | Meaning |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `STATELOOM_PROJECT_FILE` | `./statemachine.smdf.json` | The document read and written; also the live room key |
|
|
41
|
+
| `STATELOOM_BRIDGE_URL` | — | Hub URL; without it the server persists to disk only |
|
|
42
|
+
| `STATELOOM_AGENT_NAME` | — | Presence label shown to other people in the room |
|
|
43
|
+
| `STATELOOM_BRIDGE_TOKEN` | — | Handshake token, when the hub requires one |
|
|
44
|
+
|
|
45
|
+
The legacy `SMCRAFT_*` twin of each name is still honored. The package ships two bins — `stateloom-mcp` and, for existing registrations, `smcraft-mcp`.
|
|
46
|
+
|
|
47
|
+
## Tools
|
|
48
|
+
|
|
49
|
+
| Tool | What it does |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `create_state_machine` | Start a definition with a namespace and name |
|
|
52
|
+
| `add_state` | Add a state; parent defaults to `Root` |
|
|
53
|
+
| `add_event` | Add an event to an event source |
|
|
54
|
+
| `add_transition` | Add a transition from a state, triggered by an event |
|
|
55
|
+
| `remove_state` | Remove a state |
|
|
56
|
+
| `get_definition` | Return the whole definition as JSON |
|
|
57
|
+
| `load_definition` | Replace the definition from JSON |
|
|
58
|
+
| `list_states` | Every state with its kind and transitions |
|
|
59
|
+
| `list_events` | Every event |
|
|
60
|
+
| `validate_definition` | Run the rule-coded validator |
|
|
61
|
+
| `generate_code` | Emit Python or TypeScript from the definition |
|
|
62
|
+
| `generate_rispec` | Emit a RISE rispec in markdown, folding in the source PDE's intent when the SMDF carries one |
|
|
63
|
+
| `render_diagram` | Draw the machine as `png`, `svg`, `mermaid` or `ascii`, write it beside the document, and hand a `png` back inside the tool result so the agent sees what it designed |
|
|
64
|
+
| `set_project_file` | Re-point the loom at another `.smdf.json` mid-session — disk target and live room both move |
|
|
65
|
+
| `get_project_file` | The active document path, whether it exists, and bridge status |
|
|
66
|
+
|
|
67
|
+
Also exposed: the resource `smcraft://definition` (the current definition), and the prompt `design-state-machine` — a guided conversation for building one from a domain description.
|
|
68
|
+
|
|
69
|
+
## What a session looks like
|
|
70
|
+
|
|
71
|
+
> **You:** design an order fulfillment workflow
|
|
72
|
+
>
|
|
73
|
+
> The agent calls `create_state_machine`, then `add_event` and `add_state` and `add_transition` a few times, then `validate_definition` to check its work, then `render_diagram` to look at it. Every one of those writes `/abs/path/to/statemachine.smdf.json` and pushes the change to the hub. Your canvas redraws as it goes.
|
|
74
|
+
|
|
75
|
+
Because rooms are keyed by the document's absolute path, `set_project_file` is how one agent moves between machines in a session — point it at another episode's diagram and the next edit lands there instead.
|
|
76
|
+
|
|
77
|
+
## Part of the stateloom stack
|
|
78
|
+
|
|
79
|
+
| Package | Role |
|
|
80
|
+
|---|---|
|
|
81
|
+
| [`smcraft`](https://www.npmjs.com/package/smcraft) | State machine engine: parser, validator, interpreter, code generators |
|
|
82
|
+
| [`@miadi/stateloom-protocol`](https://www.npmjs.com/package/@miadi/stateloom-protocol) | Patch ops, diff/apply, layout, renderers — zero runtime deps |
|
|
83
|
+
| [`@miadi/stateloom`](https://www.npmjs.com/package/@miadi/stateloom) | socket.io hub holding the live document |
|
|
84
|
+
| [`@miadi/stateloom-client`](https://www.npmjs.com/package/@miadi/stateloom-client) | Framework-agnostic client for the hub |
|
|
85
|
+
| [`@miadi/stateloom-react`](https://www.npmjs.com/package/@miadi/stateloom-react) | React binding over the client |
|
|
86
|
+
| [`@miadi/stateloom-cli`](https://www.npmjs.com/package/@miadi/stateloom-cli) | `smcx` — terminal design surface and renderers |
|
|
87
|
+
| [`@miadi/stateloom-mcp`](https://www.npmjs.com/package/@miadi/stateloom-mcp) | MCP server so LLM agents can design machines — **this package** |
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT © Guillaume Isabelle
|
package/dist/server.js
CHANGED
|
@@ -578,8 +578,8 @@ function glyph(direction) {
|
|
|
578
578
|
}
|
|
579
579
|
// ─── MCP Server ──────────────────────────────────────────────────────
|
|
580
580
|
const server = new McpServer({
|
|
581
|
-
name: "
|
|
582
|
-
version: "0.1.
|
|
581
|
+
name: "stateloom-mcp",
|
|
582
|
+
version: "0.1.3",
|
|
583
583
|
});
|
|
584
584
|
// Tools
|
|
585
585
|
server.tool("create_state_machine", "Create a new state machine definition with namespace and name", { namespace: z.string(), name: z.string(), asynchronous: z.boolean().optional() }, async ({ namespace, name, asynchronous }) => {
|
package/package.json
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miadi/stateloom-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "MCP server for miadi-stateloom (formerly smcraft-mcp) — lets LLM agents design state machines through conversation",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jgwill/smcraft.git",
|
|
8
|
+
"directory": "mcp"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/jgwill/smcraft#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/jgwill/smcraft/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": "Guillaume Isabelle <jgi@jgwill.com>",
|
|
5
15
|
"type": "module",
|
|
6
16
|
"main": "dist/server.js",
|
|
17
|
+
"types": "dist/server.d.ts",
|
|
7
18
|
"bin": {
|
|
8
19
|
"stateloom-mcp": "dist/server.js",
|
|
9
20
|
"smcraft-mcp": "dist/server.js"
|
|
@@ -13,15 +24,33 @@
|
|
|
13
24
|
"start": "node dist/server.js",
|
|
14
25
|
"test": "node --test dist/tests/*.test.js"
|
|
15
26
|
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"state-machine",
|
|
29
|
+
"smdf",
|
|
30
|
+
"mcp",
|
|
31
|
+
"model-context-protocol",
|
|
32
|
+
"agent",
|
|
33
|
+
"llm",
|
|
34
|
+
"stateloom"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20"
|
|
39
|
+
},
|
|
16
40
|
"dependencies": {
|
|
17
|
-
"@miadi/stateloom-cli": "^0.1.
|
|
18
|
-
"@miadi/stateloom-client": "^0.1.
|
|
19
|
-
"@miadi/stateloom-protocol": "^0.1.
|
|
41
|
+
"@miadi/stateloom-cli": "^0.1.2",
|
|
42
|
+
"@miadi/stateloom-client": "^0.1.1",
|
|
43
|
+
"@miadi/stateloom-protocol": "^0.1.3",
|
|
20
44
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
21
45
|
"zod": "^4.3.6"
|
|
22
46
|
},
|
|
23
47
|
"devDependencies": {
|
|
24
48
|
"@types/node": "^22.19.13",
|
|
25
49
|
"typescript": "^5.7.0"
|
|
26
|
-
}
|
|
50
|
+
},
|
|
51
|
+
"files": [
|
|
52
|
+
"dist/**/*.js",
|
|
53
|
+
"dist/**/*.d.ts",
|
|
54
|
+
"!dist/tests/"
|
|
55
|
+
]
|
|
27
56
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Path power (HANDOFF v2, Phase 2 deliverable 1): the pure logic behind the
|
|
3
|
-
* `set_project_file` MCP tool. An agent may point the loom at any `.json`
|
|
4
|
-
* document path; the switch must absolute-resolve the path, report whether a
|
|
5
|
-
* file is already there (missing is legitimate — create_state_machine writes
|
|
6
|
-
* it next), and refuse non-JSON paths outright.
|
|
7
|
-
*/
|
|
8
|
-
import { test } from "node:test";
|
|
9
|
-
import assert from "node:assert/strict";
|
|
10
|
-
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
11
|
-
import { join, resolve } from "node:path";
|
|
12
|
-
import { tmpdir } from "node:os";
|
|
13
|
-
import { resolveProjectSwitch } from "../projectSwitch.js";
|
|
14
|
-
test("projectSwitch: relative path resolves absolute and previous is carried", () => {
|
|
15
|
-
const r = resolveProjectSwitch("./some/machine.smdf.json", "/prev/doc.smdf.json");
|
|
16
|
-
assert.equal(r.path, resolve("./some/machine.smdf.json"));
|
|
17
|
-
assert.equal(r.previous, "/prev/doc.smdf.json");
|
|
18
|
-
assert.equal(r.exists, false);
|
|
19
|
-
});
|
|
20
|
-
test("projectSwitch: reports exists=true for a file already on disk", () => {
|
|
21
|
-
const dir = mkdtempSync(join(tmpdir(), "loom-switch-"));
|
|
22
|
-
const file = join(dir, "episode.smdf.json");
|
|
23
|
-
writeFileSync(file, JSON.stringify({ stateMachine: { settings: {}, events: [], state: { name: "Root" } } }));
|
|
24
|
-
try {
|
|
25
|
-
const r = resolveProjectSwitch(file, "/prev/doc.smdf.json");
|
|
26
|
-
assert.equal(r.path, file);
|
|
27
|
-
assert.equal(r.exists, true);
|
|
28
|
-
}
|
|
29
|
-
finally {
|
|
30
|
-
rmSync(dir, { recursive: true, force: true });
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
test("projectSwitch: refuses a non-.json path", () => {
|
|
34
|
-
assert.throws(() => resolveProjectSwitch("/tmp/machine.yaml", "/prev/doc.smdf.json"), /\.json/);
|
|
35
|
-
});
|
|
36
|
-
test("projectSwitch: switching to the same path is allowed and reports it", () => {
|
|
37
|
-
const r = resolveProjectSwitch("/prev/doc.smdf.json", "/prev/doc.smdf.json");
|
|
38
|
-
assert.equal(r.path, "/prev/doc.smdf.json");
|
|
39
|
-
assert.equal(r.previous, "/prev/doc.smdf.json");
|
|
40
|
-
assert.equal(r.unchanged, true);
|
|
41
|
-
});
|
package/src/projectSwitch.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure logic behind the `set_project_file` MCP tool (path power — the agent
|
|
3
|
-
* chooses which `.smdf.json` document the loom weaves).
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync } from "fs";
|
|
6
|
-
import { resolve } from "path";
|
|
7
|
-
|
|
8
|
-
export interface SwitchResult {
|
|
9
|
-
/** Absolute path of the newly active document. */
|
|
10
|
-
path: string;
|
|
11
|
-
/** The previously active absolute path. */
|
|
12
|
-
previous: string;
|
|
13
|
-
/** Whether a file already exists at the new path (missing is fine). */
|
|
14
|
-
exists: boolean;
|
|
15
|
-
/** True when the new path equals the previous one. */
|
|
16
|
-
unchanged: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function resolveProjectSwitch(next: string, previous: string): SwitchResult {
|
|
20
|
-
const path = resolve(next);
|
|
21
|
-
if (!path.endsWith(".json")) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
`project file must be a .json document (got '${path}') — the loom weaves .smdf.json files`,
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
path,
|
|
28
|
-
previous,
|
|
29
|
-
exists: existsSync(path),
|
|
30
|
-
unchanged: path === previous,
|
|
31
|
-
};
|
|
32
|
-
}
|