dodraw-mcp-server 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,4 +45,4 @@ Prompt to my agent:
45
45
  "Using dodraw MCP server generate example of decision diagram of some real life situation"
46
46
 
47
47
  Agent response:
48
- ![alt text](image.png)
48
+ ![alt text](https://raw.githubusercontent.com/Fiik-Ringo/dodrawmcp/refs/heads/main/image.png)
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const diagramTools_1 = require("./tools/diagramTools");
4
+ async function createClassDiagram() {
5
+ const FILE_PATH = "class_diagram_example.3duml";
6
+ console.log(`Creating class diagram example at ${FILE_PATH}...`);
7
+ // 1. Create Diagram
8
+ try {
9
+ await (0, diagramTools_1.handleToolCall)("create_new_diagram", { filePath: FILE_PATH });
10
+ }
11
+ catch (e) {
12
+ console.log("File might exist, continuing...");
13
+ }
14
+ // 2. Add Layer
15
+ await (0, diagramTools_1.handleToolCall)("add_layer", { filePath: FILE_PATH, name: "Classes" });
16
+ // 3. Create Classes
17
+ // Abstract Class: GraphicObject
18
+ await (0, diagramTools_1.handleToolCall)("create_uml_class", {
19
+ filePath: FILE_PATH,
20
+ className: "GraphicObject",
21
+ attributes: ["x: int", "y: int"],
22
+ methods: ["move(dx: int, dy: int)", "draw()"],
23
+ x: 0, y: -4,
24
+ id: "GraphicObject"
25
+ });
26
+ // Subclass: Circle
27
+ await (0, diagramTools_1.handleToolCall)("create_uml_class", {
28
+ filePath: FILE_PATH,
29
+ className: "Circle",
30
+ attributes: ["radius: float"],
31
+ methods: ["draw()", "resize(scale: float)"],
32
+ x: -4, y: 0,
33
+ id: "Circle"
34
+ });
35
+ // Subclass: Rectangle
36
+ await (0, diagramTools_1.handleToolCall)("create_uml_class", {
37
+ filePath: FILE_PATH,
38
+ className: "Rectangle",
39
+ attributes: ["width: float", "height: float"],
40
+ methods: ["draw()", "resize(scale: float)"],
41
+ x: 4, y: 0,
42
+ id: "Rectangle"
43
+ });
44
+ // Container: Group
45
+ await (0, diagramTools_1.handleToolCall)("create_uml_class", {
46
+ filePath: FILE_PATH,
47
+ className: "Group",
48
+ attributes: [],
49
+ methods: ["add(obj: GraphicObject)", "remove(obj: GraphicObject)", "draw()"],
50
+ x: 0, y: 4,
51
+ id: "Group"
52
+ });
53
+ // Client: Canvas
54
+ await (0, diagramTools_1.handleToolCall)("create_uml_class", {
55
+ filePath: FILE_PATH,
56
+ className: "Canvas",
57
+ attributes: ["width: int", "height: int", "backgroundColor: string"],
58
+ methods: ["render()", "clear()"],
59
+ x: 8, y: -4,
60
+ id: "Canvas"
61
+ });
62
+ // 4. Create Edges (Relationships)
63
+ // Inheritance: Circle -> GraphicObject
64
+ await (0, diagramTools_1.handleToolCall)("add_edge", {
65
+ filePath: FILE_PATH,
66
+ sourceId: "Circle",
67
+ targetId: "GraphicObject",
68
+ sourcePointIndex: 0, // Top
69
+ targetPointIndex: 3, // Left (or Bottom of GraphicObject depending on layout) -> GraphicObject is at -4 Z (UP). Circle at 0 Z.
70
+ // Circle (0,0) is BELOW GraphicObject (0, -4).
71
+ // So Circle TOP (0) connects to GraphicObject BOTTOM (2).
72
+ // Wait, Z is vertical in 2D top-down view?
73
+ // Logic: Z decreases going UP.
74
+ // GraphicObject (z=-4) is UP. Circle (z=0) is DOWN.
75
+ // Connect Circle Top (0) to GraphicObject Bottom (2).
76
+ terminationEnd: "triangle-empty", // Inheritance arrow on Parent
77
+ terminationStart: "none",
78
+ routingType: "orthogonal",
79
+ label: "extends",
80
+ id: "edge_circle_extends"
81
+ });
82
+ // Inheritance: Rectangle -> GraphicObject
83
+ await (0, diagramTools_1.handleToolCall)("add_edge", {
84
+ filePath: FILE_PATH,
85
+ sourceId: "Rectangle",
86
+ targetId: "GraphicObject",
87
+ sourcePointIndex: 0, // Top
88
+ targetPointIndex: 2, // Bottom
89
+ terminationEnd: "triangle-empty",
90
+ terminationStart: "none",
91
+ routingType: "orthogonal",
92
+ id: "edge_rect_extends"
93
+ });
94
+ // Aggregation: Group has GraphicObjects
95
+ // Group (z=4) is BELOW GraphicObject (z=-4)?
96
+ // No, Group is at z=4 (Down). GraphicObject is at z=-4 (Up).
97
+ // Connection: Group (Top/0) -> GraphicObject (Bottom/2) ??
98
+ // Actually Group "contains" GraphicObject.
99
+ // The Diamond is on the Group end.
100
+ // Source: Group. Target: GraphicObject.
101
+ // Diamond at Source. Arrow/None at Target.
102
+ await (0, diagramTools_1.handleToolCall)("add_edge", {
103
+ filePath: FILE_PATH,
104
+ sourceId: "Group",
105
+ targetId: "GraphicObject",
106
+ sourcePointIndex: 0, // Top
107
+ targetPointIndex: 2, // Bottom
108
+ terminationStart: "diamond-empty", // Aggregation at Source
109
+ terminationEnd: "arrow", // Association at Target (or none)
110
+ routingType: "orthogonal",
111
+ label: "contains",
112
+ id: "edge_group_aggregation"
113
+ });
114
+ // Composition: Canvas owns GraphicObjects?
115
+ // Or maybe Canvas owns a Root Group?
116
+ // Let's say Canvas has a list of Shapes.
117
+ // Canvas (8, -4) -> GraphicObject (0, -4).
118
+ // Canvas Left (3) -> GraphicObject Right (1).
119
+ await (0, diagramTools_1.handleToolCall)("add_edge", {
120
+ filePath: FILE_PATH,
121
+ sourceId: "Canvas",
122
+ targetId: "GraphicObject",
123
+ sourcePointIndex: 3, // Left
124
+ targetPointIndex: 1, // Right
125
+ terminationStart: "diamond-filled", // Composition (Canvas owns Objects)
126
+ terminationEnd: "none",
127
+ routingType: "straight",
128
+ label: "renders",
129
+ id: "edge_canvas_composition"
130
+ });
131
+ console.log("Class Diagram created successfully: " + FILE_PATH);
132
+ }
133
+ createClassDiagram().catch(err => {
134
+ console.error("Error creating class diagram:", err);
135
+ process.exit(1);
136
+ });
@@ -0,0 +1,88 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const diagramTools_1 = require("./tools/diagramTools");
4
+ async function createReference() {
5
+ const FILE_PATH = "edge_terminations_test.3duml";
6
+ console.log(`Creating diagram at ${FILE_PATH}...`);
7
+ // 1. Create Diagram
8
+ try {
9
+ await (0, diagramTools_1.handleToolCall)("create_new_diagram", { filePath: FILE_PATH });
10
+ }
11
+ catch (e) {
12
+ console.log("File might exist, continuing...");
13
+ }
14
+ // 2. Add Layer
15
+ await (0, diagramTools_1.handleToolCall)("add_layer", { filePath: FILE_PATH, name: "Terminations" });
16
+ // 3. Define Types
17
+ const types = [
18
+ { id: 'none', label: 'None', description: 'No visual marker' },
19
+ { id: 'arrow', label: 'Arrow (Association)', description: 'Solid cone/V-shape' },
20
+ { id: 'triangle-empty', label: 'Inheritance', description: 'Hollow triangle' },
21
+ { id: 'triangle-empty-dashed', label: 'Realization', description: 'Hollow triangle (dashed edge)', isDashed: true },
22
+ { id: 'diamond-filled', label: 'Composition', description: 'Filled diamond' },
23
+ { id: 'diamond-empty', label: 'Aggregation', description: 'Hollow diamond' },
24
+ { id: 'arrow-open', label: 'Dependency', description: 'Open V-shape (dashed)', isDashed: true },
25
+ { id: 'crows-one', label: 'Crow One', description: 'Perpendicular bar |' },
26
+ { id: 'crows-many', label: 'Crow Many', description: 'Trident |<' },
27
+ { id: 'crows-zero-one', label: 'Crow Zero-One', description: 'Circle and bar O|' },
28
+ { id: 'crows-zero-many', label: 'Crow Zero-Many', description: 'Circle and trident O|<' },
29
+ { id: 'circle', label: 'Circle', description: 'Hollow circle' },
30
+ { id: 'circle-plus', label: 'Socket', description: 'Circle with plus (Socket)' }
31
+ ];
32
+ let z = 0;
33
+ const SPACING_Z = 3.5;
34
+ // Use manual IDs to avoid importing randomUUID
35
+ let counter = 0;
36
+ for (const t of types) {
37
+ const sourceId = `src_${t.id}`;
38
+ const targetId = `tgt_${t.id}`;
39
+ // Start Node
40
+ await (0, diagramTools_1.handleToolCall)("add_node", {
41
+ filePath: FILE_PATH,
42
+ label: `Start`,
43
+ id: sourceId,
44
+ x: -4, y: z,
45
+ width: 2, height: 1.5,
46
+ color: "#eeeeee"
47
+ });
48
+ // End Node
49
+ await (0, diagramTools_1.handleToolCall)("add_node", {
50
+ filePath: FILE_PATH,
51
+ label: `End`,
52
+ id: targetId,
53
+ x: 4, y: z,
54
+ width: 2, height: 1.5,
55
+ color: "#eeeeee"
56
+ });
57
+ // Description Note (Left side)
58
+ await (0, diagramTools_1.handleToolCall)("add_node", {
59
+ filePath: FILE_PATH,
60
+ label: `${t.label}\n${t.description}`,
61
+ shape: 'note',
62
+ color: '#ffffcc',
63
+ x: -10, y: z,
64
+ width: 4, height: 2,
65
+ id: `note_${t.id}`
66
+ });
67
+ // Edge
68
+ await (0, diagramTools_1.handleToolCall)("add_edge", {
69
+ filePath: FILE_PATH,
70
+ sourceId: sourceId,
71
+ targetId: targetId,
72
+ sourcePointIndex: 1, // Right
73
+ targetPointIndex: 3, // Left
74
+ label: t.id,
75
+ borderStyle: t.isDashed ? 'dashed' : 'solid',
76
+ terminationEnd: t.id,
77
+ terminationStart: 'none',
78
+ id: `edge_${t.id}`
79
+ });
80
+ z += SPACING_Z;
81
+ counter++;
82
+ }
83
+ console.log("Diagram created successfully: " + FILE_PATH);
84
+ }
85
+ createReference().catch(err => {
86
+ console.error("Error creating reference:", err);
87
+ process.exit(1);
88
+ });
@@ -35,14 +35,32 @@ exports.toolDefinitions = [
35
35
  properties: {
36
36
  filePath: { type: "string" },
37
37
  label: { type: "string" },
38
- shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
38
+ shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
39
39
  x: { type: "number", description: "Optional. defaults to auto-placement" },
40
40
  y: { type: "number", description: "Optional. defaults to 0 or same as last node" },
41
41
  z: { type: "number" },
42
42
  width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
43
43
  height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
44
44
  layerId: { type: "string", description: "Optional layer ID, defaults to active layer" },
45
- color: { type: "string" }
45
+ color: { type: "string" },
46
+ description: { type: "string", description: "Optional description or metadata for the node" },
47
+ tableData: {
48
+ type: "object",
49
+ description: "Required if shape is 'table'",
50
+ properties: {
51
+ rows: { type: "number" },
52
+ columns: { type: "number" },
53
+ hasRowHeader: { type: "boolean" },
54
+ hasColumnHeader: { type: "boolean" },
55
+ cells: {
56
+ type: "object",
57
+ description: "Map of 'row,col' string keys to cell content strings. E.g. {'0,0': 'Header'}"
58
+ }
59
+ }
60
+ },
61
+ id: { type: "string", description: "Optional explicit ID" },
62
+ attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
63
+ methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" }
46
64
  },
47
65
  required: ["filePath", "label"]
48
66
  }
@@ -60,7 +78,13 @@ exports.toolDefinitions = [
60
78
  targetPointIndex: { type: "number", minimum: 0, maximum: 3 },
61
79
  label: { type: "string" },
62
80
  color: { type: "string", description: "Edge color (hex)" },
63
- thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" }
81
+ thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" },
82
+ style: { type: "string", enum: ["line", "arrow-source", "arrow-target", "arrow-both"], description: "Visual style of arrowheads" },
83
+ routingType: { type: "string", enum: ["straight", "orthogonal"], description: "Type of path routing" },
84
+ borderStyle: { type: "string", enum: ["solid", "dashed", "dotted"], description: "Line pattern style" },
85
+ terminationStart: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "Start termination shape" },
86
+ terminationEnd: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "End termination shape" },
87
+ id: { type: "string", description: "Optional explicit ID" }
64
88
  },
