devtoolkit-mcp 0.1.1 → 0.2.0

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
@@ -1,6 +1,6 @@
1
1
  # devtoolkit-mcp
2
2
 
3
- 25+ developer utilities as an MCP server for AI-assisted workflows. JSON repair, SQL formatting, hashing, encoding, UUID generation, regex testing, CSV transforms, subnet calculation, and more.
3
+ 26 developer utilities as an MCP server for AI-assisted workflows. JSON repair, SQL formatting, hashing, encoding, UUID generation, regex testing, CSV transforms, subnet calculation, diagram generation, and more.
4
4
 
5
5
  **No API keys. No network requests. Everything runs locally.**
6
6
 
@@ -91,7 +91,7 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
91
91
  }
92
92
  ```
93
93
 
94
- ## Available Tools (25)
94
+ ## Available Tools (26)
95
95
 
96
96
  ### Data Transform
97
97
  | Tool | Description |
@@ -104,6 +104,7 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
104
104
  | `csv_transform` | Parse, filter, sort, aggregate, convert CSV data |
105
105
  | `yaml_json` | Convert between YAML and JSON |
106
106
  | `json_to_types` | Convert JSON to TypeScript interfaces, Zod schemas, or JSON Schema |
107
+ | `generate_diagram` | Generate Mermaid flowcharts and sequence diagrams from plain English descriptions |
107
108
 
108
109
  ### Decode & Parse
109
110
  | Tool | Description |
@@ -182,6 +183,10 @@ Convert this JSON to TypeScript: {"id": 1, "name": "John", "tags": ["admin"]}
182
183
  Generate 20 mock users with id, name, email, salary as CSV, then filter salary > 60000
183
184
  ```
184
185
 
186
+ ```
187
+ Draw diagram: User sends request to API Gateway, forwards to Auth Service, queries Postgres
188
+ ```
189
+
185
190
  ## Why MCP Tools vs Native AI?
186
191
 
187
192
  These tools provide capabilities that AI models **cannot do natively**:
@@ -196,6 +201,7 @@ These tools provide capabilities that AI models **cannot do natively**:
196
201
  | Date arithmetic | Often wrong | Millisecond exact |
197
202
  | CSV parsing (quoted fields) | Approximates | RFC-compliant |
198
203
  | Line-by-line diff | Misses changes | LCS algorithm |
204
+ | Diagram generation | Approximates syntax | Valid Mermaid output |
199
205
 
200
206
  ## Requirements
201
207
 
package/dist/index.js CHANGED
@@ -21025,24 +21025,24 @@ var McpServer = class {
21025
21025
  }
21026
21026
  });
