dodraw-mcp-server 0.1.7 → 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
  }
@@ -244,11 +250,11 @@ exports.toolDefinitions = [
244
250
  }
245
251
  }
246
252
  ];
247
- async function handleToolCall(name, args) {
253
+ export async function handleToolCall(name, args) {
248
254
  switch (name) {
249
255
  case "create_new_diagram": {
250
256
  // ... (existing code)
251
- const defaultLayerId = (0, crypto_1.randomUUID)();
257
+ const defaultLayerId = randomUUID();
252
258
  const initialState = {
253
259
  nodes: [],
254
260
  edges: [],
@@ -262,12 +268,12 @@ async function handleToolCall(name, args) {
262
268
  gridSnappingEnabled: true,
263
269
  gridSize: 0.5
264
270
  };
265
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, initialState);
271
+ await saveDiagramFile(args.filePath, initialState);
266
272
  return { content: [{ type: "text", text: `Created new diagram at ${args.filePath}` }] };
267
273
  }
268
274
  case "read_diagram_structure": {
269
275
  // ... (existing code)
270
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
276
+ const state = await readDiagramFile(args.filePath);
271
277
  return {
272
278
  content: [{
273
279
  type: "text",
@@ -284,7 +290,7 @@ async function handleToolCall(name, args) {
284
290
  }
285
291
  case "add_node": {
286
292
  // ... (existing code)
287
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
293
+ const state = await readDiagramFile(args.filePath);
288
294
  // Auto-placement Logic
289
295
  let x = 0;
290
296
  let z = 0;
@@ -310,7 +316,7 @@ async function handleToolCall(name, args) {
310
316
  }
311
317
  }
312
318
  const newNode = {
313
- id: args.id || (0, crypto_1.randomUUID)(),
319
+ id: args.id || randomUUID(),
314
320
  x: x,
315
321
  y: 0.05,
316
322
  z: z,
@@ -331,16 +337,16 @@ async function handleToolCall(name, args) {
331
337
  };
332
338
  console.error(`DEBUG: Adding node. Args: x=${args.x}, y=${args.y}. Computed: x=${newNode.x}, z=${newNode.z}`);
333
339
  state.nodes.push(newNode);
334
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
340
+ await saveDiagramFile(args.filePath, state);
335
341
  return { content: [{ type: "text", text: `Added node ${newNode.id} at (${newNode.x}, ${newNode.z})` }] };
336
342
  }
337
343
  case "add_directional_node": {
338
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
344
+ const state = await readDiagramFile(args.filePath);
339
345
  const sourceNode = state.nodes.find(n => n.id === args.sourceNodeId);
340
346
  if (!sourceNode) {
341
347
  throw new Error(`Source node with ID ${args.sourceNodeId} not found.`);
342
348
  }
343
- const newNodeId = (0, crypto_1.randomUUID)();
349
+ const newNodeId = randomUUID();
344
350
  // 1. Calculate base position with tighter spacing (matching App interaction)
345
351
  const INITIAL_SPACING = 2.0;
346
352
  let dirX = 0;
@@ -438,7 +444,7 @@ async function handleToolCall(name, args) {
438
444
  textAlignHorizontal: 'center'
439
445
  };
440
446
  const newEdge = {
441
- id: (0, crypto_1.randomUUID)(),
447
+ id: randomUUID(),
442
448
  sourceId: sourceNode.id,
443
449
  targetId: newNodeId,
444
450
  sourcePointIndex: sourcePoint,
@@ -453,11 +459,11 @@ async function handleToolCall(name, args) {
453
459
  };
454
460
  state.nodes.push(newNode);
455
461
  state.edges.push(newEdge);
456
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
462
+ await saveDiagramFile(args.filePath, state);
457
463
  return { content: [{ type: "text", text: `Added node '${args.label}' (ID: ${newNodeId}) to the ${args.direction} of '${sourceNode.label}' and connected them.` }] };
458
464
  }
459
465
  case "add_edge": {
460
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
466
+ const state = await readDiagramFile(args.filePath);
461
467
  // Verify nodes exist
462
468
  const sourceExists = state.nodes.find(n => n.id === args.sourceId);
463
469
  const targetExists = state.nodes.find(n => n.id === args.targetId);
@@ -466,7 +472,7 @@ async function handleToolCall(name, args) {
466
472
  if (!targetExists)
467
473
  throw new Error(`Target node ${args.targetId} not found`);
468
474
  const newEdge = {
469
- id: args.id || (0, crypto_1.randomUUID)(),
475
+ id: args.id || randomUUID(),
470
476
  sourceId: args.sourceId,
471
477
  targetId: args.targetId,
472
478
  sourcePointIndex: Number(args.sourcePointIndex),
@@ -482,11 +488,11 @@ async function handleToolCall(name, args) {
482
488
  terminationEnd: args.terminationEnd
483
489
  };
484
490
  state.edges.push(newEdge);
485
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
491
+ await saveDiagramFile(args.filePath, state);
486
492
  return { content: [{ type: "text", text: `Added edge ${newEdge.id}` }] };
487
493
  }
488
494
  case "update_node": {
489
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
495
+ const state = await readDiagramFile(args.filePath);
490
496
  const nodeIndex = state.nodes.findIndex(n => n.id === args.nodeId);
491
497
  if (nodeIndex === -1) {
492
498
  throw new Error(`Node ${args.nodeId} not found`);
@@ -503,12 +509,12 @@ async function handleToolCall(name, args) {
503
509
  state.nodes[nodeIndex].height = Number(args.height);
504
510
  if (args.backgroundColor)
505
511
  state.nodes[nodeIndex].backgroundColor = args.backgroundColor;
506
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
512
+ await saveDiagramFile(args.filePath, state);
507
513
  return { content: [{ type: "text", text: `Updated node ${args.nodeId}` }] };
508
514
  }
509
515
  case "add_layer": {
510
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
511
- const newLayerId = (0, crypto_1.randomUUID)();
516
+ const state = await readDiagramFile(args.filePath);
517
+ const newLayerId = randomUUID();
512
518
  // Auto-positioning logic
513
519
  let position = { x: 0, y: 0, z: 0 };
514
520
  if (args.position) {
@@ -547,12 +553,12 @@ async function handleToolCall(name, args) {
547
553
  visible: args.visible !== false
548
554
  };
549
555
  state.layers.push(newLayer);
550
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
556
+ await saveDiagramFile(args.filePath, state);
551
557
  return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
552
558
  }
553
559
  case "create_uml_class": {
554
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
555
- const newNodeId = args.id || (0, crypto_1.randomUUID)();
560
+ const state = await readDiagramFile(args.filePath);
561
+ const newNodeId = args.id || randomUUID();
556
562
  // Standard size for class
557
563
  const width = 3;
558
564
  // dynamic height?
@@ -560,10 +566,64 @@ async function handleToolCall(name, args) {
560
566
  const methodCount = (args.methods || []).length;
561
567
  const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
562
568
  const height = Math.max(2, estimatedHeight);
563
- // Auto-placement logic if X/Y not provided
564
- let x = args.x !== undefined ? Number(args.x) : 0;
565
- let z = args.y !== undefined ? Number(args.y) : 0;
566
- 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) {
567
627
  const lastNode = state.nodes[state.nodes.length - 1];
568
628
  x = lastNode.x + 5;
569
629
  z = lastNode.z;
@@ -585,12 +645,83 @@ async function handleToolCall(name, args) {
585
645
  }
586
646
  };
587
647
  state.nodes.push(newNode);
588
- 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);
589
720
  return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
590
721
  }
591
722
  case "create_db_table": {
592
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
593
- const newNodeId = args.id || (0, crypto_1.randomUUID)();
723
+ const state = await readDiagramFile(args.filePath);
724
+ const newNodeId = args.id || randomUUID();
594
725
  const cols = args.columns || [];
595
726
  // Header + items
596
727
  const estimatedHeight = 0.8 + cols.length * 0.4;
@@ -720,7 +851,7 @@ async function handleToolCall(name, args) {
720
851
  break;
721
852
  }
722
853
  const newEdge = {
723
- id: (0, crypto_1.randomUUID)(),
854
+ id: randomUUID(),
724
855
  sourceId: args.referenceNodeId,
725
856
  targetId: newNodeId,
726
857
  sourcePointIndex: sp,
@@ -737,11 +868,11 @@ async function handleToolCall(name, args) {
737
868
  };
738
869
  state.edges.push(newEdge);
739
870
  }
740
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
871
+ await saveDiagramFile(args.filePath, state);
741
872
  return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
742
873
  }
743
874
  case "connect_entities": {
744
- const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
875
+ const state = await readDiagramFile(args.filePath);
745
876
  // Find nodes by Label (tolerant search?)
746
877
  const sourceNode = state.nodes.find(n => n.label === args.sourceName);
747
878
  const targetNode = state.nodes.find(n => n.label === args.targetName);
@@ -792,7 +923,7 @@ async function handleToolCall(name, args) {
792
923
  break;
793
924
  }
794
925
  const newEdge = {
795
- id: (0, crypto_1.randomUUID)(),
926
+ id: randomUUID(),
796
927
  sourceId: sourceNode.id,
797
928
  targetId: targetNode.id,
798
929
  sourcePointIndex: 2, // Bottom of source
@@ -808,7 +939,7 @@ async function handleToolCall(name, args) {
808
939
  terminationEnd: terminationEnd
809
940
  };
810
941
  state.edges.push(newEdge);
811
- await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
942
+ await saveDiagramFile(args.filePath, state);
812
943
  return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
813
944
  }
814
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.7",
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
  }
@@ -578,11 +588,58 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
578
588
  const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
579
589
  const height = Math.max(2, estimatedHeight);
580
590
 
581
- // Auto-placement logic if X/Y not provided
582
- let x = args.x !== undefined ? Number(args.x) : 0;
583
- 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;
584
594
 
585
- 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) {
586
643
  const lastNode = state.nodes[state.nodes.length - 1];
587
644
  x = lastNode.x + 5;
588
645
  z = lastNode.z;
@@ -606,6 +663,53 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
606
663
  };
607
664
 
608
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
+
609
713
  await saveDiagramFile(args.filePath, state);
610
714
  return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
611
715
  }
@@ -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,