65
89
  required: ["filePath", "sourceId", "targetId", "sourcePointIndex", "targetPointIndex"]
66
90
  }
@@ -124,14 +148,76 @@ exports.toolDefinitions = [
124
148
  sourceNodeId: { type: "string" },
125
149
  direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Direction to place the new node relative to source." },
126
150
  label: { type: "string" },
127
- shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
151
+ shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
128
152
  width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
129
153
  height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
130
154
  color: { type: "string" },
131
- edgeLabel: { type: "string", description: "Optional label for the connecting edge" }
155
+ edgeLabel: { type: "string", description: "Optional label for the connecting edge" },
156
+ attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
157
+ methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" }
132
158
  },
133
159
  required: ["filePath", "sourceNodeId", "direction", "label"]
134
160
  }
161
+ },
162
+ {
163
+ name: "create_uml_class",
164
+ description: "Create a UML Class node with methods and attributes",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ filePath: { type: "string" },
169
+ className: { type: "string" },
170
+ attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
171
+ methods: { type: "array", items: { type: "string" }, description: "List of methods e.g. ['+ getName(): string', '+ save(): void']" },
172
+ x: { type: "number", description: "Optional X position" },
173
+ y: { type: "number", description: "Optional Y position (mapped to Z in 3DUML)" },
174
+ id: { type: "string", description: "Optional explicit ID" }
175
+ },
176
+ required: ["filePath", "className"]
177
+ }
178
+ },
179
+ {
180
+ name: "create_db_table",
181
+ description: "Create a Database Entity (ERD Table)",
182
+ inputSchema: {
183
+ type: "object",
184
+ properties: {
185
+ filePath: { type: "string" },
186
+ tableName: { type: "string" },
187
+ columns: {
188
+ type: "array",
189
+ items: {
190
+ type: "object",
191
+ properties: {
192
+ name: { type: "string" },
193
+ type: { type: "string" },
194
+ isPk: { type: "boolean" },
195
+ isFk: { type: "boolean" }
196
+ },
197
+ required: ["name", "type"]
198
+ }
199
+ },
200
+ x: { type: "number" },
201
+ y: { type: "number" },
202
+ id: { type: "string", description: "Optional explicit ID" }
203
+ },
204
+ required: ["filePath", "tableName", "columns"]
205
+ }
206
+ },
207
+ {
208
+ name: "connect_entities",
209
+ description: "Connect two entities semantically (e.g. Inherits, Composes)",
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ filePath: { type: "string" },
214
+ sourceName: { type: "string", description: "Label of source node (e.g. Class Name)" },
215
+ targetName: { type: "string", description: "Label of target node" },
216
+ relationType: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition", "one-to-one", "one-to-many", "many-to-many"] },
217
+ label: { type: "string", description: "Optional edge label" }
218
+ },
219
+ required: ["filePath", "sourceName", "targetName", "relationType"]
220
+ }
135
221
  }