21027
21027
  this.server.setRequestHandler(ListToolsRequestSchema, () => ({
21028
- tools: Object.entries(this._registeredTools).filter(([, tool26]) => tool26.enabled).map(([name, tool26]) => {
21028
+ tools: Object.entries(this._registeredTools).filter(([, tool27]) => tool27.enabled).map(([name, tool27]) => {
21029
21029
  const toolDefinition = {
21030
21030
  name,
21031
- title: tool26.title,
21032
- description: tool26.description,
21031
+ title: tool27.title,
21032
+ description: tool27.description,
21033
21033
  inputSchema: (() => {
21034
- const obj = normalizeObjectSchema(tool26.inputSchema);
21034
+ const obj = normalizeObjectSchema(tool27.inputSchema);
21035
21035
  return obj ? toJsonSchemaCompat(obj, {
21036
21036
  strictUnions: true,
21037
21037
  pipeStrategy: "input"
21038
21038
  }) : EMPTY_OBJECT_JSON_SCHEMA;
21039
21039
  })(),
21040
- annotations: tool26.annotations,
21041
- execution: tool26.execution,
21042
- _meta: tool26._meta
21040
+ annotations: tool27.annotations,
21041
+ execution: tool27.execution,
21042
+ _meta: tool27._meta
21043
21043
  };
21044
- if (tool26.outputSchema) {
21045
- const obj = normalizeObjectSchema(tool26.outputSchema);
21044
+ if (tool27.outputSchema) {
21045
+ const obj = normalizeObjectSchema(tool27.outputSchema);
21046
21046
  if (obj) {
21047
21047
  toolDefinition.outputSchema = toJsonSchemaCompat(obj, {
21048
21048
  strictUnions: true,
@@ -21055,16 +21055,16 @@ var McpServer = class {
21055
21055
  }));
21056
21056
  this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
21057
21057
  try {
21058
- const tool26 = this._registeredTools[request.params.name];
21059
- if (!tool26) {
21058
+ const tool27 = this._registeredTools[request.params.name];
21059
+ if (!tool27) {
21060
21060
  throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
21061
21061
  }
21062
- if (!tool26.enabled) {
21062
+ if (!tool27.enabled) {
21063
21063
  throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} disabled`);
21064
21064
  }
21065
21065
  const isTaskRequest = !!request.params.task;
21066
- const taskSupport = tool26.execution?.taskSupport;
21067
- const isTaskHandler = "createTask" in tool26.handler;
21066
+ const taskSupport = tool27.execution?.taskSupport;
21067
+ const isTaskHandler = "createTask" in tool27.handler;
21068
21068
  if ((taskSupport === "required" || taskSupport === "optional") && !isTaskHandler) {
21069
21069
  throw new McpError(ErrorCode.InternalError, `Tool ${request.params.name} has taskSupport '${taskSupport}' but was not registered with registerToolTask`);
21070
21070
  }
@@ -21072,14 +21072,14 @@ var McpServer = class {
21072
21072
  throw new McpError(ErrorCode.MethodNotFound, `Tool ${request.params.name} requires task augmentation (taskSupport: 'required')`);
21073
21073
  }
21074
21074
  if (taskSupport === "optional" && !isTaskRequest && isTaskHandler) {
21075
- return await this.handleAutomaticTaskPolling(tool26, request, extra);
21075
+ return await this.handleAutomaticTaskPolling(tool27, request, extra);
21076
21076
  }
21077
- const args = await this.validateToolInput(tool26, request.params.arguments, request.params.name);
21078
- const result = await this.executeToolHandler(tool26, args, extra);
21077
+ const args = await this.validateToolInput(tool27, request.params.arguments, request.params.name);
21078
+ const result = await this.executeToolHandler(tool27, args, extra);
21079
21079
  if (isTaskRequest) {
21080
21080
  return result;
21081
21081
  }
21082
- await this.validateToolOutput(tool26, result, request.params.name);
21082
+ await this.validateToolOutput(tool27, result, request.params.name);
21083
21083
  return result;
21084
21084
  } catch (error2) {
21085
21085
  if (error2 instanceof McpError) {
@@ -21112,12 +21112,12 @@ var McpServer = class {
21112
21112
  /**
21113
21113
  * Validates tool input arguments against the tool's input schema.
21114
21114
  */
21115
- async validateToolInput(tool26, args, toolName) {
21116
- if (!tool26.inputSchema) {
21115
+ async validateToolInput(tool27, args, toolName) {
21116
+ if (!tool27.inputSchema) {
21117
21117
  return void 0;
21118
21118
  }
21119
- const inputObj = normalizeObjectSchema(tool26.inputSchema);
21120
- const schemaToParse = inputObj ?? tool26.inputSchema;
21119
+ const inputObj = normalizeObjectSchema(tool27.inputSchema);
21120
+ const schemaToParse = inputObj ?? tool27.inputSchema;
21121
21121
  const parseResult = await safeParseAsync2(schemaToParse, args);
21122
21122
  if (!parseResult.success) {
21123
21123
  const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
@@ -21129,8 +21129,8 @@ var McpServer = class {
21129
21129
  /**
21130
21130
  * Validates tool output against the tool's output schema.
21131
21131
  */
21132
- async validateToolOutput(tool26, result, toolName) {
21133
- if (!tool26.outputSchema) {
21132
+ async validateToolOutput(tool27, result, toolName) {
21133
+ if (!tool27.outputSchema) {
21134
21134
  return;
21135
21135
  }
21136
21136
  if (!("content" in result)) {
@@ -21142,7 +21142,7 @@ var McpServer = class {
21142
21142
  if (!result.structuredContent) {
21143
21143
  throw new McpError(ErrorCode.InvalidParams, `Output validation error: Tool ${toolName} has an output schema but no structured content was provided`);
21144
21144
  }
21145
- const outputObj = normalizeObjectSchema(tool26.outputSchema);
21145
+ const outputObj = normalizeObjectSchema(tool27.outputSchema);
21146
21146
  const parseResult = await safeParseAsync2(outputObj, result.structuredContent);
21147
21147
  if (!parseResult.success) {
21148
21148
  const error2 = "error" in parseResult ? parseResult.error : "Unknown error";
@@ -21153,15 +21153,15 @@ var McpServer = class {
21153
21153
  /**
21154
21154
  * Executes a tool handler (either regular or task-based).
21155
21155
  */
21156
- async executeToolHandler(tool26, args, extra) {
21157
- const handler = tool26.handler;
21156
+ async executeToolHandler(tool27, args, extra) {
21157
+ const handler = tool27.handler;
21158
21158
  const isTaskHandler = "createTask" in handler;
21159
21159
  if (isTaskHandler) {
21160
21160
  if (!extra.taskStore) {
21161
21161
  throw new Error("No task store provided.");
21162
21162
  }
21163
21163
  const taskExtra = { ...extra, taskStore: extra.taskStore };
21164
- if (tool26.inputSchema) {
21164
+ if (tool27.inputSchema) {
21165
21165
  const typedHandler = handler;
21166
21166
  return await Promise.resolve(typedHandler.createTask(args, taskExtra));
21167
21167
  } else {
@@ -21169,7 +21169,7 @@ var McpServer = class {
21169
21169
  return await Promise.resolve(typedHandler.createTask(taskExtra));
21170
21170
  }
21171
21171
  }
21172
- if (tool26.inputSchema) {
21172
+ if (tool27.inputSchema) {
21173
21173
  const typedHandler = handler;
21174
21174
  return await Promise.resolve(typedHandler(args, extra));
21175
21175
  } else {
@@ -21180,12 +21180,12 @@ var McpServer = class {
21180
21180
  /**
21181
21181
  * Handles automatic task polling for tools with taskSupport 'optional'.
21182
21182
  */
21183
- async handleAutomaticTaskPolling(tool26, request, extra) {
21183
+ async handleAutomaticTaskPolling(tool27, request, extra) {
21184
21184
  if (!extra.taskStore) {
21185
21185
  throw new Error("No task store provided for task-capable tool.");
21186
21186
  }
21187
- const args = await this.validateToolInput(tool26, request.params.arguments, request.params.name);
21188
- const handler = tool26.handler;
21187
+ const args = await this.validateToolInput(tool27, request.params.arguments, request.params.name);
21188
+ const handler = tool27.handler;
21189
21189
  const taskExtra = { ...extra, taskStore: extra.taskStore };
21190
21190
  const createTaskResult = args ? await Promise.resolve(handler.createTask(args, taskExtra)) : (
21191
21191
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -67522,10 +67522,10 @@ var DETECTORS = [
67522
67522
  function detectAll(input) {
67523
67523
  if (!input.trim()) return [];
67524
67524
  const results = [];
67525
- for (const { tool: tool26, label, detect } of DETECTORS) {
67525
+ for (const { tool: tool27, label, detect } of DETECTORS) {
67526
67526
  const confidence = detect(input);
67527
67527
  if (confidence > 0) {
67528
- results.push({ tool: tool26, confidence, label });
67528
+ results.push({ tool: tool27, confidence, label });
67529
67529
  }
67530
67530
  }
67531
67531
  results.sort((a2, b2) => b2.confidence - a2.confidence);
@@ -70401,6 +70401,319 @@ ${output}`
70401
70401
  }
70402
70402
  };
70403
70403
 
70404
+ // ../utils/diagramParser.ts
70405
+ var NODE_KEYWORDS = {
70406
+ user: "user",
70407
+ client: "client",
70408
+ mobile: "client",
70409
+ browser: "client",
70410
+ app: "client",
70411
+ frontend: "client",
70412
+ api: "service",
70413
+ server: "service",
70414
+ service: "service",
70415
+ worker: "service",
70416
+ gateway: "service",
70417
+ lambda: "service",
70418
+ function: "service",
70419
+ microservice: "service",
70420
+ postgres: "database",
70421
+ postgresql: "database",
70422
+ mysql: "database",
70423
+ mongo: "database",
70424
+ mongodb: "database",
70425
+ redis: "database",
70426
+ database: "database",
70427
+ db: "database",
70428
+ dynamodb: "database",
70429
+ sqs: "queue",
70430
+ rabbitmq: "queue",
70431
+ kafka: "queue",
70432
+ queue: "queue",
70433
+ pubsub: "queue",
70434
+ sns: "queue",
70435
+ s3: "storage",
70436
+ storage: "storage",
70437
+ bucket: "storage",
70438
+ blob: "storage",
70439
+ cdn: "external",
70440
+ stripe: "external",
70441
+ twilio: "external",
70442
+ email: "external",
70443
+ webhook: "external",
70444
+ external: "external"
70445
+ };
70446
+ function inferNodeType(name) {
70447
+ const lower = name.toLowerCase();
70448
+ for (const [keyword, type] of Object.entries(NODE_KEYWORDS)) {
70449
+ if (lower.includes(keyword)) return type;
70450
+ }
70451
+ return "service";
70452
+ }
70453
+ function toId(name) {
70454
+ return name.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "").toLowerCase();
70455
+ }
70456
+ function parseSentences(input) {
70457
+ return input.split(/[.!?\n]+/).map((s2) => s2.trim()).filter((s2) => s2.length > 0);
70458
+ }
70459
+ function extractEntitiesAndActions(input) {
70460
+ const sentences = parseSentences(input);
70461
+ const nodesMap = /* @__PURE__ */ new Map();
70462
+ const steps = [];
70463
+ const edges = [];
70464
+ const entityPatterns = [
70465
+ /\b(?:from|to|in|into|on|via|through|using)\s+([A-Z][a-zA-Z0-9]*(?:\s+[A-Z][a-zA-Z0-9]*)?)/g,
70466
+ /\b([A-Z][a-zA-Z0-9]*(?:\s+[A-Z][a-zA-Z0-9]*)?)(?:\s+(?:validates?|stores?|sends?|pushes?|pulls?|processes?|uploads?|downloads?|reads?|writes?|calls?|receives?|forwards?|triggers?|creates?|deletes?|updates?|fetches?|returns?|notifies?|logs?|queues?|publishes?|subscribes?))/g,
70467
+ /\b(?:validates?|stores?|sends?|pushes?|pulls?|processes?|uploads?|downloads?|reads?|writes?|calls?|receives?|forwards?|triggers?|creates?|deletes?|updates?|fetches?|returns?|notifies?|logs?|queues?|publishes?|subscribes?)\s+(?:to|in|into|on|from|via)?\s*([A-Z][a-zA-Z0-9]*(?:\s+[A-Z][a-zA-Z0-9]*)?)/g
70468
+ ];
70469
+ const knownEntities = /* @__PURE__ */ new Set();
70470
+ for (const sentence of sentences) {
70471
+ for (const pattern of entityPatterns) {
70472
+ pattern.lastIndex = 0;
70473
+ let match;
70474
+ while ((match = pattern.exec(sentence)) !== null) {
70475
+ const entity = match[1].trim();
70476
+ if (entity.length > 1) knownEntities.add(entity);
70477
+ }
70478
+ }
70479
+ }
70480
+ const techNames = ["SQS", "S3", "SNS", "API", "CDN", "Redis", "Kafka", "Postgres", "PostgreSQL", "MySQL", "MongoDB", "DynamoDB", "RabbitMQ", "Lambda"];
70481
+ for (const sentence of sentences) {
70482
+ for (const tech of techNames) {
70483
+ if (sentence.toLowerCase().includes(tech.toLowerCase())) {
70484
+ knownEntities.add(tech);
70485
+ }
70486
+ }
70487
+ }
70488
+ for (const entity of knownEntities) {
70489
+ const id3 = toId(entity);
70490
+ if (!nodesMap.has(id3)) {
70491
+ nodesMap.set(id3, { id: id3, label: entity, type: inferNodeType(entity) });
70492
+ }
70493
+ }
70494
+ const inputLower = input.toLowerCase();
70495
+ if (!nodesMap.has("user") && /\buser\b/i.test(input)) {
70496
+ nodesMap.set("user", { id: "user", label: "User", type: "user" });
70497
+ knownEntities.add("User");
70498
+ }
70499
+ const actionVerbs = /(?:validates?|stores?|sends?|pushes?|pulls?|processes?|uploads?|downloads?|reads?|writes?|calls?|receives?|forwards?|triggers?|creates?|deletes?|updates?|fetches?|returns?|notifies?|logs?|queues?|publishes?|subscribes?)/i;
70500
+ for (const sentence of sentences) {
70501
+ const entitiesInSentence = [];
70502
+ for (const entity of knownEntities) {
70503
+ if (sentence.toLowerCase().includes(entity.toLowerCase())) {
70504
+ entitiesInSentence.push(entity);
70505
+ }
70506
+ }
70507
+ const verbMatch = sentence.match(actionVerbs);
70508
+ const action = verbMatch ? verbMatch[0].toLowerCase() : "connects";
70509
+ if (entitiesInSentence.length >= 2) {
70510
+ for (let i2 = 0; i2 < entitiesInSentence.length - 1; i2++) {
70511
+ const fromId = toId(entitiesInSentence[i2]);
70512
+ const toId_ = toId(entitiesInSentence[i2 + 1]);
70513
+ if (fromId !== toId_) {
70514
+ const edgeLabel = i2 === 0 ? action : "sends";
70515
+ steps.push({ from: fromId, to: toId_, label: edgeLabel });
70516
+ edges.push({ from: fromId, to: toId_, label: edgeLabel });
70517
+ }
70518
+ }
70519
+ }
70520
+ }
70521
+ if (edges.length === 0 && nodesMap.size > 1) {
70522
+ const nodeList = Array.from(nodesMap.values());
70523
+ for (let i2 = 0; i2 < nodeList.length - 1; i2++) {
70524
+ steps.push({ from: nodeList[i2].id, to: nodeList[i2 + 1].id, label: "sends" });
70525
+ edges.push({ from: nodeList[i2].id, to: nodeList[i2 + 1].id, label: "sends" });
70526
+ }
70527
+ }
70528
+ return {
70529
+ sequence: {
70530
+ participants: Array.from(nodesMap.values()).map((n2) => ({ id: n2.id, label: n2.label })),
70531
+ steps
70532
+ },
70533
+ flowchart: {
70534
+ nodes: Array.from(nodesMap.values()),
70535
+ edges
70536
+ }
70537
+ };
70538
+ }
70539
+ async function generateDiagramJSON(input) {
70540
+ await new Promise((resolve) => setTimeout(resolve, 800 + Math.random() * 700));
70541
+ if (!input.trim()) {
70542
+ throw new Error("Please provide a system description.");
70543
+ }
70544
+ const result = extractEntitiesAndActions(input);
70545
+ if (result.flowchart.nodes.length === 0) {
70546
+ throw new Error('Could not identify any system components. Try using proper nouns (e.g., "API", "Postgres", "S3") and action verbs (e.g., "sends", "stores", "processes").');
70547
+ }
70548
+ return result;
70549
+ }
70550
+
70551
+ // ../utils/mermaidBuilder.ts
70552
+ function buildSequenceMermaid(data) {
70553
+ const lines = ["sequenceDiagram"];
70554
+ for (const p3 of data.participants) {
70555
+ lines.push(` participant ${p3.id} as ${p3.label}`);
70556
+ }
70557
+ lines.push("");
70558
+ for (const step of data.steps) {
70559
+ lines.push(` ${step.from}->>+${step.to}: ${step.label}`);
70560
+ }
70561
+ if (data.steps.length > 0) {
70562
+ const last3 = data.steps[data.steps.length - 1];
70563
+ lines.push(` ${last3.to}-->>-${data.steps[0].from}: response`);
70564
+ }
70565
+ return lines.join("\n");
70566
+ }
70567
+ function nodeShape(node) {
70568
+ switch (node.type) {
70569
+ case "database":
70570
+ return `${node.id}[(${node.label})]`;
70571
+ case "queue":
70572
+ return `${node.id}[[${node.label}]]`;
70573
+ case "storage":
70574
+ return `${node.id}[("${node.label}")]`;
70575
+ case "user":
70576
+ return `${node.id}(("${node.label}"))`;
70577
+ case "external":
70578
+ return `${node.id}{{${node.label}}}`;
70579
+ case "client":
70580
+ return `${node.id}[/"${node.label}"\\]`;
70581
+ default:
70582
+ return `${node.id}[${node.label}]`;
70583
+ }
70584
+ }
70585
+ var STYLE_COLORS = {
70586
+ user: "#3b82f6,#fff,#3b82f6",
70587
+ client: "#8b5cf6,#fff,#8b5cf6",
70588
+ service: "#10b981,#fff,#10b981",
70589
+ database: "#f59e0b,#fff,#f59e0b",
70590
+ queue: "#ec4899,#fff,#ec4899",
70591
+ storage: "#06b6d4,#fff,#06b6d4",
70592
+ external: "#6b7280,#fff,#6b7280"
70593
+ };
70594
+ function buildStyleLines(nodes) {
70595
+ const lines = [""];
70596
+ const typeGroups = {};
70597
+ for (const node of nodes) {
70598
+ if (!typeGroups[node.type]) typeGroups[node.type] = [];
70599
+ typeGroups[node.type].push(node.id);
70600
+ }
70601
+ for (const [type, ids] of Object.entries(typeGroups)) {
70602
+ const colors = STYLE_COLORS[type] || "#64748b,#fff,#64748b";
70603
+ const [fill, color, stroke] = colors.split(",");
70604
+ lines.push(` style ${ids.join(",")} fill:${fill},color:${color},stroke:${stroke}`);
70605
+ }
70606
+ return lines;
70607
+ }
70608
+ function buildFlowchartMermaid(data) {
70609
+ const lines = ["flowchart LR"];
70610
+ const subgroups = data.subgroups || [];
70611
+ const hasSubgroups = subgroups.length > 0;
70612
+ if (hasSubgroups) {
70613
+ const grouped = /* @__PURE__ */ new Map();
70614
+ const ungrouped = [];
70615
+ for (const node of data.nodes) {
70616
+ if (node.subgroup) {
70617
+ const list = grouped.get(node.subgroup) || [];
70618
+ list.push(node);
70619
+ grouped.set(node.subgroup, list);
70620
+ } else {
70621
+ ungrouped.push(node);
70622
+ }
70623
+ }
70624
+ for (const node of ungrouped) {
70625
+ lines.push(` ${nodeShape(node)}`);
70626
+ }
70627
+ for (const sg of subgroups) {
70628
+ const nodes = grouped.get(sg.id) || [];
70629
+ lines.push(` subgraph ${sg.id}["${sg.name}"]`);
70630
+ for (const node of nodes) {
70631
+ lines.push(` ${nodeShape(node)}`);
70632
+ }
70633
+ lines.push(" end");
70634
+ }
70635
+ } else {
70636
+ for (const node of data.nodes) {
70637
+ lines.push(` ${nodeShape(node)}`);
70638
+ }
70639
+ }
70640
+ lines.push("");
70641
+ for (const edge of data.edges) {
70642
+ if (edge.label) {
70643
+ lines.push(` ${edge.from} -->|${edge.label}| ${edge.to}`);
70644
+ } else {
70645
+ lines.push(` ${edge.from} --> ${edge.to}`);
70646
+ }
70647
+ }
70648
+ lines.push(...buildStyleLines(data.nodes));
70649
+ return lines.join("\n");
70650
+ }
70651
+
70652
+ // src/tools/generate-diagram.ts
70653
+ var tool26 = {
70654
+ name: "generate_diagram",
70655
+ description: "Generate Mermaid diagram syntax from a plain English system description. Call this tool when the user asks to create a diagram, flowchart, sequence diagram, or architecture visualization from a text description. Parses entities (API, Database, Queue, S3, etc.) and their relationships (sends, stores, processes, etc.) into proper Mermaid syntax with typed node shapes and color styling. Supports two output types: 'flowchart' (system architecture with subgroups) and 'sequence' (interaction flow between participants). More accurate than writing Mermaid by hand because it auto-detects node types (database, queue, storage, service, client, external) and applies correct Mermaid shapes and styling.",
70656
+ schema: external_exports.object({
70657
+ description: external_exports.string().describe(
70658
+ "Plain English description of the system or flow. Use proper nouns for components (e.g., 'API Gateway', 'Postgres', 'S3', 'Redis') and action verbs (e.g., 'sends', 'stores', 'processes', 'validates'). Example: 'User sends request to API Gateway. API Gateway validates token and forwards to Auth Service. Auth Service checks Redis cache then queries Postgres.'"
70659
+ ),
70660
+ type: external_exports.enum(["flowchart", "sequence", "both"]).default("both").describe(
70661
+ "Diagram type: 'flowchart' for architecture/system diagrams, 'sequence' for interaction/timeline diagrams, 'both' for both outputs (default)"
70662
+ )
70663
+ }),
70664
+ annotations: { readOnlyHint: true },
70665
+ execute: async ({ description, type }) => {
70666
+ const desc = description.trim();
70667
+ const diagramType = type || "both";
70668
+ if (!desc) {
70669
+ return {
70670
+ success: false,
70671
+ error: "Please provide a system description. Use proper nouns (API, Postgres, S3) and action verbs (sends, stores, processes)."
70672
+ };
70673
+ }
70674
+ try {
70675
+ const parsed = await generateDiagramJSON(desc);
70676
+ const result = {
70677
+ entities_found: parsed.flowchart.nodes.length,
70678
+ relationships_found: parsed.flowchart.edges.length
70679
+ };
70680
+ const summaryParts = [
70681
+ `Found ${parsed.flowchart.nodes.length} components and ${parsed.flowchart.edges.length} relationships.`
70682
+ ];
70683
+ if (diagramType === "flowchart" || diagramType === "both") {
70684
+ const flowchart = buildFlowchartMermaid(parsed.flowchart);
70685
+ result.flowchart = flowchart;
70686
+ summaryParts.push("", "=== FLOWCHART ===", flowchart);
70687
+ }
70688
+ if (diagramType === "sequence" || diagramType === "both") {
70689
+ const sequence = buildSequenceMermaid(parsed.sequence);
70690
+ result.sequence = sequence;
70691
+ summaryParts.push("", "=== SEQUENCE DIAGRAM ===", sequence);
70692
+ }
70693
+ result.nodes = parsed.flowchart.nodes.map((n2) => ({
70694
+ id: n2.id,
70695
+ label: n2.label,
70696
+ type: n2.type
70697
+ }));
70698
+ result.edges = parsed.flowchart.edges.map((e2) => ({
70699
+ from: e2.from,
70700
+ to: e2.to,
70701
+ label: e2.label
70702
+ }));
70703
+ return {
70704
+ success: true,
70705
+ data: result,
70706
+ summary: summaryParts.join("\n")
70707
+ };
70708
+ } catch (err) {
70709
+ return {
70710
+ success: false,
70711
+ error: err instanceof Error ? err.message : "Failed to parse system description."
70712
+ };
70713
+ }
70714
+ }
70715
+ };
70716
+
70404
70717
  // src/index.ts
70405
70718
  var ALL_TOOLS = [
70406
70719
  tool,
@@ -70427,11 +70740,34 @@ var ALL_TOOLS = [
70427
70740
  tool22,
70428
70741
  tool23,
70429
70742
  tool24,
70430
- tool25
70743
+ tool25,
70744
+ tool26
70431
70745
  ];
70432
70746
  var server = new McpServer({
70433
70747
  name: "devtoolkit-mcp",
70434
- version: "0.1.0"
70748
+ version: "0.1.0",
70749
+ instructions: [
70750
+ "DevToolKit MCP provides 26 developer utility tools that compute exact results.",
70751
+ "",
70752
+ "WHEN TO USE THESE TOOLS instead of answering natively:",
70753
+ "- Hashing (MD5, SHA-256, etc.) \u2014 AI cannot compute hashes, use hash_text",
70754
+ "- UUID/ULID generation \u2014 AI cannot produce crypto-random IDs, use uuid_generate",
70755
+ "- Base64/URL/HTML encoding/decoding \u2014 AI often gets these wrong, use encode_decode",
70756
+ "- Regex testing with captures \u2014 AI often mismatches, use regex_test",
70757
+ "- Subnet/CIDR calculation \u2014 AI often miscalculates, use ip_subnet",
70758
+ "- Date arithmetic and timezone conversion \u2014 AI often drifts, use timestamp_calc or convert_epoch",
70759
+ "- Number base conversion (hex/bin/oct) \u2014 AI often errors on large numbers, use number_base_convert",
70760
+ "- Password generation \u2014 AI cannot produce crypto-random output, use password_generate",
70761
+ "- CSV parsing with quoted fields \u2014 AI approximates, use csv_transform",
70762
+ "- Line-by-line text diff \u2014 AI misses changes, use diff_text",
70763
+ "- Color conversion with WCAG contrast \u2014 needs exact math, use convert_color",
70764
+ "- JSON repair \u2014 needs parser, use repair_json",
70765
+ "- SQL formatting \u2014 needs formatter, use format_sql",
70766
+ "",
70767
+ "GENERAL RULE: If a task requires exact computation, cryptographic randomness,",
70768
+ "or precise parsing \u2014 use the appropriate devtoolkit tool rather than guessing.",
70769
+ "All tools run locally with zero network requests."
70770
+ ].join("\n")
70435
70771
  });
70436
70772
  for (const t2 of ALL_TOOLS) {
70437
70773
  server.tool(