@endiagram/mcp 0.2.10 → 0.2.12

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 +39 -193
  2. package/package.json +2 -1
  3. package/tools.json +113 -0
package/dist/index.js CHANGED
@@ -2,8 +2,11 @@
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { z } from "zod";
5
- import { writeFileSync, mkdirSync, existsSync } from "node:fs";
6
- import { join } from "node:path";
5
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
6
+ import { join, dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const toolsConfig = JSON.parse(readFileSync(join(__dirname, "../tools.json"), "utf-8"));
7
10
  const EN_API_URL = process.env.EN_API_URL ?? "https://api.endiagram.com";
8
11
  async function callApi(toolName, args) {
9
12
  try {
@@ -52,205 +55,48 @@ async function callApi(toolName, args) {
52
55
  };
53
56
  }
54
57
  }
55
- const EN_INSTRUCTIONS = `EN Diagram is a deterministic structural analysis engine. No AI inside — all results are backed by named mathematical theorems.
56
-
57
- To describe a system, write one statement per line:
58
- actor do: action needs: input1, input2 yields: output1, output2
59
-
60
- Shared names between yields and needs create connections automatically. Multi-word names work fine. Use commas to separate multiple inputs or outputs.
61
-
62
- 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.
63
-
64
- 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.
65
-
66
- Go deep, not wide. When a finding catches your attention — a surprising hub, an unexpected bottleneck, a structural anomaly — lock onto it. Use one tool to surface it, then chain the next tool to explain it, then the next to stress-test it. Keep narrowing until you reach the root. One thread, followed to the end, beats ten shallow observations. Only call render when the user explicitly asks to visualize.`;
58
+ const EN_INSTRUCTIONS = toolsConfig.instructions;
67
59
  const server = new McpServer({
68
60
  name: "endiagram",
69
61
  version: "0.2.0",
70
62
  });
71
- // --- analyze ---
72
- server.tool("analyze", EN_INSTRUCTIONS + " This tool gives the system overview: shape, node roles, single points of failure, failure threshold, flow hotspots.", {
73
- source: z.string().describe("EN source code describing the system"),
74
- invariants: z
75
- .string()
76
- .optional()
77
- .describe("Invariants to check against the structure"),
78
- detect_antipatterns: z
79
- .string()
80
- .optional()
81
- .describe("Set to 'true' to detect structural antipatterns"),
82
- }, async ({ source, invariants, detect_antipatterns }) => {
83
- const result = await callApi("analyze", {
84
- source,
85
- invariants,
86
- detect_antipatterns,
87
- });
88
- return {
89
- content: [{ type: "text", text: result.text }],
90
- isError: result.isError,
91
- };
92
- });
93
- // --- detail ---
94
- server.tool("detail", "Concurrency, critical path, flow landmarks, resilience, dependency chains, dominator tree.", {
95
- source: z.string().describe("EN source code describing the system"),
96
- }, async ({ source }) => {
97
- const result = await callApi("detail", { source });
98
- return {
99
- content: [{ type: "text", text: result.text }],
100
- isError: result.isError,
101
- };
102
- });
103
- // --- categorize ---
104
- server.tool("categorize", "Auto-discover subsystem boundaries from dependency structure.", {
105
- source: z.string().describe("EN source code describing the system"),
106
- }, async ({ source }) => {
107
- const result = await callApi("categorize", { source });
108
- return {
109
- content: [{ type: "text", text: result.text }],
110
- isError: result.isError,
111
- };
112
- });
113
- // --- distance ---
114
- server.tool("distance", "Structural distance between two nodes with the path between them.", {
115
- source: z.string().describe("EN source code describing the system"),
116
- from: z.string().describe("Starting node name"),
117
- to: z.string().describe("Target node name"),
118
- }, async ({ source, from, to }) => {
119
- const result = await callApi("distance", { source, from, to });
120
- return {
121
- content: [{ type: "text", text: result.text }],
122
- isError: result.isError,
123
- };
124
- });
125
- // --- diff ---
126
- server.tool("diff", "Compare two systems. What changed structurally.", {
127
- source_a: z.string().describe("EN source code for the first system"),
128
- source_b: z.string().describe("EN source code for the second system"),
129
- }, async ({ source_a, source_b }) => {
130
- const result = await callApi("diff", { source_a, source_b });
131
- return {
132
- content: [{ type: "text", text: result.text }],
133
- isError: result.isError,
134
- };
135
- });
136
- // --- trace ---
137
- server.tool("trace", "Follow directed flow from A to B. Optional: check if defense nodes cover all paths.", {
138
- source: z.string().describe("EN source code describing the system"),
139
- from: z.string().describe("Starting node name"),
140
- to: z.string().describe("Target node name"),
141
- defense_nodes: z
142
- .string()
143
- .optional()
144
- .describe("Comma-separated list of defense nodes to check coverage"),
145
- }, async ({ source, from, to, defense_nodes }) => {
146
- const result = await callApi("trace", {
147
- source,
148
- from,
149
- to,
150
- defense_nodes,
151
- });
152
- return {
153
- content: [{ type: "text", text: result.text }],
154
- isError: result.isError,
155
- };
156
- });
157
- // --- between ---
158
- server.tool("between", "How much of the system flows through a specific node.", {
159
- source: z.string().describe("EN source code describing the system"),
160
- node: z.string().describe("Node name"),
161
- }, async ({ source, node }) => {
162
- const result = await callApi("between", { source, node });
163
- return {
164
- content: [{ type: "text", text: result.text }],
165
- isError: result.isError,
166
- };
167
- });
168
- // --- extract ---
169
- server.tool("extract", "Extract a subsystem as standalone EN source. Feed back into other tools.", {
170
- source: z.string().describe("EN source code describing the system"),
171
- subsystem: z.string().describe("Name of the subsystem to extract"),
172
- }, async ({ source, subsystem }) => {
173
- const result = await callApi("extract", { source, subsystem });
174
- return {
175
- content: [{ type: "text", text: result.text }],
176
- isError: result.isError,
177
- };
178
- });
179
- // --- impact ---
180
- server.tool("impact", "What changes if a node is removed. Includes how far the effect propagates.", {
181
- source: z.string().describe("EN source code describing the system"),
182
- node: z.string().describe("Node to remove"),
183
- }, async ({ source, node }) => {
184
- const result = await callApi("impact", { source, node });
185
- return {
186
- content: [{ type: "text", text: result.text }],
187
- isError: result.isError,
188
- };
189
- });
190
- // --- evolve ---
191
- server.tool("evolve", "Dry-run a structural change before making it.", {
192
- source: z.string().describe("EN source code describing the current system"),
193
- patch: z.string().describe("EN source code patch to apply"),
194
- }, async ({ source, patch }) => {
195
- const result = await callApi("evolve", { source, patch });
196
- return {
197
- content: [{ type: "text", text: result.text }],
198
- isError: result.isError,
199
- };
200
- });
201
- // --- compose ---
202
- server.tool("compose", "Merge two systems into one by linking shared entities.", {
203
- source_a: z.string().describe("EN source code for the first system"),
204
- source_b: z.string().describe("EN source code for the second system"),
205
- links: z
206
- .string()
207
- .describe("Entity links between the two systems (e.g. 'a.node1=b.node2, a.node3=b.node4')"),
208
- }, async ({ source_a, source_b, links }) => {
209
- const result = await callApi("compose", { source_a, source_b, links });
210
- return {
211
- content: [{ type: "text", text: result.text }],
212
- isError: result.isError,
213
- };
214
- });
215
- // --- conserve ---
216
- server.tool("conserve", "Structural invariants, deadlock analysis, complexity, and resilience.", {
217
- source: z.string().describe("EN source code describing the system"),
218
- }, async ({ source }) => {
219
- const result = await callApi("conserve", { source });
220
- return {
221
- content: [{ type: "text", text: result.text }],
222
- isError: result.isError,
223
- };
224
- });
225
- // --- render ---
226
- server.tool("render", "SVG diagram. Only call when user explicitly asks to visualize. Saves SVG to a local file — no SVG content enters the conversation.", {
227
- source: z.string().describe("EN source code describing the system"),
228
- theme: z
229
- .enum(["dark", "light"])
230
- .optional()
231
- .describe("Color theme"),
232
- quality: z
233
- .enum(["small", "mid", "max"])
234
- .optional()
235
- .describe("Output quality"),
236
- }, async ({ source, theme, quality }) => {
237
- const result = await callApi("render", { source, theme, quality });
238
- if (result.isError || !result.svg) {
63
+ for (const tool of toolsConfig.tools) {
64
+ const schemaProps = {};
65
+ for (const param of tool.parameters) {
66
+ schemaProps[param.name] = param.required
67
+ ? z.string().describe(param.description)
68
+ : z.string().optional().describe(param.description);
69
+ }
70
+ server.tool(tool.name, tool.description, schemaProps, async (args) => {
71
+ // Special handling for render (save SVG to file)
72
+ if (tool.name === "render") {
73
+ const result = await callApi("render", args);
74
+ if (result.isError || !result.svg) {
75
+ return {
76
+ content: [{ type: "text", text: result.text }],
77
+ isError: result.isError,
78
+ };
79
+ }
80
+ const outDir = join(process.cwd(), ".endiagram");
81
+ if (!existsSync(outDir))
82
+ mkdirSync(outDir, { recursive: true });
83
+ const filePath = join(outDir, `en-${Date.now()}.svg`);
84
+ writeFileSync(filePath, result.svg);
85
+ return {
86
+ content: [
87
+ { type: "text", text: `SVG saved: ${filePath}` },
88
+ ],
89
+ isError: false,
90
+ };
91
+ }
92
+ // All other tools
93
+ const result = await callApi(tool.name, args);
239
94
  return {
240
95
  content: [{ type: "text", text: result.text }],
241
96
  isError: result.isError,
242
97
  };
243
- }
244
- const outDir = join(process.cwd(), ".endiagram");
245
- if (!existsSync(outDir))
246
- mkdirSync(outDir, { recursive: true });
247
- const filePath = join(outDir, `en-${Date.now()}.svg`);
248
- writeFileSync(filePath, result.svg);
249
- return {
250
- content: [{ type: "text", text: `SVG saved: ${filePath}` }],
251
- isError: false,
252
- };
253
- });
98
+ });
99
+ }
254
100
  // --- start server ---
255
101
  async function main() {
256
102
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@endiagram/mcp",
3
- "version": "0.2.10",
3
+ "version": "0.2.12",
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",
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "dist",
12
+ "tools.json",
12
13
  "README.md",
13
14
  "LICENSE"
14
15
  ],
package/tools.json ADDED
@@ -0,0 +1,113 @@
1
+ {
2
+ "instructions": "EN Diagram is a deterministic structural analysis engine. No AI inside.\n\nTo describe a system, write one statement per line:\n actor do: action needs: input1, input2 yields: output1, output2\n\nShared names between yields and needs create connections automatically. Multi-word names work fine. Use commas to separate multiple inputs or outputs.\n\nThe engine computes structural truth from the description — the tool outputs are raw mathematical findings. Translate them into the language of the system being analyzed. If analyzing a payment flow, say 'the payment gateway is structurally isolated' not 'node X has high centrality'. Speak in the user's domain, not in graph theory.\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. One tool surfaces a fact. The next tool explains it. The next stress-tests it. Keep narrowing until you reach the root cause. One thread followed to completion beats ten shallow observations.\n\nDo not stop at one tool call. Do not summarize after one result. 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\nIMPORTANT: Load ALL tool descriptions before starting analysis. Each tool's description explains how to read its output — without that context, results are uninterpretable. The 13 tools are: analyze, detail, categorize, distance, diff, trace, between, extract, impact, evolve, compose, conserve, render. Speak in the user's domain, not in graph theory. Only call render when the user explicitly asks to visualize.",
3
+ "tools": [
4
+ {
5
+ "name": "analyze",
6
+ "description": "System overview. Output: shape is the proven topology — known shapes are Pipeline, Tree, Star, Fork-Join, Mesh, State-Machine, Disconnected; Unknown means the system is too complex for any named category. bridgeNodes are nodes whose removal disconnects the graph. nodeRemovalThreshold is the fraction of random node removals before the system fragments. steadyStateShare is where flow naturally accumulates — share of time spent at each node if you follow connections randomly. unmatchedEntities are entities that cannot be simultaneously served by unique actions — a high count relative to entityCount means the system has more demand than capacity. structuralFindings are observed structural patterns (set detect_antipatterns to true).",
7
+ "parameters": [
8
+ {"name": "source", "type": "string", "description": "EN source code describing the system", "required": true},
9
+ {"name": "invariants", "type": "string", "description": "Structural rules to check, one per line", "required": false},
10
+ {"name": "detect_findings", "type": "string", "description": "Set to 'true' to detect structural findings", "required": false}
11
+ ]
12
+ },
13
+ {
14
+ "name": "detail",
15
+ "description": "Deep structural metrics. Output: landmarks are flow points where the graph diverges or converges, with stage names. entropy is normalized structural disorder (0-1, higher = more complex, irregular structure). nodeRemovalThreshold is the fraction of random node removals before the system fragments. dominator shows which nodes control downstream flow — every path from root to a dominated node must pass through the dominator. minCuts are minimum vertex cuts across source-sink pairs (cutSize = nodes to remove to disconnect that pair).",
16
+ "parameters": [
17
+ {"name": "source", "type": "string", "description": "EN source code describing the system", "required": true}
18
+ ]
19
+ },
20
+ {
21
+ "name": "categorize",
22
+ "description": "Auto-discover subsystem boundaries from dependency structure. Output: interfaceNodes have subsystemOverlap 0-1 measuring how much a node spans multiple subsystems (0 = fully within one subsystem, 1 = equally shared across all).",
23
+ "parameters": [
24
+ {"name": "source", "type": "string", "description": "EN source code describing the system", "required": true}
25
+ ]
26
+ },
27
+ {
28
+ "name": "distance",
29
+ "description": "Shortest path between two nodes. Output: dominators are nodes on the path where every alternative route must also pass through. pathRedundancy measures how many alternative paths exist between the two nodes — lower means more redundancy (null if nodes are in different components). bridgeEdgesOnPath counts edges whose removal would disconnect the graph.",
30
+ "parameters": [
31
+ {"name": "source", "type": "string", "description": "EN source code", "required": true},
32
+ {"name": "from", "type": "string", "description": "Starting node name", "required": true},
33
+ {"name": "to", "type": "string", "description": "Target node name", "required": true}
34
+ ]
35
+ },
36
+ {
37
+ "name": "diff",
38
+ "description": "Compare two systems structurally. Output shows nodes, entities, roles, and subsystems that differ. editDistance is the total graph edit cost (sum of node insertions + deletions + edge insertions + deletions).",
39
+ "parameters": [
40
+ {"name": "source_a", "type": "string", "description": "EN source code for the first system", "required": true},
41
+ {"name": "source_b", "type": "string", "description": "EN source code for the second system", "required": true}
42
+ ]
43
+ },
44
+ {
45
+ "name": "trace",
46
+ "description": "Follow directed data flow from A to B. Tries directed path first (needs to action to yields); falls back to undirected if no directed path exists (usesReverseEdges=true). Optional: defense_nodes checks if at least one guard node sits on every source-to-sink path.",
47
+ "parameters": [
48
+ {"name": "source", "type": "string", "description": "EN source code", "required": true},
49
+ {"name": "from", "type": "string", "description": "Starting node name", "required": true},
50
+ {"name": "to", "type": "string", "description": "Target node name", "required": true},
51
+ {"name": "defense_nodes", "type": "string", "description": "Comma-separated list of defense nodes to check coverage", "required": false}
52
+ ]
53
+ },
54
+ {
55
+ "name": "between",
56
+ "description": "How much of the system's flow passes through one node. Output: centrality is 0-1 normalized — fraction of all shortest paths in the graph that cross this node. Values above 0.3 indicate a structural coupling point; above 0.5 is a major hub. pathsThrough is the absolute count, totalPaths is the denominator.",
57
+ "parameters": [
58
+ {"name": "source", "type": "string", "description": "EN source code", "required": true},
59
+ {"name": "node", "type": "string", "description": "Node name", "required": true}
60
+ ]
61
+ },
62
+ {
63
+ "name": "extract",
64
+ "description": "Extract a subsystem as standalone EN source. boundaryInputs are entities consumed but not produced within the subsystem. boundaryOutputs are entities produced but not consumed within. Feed the extracted EN back into other tools for deeper analysis.",
65
+ "parameters": [
66
+ {"name": "source", "type": "string", "description": "EN source code", "required": true},
67
+ {"name": "subsystem", "type": "string", "description": "Name of the subsystem to extract", "required": true}
68
+ ]
69
+ },
70
+ {
71
+ "name": "impact",
72
+ "description": "Remove a node and measure structural effect. Output: disconnectedNodes are actions that lose their sole input source. propagation shows how disruption spreads through the graph — higher diffusion means more structural proximity to the removed node.",
73
+ "parameters": [
74
+ {"name": "source", "type": "string", "description": "EN source code", "required": true},
75
+ {"name": "node", "type": "string", "description": "Node to remove", "required": true}
76
+ ]
77
+ },
78
+ {
79
+ "name": "evolve",
80
+ "description": "Dry-run a structural change. Patch adds new actions; prefix action name with - to remove. Output includes the full diff plus newBridgeNodes and lostBridgeNodes showing how bridge structure changed.",
81
+ "parameters": [
82
+ {"name": "source", "type": "string", "description": "EN source code for the current system", "required": true},
83
+ {"name": "patch", "type": "string", "description": "EN source code patch to apply", "required": true}
84
+ ]
85
+ },
86
+ {
87
+ "name": "compose",
88
+ "description": "Merge two systems into one by linking shared entities. Use links to specify which entities from system A map to entities in system B. Output enSource is the merged EN ready for other tools.",
89
+ "parameters": [
90
+ {"name": "source_a", "type": "string", "description": "EN source code for the first system", "required": true},
91
+ {"name": "source_b", "type": "string", "description": "EN source code for the second system", "required": true},
92
+ {"name": "links", "type": "string", "description": "Entity links e.g. 'a.node1=b.node2, a.node3=b.node4'", "required": true}
93
+ ]
94
+ },
95
+ {
96
+ "name": "conserve",
97
+ "description": "Structural invariants, deadlock analysis, complexity, and resilience. Output: invariants are weighted entity sums that stay constant across all executions — positive coefficient means produced, negative means consumed in that ratio. depletableSets are entity groups where simultaneous depletion is irreversible. entropy is normalized structural disorder 0-1. nodeRemovalThreshold is the fraction of random node removals before fragmentation. behavioral.deficiency 0 means structure fully determines execution dynamics. behavioral.isReversible means every reachable state can return to any prior state. behavioral.hasUniqueEquilibrium means convergence to one steady state regardless of initial conditions.",
98
+ "parameters": [
99
+ {"name": "source", "type": "string", "description": "EN source code describing the system", "required": true}
100
+ ]
101
+ },
102
+ {
103
+ "name": "render",
104
+ "description": "SVG diagram. Only call when user explicitly asks to visualize.",
105
+ "parameters": [
106
+ {"name": "source", "type": "string", "description": "EN source code describing the system", "required": true},
107
+ {"name": "theme", "type": "string", "description": "Color theme: dark or light", "required": false},
108
+ {"name": "quality", "type": "string", "description": "Output quality: small, mid, or max", "required": false}
109
+ ]
110
+ }
111
+ ]
112
+ }
113
+