136
222
  ];
137
223
  async function handleToolCall(name, args) {
@@ -200,7 +286,7 @@ async function handleToolCall(name, args) {
200
286
  }
201
287
  }
202
288
  const newNode = {
203
- id: (0, crypto_1.randomUUID)(),
289
+ id: args.id || (0, crypto_1.randomUUID)(),
204
290
  x: x,
205
291
  y: 0.05,
206
292
  z: z,
@@ -210,6 +296,12 @@ async function handleToolCall(name, args) {
210
296
  shape: args.shape || "rectangle",
211
297
  layerId: args.layerId || state.activeLayerId,
212
298
  backgroundColor: args.color,
299
+ description: args.description,
300
+ tableData: args.tableData,
301
+ classData: (args.attributes || args.methods) ? {
302
+ attributes: args.attributes || [],
303
+ methods: args.methods || []
304
+ } : undefined,
213
305
  textAlignVertical: 'center',
214
306
  textAlignHorizontal: 'center'
215
307
  };
@@ -306,6 +398,10 @@ async function handleToolCall(name, args) {
306
398
  shape: args.shape || "rectangle",
307
399
  layerId: sourceNode.layerId, // Inherit layer
308
400
  backgroundColor: args.color || sourceNode.backgroundColor, // Inherit or new
401
+ classData: (args.attributes || args.methods) ? {
402
+ attributes: args.attributes || [],
403
+ methods: args.methods || []
404
+ } : undefined,
309
405
  textAlignVertical: 'center',
310
406
  textAlignHorizontal: 'center'
311
407
  };
@@ -338,18 +434,20 @@ async function handleToolCall(name, args) {
338
434
  if (!targetExists)
339
435
  throw new Error(`Target node ${args.targetId} not found`);
340
436
  const newEdge = {
341
- id: (0, crypto_1.randomUUID)(),
437
+ id: args.id || (0, crypto_1.randomUUID)(),
342
438
  sourceId: args.sourceId,
343
439
  targetId: args.targetId,
344
440
  sourcePointIndex: Number(args.sourcePointIndex),
345
441
  targetPointIndex: Number(args.targetPointIndex),
346
442
  label: args.label,
347
- style: 'line', // default
348
- routingType: 'straight', // default
443
+ style: args.style || 'line',
444
+ routingType: args.routingType || 'straight',
349
445
  color: args.color || '#000000',
350
446
  thickness: Math.max(0.005, Math.min(0.5, args.thickness ? Number(args.thickness) : 0.01)),
351
447
  fontSize: 20,
352
- borderStyle: 'solid'
448
+ borderStyle: args.borderStyle || 'solid',
449
+ terminationStart: args.terminationStart,
450
+ terminationEnd: args.terminationEnd
353
451
  };
354
452
  state.edges.push(newEdge);
355
453
  await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
@@ -420,6 +518,139 @@ async function handleToolCall(name, args) {
420
518
  await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
421
519
  return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
422
520
  }
521
+ case "create_uml_class": {
522
+ const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
523
+ const newNodeId = args.id || (0, crypto_1.randomUUID)();
524
+ // Standard size for class
525
+ const width = 3;
526
+ // dynamic height?
527
+ const attrCount = (args.attributes || []).length;
528
+ const methodCount = (args.methods || []).length;
529
+ const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
530
+ const height = Math.max(2, estimatedHeight);
531
+ const newNode = {
532
+ id: newNodeId,
533
+ x: args.x ? Number(args.x) : 0,
534
+ y: 0,
535
+ z: args.y ? Number(args.y) : 0,
536
+ width: width,
537
+ height: height,
538
+ label: args.className,
539
+ shape: "class",
540
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
541
+ backgroundColor: "#ffffff",
542
+ classData: {
543
+ attributes: args.attributes || [],
544
+ methods: args.methods || []
545
+ }
546
+ };
547
+ state.nodes.push(newNode);
548
+ await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
549
+ return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
550
+ }
551
+ case "create_db_table": {
552
+ const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
553
+ const newNodeId = args.id || (0, crypto_1.randomUUID)();
554
+ const cols = args.columns || [];
555
+ // Header + items
556
+ const estimatedHeight = 0.8 + cols.length * 0.4;
557
+ const height = Math.max(2, estimatedHeight);
558
+ const width = 3;
559
+ const newNode = {
560
+ id: newNodeId,
561
+ x: args.x ? Number(args.x) : 0,
562
+ y: 0,
563
+ z: args.y ? Number(args.y) : 0,
564
+ width: width,
565
+ height: height,
566
+ label: args.tableName,
567
+ shape: "db-table",
568
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
569
+ backgroundColor: "#ffffff",
570
+ dbTableData: {
571
+ columns: cols.map((c) => ({
572
+ name: c.name,
573
+ type: c.type,
574
+ isPk: !!c.isPk,
575
+ isFk: !!c.isFk
576
+ }))
577
+ }
578
+ };
579
+ state.nodes.push(newNode);
580
+ await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
581
+ return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
582
+ }
583
+ case "connect_entities": {
584
+ const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
585
+ // Find nodes by Label (tolerant search?)
586
+ const sourceNode = state.nodes.find(n => n.label === args.sourceName);
587
+ const targetNode = state.nodes.find(n => n.label === args.targetName);
588
+ if (!sourceNode)
589
+ throw new Error(`Source entity '${args.sourceName}' not found`);
590
+ if (!targetNode)
591
+ throw new Error(`Target entity '${args.targetName}' not found`);
592
+ let terminationStart = 'none';
593
+ let terminationEnd = 'none';
594
+ let style = 'line';
595
+ let borderStyle = 'solid';
596
+ // Map relationship to terminations
597
+ switch (args.relationType) {
598
+ case 'inheritance':
599
+ terminationEnd = 'triangle-empty';
600
+ break;
601
+ case 'implementation':
602
+ terminationEnd = 'triangle-empty';
603
+ borderStyle = 'dashed';
604
+ break;
605
+ case 'dependency':
606
+ terminationEnd = 'arrow';
607
+ borderStyle = 'dashed';
608
+ break;
609
+ case 'association':
610
+ terminationEnd = 'none'; // simple line? or arrow? normally association is simple line OR arrow
611
+ // Let's use arrow for directed association if implied?
612
+ // Usually "connect A to B" implies direction.
613
+ terminationEnd = 'arrow';
614
+ break;
615
+ case 'aggregation':
616
+ terminationStart = 'diamond-empty';
617
+ break;
618
+ case 'composition':
619
+ terminationStart = 'diamond-filled';
620
+ break;
621
+ case 'one-to-many':
622
+ terminationStart = 'crows-one';
623
+ terminationEnd = 'crows-many';
624
+ break;
625
+ case 'many-to-many':
626
+ terminationStart = 'crows-many';
627
+ terminationEnd = 'crows-many';
628
+ break;
629
+ case 'one-to-one':
630
+ terminationStart = 'crows-one';
631
+ terminationEnd = 'crows-one';
632
+ break;
633
+ }
634
+ const newEdge = {
635
+ id: (0, crypto_1.randomUUID)(),
636
+ sourceId: sourceNode.id,
637
+ targetId: targetNode.id,
638
+ sourcePointIndex: 2, // Bottom of source
639
+ targetPointIndex: 0, // Top of target (simple default)
640
+ label: args.label,
641
+ style: style,
642
+ routingType: 'orthogonal', // Semantic diagrams usually look better with orthogonal
643
+ color: '#000000',
644
+ thickness: 0.01,
645
+ fontSize: 20,
646
+ borderStyle: borderStyle,
647
+ terminationStart: terminationStart,
648
+ terminationEnd: terminationEnd
649
+ };
650
+ state.edges.push(newEdge);
651
+ await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
652
+ return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
653
+ }
423
654
  default:
424
655
  throw new Error(`Unknown tool: ${name}`);
425
656
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodraw-mcp-server",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "MCP server for DoDraw",
5
5
  "main": "dist/src/index.js",
6
6
  "bin": {
@@ -0,0 +1,151 @@
1
+
2
+ import { handleToolCall } from './tools/diagramTools';
3
+
4
+ async function createClassDiagram() {
5
+ const FILE_PATH = "class_diagram_example.3duml";
6
+
7
+ console.log(`Creating class diagram example at ${FILE_PATH}...`);
8
+
9
+ // 1. Create Diagram
10
+ try {
11
+ await handleToolCall("create_new_diagram", { filePath: FILE_PATH });
12
+ } catch (e) {
13
+ console.log("File might exist, continuing...");
14
+ }
15
+
16
+ // 2. Add Layer
17
+ await handleToolCall("add_layer", { filePath: FILE_PATH, name: "Classes" });
18
+
19
+ // 3. Create Classes
20
+
21
+ // Abstract Class: GraphicObject
22
+ await handleToolCall("create_uml_class", {
23
+ filePath: FILE_PATH,
24
+ className: "GraphicObject",
25
+ attributes: ["x: int", "y: int"],
26
+ methods: ["move(dx: int, dy: int)", "draw()"],
27
+ x: 0, y: -4,
28
+ id: "GraphicObject"
29
+ });
30
+
31
+ // Subclass: Circle
32
+ await handleToolCall("create_uml_class", {
33
+ filePath: FILE_PATH,
34
+ className: "Circle",
35
+ attributes: ["radius: float"],
36
+ methods: ["draw()", "resize(scale: float)"],
37
+ x: -4, y: 0,
38
+ id: "Circle"
39
+ });
40
+
41
+ // Subclass: Rectangle
42
+ await handleToolCall("create_uml_class", {
43
+ filePath: FILE_PATH,
44
+ className: "Rectangle",
45
+ attributes: ["width: float", "height: float"],
46
+ methods: ["draw()", "resize(scale: float)"],
47
+ x: 4, y: 0,
48
+ id: "Rectangle"
49
+ });
50
+
51
+ // Container: Group
52
+ await handleToolCall("create_uml_class", {
53
+ filePath: FILE_PATH,
54
+ className: "Group",
55
+ attributes: [],
56
+ methods: ["add(obj: GraphicObject)", "remove(obj: GraphicObject)", "draw()"],
57
+ x: 0, y: 4,
58
+ id: "Group"
59
+ });
60
+
61
+ // Client: Canvas
62
+ await handleToolCall("create_uml_class", {
63
+ filePath: FILE_PATH,
64
+ className: "Canvas",
65
+ attributes: ["width: int", "height: int", "backgroundColor: string"],
66
+ methods: ["render()", "clear()"],
67
+ x: 8, y: -4,
68
+ id: "Canvas"
69
+ });
70
+
71
+ // 4. Create Edges (Relationships)
72
+
73
+ // Inheritance: Circle -> GraphicObject
74
+ await handleToolCall("add_edge", {
75
+ filePath: FILE_PATH,
76
+ sourceId: "Circle",
77
+ targetId: "GraphicObject",
78
+ sourcePointIndex: 0, // Top
79
+ targetPointIndex: 3, // Left (or Bottom of GraphicObject depending on layout) -> GraphicObject is at -4 Z (UP). Circle at 0 Z.
80
+ // Circle (0,0) is BELOW GraphicObject (0, -4).
81
+ // So Circle TOP (0) connects to GraphicObject BOTTOM (2).
82
+ // Wait, Z is vertical in 2D top-down view?
83
+ // Logic: Z decreases going UP.
84
+ // GraphicObject (z=-4) is UP. Circle (z=0) is DOWN.
85
+ // Connect Circle Top (0) to GraphicObject Bottom (2).
86
+ terminationEnd: "triangle-empty", // Inheritance arrow on Parent
87
+ terminationStart: "none",
88
+ routingType: "orthogonal",
89
+ label: "extends",
90
+ id: "edge_circle_extends"
91
+ });
92
+
93
+ // Inheritance: Rectangle -> GraphicObject
94
+ await handleToolCall("add_edge", {
95
+ filePath: FILE_PATH,
96
+ sourceId: "Rectangle",
97
+ targetId: "GraphicObject",
98
+ sourcePointIndex: 0, // Top
99
+ targetPointIndex: 2, // Bottom
100
+ terminationEnd: "triangle-empty",
101
+ terminationStart: "none",
102
+ routingType: "orthogonal",
103
+ id: "edge_rect_extends"
104
+ });
105
+
106
+ // Aggregation: Group has GraphicObjects
107
+ // Group (z=4) is BELOW GraphicObject (z=-4)?
108
+ // No, Group is at z=4 (Down). GraphicObject is at z=-4 (Up).
109
+ // Connection: Group (Top/0) -> GraphicObject (Bottom/2) ??
110
+ // Actually Group "contains" GraphicObject.
111
+ // The Diamond is on the Group end.
112
+ // Source: Group. Target: GraphicObject.
113
+ // Diamond at Source. Arrow/None at Target.
114
+ await handleToolCall("add_edge", {
115
+ filePath: FILE_PATH,
116
+ sourceId: "Group",
117
+ targetId: "GraphicObject",
118
+ sourcePointIndex: 0, // Top
119
+ targetPointIndex: 2, // Bottom
120
+ terminationStart: "diamond-empty", // Aggregation at Source
121
+ terminationEnd: "arrow", // Association at Target (or none)
122
+ routingType: "orthogonal",
123
+ label: "contains",
124
+ id: "edge_group_aggregation"
125
+ });
126
+
127
+ // Composition: Canvas owns GraphicObjects?
128
+ // Or maybe Canvas owns a Root Group?
129
+ // Let's say Canvas has a list of Shapes.
130
+ // Canvas (8, -4) -> GraphicObject (0, -4).
131
+ // Canvas Left (3) -> GraphicObject Right (1).
132
+ await handleToolCall("add_edge", {
133
+ filePath: FILE_PATH,
134
+ sourceId: "Canvas",
135
+ targetId: "GraphicObject",
136
+ sourcePointIndex: 3, // Left
137
+ targetPointIndex: 1, // Right
138
+ terminationStart: "diamond-filled", // Composition (Canvas owns Objects)
139
+ terminationEnd: "none",
140
+ routingType: "straight",
141
+ label: "renders",
142
+ id: "edge_canvas_composition"
143
+ });
144
+
145
+ console.log("Class Diagram created successfully: " + FILE_PATH);
146
+ }
147
+
148
+ createClassDiagram().catch(err => {
149
+ console.error("Error creating class diagram:", err);
150
+ process.exit(1);
151
+ });
@@ -0,0 +1,108 @@
1
+
2
+ import { handleToolCall } from './tools/diagramTools';
3
+
4
+ interface TerminationType {
5
+ id: string;
6
+ label: string;
7
+ description: string;
8
+ isDashed?: boolean;
9
+ }
10
+
11
+ async function createReference() {
12
+ const FILE_PATH = "edge_terminations_test.3duml";
13
+
14
+ console.log(`Creating diagram at ${FILE_PATH}...`);
15
+
16
+ // 1. Create Diagram
17
+ try {
18
+ await handleToolCall("create_new_diagram", { filePath: FILE_PATH });
19
+ } catch (e) {
20
+ console.log("File might exist, continuing...");
21
+ }
22
+
23
+ // 2. Add Layer
24
+ await handleToolCall("add_layer", { filePath: FILE_PATH, name: "Terminations" });
25
+
26
+ // 3. Define Types
27
+ const types: TerminationType[] = [
28
+ { id: 'none', label: 'None', description: 'No visual marker' },
29
+ { id: 'arrow', label: 'Arrow (Association)', description: 'Solid cone/V-shape' },
30
+ { id: 'triangle-empty', label: 'Inheritance', description: 'Hollow triangle' },
31
+ { id: 'triangle-empty-dashed', label: 'Realization', description: 'Hollow triangle (dashed edge)', isDashed: true },
32
+ { id: 'diamond-filled', label: 'Composition', description: 'Filled diamond' },
33
+ { id: 'diamond-empty', label: 'Aggregation', description: 'Hollow diamond' },
34
+ { id: 'arrow-open', label: 'Dependency', description: 'Open V-shape (dashed)', isDashed: true },
35
+ { id: 'crows-one', label: 'Crow One', description: 'Perpendicular bar |' },
36
+ { id: 'crows-many', label: 'Crow Many', description: 'Trident |<' },
37
+ { id: 'crows-zero-one', label: 'Crow Zero-One', description: 'Circle and bar O|' },
38
+ { id: 'crows-zero-many', label: 'Crow Zero-Many', description: 'Circle and trident O|<' },
39
+ { id: 'circle', label: 'Circle', description: 'Hollow circle' },
40
+ { id: 'circle-plus', label: 'Socket', description: 'Circle with plus (Socket)' }
41
+ ];
42
+
43
+ let z = 0;
44
+ const SPACING_Z = 3.5;
45
+
46
+ // Use manual IDs to avoid importing randomUUID
47
+ let counter = 0;
48
+
49
+ for (const t of types) {
50
+ const sourceId = `src_${t.id}`;
51
+ const targetId = `tgt_${t.id}`;
52
+
53
+ // Start Node
54
+ await handleToolCall("add_node", {
55
+ filePath: FILE_PATH,
56
+ label: `Start`,
57
+ id: sourceId,
58
+ x: -4, y: z,
59
+ width: 2, height: 1.5,
60
+ color: "#eeeeee"
61
+ });
62
+
63
+ // End Node
64
+ await handleToolCall("add_node", {
65
+ filePath: FILE_PATH,
66
+ label: `End`,
67
+ id: targetId,
68
+ x: 4, y: z,
69
+ width: 2, height: 1.5,
70
+ color: "#eeeeee"
71
+ });
72
+
73
+ // Description Note (Left side)
74
+ await handleToolCall("add_node", {
75
+ filePath: FILE_PATH,
76
+ label: `${t.label}\n${t.description}`,
77
+ shape: 'note',
78
+ color: '#ffffcc',
79
+ x: -10, y: z,
80
+ width: 4, height: 2,
81
+ id: `note_${t.id}`
82
+ });
83
+
84
+ // Edge
85
+ await handleToolCall("add_edge", {
86
+ filePath: FILE_PATH,
87
+ sourceId: sourceId,
88
+ targetId: targetId,
89
+ sourcePointIndex: 1, // Right
90
+ targetPointIndex: 3, // Left
91
+ label: t.id,
92
+ borderStyle: t.isDashed ? 'dashed' : 'solid',
93
+ terminationEnd: t.id,
94
+ terminationStart: 'none',
95
+ id: `edge_${t.id}`
96
+ });
97
+
98
+ z += SPACING_Z;
99
+ counter++;
100
+ }
101
+
102
+ console.log("Diagram created successfully: " + FILE_PATH);
103
+ }
104
+
105
+ createReference().catch(err => {
106
+ console.error("Error creating reference:", err);
107
+ process.exit(1);
108
+ });
@@ -35,14 +35,32 @@ export const toolDefinitions: Tool[] = [
35
35
  properties: {
36
36
  filePath: { type: "string" },
37
37
  label: { type: "string" },
38
- shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
38
+ shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
39
39
  x: { type: "number", description: "Optional. defaults to auto-placement" },
40
40
  y: { type: "number", description: "Optional. defaults to 0 or same as last node" },
41
41
  z: { type: "number" },
42
42
  width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
43
43
  height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
44
44
  layerId: { type: "string", description: "Optional layer ID, defaults to active layer" },
45
- color: { type: "string" }
45
+ color: { type: "string" },
46
+ description: { type: "string", description: "Optional description or metadata for the node" },
47
+ tableData: {
48
+ type: "object",
49
+ description: "Required if shape is 'table'",
50
+ properties: {
51
+ rows: { type: "number" },
52
+ columns: { type: "number" },
53
+ hasRowHeader: { type: "boolean" },
54
+ hasColumnHeader: { type: "boolean" },
55
+ cells: {
56
+ type: "object",
57
+ description: "Map of 'row,col' string keys to cell content strings. E.g. {'0,0': 'Header'}"
58
+ }
59
+ }
60
+ },
61
+ id: { type: "string", description: "Optional explicit ID" },
62
+ attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
63
+ methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" }
46
64
  },
47
65
  required: ["filePath", "label"]
48
66
  }
@@ -60,7 +78,13 @@ export const toolDefinitions: Tool[] = [
60
78
  targetPointIndex: { type: "number", minimum: 0, maximum: 3 },
61
79
  label: { type: "string" },
62
80
  color: { type: "string", description: "Edge color (hex)" },
63
- thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" }
81
+ thickness: { type: "number", description: "Default: 0.01. Min: 0.005, Max: 0.5" },
82
+ style: { type: "string", enum: ["line", "arrow-source", "arrow-target", "arrow-both"], description: "Visual style of arrowheads" },
83
+ routingType: { type: "string", enum: ["straight", "orthogonal"], description: "Type of path routing" },
84
+ borderStyle: { type: "string", enum: ["solid", "dashed", "dotted"], description: "Line pattern style" },
85
+ terminationStart: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "Start termination shape" },
86
+ terminationEnd: { type: "string", enum: ['none', 'arrow', 'triangle-empty', 'triangle-filled', 'diamond-empty', 'diamond-filled', 'circle', 'circle-plus', 'crows-one', 'crows-many', 'crows-zero-one', 'crows-zero-many'], description: "End termination shape" },
87
+ id: { type: "string", description: "Optional explicit ID" }
64
88
  },
65
89
  required: ["filePath", "sourceId", "targetId", "sourcePointIndex", "targetPointIndex"]
66
90
  }
@@ -124,14 +148,76 @@ export const toolDefinitions: Tool[] = [
124
148
  sourceNodeId: { type: "string" },
125
149
  direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Direction to place the new node relative to source." },
126
150
  label: { type: "string" },
127
- shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table"] },
151
+ shape: { type: "string", enum: ["rectangle", "rounded", "ellipse", "diamond", "actor", "cylinder", "note", "cloud", "class", "text", "table", "start-state", "end-state", "fork-join"] },
128
152
  width: { type: "number", description: "Default: 2. Min: 0.1, Max: 50" },
129
153
  height: { type: "number", description: "Default: 1.5. Min: 0.1, Max: 50" },
130
154
  color: { type: "string" },
131
- edgeLabel: { type: "string", description: "Optional label for the connecting edge" }
155
+ edgeLabel: { type: "string", description: "Optional label for the connecting edge" },
156
+ attributes: { type: "array", items: { type: "string" }, description: "Optional list of attributes for Class nodes" },
157
+ methods: { type: "array", items: { type: "string" }, description: "Optional list of methods for Class nodes" }
132
158
  },
133
159
  required: ["filePath", "sourceNodeId", "direction", "label"]
134
160
  }
