@endiagram/mcp 0.1.16 → 0.1.17

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 (2) hide show
  1. package/dist/index.js +39 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -27,13 +27,22 @@ async function callApi(toolName, args) {
27
27
  };
28
28
  }
29
29
  }
30
- const PLAIN_LANG = " When presenting findings to the user, use plain everyday language. Never use jargon like 'betweenness centrality', 'min-cut', 'bridge node', 'dominator tree', 'vertex-disjoint paths', or 'topology classification'. Instead say 'bottleneck', 'single point of failure', 'no backup path', 'what controls what'. The raw data helps your analysis -- give the user clear, simple insights.";
30
+ const EN_INSTRUCTIONS = `EN Diagram is a deterministic structural analysis engine. No AI inside all results are backed by named mathematical theorems.
31
+
32
+ To describe a system, write one statement per line:
33
+ actor do: action needs: input1, input2 yields: output1, output2
34
+
35
+ Shared names between yields and needs create connections automatically. Multi-word names work fine. Use commas to separate multiple inputs or outputs.
36
+
37
+ The engine computes structural truth from the description — the tool outputs are raw mathematical findings. Translate them into clear, practical insights relevant to what the user is trying to achieve.
38
+
39
+ How to use these tools effectively: model the system first, then explore. The first tool call reveals the structure — but the real insights come from following up. A node labeled HUB that should be simple? Dig into it with between or impact. A surprising subsystem boundary? Extract it and analyze deeper. An unexpected dependency chain? Trace it. A proposed change? Evolve it and diff. The tools are designed to chain — each finding opens a question that another tool can answer. Don't stop at one call. Explore, tinker, compare, and let the math surface what no one expected.`;
31
40
  const server = new McpServer({
32
41
  name: "endiagram",
33
- version: "0.1.0",
42
+ version: "0.2.0",
34
43
  });
