@miadi/stateloom-mcp 0.3.0 → 0.3.2
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 +41 -7
- package/dist/system.js +3 -3
- package/package.json +2 -2
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
|
|
@@ -657,6 +667,11 @@ function renderStateSpec(s, lines, depthLevel) {
|
|
|
657
667
|
const indent = " ".repeat(depthLevel);
|
|
658
668
|
const kind = s.kind && s.kind !== "normal" ? ` *(${s.kind})*` : "";
|
|
659
669
|
lines.push(`${indent}- **${s.name}**${kind}${s.description ? ` — ${s.description}` : ""}`);
|
|
670
|
+
if (s.prompt?.trim()) {
|
|
671
|
+
lines.push(`${indent} - *the agent resolves here:*`);
|
|
672
|
+
for (const line of s.prompt.trim().split("\n"))
|
|
673
|
+
lines.push(`${indent} > ${line}`);
|
|
674
|
+
}
|
|
660
675
|
for (const t of s.transitions ?? []) {
|
|
661
676
|
lines.push(`${indent} - on \`${t.event}\` ${t.nextState ? `→ \`${t.nextState}\`` : "(internal)"}`);
|
|
662
677
|
}
|
|
@@ -737,12 +752,13 @@ function buildServer() {
|
|
|
737
752
|
],
|
|
738
753
|
};
|
|
739
754
|
});
|
|
740
|
-
server.tool("add_state", "Add a state to the state machine. Parent defaults to Root.", {
|
|
755
|
+
server.tool("add_state", "Add a state to the state machine. Parent defaults to Root. `prompt` is what an agent is asked to resolve while the machine is in this state (see set_prompt).", {
|
|
741
756
|
name: z.string(),
|
|
742
757
|
parent: z.string().optional(),
|
|
743
758
|
kind: z.enum(["normal", "final", "history"]).optional(),
|
|
744
759
|
description: z.string().optional(),
|
|
745
|
-
|
|
760
|
+
prompt: z.string().optional(),
|
|
761
|
+
}, async ({ name, parent, kind, description, prompt }) => {
|
|
746
762
|
const def = readDef();
|
|
747
763
|
if (!def)
|
|
748
764
|
return {
|
|
@@ -758,15 +774,33 @@ function buildServer() {
|
|
|
758
774
|
};
|
|
759
775
|
if (!parentState.states)
|
|
760
776
|
parentState.states = [];
|
|
761
|
-
|
|
777
|
+
const added = { name, kind: kind ?? "normal", description, ...(prompt?.trim() ? { prompt } : {}) };
|
|
778
|
+
parentState.states.push(added);
|
|
762
779
|
writeDef(def);
|
|
763
780
|
bridgeEmitPatch([
|
|
764
|
-
{ op: "state.add", parent: parentName, state: { name, kind: kind ?? "normal", description } },
|
|
781
|
+
{ op: "state.add", parent: parentName, state: { name, kind: kind ?? "normal", description, ...(added.prompt ? { prompt: added.prompt } : {}) } },
|
|
765
782
|
]);
|
|
766
783
|
return {
|
|
767
784
|
content: [{ type: "text", text: `Added state '${name}' under '${parentName}' (${kind ?? "normal"}).` }],
|
|
768
785
|
};
|
|
769
786
|
});
|
|
787
|
+
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 }) => {
|
|
788
|
+
const def = readDef();
|
|
789
|
+
if (!def)
|
|
790
|
+
return { content: [{ type: "text", text: noMachine() }], isError: true };
|
|
791
|
+
const target = findState(def.state, state);
|
|
792
|
+
if (!target)
|
|
793
|
+
return { content: [{ type: "text", text: `State '${state}' not found.` }], isError: true };
|
|
794
|
+
if (prompt.trim())
|
|
795
|
+
target.prompt = prompt;
|
|
796
|
+
else
|
|
797
|
+
delete target.prompt;
|
|
798
|
+
writeDef(def);
|
|
799
|
+
bridgeEmitPatch([{ op: "state.update", name: state, patch: { prompt: prompt.trim() ? prompt : "" } }]);
|
|
800
|
+
return {
|
|
801
|
+
content: [{ type: "text", text: prompt.trim() ? `Prompt set on '${state}' (${prompt.length} characters).` : `Prompt cleared on '${state}'.` }],
|
|
802
|
+
};
|
|
803
|
+
});
|
|
770
804
|
server.tool("add_event", "Add an event to the state machine", { id: z.string(), description: z.string().optional() }, async ({ id, description }) => {
|
|
771
805
|
const def = readDef();
|
|
772
806
|
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.2",
|
|
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",
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@miadi/stateloom-cli": "^0.1.3",
|
|
42
42
|
"@miadi/stateloom-client": "^0.1.3",
|
|
43
|
-
"@miadi/stateloom-protocol": "^0.1.
|
|
43
|
+
"@miadi/stateloom-protocol": "^0.1.11",
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
45
45
|
"zod": "^4.3.6"
|
|
46
46
|
},
|