161
+ },
162
+ {
163
+ name: "create_uml_class",
164
+ description: "Create a UML Class node with methods and attributes",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ filePath: { type: "string" },
169
+ className: { type: "string" },
170
+ attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
171
+ methods: { type: "array", items: { type: "string" }, description: "List of methods e.g. ['+ getName(): string', '+ save(): void']" },
172
+ x: { type: "number", description: "Optional X position" },
173
+ y: { type: "number", description: "Optional Y position (mapped to Z in 3DUML)" },
174
+ id: { type: "string", description: "Optional explicit ID" }
175
+ },
176
+ required: ["filePath", "className"]
177
+ }
178
+ },
179
+ {
180
+ name: "create_db_table",
181
+ description: "Create a Database Entity (ERD Table)",
182
+ inputSchema: {
183
+ type: "object",
184
+ properties: {
185
+ filePath: { type: "string" },
186
+ tableName: { type: "string" },
187
+ columns: {
188
+ type: "array",
189
+ items: {
190
+ type: "object",
191
+ properties: {
192
+ name: { type: "string" },
193
+ type: { type: "string" },
194
+ isPk: { type: "boolean" },
195
+ isFk: { type: "boolean" }
196
+ },
197
+ required: ["name", "type"]
198
+ }
199
+ },
200
+ x: { type: "number" },
201
+ y: { type: "number" },
202
+ id: { type: "string", description: "Optional explicit ID" }
203
+ },
204
+ required: ["filePath", "tableName", "columns"]
205
+ }
206
+ },
207
+ {
208
+ name: "connect_entities",
209
+ description: "Connect two entities semantically (e.g. Inherits, Composes)",
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ filePath: { type: "string" },
214
+ sourceName: { type: "string", description: "Label of source node (e.g. Class Name)" },
215
+ targetName: { type: "string", description: "Label of target node" },
216
+ relationType: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition", "one-to-one", "one-to-many", "many-to-many"] },
217
+ label: { type: "string", description: "Optional edge label" }
218
+ },
219
+ required: ["filePath", "sourceName", "targetName", "relationType"]
220
+ }
135
221
  }