35
- // --- analyze_system ---
36
- server.tool("analyze_system", "Structural signal. You describe the system, the tool computes structural facts. All computation is deterministic -- no AI inside. EN syntax: subject do: action needs: inputs yields: outputs. Returns topology classification (Pipeline, Tree, Fork-Join, Series-Parallel, State Machine, Disconnected, or Unknown), node roles (SOURCE, SINK, FORK, JOIN, HUB, PIPELINE, CYCLE), and bridges (single points of failure). A node labeled HUB that you expected to be PIPELINE is a real finding." + PLAIN_LANG, {
44
+ // --- analyze ---
45
+ server.tool("analyze", EN_INSTRUCTIONS + " This tool gives the system overview: shape, node roles, single points of failure, failure threshold, flow hotspots.", {
37
46
  source: z.string().describe("EN source code describing the system"),
38
47
  invariants: z
39
48
  .string()
@@ -55,7 +64,7 @@ server.tool("analyze_system", "Structural signal. You describe the system, the t
55
64
  };
56
65
  });
57
66
  // --- detail ---
58
- server.tool("detail", "The depth layer. Run after analyze_system to get the full picture Returns: concurrency metrics (max parallelism, critical path length, parallel paths per depth level), flow landmarks (exact depths where the graph diverges/converges -- these are your bottleneck boundaries), full resilience analysis (bridge implications with which subsystems disconnect if each bridge fails), and structural dependency chains (what feeds what, what must complete first). analyze_system tells you WHERE to look. detail tells you WHY it matters. Use categorize -> extract to isolate a subsystem first, then detail on the extracted source for focused depth.", {
67
+ server.tool("detail", "Concurrency, critical path, flow landmarks, resilience, dependency chains, dominator tree.", {
59
68
  source: z.string().describe("EN source code describing the system"),
60
69
  }, async ({ source }) => {
61
70
  const result = await callApi("detail", { source });
@@ -65,7 +74,7 @@ server.tool("detail", "The depth layer. Run after analyze_system to get the full
65
74
  };
66
75
  });
67
76
  // --- categorize ---
68
- server.tool("categorize", "Auto-organize a flat list into named groups. You give it ungrouped actions with inputs and outputs -- the tool discovers subsystem boundaries from the dependency structure and names them. 25 nodes become 5-6 named subsystems. You don't define the groups. The structure does. When the discovered boundaries differ from your module structure, that difference is a finding. Use after analyze_system to see how the system organizes itself. Then feed subsystem names into extract for fractal zoom.", {
77
+ server.tool("categorize", "Auto-discover subsystem boundaries from dependency structure.", {
69
78
  source: z.string().describe("EN source code describing the system"),
70
79
  }, async ({ source }) => {
71
80
  const result = await callApi("categorize", { source });
@@ -75,7 +84,7 @@ server.tool("categorize", "Auto-organize a flat list into named groups. You give
75
84
  };
76
85
  });
77
86
  // --- distance ---
78
- server.tool("distance", "Structural ruler. Computes shortest path between any two nodes (actions or entities) with annotations at every step. Returns: edge count, subsystem boundary crossings (how many module boundaries the path crosses), bridge edges on path (fragile links), and the full path with subsystem labels. Use to answer: 'how coupled are these two things?' If distance is 1-2 edges, they're tightly coupled. If it crosses 2+ subsystem boundaries, a change to one will ripple far.", {
87
+ server.tool("distance", "Structural distance between two nodes with the path between them.", {
79
88
  source: z.string().describe("EN source code describing the system"),
80
89
  from: z.string().describe("Starting node name"),
81
90
  to: z.string().describe("Target node name"),
@@ -87,7 +96,7 @@ server.tool("distance", "Structural ruler. Computes shortest path between any tw
87
96
  };
88
97
  });
89
98
  // --- diff ---
90
- server.tool("diff", "Structural diff between two systems. Computes both graphs independently, then reports the delta: nodes/entities present in one but not the other, role changes (a node that was PIPELINE in A became HUB in B -- that's a coupling regression), subsystem membership changes (node migrated between clusters), topology classification changes, and stage count differences. Use for: spec vs implementation (does the code match the design?), version 1 vs version 2 (did the refactor improve or worsen structure?), intended vs actual (model what you think exists, model what does exist, diff them).", {
99
+ server.tool("diff", "Compare two systems. What changed structurally.", {
91
100
  source_a: z.string().describe("EN source code for the first system"),
92
101
  source_b: z.string().describe("EN source code for the second system"),
93
102
  }, async ({ source_a, source_b }) => {
@@ -98,7 +107,7 @@ server.tool("diff", "Structural diff between two systems. Computes both graphs i
98
107
  };
99
108
  });
100
109
  // --- trace ---
101
- server.tool("trace", "Follow the flow -- data, materials, authority, money, risk. Computes directed shortest path from node A to node B, respecting the yields->needs flow direction. Every node on the path is annotated with its structural role and subsystem membership, so you can see role transitions along the flow (e.g., SOURCE -> PIPELINE -> HUB -> SINK). If no directed path exists, falls back to undirected and flags which edges are traversed backwards -- reverse edges often indicate missing abstractions or circular dependencies. Optional defense_nodes parameter checks whether specified nodes cover all source-to-sink paths.", {
110
+ server.tool("trace", "Follow directed flow from A to B. Optional: check if defense nodes cover all paths.", {
102
111
  source: z.string().describe("EN source code describing the system"),
103
112
  from: z.string().describe("Starting node name"),
104
113
  to: z.string().describe("Target node name"),
@@ -119,9 +128,9 @@ server.tool("trace", "Follow the flow -- data, materials, authority, money, risk
119
128
  };
120
129
  });
121
130
  // --- between ---
122
- server.tool("between", "Quantify coupling. Computes betweenness centrality for a node: what fraction of all shortest paths in the system flow through it. Returns normalized score [0-1], absolute shortest-paths-through count, and total paths. A score of 0.25 means one quarter of all communication in the system passes through this node -- it's a coupling hotspot. Use on nodes flagged as HUB or FORK by analyze_system to get a precise number. Compare centrality scores across nodes to find the true bottleneck vs nodes that just look important.", {
131
+ server.tool("between", "How much of the system flows through a specific node.", {
123
132
  source: z.string().describe("EN source code describing the system"),
124
- node: z.string().describe("Node to compute betweenness centrality for"),
133
+ node: z.string().describe("Node name"),
125
134
  }, async ({ source, node }) => {
126
135
  const result = await callApi("between", { source, node });
127
136
  return {
@@ -130,7 +139,7 @@ server.tool("between", "Quantify coupling. Computes betweenness centrality for a
130
139
  };
131
140
  });
132
141
  // --- extract ---
133
- server.tool("extract", "Fractal zoom. Extract a named subsystem as standalone EN source code you can feed back into analyze_system for a deeper look. Reports boundary inputs (dependencies from outside the subsystem), boundary outputs (consumed by other subsystems), and internal entities. This is how you go from surface findings to root causes: categorize gives you subsystem names -> extract gives you the subsystem as its own graph -> analyze_system on that graph reveals internal structure invisible at the top level.", {
142
+ server.tool("extract", "Extract a subsystem as standalone EN source. Feed back into other tools.", {
134
143
  source: z.string().describe("EN source code describing the system"),
135
144
  subsystem: z.string().describe("Name of the subsystem to extract"),
136
145
  }, async ({ source, subsystem }) => {
@@ -141,9 +150,9 @@ server.tool("extract", "Fractal zoom. Extract a named subsystem as standalone EN
141
150
  };
142
151
  });
143
152
  // --- impact ---
144
- server.tool("impact", "Blast radius calculator. Remove a node and see what breaks. Returns which nodes become disconnected (unreachable), whether the overall topology classification changes, and connected component count before vs after. Use to answer: 'what breaks if this node goes down?' If removing a node disconnects 0 others, it's safely removable. If it splits the graph into multiple components, it's load-bearing. Run this on every bridge node from analyze_system to Works for any domain -- remove a team from an org, a control from a compliance flow, a step from a clinical pathway. Same math.", {
153
+ server.tool("impact", "What changes if a node is removed. Includes how far the effect propagates.", {
145
154
  source: z.string().describe("EN source code describing the system"),
146
- node: z.string().describe("Node to remove for impact analysis"),
155
+ node: z.string().describe("Node to remove"),
147
156
  }, async ({ source, node }) => {
148
157
  const result = await callApi("impact", { source, node });
149
158
  return {
@@ -152,7 +161,7 @@ server.tool("impact", "Blast radius calculator. Remove a node and see what break
152
161
  };
153
162
  });
154
163
  // --- evolve ---
155
- server.tool("evolve", "Dry-run for architectural changes. Apply a patch to a system and see the structural delta before writing any code. Patch nodes with the same name replace originals; new names are additions. Returns the full diff (topology change, role changes, subsystem shifts) plus new bridge nodes created and bridge nodes eliminated. Use to test: 'What happens if I add a validation step here?' 'Does adding a cache layer create a new single point of failure?' 'Will splitting this service improve or worsen coupling?' Answer these questions in seconds, not hours.", {
164
+ server.tool("evolve", "Dry-run a structural change before making it.", {
156
165
  source: z.string().describe("EN source code describing the current system"),
157
166
  patch: z.string().describe("EN source code patch to apply"),
158
167
  }, async ({ source, patch }) => {
@@ -163,7 +172,7 @@ server.tool("evolve", "Dry-run for architectural changes. Apply a patch to a sys
163
172
  };
164
173
  });
165
174
  // --- compose ---
166
- server.tool("compose", "Merge two EN graphs into one. Takes two separate system descriptions and a list of entity links that connect them. Linked entities are merged under source A's name. Unlinked entities that share a name are automatically disambiguated. Returns combined EN source that you feed into any other tool -- analyze_system sees cross-graph bridges, impact shows blast radius across both graphs, distance measures paths crossing graph boundaries. Use when modeling two interacting systems (client/server, producer/consumer, spec/implementation) that share specific data points.", {
175
+ server.tool("compose", "Merge two systems into one by linking shared entities.", {
167
176
  source_a: z.string().describe("EN source code for the first system"),
168
177
  source_b: z.string().describe("EN source code for the second system"),
169
178
  links: z
@@ -176,17 +185,27 @@ server.tool("compose", "Merge two EN graphs into one. Takes two separate system
176
185
  isError: result.isError,
177
186
  };
178
187
  });
179
- // --- render (last — only use when user explicitly asks to visualize) ---
180
- server.tool("render", "Render an EN dependency graph as a publication-quality SVG image. Only call this when the user explicitly asks to visualize or render. Nodes are colored by structural role (source, sink, hub, etc.) and grouped by auto-detected subsystem. The visual reveals patterns (clusters, isolated subgraphs, fan-out imbalance) that text output alone misses.", {
188
+ // --- conserve ---
189
+ server.tool("conserve", "Structural invariants, deadlock analysis, complexity, and resilience.", {
190
+ source: z.string().describe("EN source code describing the system"),
191
+ }, async ({ source }) => {
192
+ const result = await callApi("conserve", { source });
193
+ return {
194
+ content: [{ type: "text", text: result.text }],
195
+ isError: result.isError,
196
+ };
197
+ });
198
+ // --- render ---
199
+ server.tool("render", "SVG diagram. Only when user asks to visualize.", {
181
200
  source: z.string().describe("EN source code describing the system"),
182
201
  theme: z
183
202
  .enum(["dark", "light"])
184
203
  .optional()
185
- .describe("Color theme for the rendered image"),
204
+ .describe("Color theme"),
186
205
  quality: z
187
206
  .enum(["small", "mid", "max"])
188
207
  .optional()
189
- .describe("Output quality / resolution"),
208
+ .describe("Output quality"),
190
209
  }, async ({ source, theme, quality }) => {
191
210
  const result = await callApi("render", { source, theme, quality });
192
211
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@endiagram/mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "MCP server for EN Diagram — structural analysis for any system",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",