@endiagram/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.
Files changed (3) hide show
  1. package/dist/index.js +31 -12
  2. package/package.json +1 -1
  3. package/tools.json +14 -11
package/dist/index.js CHANGED
@@ -146,12 +146,17 @@ async function callApi(toolName, args) {
146
146
  const content = result?.content?.[0];
147
147
  const text = content?.text ?? body;
148
148
  const isError = result?.isError ?? false;
149
- // Check for SVG in second content block (render tool)
150
- const svgContent = result?.content?.[1];
151
- const svg = svgContent?.text?.startsWith("<svg")
152
- ? svgContent.text
149
+ // Check for rendered content in second content block (render tool).
150
+ // SVG: text block whose text starts with "<svg".
151
+ // PNG: image block with base64 data + mimeType "image/png".
152
+ const renderContent = result?.content?.[1];
153
+ const svg = renderContent?.text?.startsWith("<svg")
154
+ ? renderContent.text
153
155
  : undefined;
154
- return { text, isError, svg, data: undefined };
156
+ const pngBase64 = renderContent?.type === "image" && renderContent?.mimeType === "image/png"
157
+ ? renderContent.data
158
+ : undefined;
159
+ return { text, isError, svg, pngBase64, data: undefined };
155
160
  }
156
161
  catch {
157
162
  return { text: body, isError: false };
@@ -188,33 +193,47 @@ for (const tool of toolsConfig.tools) {
188
193
  const msg = e instanceof Error ? e.message : String(e);
189
194
  return { content: [{ type: "text", text: `Failed to read source file: ${msg}` }], isError: true };
190
195
  }
191
- // Special handling for render (save SVG to file)
196
+ // Special handling for render save SVG or PNG to file.
197
+ // The rendered image is for the user's eyes (audience:user), never
198
+ // injected into the model's context. We save to disk and return the
199
+ // file path as a text content block.
192
200
  if (tool.name === "render") {
193
201
  const result = await callApi("render", args);
194
- if (result.isError || !result.svg) {
202
+ const isPng = !!result.pngBase64;
203
+ const hasContent = isPng || !!result.svg;
204
+ if (result.isError || !hasContent) {
195
205
  return {
196
206
  content: [{ type: "text", text: result.text }],
197
207
  isError: result.isError,
198
208
  };
199
209
  }
200
210
  const outputPath = args.output;
211
+ const ext = isPng ? ".png" : ".svg";
201
212
  let filePath;
202
213
  if (outputPath) {
203
- const dir = dirname(outputPath);
214
+ // If the user gave a path, respect it but fix the extension.
215
+ const base = outputPath.replace(/\.(svg|png)$/i, "");
216
+ filePath = base + ext;
217
+ const dir = dirname(filePath);
204
218
  if (!existsSync(dir))
205
219
  mkdirSync(dir, { recursive: true });
206
- filePath = outputPath;
207
220
  }
208
221
  else {
209
222
  const outDir = join(process.cwd(), ".endiagram");
210
223
  if (!existsSync(outDir))
211
224
  mkdirSync(outDir, { recursive: true });
212
- filePath = join(outDir, `en-${Date.now()}.svg`);
225
+ filePath = join(outDir, `en-${Date.now()}${ext}`);
226
+ }
227
+ if (isPng) {
228
+ writeFileSync(filePath, Buffer.from(result.pngBase64, "base64"));
229
+ }
230
+ else {
231
+ writeFileSync(filePath, result.svg);
213
232
  }
214
- writeFileSync(filePath, result.svg);
233
+ const label = isPng ? "PNG" : "SVG";
215
234
  return {
216
235
  content: [
217
- { type: "text", text: `SVG saved: ${filePath}` },
236
+ { type: "text", text: `${label} saved: ${filePath}` },
218
237
  ],
219
238
  isError: false,
220
239
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@endiagram/mcp",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "MCP server for EN Diagram — deterministic structural analysis backed by named theorems",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/tools.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
- "instructions": "EN Diagram is a structural verification engine for concurrent systems. No AI inside.\n\nTo describe a system, write one statement per line:\n actor do: action needs: input1, input2 yields: output1, output2 at: context\n\nEvery statement requires: actor, do:, needs:, and yields:. Shared names between yields and needs create connections automatically. Multi-word names work fine. Use commas to separate multiple inputs or outputs. at: scopes the action to a context (service, team, region, domain, layer whatever boundary fits the system).\n\nMultiple actors can share the same action: actor1, actor2 do: action needs: X yields: Y. Two separate statements with the same action, needs, yields, and location are automatically merged into one shared action.\n\nSix tools answer six questions from concurrency theory. Each answer includes who (actors) and where (locations) alongside the structural findings.\n\n1. structure — what is this system?\n2. invariant — what's always true?\n3. live — can it deadlock? can entities overflow?\n4. reachable — can X reach Y?\n5. equivalent — are two systems the same?\n6. compose — how do parts combine?\n\nEvery tool output contains node names, values, and structural properties that can be fed directly into other tools. Read the output. Find the surprising node, the unexpected value, the structural anomaly. Then pick the tool that investigates THAT specific finding.\n\nDo not stop at one tool call. The first call reveals the structure — the real insight comes from the second, third, fourth call that digs into what the first one found.\n\nSpeak in the user's domain, not in graph theory. Only call render when the user explicitly asks to visualize.",
2
+ "instructions": "EN Diagram structural verification for concurrent systems. Pure math, no AI.\n\n## Syntax\nOne statement per line:\n actor do: action needs: input1, input2 yields: output1, output2 at: context\n\n- All five parts required.\n- Names span words; whitespace preserved. Quote only if name contains `,`, `:`, or `#`.\n- Shared names = connections: one action's `yields:` matching another's `needs:` wires them automatically. String equality is the link.\n- Multi-actor: `a, b do: action needs: X yields: Y` = one joint action. Identical statements merge.\n- `#` starts a comment.\n\n## Tools (six questions)\n1. structure — what is this system?\n2. invariant — what's always true?\n3. live — can it deadlock or overflow?\n4. reachable — can X reach Y?\n5. equivalent — are two systems the same?\n6. compose — how do parts combine?\n\n## How to use them\nEvery tool output contains node names, values, and structural properties that can be fed directly into other tools. Read the output. Find the surprising node, the unexpected value, the structural anomaly. Then pick the tool that investigates THAT specific finding.\n\nDo not stop at one tool call. The first call reveals the structure — the real insight comes from the second, third, fourth call that digs into what the first one found.\n\nSpeak in the user's domain, not in graph theory. Only call render when the user explicitly asks to visualize.",
3
3
  "tools": [
4
4
  {
5
5
  "name": "structure",
6
- "description": "What is this system? Complete structural overview: shape (topology), stages with roles, bridge nodes, cycles, parallelism, critical path, dominator tree, min-cuts, subsystems, interface nodes. Includes actors (who does what, workload entropy) and locations (where work happens, boundary crossings). Optional: pass node for per-node centrality, detect_findings for structural pattern detection.",
6
+ "description": "What is this system? Complete structural overview: shape (topology), stages with roles, bridge nodes, cycles, parallelism, critical path, dominator tree, min-cuts, subsystems, interface nodes. Includes actors (who does what, workload entropy) and locations (where work happens, boundary crossings). Optional: pass node for per-node centrality, detect_findings for structural pattern detection. See the server instructions for EN language syntax (spaces in names, # comments, shared actions).",
7
7
  "parameters": [
8
8
  {"name": "source", "type": "string", "description": "EN source code, or path to .en/.txt file", "required": true},
9
9
  {"name": "node", "type": "string", "description": "Node name for centrality query", "required": false},
@@ -12,7 +12,7 @@
12
12
  },
13
13
  {
14
14
  "name": "invariant",
15
- "description": "What's always true? conservationLaws are weighted entity sums constant across all executions. sustainableCycles are action sequences that return the system to its starting state (T-invariants). depletableSets are entity groups where simultaneous depletion is irreversible. behavioral.deficiency 0 means structure fully determines dynamics. behavioral.isReversible and behavioral.hasUniqueEquilibrium describe convergence properties.",
15
+ "description": "What's always true? conservationLaws are weighted entity sums constant across all executions. sustainableCycles are action sequences that return the system to its starting state (T-invariants). depletableSets are entity groups where simultaneous depletion is irreversible. behavioral.deficiency 0 means structure fully determines dynamics. behavioral.isReversible and behavioral.hasUniqueEquilibrium describe convergence properties. See the server instructions for EN language syntax.",
16
16
  "parameters": [
17
17
  {"name": "source", "type": "string", "description": "EN source code, or path to .en/.txt file", "required": true},
18
18
  {"name": "rules", "type": "string", "description": "Structural rules to check, one per line", "required": false}
@@ -20,14 +20,14 @@
20
20
  },
21
21
  {
22
22
  "name": "live",
23
- "description": "Can it deadlock? Can entities overflow? isStructurallyLive means every siphon contains a trap — no structural deadlock possible. uncoveredSiphons are entity groups that can drain permanently, with the actors and locations affected. isStructurallyBounded means no entity can accumulate without limit. unboundedCycles are action sequences that could cause overflow.",
23
+ "description": "Can it deadlock? Can entities overflow? isStructurallyLive means every siphon contains a trap — no structural deadlock possible. uncoveredSiphons are entity groups that can drain permanently, with the actors and locations affected. isStructurallyBounded means no entity can accumulate without limit. unboundedCycles are action sequences that could cause overflow. See the server instructions for EN language syntax.",
24
24
  "parameters": [
25
25
  {"name": "source", "type": "string", "description": "EN source code, or path to .en/.txt file", "required": true}
26
26
  ]
27
27
  },
28
28
  {
29
29
  "name": "reachable",
30
- "description": "Can X reach Y? Follows directed data flow first; falls back to undirected. Path shows each step with actor and location. locationCrossings counts boundary transitions. defense_nodes checks if guards cover all paths. coverage.fullCoverage false means unguarded routes exist.",
30
+ "description": "Can X reach Y? Follows directed data flow first; falls back to undirected. Path shows each step with actor and location. locationCrossings counts boundary transitions. defense_nodes checks if guards cover all paths. coverage.fullCoverage false means unguarded routes exist. See the server instructions for EN language syntax.",
31
31
  "parameters": [
32
32
  {"name": "source", "type": "string", "description": "EN source code", "required": true},
33
33
  {"name": "from", "type": "string", "description": "Starting node name", "required": true},
@@ -37,7 +37,7 @@
37
37
  },
38
38
  {
39
39
  "name": "equivalent",
40
- "description": "Are two systems the same? Compare mode (source_a + source_b): shows structural differences, edit distance, and spectral equivalence — isCospectral true means identical structure despite different names. Evolve mode (source + patch): dry-run a change, shows diff plus new/lost bridge nodes. Prefix action name with - in patch to remove it.",
40
+ "description": "Are two systems the same? Compare mode (source_a + source_b): shows structural differences, edit distance, and spectral equivalence — isCospectral true means identical structure despite different names. Evolve mode (source + patch): dry-run a change, shows diff plus new/lost bridge nodes. Prefix action name with - in patch to remove it. See the server instructions for EN language syntax.",
41
41
  "parameters": [
42
42
  {"name": "source_a", "type": "string", "description": "EN source code or path to .en/.txt file for the first system", "required": false},
43
43
  {"name": "source_b", "type": "string", "description": "EN source code or path to .en/.txt file for the second system", "required": false},
@@ -47,7 +47,7 @@
47
47
  },
48
48
  {
49
49
  "name": "compose",
50
- "description": "How do parts combine? Merge mode (source_a + source_b + links): merge two systems by linking shared entities. Extract mode (source + subsystem): extract a subsystem as standalone EN with boundary inputs/outputs, actors, and locations.",
50
+ "description": "How do parts combine? Merge mode (source_a + source_b + links): merge two systems by linking shared entities. Extract mode (source + subsystem): extract a subsystem as standalone EN with boundary inputs/outputs, actors, and locations. See the server instructions for EN language syntax.",
51
51
  "parameters": [
52
52
  {"name": "source_a", "type": "string", "description": "EN source code or path to .en/.txt file for the first system", "required": false},
53
53
  {"name": "source_b", "type": "string", "description": "EN source code or path to .en/.txt file for the second system", "required": false},
@@ -58,15 +58,18 @@
58
58
  },
59
59
  {
60
60
  "name": "render",
61
- "description": "SVG diagram. Only call when user explicitly asks to visualize.",
61
+ "description": "SVG or PNG diagram. Only call when user explicitly asks to visualize. The rendered image is delivered to the user, not injected into the model's context. See the server instructions for EN language syntax.",
62
62
  "parameters": [
63
63
  {"name": "source", "type": "string", "description": "EN source code, or path to .en/.txt file", "required": true},
64
- {"name": "theme", "type": "string", "description": "Color theme: dark or light", "required": false},
64
+ {"name": "theme", "type": "string", "description": "Color theme: named preset (Blueprint, Swiss, etc.) or dark/light", "required": false},
65
+ {"name": "isDark", "type": "string", "description": "true or false. Selects the dark or light variant of a named preset. If omitted, defaults to dark unless theme=light.", "required": false},
66
+ {"name": "type", "type": "string", "description": "Output format: png (default) or svg. PNG is rasterized server-side via Batik.", "required": false},
65
67
  {"name": "quality", "type": "string", "description": "Output quality: small, mid, or max", "required": false},
66
68
  {"name": "view", "type": "string", "description": "Group by: actors (partition by actor) or locations (partition by location). Default auto-detects topology.", "required": false},
67
- {"name": "structure_layers", "type": "string", "description": "Bitmask for structure overlays. Bits: 1=subsystems, 2=pipelines, 4=cycles, 8=forks, 16=joins, 32=hubs. Default 63 (all on). Pass 0 to hide all.", "required": false},
69
+ {"name": "structure_layers", "type": "string", "description": "Bitmask for structure overlays. Bits: 1=subsystems, 2=pipelines, 4=cycles, 8=forks, 16=joins, 32=hubs, 64=deadlock, 128=overflow. Default 255 (all on). Pass 0 to hide all.", "required": false},
68
70
  {"name": "color", "type": "string", "description": "Seed color hex (#RRGGBB) to generate a custom theme. Overrides theme parameter. One color generates the entire palette.", "required": false},
69
- {"name": "output", "type": "string", "description": "File path to save the SVG", "required": false}
71
+ {"name": "direction", "type": "string", "description": "Layout direction: LR (left-to-right) or TB (top-to-bottom). Default auto-detects from condensation DAG aspect ratio.", "required": false},
72
+ {"name": "output", "type": "string", "description": "File path to save the rendered image", "required": false}
70
73
  ]
71
74
  }
72
75
  ]