@miadi/stateloom-mcp 0.2.4 → 0.2.6

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/README.md CHANGED
@@ -116,8 +116,28 @@ outside the root. `generate_code` writes only to a temporary directory. Note tha
116
116
  | `generate_code` | Emit Python or TypeScript from the definition |
117
117
  | `generate_rispec` | Emit a RISE rispec in markdown, folding in the source PDE's intent when the SMDF carries one |
118
118
  | `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 |
119
- | `set_project_file` | Re-point the loom at another `.smdf.json` mid-session — disk target and live room both move |
119
+ | `set_project_file` | Re-point the loom at another document mid-session — a `.smdf.json` or a `.erdf.json`; disk target and live room both move |
120
120
  | `get_project_file` | The active document path, whether it exists, and bridge status |
121
+ | `get_notes` | Every note in the active document in one call — the diagram's own first, then each shape that has one |
122
+ | `set_notes` | Save (or, with an empty string, clear) working notes on the diagram or on one state / entity. Kept in the document for whoever opens it next; ignored by engines and codegen |
123
+
124
+ ### Entity-relationship diagrams
125
+
126
+ The same server designs the **data** beside the machines, in a sibling `.erdf.json`. The
127
+ active document's type is its extension: while an ERD is active, `get_definition`,
128
+ `load_definition` and `render_diagram` (mermaid `erDiagram`) answer for it, and the
129
+ state-machine tools refuse to write a machine over it.
130
+
131
+ | Tool | What it does |
132
+ |---|---|
133
+ | `create_erd` | Start an ERD beside the active document and make it the active one |
134
+ | `add_entity` / `update_entity` / `remove_entity` | An entity, optionally `weak`; removing one takes its relationships with it |
135
+ | `add_attribute` / `remove_attribute` | `type`, `key` (`pk` / `uk`), `references` (the foreign key), `nullable`, and `stateOf` — the state machine whose state this attribute stores |
136
+ | `add_relationship` / `remove_relationship` | `1:1`, `1:N`, `N:1`, `N:M`, read left to right, with a verb as the label |
137
+ | `validate_erd` | Rules E001–E005 |
138
+ | `check_links` | Rules L001–L004: what a state machine names — an object's class, a field in a guard, a `stateOf` — that the ERD does not carry |
139
+
140
+ Format and rules: [Spec 80 — ERDF](https://github.com/jgwill/smcraft/blob/main/rispecs/80-erdf-format.spec.md).
121
141
 
122
142
  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.
123
143
 
package/dist/erd.d.ts CHANGED
@@ -43,5 +43,12 @@ export declare function erdRender(host: ErdHost, opts: {
43
43
  path?: string;
44
44
  stamp?: boolean;
45
45
  }): ToolResult;
46
+ /** One block per note, the diagram's own first. What `get_notes` answers with for either document type. */
47
+ export declare function formatNotes(name: string, path: string, entries: {
48
+ target: string | null;
49
+ notes: string;
50
+ }[]): string;
51
+ export declare function erdGetNotes(host: ErdHost): ToolResult;
52
+ export declare function erdSetNotes(host: ErdHost, target: string | undefined, notes: string): ToolResult;
46
53
  export declare function registerErdTools(server: McpServer, host: ErdHost): void;
47
54
  export {};
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, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, summarizeErd, validateErd, } from "@miadi/stateloom-protocol";
4
+ import { addAttribute, addEntity, addRelationship, checkLinks, checkStateOf, collectNotes, emptyErd, ERD_CARDINALITIES, isErdDefinition, isErdfPath, removeAttribute, removeEntity, removeRelationship, renderMermaidEr, updateEntity, updateErdSettings, summarizeErd, validateErd, } 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 });
@@ -101,6 +101,22 @@ export function erdRender(host, opts) {
101
101
  ],
102
102
  };
103
103
  }
104
+ /** One block per note, the diagram's own first. What `get_notes` answers with for either document type. */
105
+ export function formatNotes(name, path, entries) {
106
+ if (entries.length === 0)
107
+ return `No notes in '${name}' (${path}).`;
108
+ const blocks = entries.map((n) => `[${n.target ?? "diagram"}]\n${n.notes}`);
109
+ return `${entries.length} note(s) in '${name}' (${path}):\n\n${blocks.join("\n\n")}`;
110
+ }
111
+ export function erdGetNotes(host) {
112
+ const def = readErd(host.projectFile());
113
+ if (!def)
114
+ return fail(`No ERD at ${host.projectFile()}.`);
115
+ return ok(formatNotes(def.settings?.name ?? "", host.projectFile(), collectNotes(def)));
116
+ }
117
+ export function erdSetNotes(host, target, notes) {
118
+ return mutate(host, (def) => (target ? updateEntity(def, target, { notes }) : updateErdSettings(def, { notes })), () => `${notes.trim() ? "Saved" : "Cleared"} the notes on ${target ? `entity '${target}'` : "the diagram"}.`);
119
+ }
104
120
  const formatErrors = (errors) => errors.map((e) => `[${e.ruleId}] ${e.message}`).join("\n");
105
121
  function readSmdf(path) {
106
122
  if (!existsSync(path))
@@ -142,7 +158,8 @@ export function registerErdTools(server, host) {
142
158
  host.emitFull(def);
143
159
  return ok(`Created ERD '${name}' in namespace '${namespace}' → ${target}. It is now the active document. Add entities next.`);
144
160
  });
145
- server.tool("add_entity", "Add an entity to the active ERD", { name: z.string(), description: z.string().optional() }, async ({ name, description }) => mutate(host, (def) => addEntity(def, { name, description }), () => `Added entity '${name}'.`));
161
+ server.tool("add_entity", "Add an entity to the active ERD. `weak` marks an entity that exists only through another one (an order line, without its order); Chen notation draws it as a double rectangle.", { name: z.string(), description: z.string().optional(), weak: z.boolean().optional() }, async ({ name, description, weak }) => mutate(host, (def) => addEntity(def, { name, description, weak: weak || undefined }), () => `Added ${weak ? "weak " : ""}entity '${name}'.`));
162
+ server.tool("update_entity", "Change an entity's description or whether it is weak, keeping its attributes and relationships. Omit a field to leave it alone; an empty description or weak:false clears it.", { name: z.string(), description: z.string().optional(), weak: z.boolean().optional() }, async ({ name, description, weak }) => mutate(host, (def) => updateEntity(def, name, { description, weak }), () => `Updated entity '${name}'.`));
146
163
  server.tool("add_attribute", "Add an attribute to an entity of the active ERD. `key` is pk or uk; a foreign key is stated by `references` ('Entity' or 'Entity.attribute'), so one attribute can be both. `stateOf` names the state machine(s) whose current state this attribute stores — the link between the two diagrams.", {
147
164
  entity: z.string(),
148
165
  name: z.string(),
package/dist/server.d.ts CHANGED
@@ -22,9 +22,12 @@
22
22
  * - get_project_file: Report the active document path and bridge status
23
23
  *
24
24
  * ERD tools (Spec 80, ./erd.ts) — the active document's type is its extension:
25
- * - create_erd, add_entity, add_attribute, add_relationship
25
+ * - create_erd, add_entity, update_entity, add_attribute, add_relationship
26
26
  * - remove_entity, remove_attribute, remove_relationship
27
27
  * - validate_erd, check_links
28
+ *
29
+ * Notes (either document type): set_notes, get_notes — what a person or an
30
+ * agent wrote down about the diagram or one of its shapes, saved in the file.
28
31
  * get_definition, load_definition and render_diagram answer for an ERD when
29
32
  * the active document is one.
30
33
  *
package/dist/server.js CHANGED
@@ -22,9 +22,12 @@
22
22
  * - get_project_file: Report the active document path and bridge status
23
23
  *
24
24
  * ERD tools (Spec 80, ./erd.ts) — the active document's type is its extension:
25
- * - create_erd, add_entity, add_attribute, add_relationship
25
+ * - create_erd, add_entity, update_entity, add_attribute, add_relationship
26
26
  * - remove_entity, remove_attribute, remove_relationship
27
27
  * - validate_erd, check_links
28
+ *
29
+ * Notes (either document type): set_notes, get_notes — what a person or an
30
+ * agent wrote down about the diagram or one of its shapes, saved in the file.
28
31
  * get_definition, load_definition and render_diagram answer for an ERD when
29
32
  * the active document is one.
30
33
  *
@@ -46,10 +49,10 @@ import { basename, dirname, join, resolve } from "path";
46
49
  import { fileURLToPath } from "node:url";
47
50
  import { tmpdir } from "os";
48
51
  import { createBridgeClient } from "@miadi/stateloom-client";
49
- import { envAlias, isErdfPath, } from "@miadi/stateloom-protocol";
52
+ import { collectNotes, envAlias, isErdfPath, } from "@miadi/stateloom-protocol";
50
53
  import { renderDiagramToFile, defaultOutputPath } from "@miadi/stateloom-cli/render";
51
54
  import { resolveProjectSwitch } from "./projectSwitch.js";
52
- import { registerErdTools, erdSummary, erdGetDefinition, erdLoadDefinition, erdRender, } from "./erd.js";
55
+ import { registerErdTools, erdSummary, erdGetDefinition, erdLoadDefinition, erdRender, erdGetNotes, erdSetNotes, formatNotes, } from "./erd.js";
53
56
  // STATELOOM_* read first, SMCRAFT_* legacy twin honored — the live MCP
54
57
  // registration bakes SMCRAFT_PROJECT_FILE and must keep working (2026-07-27
55
58
  // rename brief). Resolved once at boot, like the direct reads they replace.
@@ -1006,6 +1009,42 @@ function buildServer() {
1006
1009
  }
1007
1010
  });
1008
1011
  registerErdTools(server, erdHost);
1012
+ // Notes — the same two tools for either document type.
1013
+ server.tool("set_notes", "Save working notes on the active diagram or on one of its shapes — what was said about it, an open question, a decision — so whoever opens the document next (a person or an agent) finds them. Omit `target` for the whole diagram; otherwise `target` is a state name (state machine) or an entity name (ERD). The text replaces what was there; an empty string clears it. Notes are kept in the document and are not part of the model: engines and code generation ignore them.", { target: z.string().optional(), notes: z.string() }, async ({ target, notes }) => {
1014
+ if (isErdfPath(PROJECT_FILE))
1015
+ return erdSetNotes(erdHost, target, notes);
1016
+ const def = readDef();
1017
+ if (!def)
1018
+ return { content: [{ type: "text", text: `No state machine at ${PROJECT_FILE}.` }], isError: true };
1019
+ const holder = target ? findState(def.state, target) : def.settings;
1020
+ if (!holder)
1021
+ return { content: [{ type: "text", text: `State '${target}' not found.` }], isError: true };
1022
+ if (notes.trim())
1023
+ holder.notes = notes;
1024
+ else
1025
+ delete holder.notes;
1026
+ writeDef(def);
1027
+ // "" is how an erased note travels; the far side removes the key.
1028
+ const patch = { notes: notes.trim() ? notes : "" };
1029
+ bridgeEmitPatch([target ? { op: "state.update", name: target, patch } : { op: "settings.update", patch }]);
1030
+ return {
1031
+ content: [
1032
+ {
1033
+ type: "text",
1034
+ text: `${notes.trim() ? "Saved" : "Cleared"} the notes on ${target ? `state '${target}'` : "the diagram"}.`,
1035
+ },
1036
+ ],
1037
+ };
1038
+ });
1039
+ server.tool("get_notes", "Read every note in the active document in one call — the diagram's own first, then each shape that has one. Read them before changing a diagram somebody else has been working on: they are where a person leaves what they were thinking.", {}, async () => {
1040
+ if (isErdfPath(PROJECT_FILE))
1041
+ return erdGetNotes(erdHost);
1042
+ const def = readDef();
1043
+ if (!def)
1044
+ return { content: [{ type: "text", text: `No state machine at ${PROJECT_FILE}.` }], isError: true };
1045
+ const entries = collectNotes(def);
1046
+ return { content: [{ type: "text", text: formatNotes(def.settings.name, PROJECT_FILE, entries) }] };
1047
+ });
1009
1048
  // Resources
1010
1049
  server.resource("definition", "smcraft://definition", async () => {
1011
1050
  const def = readDef();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miadi/stateloom-mcp",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
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.2",
43
- "@miadi/stateloom-protocol": "^0.1.6",
43
+ "@miadi/stateloom-protocol": "^0.1.8",
44
44
  "@modelcontextprotocol/sdk": "^1.12.1",
45
45
  "zod": "^4.3.6"
46
46
  },