136
222
  ];
137
223
 
@@ -205,7 +291,7 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
205
291
  }
206
292
 
207
293
  const newNode: NodeData = {
208
- id: randomUUID(),
294
+ id: args.id || randomUUID(),
209
295
  x: x,
210
296
  y: 0.05,
211
297
  z: z,
@@ -215,6 +301,12 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
215
301
  shape: args.shape || "rectangle",
216
302
  layerId: args.layerId || state.activeLayerId,
217
303
  backgroundColor: args.color,
304
+ description: args.description,
305
+ tableData: args.tableData,
306
+ classData: (args.attributes || args.methods) ? {
307
+ attributes: args.attributes || [],
308
+ methods: args.methods || []
309
+ } : undefined,
218
310
  textAlignVertical: 'center',
219
311
  textAlignHorizontal: 'center'
220
312
  };
@@ -321,6 +413,10 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
321
413
  shape: args.shape || "rectangle",
322
414
  layerId: sourceNode.layerId, // Inherit layer
323
415
  backgroundColor: args.color || sourceNode.backgroundColor, // Inherit or new
416
+ classData: (args.attributes || args.methods) ? {
417
+ attributes: args.attributes || [],
418
+ methods: args.methods || []
419
+ } : undefined,
324
420
  textAlignVertical: 'center',
