@miadi/stateloom-mcp 0.3.1 → 0.3.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/dist/erd.js +3 -3
- package/dist/sequence.js +2 -2
- package/dist/server.js +69 -3
- package/dist/system.js +3 -3
- package/package.json +3 -3
package/dist/erd.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
|
-
import { addAttribute, addEntity, addRelationship, checkLinks, checkStateOf, collectNotes, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, isSqdfPath, isSysdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, updateEntity, updateErdSettings, summarizeErd, validateErd, } from "@miadi/stateloom-protocol";
|
|
4
|
+
import { addAttribute, addEntity, addRelationship, checkLinks, checkStateOf, collectNotes, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, isSqdfPath, isSysdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, updateEntity, updateErdSettings, summarizeErd, validateErd, normalizeNotes } from "@miadi/stateloom-protocol";
|
|
5
5
|
import { stampedOutputPath } from "@miadi/stateloom-cli/render";
|
|
6
6
|
const ok = (text) => ({ content: [{ type: "text", text }] });
|
|
7
7
|
const fail = (text) => ({ content: [{ type: "text", text }], isError: true });
|
|
@@ -9,7 +9,7 @@ export function readErd(path) {
|
|
|
9
9
|
if (!existsSync(path))
|
|
10
10
|
return null;
|
|
11
11
|
try {
|
|
12
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
12
|
+
const parsed = normalizeNotes(JSON.parse(readFileSync(path, "utf8")));
|
|
13
13
|
return isErdDefinition(parsed) ? parsed : null;
|
|
14
14
|
}
|
|
15
15
|
catch {
|
|
@@ -123,7 +123,7 @@ function readSmdf(path) {
|
|
|
123
123
|
return null;
|
|
124
124
|
try {
|
|
125
125
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
126
|
-
const def = parsed.stateMachine ?? parsed.StateMachine ?? parsed;
|
|
126
|
+
const def = normalizeNotes(parsed.stateMachine ?? parsed.StateMachine ?? parsed);
|
|
127
127
|
return def && typeof def === "object" && def.state ? def : null;
|
|
128
128
|
}
|
|
129
129
|
catch {
|
package/dist/sequence.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
3
3
|
import { dirname, join, resolve } from "path";
|
|
4
|
-
import { SQD_FRAGMENT_KINDS, addFragment, addFragmentMessage, addMessage, addParticipant, allMessages, docKindOfPath, emptySequence, isSequenceDefinition, isSqdfPath, moveMessage, moveParticipant, parseMessageRef, removeFragment, removeMessage, removeParticipant, renameParticipant, renderMermaidSequence, summarizeSequence, updateFragment, updateMessage, updateParticipant, updateSqdSettings, validateSequence, } from "@miadi/stateloom-protocol";
|
|
4
|
+
import { SQD_FRAGMENT_KINDS, addFragment, addFragmentMessage, addMessage, addParticipant, allMessages, docKindOfPath, emptySequence, isSequenceDefinition, isSqdfPath, moveMessage, moveParticipant, parseMessageRef, removeFragment, removeMessage, removeParticipant, renameParticipant, renderMermaidSequence, summarizeSequence, updateFragment, updateMessage, updateParticipant, updateSqdSettings, validateSequence, normalizeNotes } from "@miadi/stateloom-protocol";
|
|
5
5
|
import { stampedOutputPath } from "@miadi/stateloom-cli/render";
|
|
6
6
|
import { formatNotes } from "./erd.js";
|
|
7
7
|
import { writeFileAtomic } from "./atomicWrite.js";
|
|
@@ -13,7 +13,7 @@ export function readSequence(path) {
|
|
|
13
13
|
if (!existsSync(path))
|
|
14
14
|
return null;
|
|
15
15
|
try {
|
|
16
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
16
|
+
const parsed = normalizeNotes(JSON.parse(readFileSync(path, "utf8")));
|
|
17
17
|
return isSequenceDefinition(parsed) ? parsed : null;
|
|
18
18
|
}
|
|
19
19
|
catch {
|
package/dist/server.js
CHANGED
|
@@ -65,7 +65,7 @@ import { basename, dirname, join, resolve } from "path";
|
|
|
65
65
|
import { fileURLToPath } from "node:url";
|
|
66
66
|
import { tmpdir } from "os";
|
|
67
67
|
import { createBridgeClient } from "@miadi/stateloom-client";
|
|
68
|
-
import { collectNotes, docKindOfPath, envAlias, isErdfPath, isSqdfPath, isSysdfPath, } from "@miadi/stateloom-protocol";
|
|
68
|
+
import { collectNotes, docKindOfPath, envAlias, isErdfPath, isSqdfPath, isSysdfPath, normalizeNotes, } from "@miadi/stateloom-protocol";
|
|
69
69
|
import { renderDiagramToFile, defaultOutputPath } from "@miadi/stateloom-cli/render";
|
|
70
70
|
import { resolveProjectSwitch } from "./projectSwitch.js";
|
|
71
71
|
import { registerErdTools, erdSummary, erdGetDefinition, erdLoadDefinition, erdRender, erdGetNotes, erdSetNotes, formatNotes, } from "./erd.js";
|
|
@@ -108,7 +108,7 @@ function readDef() {
|
|
|
108
108
|
try {
|
|
109
109
|
const raw = readFileSync(PROJECT_FILE, "utf8");
|
|
110
110
|
const parsed = JSON.parse(raw);
|
|
111
|
-
return parsed.stateMachine ?? parsed.StateMachine ?? parsed;
|
|
111
|
+
return normalizeNotes(parsed.stateMachine ?? parsed.StateMachine ?? parsed);
|
|
112
112
|
}
|
|
113
113
|
catch (e) {
|
|
114
114
|
console.error(`[smcraft-mcp] Failed to read ${PROJECT_FILE}:`, e);
|
|
@@ -120,7 +120,17 @@ function writeDef(def) {
|
|
|
120
120
|
// and load_definition write without reading first, so this is the one place that can refuse.
|
|
121
121
|
if (docKindOfPath(PROJECT_FILE) !== "machine")
|
|
122
122
|
throw new Error(noMachine());
|
|
123
|
-
|
|
123
|
+
// Keep the file's own shape: a machine written bare stays bare, so one edit is
|
|
124
|
+
// a small diff and not the whole file re-indented under `stateMachine`.
|
|
125
|
+
let bare = false;
|
|
126
|
+
try {
|
|
127
|
+
const current = JSON.parse(readFileSync(PROJECT_FILE, "utf8"));
|
|
128
|
+
bare = !!current && typeof current === "object" && "state" in current && !("stateMachine" in current) && !("StateMachine" in current);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// A new or unreadable file is written in the loom's own shape.
|
|
132
|
+
}
|
|
133
|
+
writeFileSync(PROJECT_FILE, JSON.stringify(bare ? def : { stateMachine: def }, null, 2) + (bare ? "\n" : ""), "utf8");
|
|
124
134
|
}
|
|
125
135
|
// ─── Optional real-time design bridge (env-gated, best-effort) ───────
|
|
126
136
|
// When STATELOOM_BRIDGE_URL (or legacy SMCRAFT_BRIDGE_URL) is set, each
|
|
@@ -774,6 +784,62 @@ function buildServer() {
|
|
|
774
784
|
content: [{ type: "text", text: `Added state '${name}' under '${parentName}' (${kind ?? "normal"}).` }],
|
|
775
785
|
};
|
|
776
786
|
});
|
|
787
|
+
server.tool("update_state", "Change what a state says about itself without rewriting the machine: its description, its kind (normal, final, history) or its prompt. Omitted fields stay as they are; an empty description or prompt clears it. Use it after a scenario built states in auto mode, to say what each one is.", {
|
|
788
|
+
name: z.string(),
|
|
789
|
+
description: z.string().optional(),
|
|
790
|
+
kind: z.enum(["normal", "final", "history"]).optional(),
|
|
791
|
+
prompt: z.string().optional(),
|
|
792
|
+
}, async ({ name, description, kind, prompt }) => {
|
|
793
|
+
const def = readDef();
|
|
794
|
+
if (!def)
|
|
795
|
+
return { content: [{ type: "text", text: noMachine() }], isError: true };
|
|
796
|
+
const target = findState(def.state, name);
|
|
797
|
+
if (!target)
|
|
798
|
+
return { content: [{ type: "text", text: `State '${name}' not found.` }], isError: true };
|
|
799
|
+
const patch = {};
|
|
800
|
+
const said = [];
|
|
801
|
+
if (description !== undefined) {
|
|
802
|
+
if (description.trim())
|
|
803
|
+
target.description = description;
|
|
804
|
+
else
|
|
805
|
+
delete target.description;
|
|
806
|
+
patch.description = description;
|
|
807
|
+
said.push(description.trim() ? "description" : "description cleared");
|
|
808
|
+
}
|
|
809
|
+
if (kind !== undefined) {
|
|
810
|
+
target.kind = kind;
|
|
811
|
+
patch.kind = kind;
|
|
812
|
+
said.push(`kind ${kind}`);
|
|
813
|
+
}
|
|
814
|
+
if (prompt !== undefined) {
|
|
815
|
+
if (prompt.trim())
|
|
816
|
+
target.prompt = prompt;
|
|
817
|
+
else
|
|
818
|
+
delete target.prompt;
|
|
819
|
+
patch.prompt = prompt.trim() ? prompt : "";
|
|
820
|
+
said.push(prompt.trim() ? "prompt" : "prompt cleared");
|
|
821
|
+
}
|
|
822
|
+
if (!said.length)
|
|
823
|
+
return { content: [{ type: "text", text: `Nothing to change on '${name}': give a description, a kind or a prompt.` }], isError: true };
|
|
824
|
+
writeDef(def);
|
|
825
|
+
bridgeEmitPatch([{ op: "state.update", name, patch }]);
|
|
826
|
+
return { content: [{ type: "text", text: `Updated '${name}': ${said.join(", ")}.` }] };
|
|
827
|
+
});
|
|
828
|
+
server.tool("update_event", "Change an event's description without rewriting the machine. An empty description clears it.", { id: z.string(), description: z.string() }, async ({ id, description }) => {
|
|
829
|
+
const def = readDef();
|
|
830
|
+
if (!def)
|
|
831
|
+
return { content: [{ type: "text", text: noMachine() }], isError: true };
|
|
832
|
+
const event = (def.events ?? []).flatMap((s) => s.events ?? []).find((e) => e.id === id);
|
|
833
|
+
if (!event)
|
|
834
|
+
return { content: [{ type: "text", text: `Event '${id}' not found.` }], isError: true };
|
|
835
|
+
if (description.trim())
|
|
836
|
+
event.description = description;
|
|
837
|
+
else
|
|
838
|
+
delete event.description;
|
|
839
|
+
writeDef(def);
|
|
840
|
+
bridgeEmitPatch([{ op: "event.update", id, patch: { description } }]);
|
|
841
|
+
return { content: [{ type: "text", text: description.trim() ? `Described event '${id}'.` : `Cleared the description of '${id}'.` }] };
|
|
842
|
+
});
|
|
777
843
|
server.tool("set_prompt", "Say what an agent is asked to resolve while the machine is in a state: the instruction it receives when the state is entered — an opening message, the frame of every later turn. `description` says what the state is and notes are the conversation about it; the prompt is what to do in it. The text replaces what was there; an empty string clears it. Engines and code generation ignore it; get_definition and generate_rispec show it.", { state: z.string(), prompt: z.string() }, async ({ state, prompt }) => {
|
|
778
844
|
const def = readDef();
|
|
779
845
|
if (!def)
|
package/dist/system.js
CHANGED
|
@@ -2,7 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
3
3
|
import { writeFileAtomic } from "./atomicWrite.js";
|
|
4
4
|
import { basename, dirname, join, resolve } from "path";
|
|
5
|
-
import { RECONCILE_MODES, SYSTEM_ACTOR_KINDS, addActor, addMember, checkSystem, docKindOfPath, emptySystem, formatReplay, isSqdfPath, isSysdfPath, isSystemDefinition, machineName, membersByKind, parseFocus, parseMessageRef, reconcileModeOf, reconcileSystem, relativeMemberPath, removeActor, removeMember, renderMermaid, renderMermaidEr, renderMermaidSequence, replayScenario, resolveMemberPath, setReconcileMode, stateOfList, summarizeReconcile, summarizeSystem, updateMember, updateSystemSettings, } from "@miadi/stateloom-protocol";
|
|
5
|
+
import { RECONCILE_MODES, SYSTEM_ACTOR_KINDS, addActor, addMember, checkSystem, docKindOfPath, emptySystem, formatReplay, isSqdfPath, isSysdfPath, isSystemDefinition, machineName, membersByKind, parseFocus, parseMessageRef, reconcileModeOf, reconcileSystem, relativeMemberPath, removeActor, removeMember, renderMermaid, renderMermaidEr, renderMermaidSequence, replayScenario, resolveMemberPath, setReconcileMode, stateOfList, summarizeReconcile, summarizeSystem, updateMember, updateSystemSettings, normalizeNotes } from "@miadi/stateloom-protocol";
|
|
6
6
|
import { stampedOutputPath } from "@miadi/stateloom-cli/render";
|
|
7
7
|
import { formatNotes } from "./erd.js";
|
|
8
8
|
const ok = (text) => ({ content: [{ type: "text", text }] });
|
|
@@ -14,7 +14,7 @@ export function readSystem(path) {
|
|
|
14
14
|
if (!existsSync(path))
|
|
15
15
|
return null;
|
|
16
16
|
try {
|
|
17
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
17
|
+
const parsed = normalizeNotes(JSON.parse(readFileSync(path, "utf8")));
|
|
18
18
|
return isSystemDefinition(parsed) ? parsed : null;
|
|
19
19
|
}
|
|
20
20
|
catch {
|
|
@@ -76,7 +76,7 @@ function loadMember(host, path, absolute) {
|
|
|
76
76
|
catch {
|
|
77
77
|
return { path, kind, def: null, error: "it is not valid JSON" };
|
|
78
78
|
}
|
|
79
|
-
const def = kind === "machine" ? (parsed?.stateMachine ?? parsed?.StateMachine ?? parsed) : parsed;
|
|
79
|
+
const def = normalizeNotes(kind === "machine" ? (parsed?.stateMachine ?? parsed?.StateMachine ?? parsed) : parsed);
|
|
80
80
|
return { path, kind, def };
|
|
81
81
|
}
|
|
82
82
|
/** One line for set_project_file / get_project_file when the active document is a system. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miadi/stateloom-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "MCP server for miadi-stateloom (formerly smcraft-mcp) — lets LLM agents design state machines through conversation",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"node": ">=20"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@miadi/stateloom-cli": "^0.1.
|
|
41
|
+
"@miadi/stateloom-cli": "^0.1.4",
|
|
42
42
|
"@miadi/stateloom-client": "^0.1.3",
|
|
43
|
-
"@miadi/stateloom-protocol": "^0.1.
|
|
43
|
+
"@miadi/stateloom-protocol": "^0.1.12",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
45
45
|
"zod": "^4.3.6"
|
|
46
46
|
},
|