@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.
- package/dist/index.js +31 -12
- package/package.json +1 -1
- 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
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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()}
|
|
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
|
-
|
|
233
|
+
const label = isPng ? "PNG" : "SVG";
|
|
215
234
|
return {
|
|
216
235
|
content: [
|
|
217
|
-
{ type: "text", text:
|
|
236
|
+
{ type: "text", text: `${label} saved: ${filePath}` },
|
|
218
237
|
],
|
|
219
238
|
isError: false,
|
|
220
239
|
};
|
package/package.json
CHANGED
package/tools.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"instructions": "EN Diagram
|
|
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:
|
|
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
|
|
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": "
|
|
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
|
]
|