325
421
  textAlignHorizontal: 'center'
326
422
  };
@@ -355,18 +451,20 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
355
451
  if (!targetExists) throw new Error(`Target node ${args.targetId} not found`);
356
452
 
357
453
  const newEdge: EdgeData = {
358
- id: randomUUID(),
454
+ id: args.id || randomUUID(),
359
455
  sourceId: args.sourceId,
360
456
  targetId: args.targetId,
361
457
  sourcePointIndex: Number(args.sourcePointIndex),
362
458
  targetPointIndex: Number(args.targetPointIndex),
363
459
  label: args.label,
364
- style: 'line', // default
365
- routingType: 'straight', // default
460
+ style: args.style || 'line',
461
+ routingType: args.routingType || 'straight',
366
462
  color: args.color || '#000000',
367
463
  thickness: Math.max(0.005, Math.min(0.5, args.thickness ? Number(args.thickness) : 0.01)),
368
464
  fontSize: 20,
369
- borderStyle: 'solid'
465
+ borderStyle: args.borderStyle || 'solid',
466
+ terminationStart: args.terminationStart,
467
+ terminationEnd: args.terminationEnd
370
468
  };
371
469
  state.edges.push(newEdge);
372
470
  await saveDiagramFile(args.filePath, state);
@@ -434,6 +532,149 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
434
532
  await saveDiagramFile(args.filePath, state);
