dodraw-mcp-server 0.1.6 → 0.1.8

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.
@@ -1,21 +1,19 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const diagramTools_1 = require("./tools/diagramTools");
1
+ import { handleToolCall } from './tools/diagramTools.js';
4
2
  async function createClassDiagram() {
5
3
  const FILE_PATH = "class_diagram_example.3duml";
6
4
  console.log(`Creating class diagram example at ${FILE_PATH}...`);
7
5
  // 1. Create Diagram
8
6
  try {
9
- await (0, diagramTools_1.handleToolCall)("create_new_diagram", { filePath: FILE_PATH });
7
+ await handleToolCall("create_new_diagram", { filePath: FILE_PATH });
10
8
  }
11
9
  catch (e) {
12
10
  console.log("File might exist, continuing...");
13
11
  }
14
12
  // 2. Add Layer
15
- await (0, diagramTools_1.handleToolCall)("add_layer", { filePath: FILE_PATH, name: "Classes" });
13
+ await handleToolCall("add_layer", { filePath: FILE_PATH, name: "Classes" });
16
14
  // 3. Create Classes
17
15
  // Abstract Class: GraphicObject
18
- await (0, diagramTools_1.handleToolCall)("create_uml_class", {
16
+ await handleToolCall("create_uml_class", {
19
17
  filePath: FILE_PATH,
20
18
  className: "GraphicObject",
21
19
  attributes: ["x: int", "y: int"],
@@ -24,7 +22,7 @@ async function createClassDiagram() {
24
22
  id: "GraphicObject"
25
23
  });
26
24
  // Subclass: Circle
27
- await (0, diagramTools_1.handleToolCall)("create_uml_class", {
25
+ await handleToolCall("create_uml_class", {
28
26
  filePath: FILE_PATH,
29
27
  className: "Circle",
30
28
  attributes: ["radius: float"],
@@ -33,7 +31,7 @@ async function createClassDiagram() {
33
31
  id: "Circle"
34
32
  });
35
33
  // Subclass: Rectangle
36
- await (0, diagramTools_1.handleToolCall)("create_uml_class", {
34
+ await handleToolCall("create_uml_class", {
37
35
  filePath: FILE_PATH,
38
36
  className: "Rectangle",
39
37
  attributes: ["width: float", "height: float"],
@@ -42,7 +40,7 @@ async function createClassDiagram() {
42
40
  id: "Rectangle"
43
41
  });
44
42
  // Container: Group
45
- await (0, diagramTools_1.handleToolCall)("create_uml_class", {
43
+ await handleToolCall("create_uml_class", {
46
44
  filePath: FILE_PATH,
47
45
  className: "Group",
48
46
  attributes: [],
@@ -51,7 +49,7 @@ async function createClassDiagram() {
51
49
  id: "Group"
52
50
  });
53
51
  // Client: Canvas
54
- await (0, diagramTools_1.handleToolCall)("create_uml_class", {
52
+ await handleToolCall("create_uml_class", {
55
53
  filePath: FILE_PATH,
56
54
  className: "Canvas",
57
55
  attributes: ["width: int", "height: int", "backgroundColor: string"],
@@ -61,7 +59,7 @@ async function createClassDiagram() {
61
59
  });
62
60
  // 4. Create Edges (Relationships)
63
61
  // Inheritance: Circle -> GraphicObject
64
- await (0, diagramTools_1.handleToolCall)("add_edge", {
62
+ await handleToolCall("add_edge", {
65
63
  filePath: FILE_PATH,
66
64
  sourceId: "Circle",
67
65
  targetId: "GraphicObject",
@@ -80,7 +78,7 @@ async function createClassDiagram() {
80
78
  id: "edge_circle_extends"
81
79
  });
82
80
  // Inheritance: Rectangle -> GraphicObject
83
- await (0, diagramTools_1.handleToolCall)("add_edge", {
81
+ await handleToolCall("add_edge", {
84
82
  filePath: FILE_PATH,
85
83
  sourceId: "Rectangle",
86
84
  targetId: "GraphicObject",
@@ -99,7 +97,7 @@ async function createClassDiagram() {
99
97
  // The Diamond is on the Group end.
100
98
  // Source: Group. Target: GraphicObject.
101
99
  // Diamond at Source. Arrow/None at Target.
102
- await (0, diagramTools_1.handleToolCall)("add_edge", {
100
+ await handleToolCall("add_edge", {
103
101
  filePath: FILE_PATH,
104
102
  sourceId: "Group",
105
103
  targetId: "GraphicObject",
@@ -116,7 +114,7 @@ async function createClassDiagram() {
116
114
  // Let's say Canvas has a list of Shapes.
117
115
  // Canvas (8, -4) -> GraphicObject (0, -4).
118
116
  // Canvas Left (3) -> GraphicObject Right (1).
119
- await (0, diagramTools_1.handleToolCall)("add_edge", {
117
+ await handleToolCall("add_edge", {
120
118
  filePath: FILE_PATH,
121
119
  sourceId: "Canvas",
122
120
  targetId: "GraphicObject",
@@ -1,18 +1,16 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const diagramTools_1 = require("./tools/diagramTools");
1
+ import { handleToolCall } from './tools/diagramTools.js';
4
2
  async function createReference() {
5
3
  const FILE_PATH = "edge_terminations_test.3duml";
6
4
  console.log(`Creating diagram at ${FILE_PATH}...`);
7
5
  // 1. Create Diagram
8
6
  try {
9
- await (0, diagramTools_1.handleToolCall)("create_new_diagram", { filePath: FILE_PATH });
7
+ await handleToolCall("create_new_diagram", { filePath: FILE_PATH });
10
8
  }
11
9
  catch (e) {
12
10
  console.log("File might exist, continuing...");
13
11
  }
14
12
  // 2. Add Layer
15
- await (0, diagramTools_1.handleToolCall)("add_layer", { filePath: FILE_PATH, name: "Terminations" });
13
+ await handleToolCall("add_layer", { filePath: FILE_PATH, name: "Terminations" });
16
14
  // 3. Define Types
17
15
  const types = [
18
16
  { id: 'none', label: 'None', description: 'No visual marker' },
@@ -37,7 +35,7 @@ async function createReference() {
37
35
  const sourceId = `src_${t.id}`;
38
36
  const targetId = `tgt_${t.id}`;
39
37
  // Start Node
40
- await (0, diagramTools_1.handleToolCall)("add_node", {
38
+ await handleToolCall("add_node", {
41
39
  filePath: FILE_PATH,
42
40
  label: `Start`,
43
41
  id: sourceId,
@@ -46,7 +44,7 @@ async function createReference() {
46
44
  color: "#eeeeee"
47
45
  });
48
46
  // End Node
49
- await (0, diagramTools_1.handleToolCall)("add_node", {
47
+ await handleToolCall("add_node", {
50
48
  filePath: FILE_PATH,
51
49
  label: `End`,
52
50
  id: targetId,
@@ -55,7 +53,7 @@ async function createReference() {
55
53
  color: "#eeeeee"
56
54
  });
57
55
  // Description Note (Left side)
58
- await (0, diagramTools_1.handleToolCall)("add_node", {
56
+ await handleToolCall("add_node", {
59
57
  filePath: FILE_PATH,
60
58
  label: `${t.label}\n${t.description}`,
61
59
  shape: 'note',
@@ -65,7 +63,7 @@ async function createReference() {
65
63
  id: `note_${t.id}`
66
64
  });
67
65
  // Edge
68
- await (0, diagramTools_1.handleToolCall)("add_edge", {
66
+ await handleToolCall("add_edge", {
69
67
  filePath: FILE_PATH,
70
68
  sourceId: sourceId,
71
69
  targetId: targetId,
package/dist/src/index.js CHANGED
@@ -1,11 +1,9 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- Object.defineProperty(exports, "__esModule", { value: true });
4
- const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
5
- const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
6
- const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
- const diagramTools_js_1 = require("./tools/diagramTools.js");
8
- const server = new index_js_1.Server({
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
5
+ import { toolDefinitions, handleToolCall } from "./tools/diagramTools.js";
6
+ const server = new Server({
9
7
  name: "dodraw-mcp-server",
10
8
  version: "0.1.0",
11
9
  }, {
@@ -13,14 +11,14 @@ const server = new index_js_1.Server({
13
11
  tools: {},
14
12
  },
15
13
  });
16
- server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
14
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
17
15
  return {
18
- tools: diagramTools_js_1.toolDefinitions,
16
+ tools: toolDefinitions,
19
17
  };
20
18
  });
21
- server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
19
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
22
20
  try {
23
- return await (0, diagramTools_js_1.handleToolCall)(request.params.name, request.params.arguments);
21
+ return await handleToolCall(request.params.name, request.params.arguments);
24
22
  }
25
23
  catch (error) {
26
24
  return {
@@ -35,7 +33,7 @@ server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
35
33
  }
36
34
  });
37
35
  async function main() {
38
- const transport = new stdio_js_1.StdioServerTransport();
36
+ const transport = new StdioServerTransport();
39
37
  await server.connect(transport);
40
38
  console.error("DoDraw MCP Server running on stdio");
41
39
  }
@@ -1,10 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toolDefinitions = void 0;
4
- exports.handleToolCall = handleToolCall;
5
- const fileHandler_1 = require("../utils/fileHandler");
6
- const crypto_1 = require("crypto");
7
- exports.toolDefinitions = [
1
+ import { readDiagramFile, saveDiagramFile } from "../utils/fileHandler.js";
2
+ import { randomUUID } from "crypto";
3
+ export const toolDefinitions = [
8
4
  {
9
5
  name: "create_new_diagram",
10
6
  description: "Create a new empty DoDraw diagram file",
@@ -175,7 +171,7 @@ exports.toolDefinitions = [
175
171
  },
176
172
  {
177
173
  name: "create_uml_class",
178
- description: "Create a UML Class node. Use this for the FIRST class. For connected classes, use 'add_directional_node' to ensure correct relative positioning.",
174
+ description: "Create a UML Class node. Supports absolute positioning (x,y) OR relative positioning (referenceNodeId, direction). Can also auto-connect.",
179
175
  inputSchema: {
180
176
  type: "object",
181
177
  properties: {
@@ -183,9 +179,19 @@ exports.toolDefinitions = [
183
179
  className: { type: "string" },
184
180
  attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
185
181
  methods: { type: "array", items: { type: "string" }, description: "List of methods e.g. ['+ getName(): string', '+ save(): void']" },
186
- x: { type: "number", description: "Optional X position" },
187
- y: { type: "number", description: "Optional Y position (mapped to Z in 3DUML)" },
188
- id: { type: "string", description: "Optional explicit ID" }
182
+ id: { type: "string", description: "Optional explicit ID" },
183
+ referenceNodeId: { type: "string", description: "Optional. ID of an existing node to place this new class relative to." },
184
+ direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Optional. Direction to place relative to referenceNodeId." },
185
+ connection: {
186
+ type: "object",
187
+ description: "Optional. If provided, creates an edge connecting the reference node to this new class.",
188
+ properties: {
189
+ type: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition"], description: "Relationship type" },
190
+ label: { type: "string" },
191
+ direction: { type: "string", enum: ["source-to-target", "target-to-source", "bi-directional"], description: "Edge direction flow" }
192
+ },
193
+ required: ["type"]
194
+ }
189
195
  },
190
196
  required: ["filePath", "className"]
191
197
  }
@@ -211,8 +217,6 @@ exports.toolDefinitions = [
211
217
  required: ["name", "type"]
212
218
  }
213
219
  },
214
- x: { type: "number" },
215
- y: { type: "number" },
216
220
  id: { type: "string", description: "Optional explicit ID" },
217
221
  referenceNodeId: { type: "string", description: "Optional. ID of an existing node to place this new table relative to." },
218
222
  direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Optional. Direction to place relative to referenceNodeId." },
@@ -246,11 +250,11 @@ exports.toolDefinitions = [
246
250
  }
247
251
  }
248
252
  ];
249
- async function handleToolCall(name, args) {
253
+ export async function handleToolCall(name, args) {
250
254
  switch (name) {
251
255
  case "create_new_diagram": {
252
256
  // ... (existing code)
253
- const defaultLayerId = (0, crypto_1.randomUUID)();
257
+ const defaultLayerId = randomUUID();
254
258
  const initialState = {
255
259
  nodes: [],
256
260
  edges: [],
@@ -264,12 +268,12 @@ async function handleToolCall(name, args) {
264
268
  gridSnappingEnabled: true,
265
269
  gridSize: 0.5
266
270
  };
267
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, initialState);
271
+ await saveDiagramFile(args.filePath, initialState);
268
272
  return { content: [{ type: "text", text: `Created new diagram at ${args.filePath}` }] };
269
273
  }
270
274
  case "read_diagram_structure": {
271
275
  // ... (existing code)
272
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
276
+ const state = await readDiagramFile(args.filePath);
273
277
  return {
274
278
  content: [{
275
279
  type: "text",
@@ -286,7 +290,7 @@ async function handleToolCall(name, args) {
286
290
  }
287
291
  case "add_node": {
288
292
  // ... (existing code)
289
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
293
+ const state = await readDiagramFile(args.filePath);
290
294
  // Auto-placement Logic
291
295
  let x = 0;
292
296
  let z = 0;
@@ -312,7 +316,7 @@ async function handleToolCall(name, args) {
312
316
  }
313
317
  }
314
318
  const newNode = {
315
- id: args.id || (0, crypto_1.randomUUID)(),
319
+ id: args.id || randomUUID(),
316
320
  x: x,
317
321
  y: 0.05,
318
322
  z: z,
@@ -333,16 +337,16 @@ async function handleToolCall(name, args) {
333
337
  };
334
338
  console.error(`DEBUG: Adding node. Args: x=${args.x}, y=${args.y}. Computed: x=${newNode.x}, z=${newNode.z}`);
335
339
  state.nodes.push(newNode);
336
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
340
+ await saveDiagramFile(args.filePath, state);
337
341
  return { content: [{ type: "text", text: `Added node ${newNode.id} at (${newNode.x}, ${newNode.z})` }] };
338
342
  }
339
343
  case "add_directional_node": {
340
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
344
+ const state = await readDiagramFile(args.filePath);
341
345
  const sourceNode = state.nodes.find(n => n.id === args.sourceNodeId);
342
346
  if (!sourceNode) {
343
347
  throw new Error(`Source node with ID ${args.sourceNodeId} not found.`);
344
348
  }
345
- const newNodeId = (0, crypto_1.randomUUID)();
349
+ const newNodeId = randomUUID();
346
350
  // 1. Calculate base position with tighter spacing (matching App interaction)
347
351
  const INITIAL_SPACING = 2.0;
348
352
  let dirX = 0;
@@ -440,7 +444,7 @@ async function handleToolCall(name, args) {
440
444
  textAlignHorizontal: 'center'
441
445
  };
442
446
  const newEdge = {
443
- id: (0, crypto_1.randomUUID)(),
447
+ id: randomUUID(),
444
448
  sourceId: sourceNode.id,
445
449
  targetId: newNodeId,
446
450
  sourcePointIndex: sourcePoint,
@@ -455,11 +459,11 @@ async function handleToolCall(name, args) {
455
459
  };
456
460
  state.nodes.push(newNode);
457
461
  state.edges.push(newEdge);
458
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
462
+ await saveDiagramFile(args.filePath, state);
459
463
  return { content: [{ type: "text", text: `Added node '${args.label}' (ID: ${newNodeId}) to the ${args.direction} of '${sourceNode.label}' and connected them.` }] };
460
464
  }
461
465
  case "add_edge": {
462
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
466
+ const state = await readDiagramFile(args.filePath);
463
467
  // Verify nodes exist
464
468
  const sourceExists = state.nodes.find(n => n.id === args.sourceId);
465
469
  const targetExists = state.nodes.find(n => n.id === args.targetId);
@@ -468,7 +472,7 @@ async function handleToolCall(name, args) {
468
472
  if (!targetExists)
469
473
  throw new Error(`Target node ${args.targetId} not found`);
470
474
  const newEdge = {
471
- id: args.id || (0, crypto_1.randomUUID)(),
475
+ id: args.id || randomUUID(),
472
476
  sourceId: args.sourceId,
473
477
  targetId: args.targetId,
474
478
  sourcePointIndex: Number(args.sourcePointIndex),
@@ -484,11 +488,11 @@ async function handleToolCall(name, args) {
484
488
  terminationEnd: args.terminationEnd
485
489
  };
486
490
  state.edges.push(newEdge);
487
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
491
+ await saveDiagramFile(args.filePath, state);
488
492
  return { content: [{ type: "text", text: `Added edge ${newEdge.id}` }] };
489
493
  }
490
494
  case "update_node": {
491
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
495
+ const state = await readDiagramFile(args.filePath);
492
496
  const nodeIndex = state.nodes.findIndex(n => n.id === args.nodeId);
493
497
  if (nodeIndex === -1) {
494
498
  throw new Error(`Node ${args.nodeId} not found`);
@@ -505,12 +509,12 @@ async function handleToolCall(name, args) {
505
509
  state.nodes[nodeIndex].height = Number(args.height);
506
510
  if (args.backgroundColor)
507
511
  state.nodes[nodeIndex].backgroundColor = args.backgroundColor;
508
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
512
+ await saveDiagramFile(args.filePath, state);
509
513
  return { content: [{ type: "text", text: `Updated node ${args.nodeId}` }] };
510
514
  }
511
515
  case "add_layer": {
512
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
513
- const newLayerId = (0, crypto_1.randomUUID)();
516
+ const state = await readDiagramFile(args.filePath);
517
+ const newLayerId = randomUUID();
514
518
  // Auto-positioning logic
515
519
  let position = { x: 0, y: 0, z: 0 };
516
520
  if (args.position) {
@@ -549,12 +553,12 @@ async function handleToolCall(name, args) {
549
553
  visible: args.visible !== false
550
554
  };
551
555
  state.layers.push(newLayer);
552
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
556
+ await saveDiagramFile(args.filePath, state);
553
557
  return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
554
558
  }
555
559
  case "create_uml_class": {
556
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
557
- const newNodeId = args.id || (0, crypto_1.randomUUID)();
560
+ const state = await readDiagramFile(args.filePath);
561
+ const newNodeId = args.id || randomUUID();
558
562
  // Standard size for class
559
563
  const width = 3;
560
564
  // dynamic height?
@@ -562,10 +566,64 @@ async function handleToolCall(name, args) {
562
566
  const methodCount = (args.methods || []).length;
563
567
  const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
564
568
  const height = Math.max(2, estimatedHeight);
565
- // Auto-placement logic if X/Y not provided
566
- let x = args.x !== undefined ? Number(args.x) : 0;
567
- let z = args.y !== undefined ? Number(args.y) : 0;
568
- if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
569
+ // Auto-placement logic (defaults to 0,0 for first node)
570
+ let x = 0;
571
+ let z = 0;
572
+ // Relative Placement Logic
573
+ if (args.referenceNodeId && args.direction) {
574
+ const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
575
+ if (!sourceNode)
576
+ throw new Error(`Reference node ${args.referenceNodeId} not found`);
577
+ const INITIAL_SPACING = 2.0;
578
+ let dirX = 0;
579
+ let dirZ = 0;
580
+ const srcW = Number(sourceNode.width) || 2;
581
+ const srcH = Number(sourceNode.height) || 1.5;
582
+ switch (args.direction) {
583
+ case "RIGHT":
584
+ dirX = 1;
585
+ break;
586
+ case "LEFT":
587
+ dirX = -1;
588
+ break;
589
+ case "DOWN":
590
+ dirZ = 1;
591
+ break;
592
+ case "UP":
593
+ dirZ = -1;
594
+ break;
595
+ }
596
+ x = sourceNode.x + (dirX * (srcW + INITIAL_SPACING));
597
+ z = sourceNode.z + (dirZ * (srcH + INITIAL_SPACING));
598
+ // Collision Avoidance reuse
599
+ const COLLISION_SPACING = 0.5;
600
+ const shiftStepX = srcW + COLLISION_SPACING;
601
+ const shiftStepZ = srcH + COLLISION_SPACING;
602
+ const newW = width;
603
+ const newH = height;
604
+ const checkCollision = (cx, cz) => {
605
+ return state.nodes.some(n => {
606
+ if (n.layerId !== sourceNode.layerId)
607
+ return false;
608
+ const dx = Math.abs(n.x - cx);
609
+ const dz = Math.abs(n.z - cz);
610
+ const otherW = Number(n.width) || 2;
611
+ const otherH = Number(n.height) || 1.5;
612
+ const combinedHalfWidth = (otherW + newW) / 2;
613
+ const combinedHalfHeight = (otherH + newH) / 2;
614
+ return dx < combinedHalfWidth && dz < combinedHalfHeight;
615
+ });
616
+ };
617
+ let iterations = 0;
618
+ while (checkCollision(x, z) && iterations < 50) {
619
+ iterations++;
620
+ if (dirX !== 0)
621
+ z += shiftStepZ;
622
+ else
623
+ x += shiftStepX;
624
+ }
625
+ }
626
+ else if (state.nodes.length > 0) {
569
627
  const lastNode = state.nodes[state.nodes.length - 1];
570
628
  x = lastNode.x + 5;
571
629
  z = lastNode.z;
@@ -587,20 +645,91 @@ async function handleToolCall(name, args) {
587
645
  }
588
646
  };
589
647
  state.nodes.push(newNode);
590
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
648
+ // Auto-Connect logic
649
+ if (args.connection && args.referenceNodeId) {
650
+ let termStart = 'none';
651
+ let termEnd = 'none';
652
+ let borderStyle = 'solid';
653
+ // UML Mapping
654
+ switch (args.connection.type) {
655
+ case 'inheritance':
656
+ termEnd = 'triangle-empty';
657
+ break;
658
+ case 'implementation':
659
+ termEnd = 'triangle-empty';
660
+ borderStyle = 'dashed';
661
+ break;
662
+ case 'dependency':
663
+ termEnd = 'arrow';
664
+ borderStyle = 'dashed';
665
+ break;
666
+ case 'association':
667
+ termEnd = 'arrow';
668
+ break;
669
+ case 'aggregation':
670
+ termStart = 'diamond-empty';
671
+ break;
672
+ case 'composition':
673
+ termStart = 'diamond-filled';
674
+ break;
675
+ }
676
+ // Points? Simple defaults based on direction
677
+ let sp = 0;
678
+ let tp = 0;
679
+ switch (args.direction) {
680
+ case "RIGHT":
681
+ sp = 1;
682
+ tp = 3;
683
+ break;
684
+ case "LEFT":
685
+ sp = 3;
686
+ tp = 1;
687
+ break;
688
+ case "DOWN":
689
+ sp = 2;
690
+ tp = 0;
691
+ break;
692
+ case "UP":
693
+ sp = 0;
694
+ tp = 2;
695
+ break;
696
+ default:
697
+ sp = 2;
698
+ tp = 0;
699
+ break;
700
+ }
701
+ const newEdge = {
702
+ id: randomUUID(),
703
+ sourceId: args.referenceNodeId,
704
+ targetId: newNodeId,
705
+ sourcePointIndex: sp,
706
+ targetPointIndex: tp,
707
+ label: args.connection.label,
708
+ style: 'line',
709
+ routingType: 'orthogonal',
710
+ color: '#000000',
711
+ thickness: 0.01,
712
+ fontSize: 20,
713
+ borderStyle: borderStyle,
714
+ terminationStart: termStart,
715
+ terminationEnd: termEnd
716
+ };
717
+ state.edges.push(newEdge);
718
+ }
719
+ await saveDiagramFile(args.filePath, state);
591
720
  return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
592
721
  }
593
722
  case "create_db_table": {
594
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
595
- const newNodeId = args.id || (0, crypto_1.randomUUID)();
723
+ const state = await readDiagramFile(args.filePath);
724
+ const newNodeId = args.id || randomUUID();
596
725
  const cols = args.columns || [];
597
726
  // Header + items
598
727
  const estimatedHeight = 0.8 + cols.length * 0.4;
599
728
  const height = Math.max(2, estimatedHeight);
600
729
  const width = 3;
601
- // Auto-placement logic if X/Y not provided
602
- let x = args.x !== undefined ? Number(args.x) : 0;
603
- let z = args.y !== undefined ? Number(args.y) : 0;
730
+ // Auto-placement logic (defaults to 0,0 for first node)
731
+ let x = 0;
732
+ let z = 0;
604
733
  // Relative Placement Logic
605
734
  if (args.referenceNodeId && args.direction) {
606
735
  const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
@@ -646,16 +775,8 @@ async function handleToolCall(name, args) {
646
775
  return dx < combinedHalfWidth && dz < combinedHalfHeight;
647
776
  });
648
777
  };
649
- let iterations = 0;
650
- while (checkCollision(x, z) && iterations < 50) {
651
- iterations++;
652
- if (dirX !== 0)
653
- z += shiftStepZ;
654
- else
655
- x += shiftStepX;
656
- }
657
778
  }
658
- else if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
779
+ else if (state.nodes.length > 0) {
659
780
  // Fallback to "next to last" if no explicit relative info
660
781
  const lastNode = state.nodes[state.nodes.length - 1];
661
782
  x = lastNode.x + 5;
@@ -730,7 +851,7 @@ async function handleToolCall(name, args) {
730
851
  break;
731
852
  }
732
853
  const newEdge = {
733
- id: (0, crypto_1.randomUUID)(),
854
+ id: randomUUID(),
734
855
  sourceId: args.referenceNodeId,
735
856
  targetId: newNodeId,
736
857
  sourcePointIndex: sp,
@@ -747,11 +868,11 @@ async function handleToolCall(name, args) {
747
868
  };
748
869
  state.edges.push(newEdge);
749
870
  }
750
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
871
+ await saveDiagramFile(args.filePath, state);
751
872
  return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
752
873
  }
753
874
  case "connect_entities": {
754
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
875
+ const state = await readDiagramFile(args.filePath);
755
876
  // Find nodes by Label (tolerant search?)
756
877
  const sourceNode = state.nodes.find(n => n.label === args.sourceName);
757
878
  const targetNode = state.nodes.find(n => n.label === args.targetName);
@@ -802,7 +923,7 @@ async function handleToolCall(name, args) {
802
923
  break;
803
924
  }
804
925
  const newEdge = {
805
- id: (0, crypto_1.randomUUID)(),
926
+ id: randomUUID(),
806
927
  sourceId: sourceNode.id,
807
928
  targetId: targetNode.id,
808
929
  sourcePointIndex: 2, // Bottom of source
@@ -818,7 +939,7 @@ async function handleToolCall(name, args) {
818
939
  terminationEnd: terminationEnd
819
940
  };
820
941
  state.edges.push(newEdge);
821
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
942
+ await saveDiagramFile(args.filePath, state);
822
943
  return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
823
944
  }
824
945
  default:
package/dist/src/types.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
@@ -1,16 +1,9 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.readDiagramFile = readDiagramFile;
7
- exports.saveDiagramFile = saveDiagramFile;
8
- const promises_1 = __importDefault(require("fs/promises"));
9
- const jszip_1 = __importDefault(require("jszip"));
10
- async function readDiagramFile(filePath) {
1
+ import fs from 'fs/promises';
2
+ import JSZip from 'jszip';
3
+ export async function readDiagramFile(filePath) {
11
4
  try {
12
- const fileContent = await promises_1.default.readFile(filePath);
13
- const zip = await jszip_1.default.loadAsync(fileContent);
5
+ const fileContent = await fs.readFile(filePath);
6
+ const zip = await JSZip.loadAsync(fileContent);
14
7
  const diagramFile = zip.file("diagram.json");
15
8
  if (!diagramFile) {
16
9
  throw new Error("Invalid .DoDraw file: diagram.json missing");
@@ -25,17 +18,17 @@ async function readDiagramFile(filePath) {
25
18
  throw error;
26
19
  }
27
20
  }
28
- async function saveDiagramFile(filePath, state) {
21
+ export async function saveDiagramFile(filePath, state) {
29
22
  let zip;
30
23
  // Try to load existing zip to preserve other files (e.g. images)
31
24
  try {
32
- const fileContent = await promises_1.default.readFile(filePath);
33
- zip = await jszip_1.default.loadAsync(fileContent);
25
+ const fileContent = await fs.readFile(filePath);
26
+ zip = await JSZip.loadAsync(fileContent);
34
27
  }
35
28
  catch (error) {
36
29
  if (error.code === 'ENOENT') {
37
30
  // Create new zip if file doesn't exist
38
- zip = new jszip_1.default();
31
+ zip = new JSZip();
39
32
  }
40
33
  else {
41
34
  throw error;
@@ -49,5 +42,5 @@ async function saveDiagramFile(filePath, state) {
49
42
  mimeType: "application/zip",
50
43
  compression: "DEFLATE"
51
44
  });
52
- await promises_1.default.writeFile(filePath, content);
45
+ await fs.writeFile(filePath, content);
53
46
  }
@@ -1,23 +1,18 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting to server...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "dodraw-test-client", version: "1.0.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "dodraw-test-client", version: "1.0.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  console.log("Connected.");
19
14
  try {
20
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_autoplacement.3duml");
15
+ const filePath = path.join(process.cwd(), "test_output", "test_autoplacement.3duml");
21
16
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
22
17
  // 1. Add First Node (auto) -> Should be 0,0
23
18
  console.log("Adding Node A (auto)...");
@@ -1,19 +1,14 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const fs_1 = __importDefault(require("fs"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import fs from "fs";
10
5
  async function main() {
11
- const serverPath = path_1.default.join(process.cwd(), "dist/src/index.js");
12
- const transport = new stdio_js_1.StdioClientTransport({
6
+ const serverPath = path.join(process.cwd(), "dist/src/index.js");
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
9
  args: [serverPath]
15
10
  });
16
- const client = new index_js_1.Client({
11
+ const client = new Client({
17
12
  name: "test-client",
18
13
  version: "1.0.0",
19
14
  }, {
@@ -26,14 +21,14 @@ async function main() {
26
21
  const tools = await client.listTools();
27
22
  console.log("Tools found:", tools.tools.map(t => t.name).join(", "));
28
23
  // Create diagram
29
- const testDir = path_1.default.join(__dirname, "../../test_output");
30
- if (!fs_1.default.existsSync(testDir)) {
31
- fs_1.default.mkdirSync(testDir);
24
+ const testDir = path.join(__dirname, "../../test_output");
25
+ if (!fs.existsSync(testDir)) {
26
+ fs.mkdirSync(testDir);
32
27
  }
33
- const filePath = path_1.default.join(testDir, "test_diagram.3duml");
28
+ const filePath = path.join(testDir, "test_diagram.3duml");
34
29
  // Clean up previous run
35
- if (fs_1.default.existsSync(filePath)) {
36
- fs_1.default.unlinkSync(filePath);
30
+ if (fs.existsSync(filePath)) {
31
+ fs.unlinkSync(filePath);
37
32
  }
38
33
  console.log(`Creating diagram at ${filePath}...`);
39
34
  await client.callTool({
@@ -1,22 +1,17 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "test-collision", version: "1.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "test-collision", version: "1.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  try {
19
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_collision.3duml");
14
+ const filePath = path.join(process.cwd(), "test_output", "test_collision.3duml");
20
15
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
21
16
  // 1. Add Root Node
22
17
  console.log("Adding Root Node...");
@@ -75,7 +70,7 @@ async function main() {
75
70
  }
76
71
  catch (e) {
77
72
  console.error("FAIL:", e);
78
- process_1.default.exit(1);
73
+ process.exit(1);
79
74
  }
80
75
  finally {
81
76
  await client.close();
@@ -1,22 +1,17 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "test-constraints", version: "1.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "test-constraints", version: "1.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  try {
19
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_constraints.3duml");
14
+ const filePath = path.join(process.cwd(), "test_output", "test_constraints.3duml");
20
15
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
21
16
  // 1. Test Layer Auto-Stacking
22
17
  console.log("Adding Layer 2 (auto)...");
@@ -1,22 +1,17 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "debug", version: "1.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "debug", version: "1.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  try {
19
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_debug.3duml");
14
+ const filePath = path.join(process.cwd(), "test_output", "test_debug.3duml");
20
15
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
21
16
  console.log("Adding Node C...");
22
17
  await client.callTool({
@@ -1,22 +1,17 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "test-directional", version: "1.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "test-directional", version: "1.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  try {
19
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_directional.3duml");
14
+ const filePath = path.join(process.cwd(), "test_output", "test_directional.3duml");
20
15
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
21
16
  // 1. Add Root Node
22
17
  console.log("Adding Root Node...");
@@ -1,23 +1,18 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting to server...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "dodraw-test-client", version: "1.0.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "dodraw-test-client", version: "1.0.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  console.log("Connected.");
19
14
  try {
20
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_layers.3duml");
15
+ const filePath = path.join(process.cwd(), "test_output", "test_layers.3duml");
21
16
  // 1. Create Diagram
22
17
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
23
18
  console.log("Created diagram.");
@@ -1,22 +1,17 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
7
- const stdio_js_1 = require("@modelcontextprotocol/sdk/client/stdio.js");
8
- const path_1 = __importDefault(require("path"));
9
- const process_1 = __importDefault(require("process"));
1
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3
+ import path from "path";
4
+ import process from "process";
10
5
  async function main() {
11
6
  console.log("Connecting...");
12
- const transport = new stdio_js_1.StdioClientTransport({
7
+ const transport = new StdioClientTransport({
13
8
  command: "node",
14
- args: [path_1.default.join(process_1.default.cwd(), "dist", "src", "index.js")],
9
+ args: [path.join(process.cwd(), "dist", "src", "index.js")],
15
10
  });
16
- const client = new index_js_1.Client({ name: "test-refined", version: "1.0" }, { capabilities: {} });
11
+ const client = new Client({ name: "test-refined", version: "1.0" }, { capabilities: {} });
17
12
  await client.connect(transport);
18
13
  try {
19
- const filePath = path_1.default.join(process_1.default.cwd(), "test_output", "test_refined_spacing.3duml");
14
+ const filePath = path.join(process.cwd(), "test_output", "test_refined_spacing.3duml");
20
15
  await client.callTool({ name: "create_new_diagram", arguments: { filePath } });
21
16
  // Default Layer 1 is at Y=0
22
17
  // 1. Add Layer 2 (auto) -> Should be -2
@@ -1,15 +1,10 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const promises_1 = __importDefault(require("fs/promises"));
7
- const jszip_1 = __importDefault(require("jszip"));
1
+ import fs from 'fs/promises';
2
+ import JSZip from 'jszip';
8
3
  async function checkFile(filePath, label) {
9
4
  try {
10
5
  console.log(`Checking ${label}: ${filePath}`);
11
- const content = await promises_1.default.readFile(filePath);
12
- const zip = await jszip_1.default.loadAsync(content);
6
+ const content = await fs.readFile(filePath);
7
+ const zip = await JSZip.loadAsync(content);
13
8
  const jsonStr = await zip.file("diagram.json")?.async("string");
14
9
  if (!jsonStr)
15
10
  throw new Error("No diagram.json");
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "dodraw-mcp-server",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "MCP server for DoDraw",
5
+ "type": "module",
5
6
  "main": "dist/src/index.js",
6
7
  "bin": {
7
8
  "dodraw-mcp-server": "dist/src/index.js"
@@ -1,5 +1,5 @@
1
1
 
2
- import { handleToolCall } from './tools/diagramTools';
2
+ import { handleToolCall } from './tools/diagramTools.js';
3
3
 
4
4
  async function createClassDiagram() {
5
5
  const FILE_PATH = "class_diagram_example.3duml";
@@ -1,5 +1,5 @@
1
1
 
2
- import { handleToolCall } from './tools/diagramTools';
2
+ import { handleToolCall } from './tools/diagramTools.js';
3
3
 
4
4
  interface TerminationType {
5
5
  id: string;
@@ -1,7 +1,7 @@
1
1
 
2
2
  import { CallToolRequestSchema, ListToolsRequestSchema, Tool } from "@modelcontextprotocol/sdk/types.js";
3
- import { readDiagramFile, saveDiagramFile } from "../utils/fileHandler";
4
- import { DiagramState, NodeData, EdgeData } from "../types";
3
+ import { readDiagramFile, saveDiagramFile } from "../utils/fileHandler.js";
4
+ import { DiagramState, NodeData, EdgeData } from "../types.js";
5
5
  import { randomUUID } from "crypto";
6
6
 
7
7
  export const toolDefinitions: Tool[] = [
@@ -175,7 +175,7 @@ export const toolDefinitions: Tool[] = [
175
175
  },
176
176
  {
177
177
  name: "create_uml_class",
178
- description: "Create a UML Class node. Use this for the FIRST class. For connected classes, use 'add_directional_node' to ensure correct relative positioning.",
178
+ description: "Create a UML Class node. Supports absolute positioning (x,y) OR relative positioning (referenceNodeId, direction). Can also auto-connect.",
179
179
  inputSchema: {
180
180
  type: "object",
181
181
  properties: {
@@ -183,9 +183,19 @@ export const toolDefinitions: Tool[] = [
183
183
  className: { type: "string" },
184
184
  attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
185
185
  methods: { type: "array", items: { type: "string" }, description: "List of methods e.g. ['+ getName(): string', '+ save(): void']" },
186
- x: { type: "number", description: "Optional X position" },
187
- y: { type: "number", description: "Optional Y position (mapped to Z in 3DUML)" },
188
- id: { type: "string", description: "Optional explicit ID" }
186
+ id: { type: "string", description: "Optional explicit ID" },
187
+ referenceNodeId: { type: "string", description: "Optional. ID of an existing node to place this new class relative to." },
188
+ direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Optional. Direction to place relative to referenceNodeId." },
189
+ connection: {
190
+ type: "object",
191
+ description: "Optional. If provided, creates an edge connecting the reference node to this new class.",
192
+ properties: {
193
+ type: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition"], description: "Relationship type" },
194
+ label: { type: "string" },
195
+ direction: { type: "string", enum: ["source-to-target", "target-to-source", "bi-directional"], description: "Edge direction flow" }
196
+ },
197
+ required: ["type"]
198
+ }
189
199
  },
190
200
  required: ["filePath", "className"]
191
201
  }
@@ -210,10 +220,9 @@ export const toolDefinitions: Tool[] = [
210
220
  isFk: { type: "boolean" }
211
221
  },
212
222
  required: ["name", "type"]
213
- }
223
+ }
214
224
  },
215
- x: { type: "number" },
216
- y: { type: "number" },
225
+
217
226
  id: { type: "string", description: "Optional explicit ID" },
218
227
  referenceNodeId: { type: "string", description: "Optional. ID of an existing node to place this new table relative to." },
219
228
  direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Optional. Direction to place relative to referenceNodeId." },
@@ -579,11 +588,58 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
579
588
  const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
580
589
  const height = Math.max(2, estimatedHeight);
581
590
 
582
- // Auto-placement logic if X/Y not provided
583
- let x = args.x !== undefined ? Number(args.x) : 0;
584
- let z = args.y !== undefined ? Number(args.y) : 0;
591
+ // Auto-placement logic (defaults to 0,0 for first node)
592
+ let x = 0;
593
+ let z = 0;
585
594
 
586
- if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
595
+ // Relative Placement Logic
596
+ if (args.referenceNodeId && args.direction) {
597
+ const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
598
+ if (!sourceNode) throw new Error(`Reference node ${args.referenceNodeId} not found`);
599
+
600
+ const INITIAL_SPACING = 2.0;
601
+ let dirX = 0;
602
+ let dirZ = 0;
603
+ const srcW = Number(sourceNode.width) || 2;
604
+ const srcH = Number(sourceNode.height) || 1.5;
605
+
606
+ switch (args.direction) {
607
+ case "RIGHT": dirX = 1; break;
608
+ case "LEFT": dirX = -1; break;
609
+ case "DOWN": dirZ = 1; break;
610
+ case "UP": dirZ = -1; break;
611
+ }
612
+
613
+ x = sourceNode.x + (dirX * (srcW + INITIAL_SPACING));
614
+ z = sourceNode.z + (dirZ * (srcH + INITIAL_SPACING));
615
+
616
+ // Collision Avoidance reuse
617
+ const COLLISION_SPACING = 0.5;
618
+ const shiftStepX = srcW + COLLISION_SPACING;
619
+ const shiftStepZ = srcH + COLLISION_SPACING;
620
+ const newW = width;
621
+ const newH = height;
622
+
623
+ const checkCollision = (cx: number, cz: number) => {
624
+ return state.nodes.some(n => {
625
+ if (n.layerId !== sourceNode.layerId) return false;
626
+ const dx = Math.abs(n.x - cx);
627
+ const dz = Math.abs(n.z - cz);
628
+ const otherW = Number(n.width) || 2;
629
+ const otherH = Number(n.height) || 1.5;
630
+ const combinedHalfWidth = (otherW + newW) / 2;
631
+ const combinedHalfHeight = (otherH + newH) / 2;
632
+ return dx < combinedHalfWidth && dz < combinedHalfHeight;
633
+ });
634
+ };
635
+
636
+ let iterations = 0;
637
+ while (checkCollision(x, z) && iterations < 50) {
638
+ iterations++;
639
+ if (dirX !== 0) z += shiftStepZ;
640
+ else x += shiftStepX;
641
+ }
642
+ } else if (state.nodes.length > 0) {
587
643
  const lastNode = state.nodes[state.nodes.length - 1];
588
644
  x = lastNode.x + 5;
589
645
  z = lastNode.z;
@@ -607,6 +663,53 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
607
663
  };
608
664
 
609
665
  state.nodes.push(newNode);
666
+
667
+ // Auto-Connect logic
668
+ if (args.connection && args.referenceNodeId) {
669
+ let termStart = 'none';
670
+ let termEnd = 'none';
671
+ let borderStyle = 'solid';
672
+
673
+ // UML Mapping
674
+ switch (args.connection.type) {
675
+ case 'inheritance': termEnd = 'triangle-empty'; break;
676
+ case 'implementation': termEnd = 'triangle-empty'; borderStyle = 'dashed'; break;
677
+ case 'dependency': termEnd = 'arrow'; borderStyle = 'dashed'; break;
678
+ case 'association': termEnd = 'arrow'; break;
679
+ case 'aggregation': termStart = 'diamond-empty'; break;
680
+ case 'composition': termStart = 'diamond-filled'; break;
681
+ }
682
+
683
+ // Points? Simple defaults based on direction
684
+ let sp = 0;
685
+ let tp = 0;
686
+ switch(args.direction) {
687
+ case "RIGHT": sp=1; tp=3; break;
688
+ case "LEFT": sp=3; tp=1; break;
689
+ case "DOWN": sp=2; tp=0; break;
690
+ case "UP": sp=0; tp=2; break;
691
+ default: sp=2; tp=0; break;
692
+ }
693
+
694
+ const newEdge: EdgeData = {
695
+ id: randomUUID(),
696
+ sourceId: args.referenceNodeId,
697
+ targetId: newNodeId,
698
+ sourcePointIndex: sp,
699
+ targetPointIndex: tp,
700
+ label: args.connection.label,
701
+ style: 'line',
702
+ routingType: 'orthogonal',
703
+ color: '#000000',
704
+ thickness: 0.01,
705
+ fontSize: 20,
706
+ borderStyle: borderStyle as any,
707
+ terminationStart: termStart as any,
708
+ terminationEnd: termEnd as any
709
+ };
710
+ state.edges.push(newEdge);
711
+ }
712
+
610
713
  await saveDiagramFile(args.filePath, state);
611
714
  return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
612
715
  }
@@ -621,9 +724,9 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
621
724
 
622
725
  const width = 3;
623
726
 
624
- // Auto-placement logic if X/Y not provided
625
- let x = args.x !== undefined ? Number(args.x) : 0;
626
- let z = args.y !== undefined ? Number(args.y) : 0;
727
+ // Auto-placement logic (defaults to 0,0 for first node)
728
+ let x = 0;
729
+ let z = 0;
627
730
 
628
731
  // Relative Placement Logic
629
732
  if (args.referenceNodeId && args.direction) {
@@ -666,13 +769,7 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
666
769
  });
667
770
  };
668
771
 
669
- let iterations = 0;
670
- while (checkCollision(x, z) && iterations < 50) {
671
- iterations++;
672
- if (dirX !== 0) z += shiftStepZ;
673
- else x += shiftStepX;
674
- }
675
- } else if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
772
+ } else if (state.nodes.length > 0) {
676
773
  // Fallback to "next to last" if no explicit relative info
677
774
  const lastNode = state.nodes[state.nodes.length - 1];
678
775
  x = lastNode.x + 5;
@@ -1,7 +1,7 @@
1
1
 
2
2
  import fs from 'fs/promises';
3
3
  import JSZip from 'jszip';
4
- import { DiagramState } from '../types';
4
+ import { DiagramState } from '../types.js';
5
5
 
6
6
  export async function readDiagramFile(filePath: string): Promise<DiagramState> {
7
7
  try {
package/tsconfig.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "commonjs",
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
5
6
  "outDir": "./dist",
6
7
  "rootDir": ".",
7
8
  "strict": true,