435
533
  return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
436
534
  }
535
+ case "create_uml_class": {
536
+ const state = await readDiagramFile(args.filePath);
537
+ const newNodeId = args.id || randomUUID();
538
+
539
+ // Standard size for class
540
+ const width = 3;
541
+ // dynamic height?
542
+ const attrCount = (args.attributes || []).length;
543
+ const methodCount = (args.methods || []).length;
544
+ const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
545
+ const height = Math.max(2, estimatedHeight);
546
+
547
+ const newNode: NodeData = {
548
+ id: newNodeId,
549
+ x: args.x ? Number(args.x) : 0,
550
+ y: 0,
551
+ z: args.y ? Number(args.y) : 0,
552
+ width: width,
553
+ height: height,
554
+ label: args.className,
555
+ shape: "class",
556
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
557
+ backgroundColor: "#ffffff",
558
+ classData: {
559
+ attributes: args.attributes || [],
560
+ methods: args.methods || []
561
+ }
562
+ };
563
+
564
+ state.nodes.push(newNode);
565
+ await saveDiagramFile(args.filePath, state);
566
+ return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
567
+ }
568
+ case "create_db_table": {
569
+ const state = await readDiagramFile(args.filePath);
570
+ const newNodeId = args.id || randomUUID();
571
+
572
+ const cols = args.columns || [];
573
+ // Header + items
574
+ const estimatedHeight = 0.8 + cols.length * 0.4;
575
+ const height = Math.max(2, estimatedHeight);
576
+ const width = 3;
577
+
578
+ const newNode: NodeData = {
579
+ id: newNodeId,
580
+ x: args.x ? Number(args.x) : 0,
581
+ y: 0,
582
+ z: args.y ? Number(args.y) : 0,
583
+ width: width,
584
+ height: height,
585
+ label: args.tableName,
586
+ shape: "db-table",
587
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
588
+ backgroundColor: "#ffffff",
589
+ dbTableData: {
590
+ columns: cols.map((c: any) => ({
591
+ name: c.name,
592
+ type: c.type,
593
+ isPk: !!c.isPk,
594
+ isFk: !!c.isFk
595
+ }))
596
+ }
597
+ };
598
+
599
+ state.nodes.push(newNode);
600
+ await saveDiagramFile(args.filePath, state);
601
+ return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
602
+ }
603
+ case "connect_entities": {
604
+ const state = await readDiagramFile(args.filePath);
605
+
606
+ // Find nodes by Label (tolerant search?)
607
+ const sourceNode = state.nodes.find(n => n.label === args.sourceName);
608
+ const targetNode = state.nodes.find(n => n.label === args.targetName);
609
+
610
+ if (!sourceNode) throw new Error(`Source entity '${args.sourceName}' not found`);
611
+ if (!targetNode) throw new Error(`Target entity '${args.targetName}' not found`);
612
+
613
+ let terminationStart = 'none';
614
+ let terminationEnd = 'none';
615
+ let style: 'line' | 'arrow-source' | 'arrow-target' | 'arrow-both' = 'line';
616
+ let borderStyle = 'solid';
617
+
618
+ // Map relationship to terminations
619
+ switch (args.relationType) {
620
+ case 'inheritance':
621
+ terminationEnd = 'triangle-empty';
622
+ break;
623
+ case 'implementation':
624
+ terminationEnd = 'triangle-empty';
625
+ borderStyle = 'dashed';
626
+ break;
627
+ case 'dependency':
628
+ terminationEnd = 'arrow';
629
+ borderStyle = 'dashed';
630
+ break;
631
+ case 'association':
632
+ terminationEnd = 'none'; // simple line? or arrow? normally association is simple line OR arrow
633
+ // Let's use arrow for directed association if implied?
634
+ // Usually "connect A to B" implies direction.
635
+ terminationEnd = 'arrow';
636
+ break;
637
+ case 'aggregation':
638
+ terminationStart = 'diamond-empty';
639
+ break;
640
+ case 'composition':
641
+ terminationStart = 'diamond-filled';
642
+ break;
643
+ case 'one-to-many':
644
+ terminationStart = 'crows-one';
645
+ terminationEnd = 'crows-many';
646
+ break;
647
+ case 'many-to-many':
648
+ terminationStart = 'crows-many';
649
+ terminationEnd = 'crows-many';
650
+ break;
651
+ case 'one-to-one':
652
+ terminationStart = 'crows-one';
653
+ terminationEnd = 'crows-one';
654
+ break;
655
+ }
656
+
657
+ const newEdge: EdgeData = {
658
+ id: randomUUID(),
659
+ sourceId: sourceNode.id,
660
+ targetId: targetNode.id,
661
+ sourcePointIndex: 2, // Bottom of source
662
+ targetPointIndex: 0, // Top of target (simple default)
663
+ label: args.label,
664
+ style: style,
665
+ routingType: 'orthogonal', // Semantic diagrams usually look better with orthogonal
666
+ color: '#000000',
667
+ thickness: 0.01,
668
+ fontSize: 20,
669
+ borderStyle: borderStyle as any,
670
+ terminationStart: terminationStart as any,
671
+ terminationEnd: terminationEnd as any
672
+ };
673
+
674
+ state.edges.push(newEdge);
675
+ await saveDiagramFile(args.filePath, state);
676
+ return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
677
+ }
437
678
  default:
438
679
  throw new Error(`Unknown tool: ${name}`);
439
680
  }
package/src/types.ts CHANGED
@@ -6,32 +6,46 @@ export interface NodeData {
6
6
  z: number;
7
7
  width: number;
8
8
  height: number;
9
- layerId: string;
10
9
  label: string;
11
- shape?: 'rectangle' | 'rounded' | 'ellipse' | 'diamond' | 'actor' | 'cylinder' | 'note' | 'cloud' | 'class' | 'text' | 'table';
12
- fontSize?: number;
10
+ shape?: 'rectangle' | 'rounded' | 'ellipse' | 'diamond' | 'actor' | 'cylinder' | 'note' | 'cloud' | 'class' | 'text' | 'table' | 'start-state' | 'end-state' | 'fork-join' | 'db-table';
11
+ layerId?: string;
12
+ backgroundColor?: string;
13
13
  borderColor?: string;
14
14
  borderWidth?: number;
15
15
  borderStyle?: 'solid' | 'dashed' | 'dotted';
16
- backgroundColor?: string;
16
+ textColor?: string;
17
+ fontSize?: number;
17
18
  opacity?: number;
18
- textAlignVertical?: 'top' | 'center' | 'bottom';
19
- textAlignHorizontal?: 'left' | 'center' | 'right';
20
- parentId?: string;
21
- imageUrl?: string;
19
+
20
+ // Font styles
22
21
  isBold?: boolean;
23
22
  isItalic?: boolean;
24
23
  isUnderline?: boolean;
25
24
  isStrikethrough?: boolean;
26
25
  isVerticalText?: boolean;
26
+ textAlignment?: 'left' | 'center' | 'right';
27
+ textAlignHorizontal?: 'left' | 'center' | 'right';
28
+ textVerticalAlignment?: 'top' | 'center' | 'bottom';
29
+ textAlignVertical?: 'top' | 'center' | 'bottom';
30
+
27
31
  description?: string;
28
32
  url?: string;
33
+ imageUrl?: string;
34
+ parentId?: string;
35
+
29
36
  tableData?: {
30
- columns: number;
31
- rows: number;
32
- hasRowHeader: boolean;
33
- hasColumnHeader: boolean;
34
- cells?: Record<string, string>;
37
+ rows: number;
38
+ columns: number;
39
+ hasRowHeader: boolean;
40
+ hasColumnHeader: boolean;
41
+ cells?: Record<string, string>;
42
+ };
43
+ classData?: {
44
+ methods: string[];
45
+ attributes: string[];
46
+ };
47
+ dbTableData?: {
48
+ columns: { name: string; type: string; isPk: boolean; isFk: boolean }[];
35
49
  };
36
50
  }
37
51
 
@@ -54,6 +68,8 @@ export interface EdgeData {
54
68
  isItalic?: boolean;
55
69
  isUnderline?: boolean;
56
70
  isStrikethrough?: boolean;
71
+ terminationStart?: 'none' | 'arrow' | 'triangle-empty' | 'triangle-filled' | 'diamond-empty' | 'diamond-filled' | 'circle' | 'circle-plus' | 'crows-one' | 'crows-many' | 'crows-zero-one' | 'crows-zero-many';
72
+ terminationEnd?: 'none' | 'arrow' | 'triangle-empty' | 'triangle-filled' | 'diamond-empty' | 'diamond-filled' | 'circle' | 'circle-plus' | 'crows-one' | 'crows-many' | 'crows-zero-one' | 'crows-zero-many';
57
73
  }
58
74
 
59
75
  export interface LayerData {