dodraw-mcp-server 0.1.3 → 0.1.5

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.
@@ -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
  }
@@ -116,7 +140,7 @@ exports.toolDefinitions = [
116
140
  },
117
141
  {
118
142
  name: "add_directional_node",
119
- description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams.",
143
+ description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams, including Class and Database diagrams. Supports 'columns' for DB tables.",
120
144
  inputSchema: {
121
145
  type: "object",
122
146
  properties: {
@@ -124,14 +148,90 @@ 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" },
158
+ columns: {
159
+ type: "array",
160
+ description: "Optional list of columns for Table nodes",
161
+ items: {
162
+ type: "object",
163
+ properties: {
164
+ name: { type: "string" },
165
+ type: { type: "string" },
166
+ isPk: { type: "boolean" },
167
+ isFk: { type: "boolean" }
168
+ },
169
+ required: ["name", "type"]
170
+ }
171
+ }
132
172
  },
133
173
  required: ["filePath", "sourceNodeId", "direction", "label"]
134
174
  }
175
+ },
176
+ {
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.",
179
+ inputSchema: {
180
+ type: "object",
181
+ properties: {
182
+ filePath: { type: "string" },
183
+ className: { type: "string" },
184
+ attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
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" }
189
+ },
190
+ required: ["filePath", "className"]
191
+ }
192
+ },
193
+ {
194
+ name: "create_db_table",
195
+ description: "Create a Database Entity (ERD Table). Use this for the FIRST table. For connected tables, use 'add_directional_node' with 'columns' to ensure correct relative positioning.",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: {
199
+ filePath: { type: "string" },
200
+ tableName: { type: "string" },
201
+ columns: {
202
+ type: "array",
203
+ items: {
204
+ type: "object",
205
+ properties: {
206
+ name: { type: "string" },
207
+ type: { type: "string" },
208
+ isPk: { type: "boolean" },
209
+ isFk: { type: "boolean" }
210
+ },
211
+ required: ["name", "type"]
212
+ }
213
+ },
214
+ x: { type: "number" },
215
+ y: { type: "number" },
216
+ id: { type: "string", description: "Optional explicit ID" }
217
+ },
218
+ required: ["filePath", "tableName", "columns"]
219
+ }
220
+ },
221
+ {
222
+ name: "connect_entities",
223
+ description: "Connect two entities semantically (e.g. Inherits, Composes)",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ filePath: { type: "string" },
228
+ sourceName: { type: "string", description: "Label of source node (e.g. Class Name)" },
229
+ targetName: { type: "string", description: "Label of target node" },
230
+ relationType: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition", "one-to-one", "one-to-many", "many-to-many"] },
231
+ label: { type: "string", description: "Optional edge label" }
232
+ },
233
+ required: ["filePath", "sourceName", "targetName", "relationType"]
234
+ }
135
235
  }
136
236
  ];
137
237
  async function handleToolCall(name, args) {
@@ -200,7 +300,7 @@ async function handleToolCall(name, args) {
200
300
  }
201
301
  }
202
302
  const newNode = {
203
- id: (0, crypto_1.randomUUID)(),
303
+ id: args.id || (0, crypto_1.randomUUID)(),
204
304
  x: x,
205
305
  y: 0.05,
206
306
  z: z,
@@ -210,6 +310,12 @@ async function handleToolCall(name, args) {
210
310
  shape: args.shape || "rectangle",
211
311
  layerId: args.layerId || state.activeLayerId,
212
312
  backgroundColor: args.color,
313
+ description: args.description,
314
+ tableData: args.tableData,
315
+ classData: (args.attributes || args.methods) ? {
316
+ attributes: args.attributes || [],
317
+ methods: args.methods || []
318
+ } : undefined,
213
319
  textAlignVertical: 'center',
214
320
  textAlignHorizontal: 'center'
215
321
  };
@@ -306,6 +412,18 @@ async function handleToolCall(name, args) {
306
412
  shape: args.shape || "rectangle",
307
413
  layerId: sourceNode.layerId, // Inherit layer
308
414
  backgroundColor: args.color || sourceNode.backgroundColor, // Inherit or new
415
+ classData: (args.attributes || args.methods) ? {
416
+ attributes: args.attributes || [],
417
+ methods: args.methods || []
418
+ } : undefined,
419
+ dbTableData: args.columns ? {
420
+ columns: args.columns.map((c) => ({
421
+ name: c.name,
422
+ type: c.type,
423
+ isPk: !!c.isPk,
424
+ isFk: !!c.isFk
425
+ }))
426
+ } : undefined,
309
427
  textAlignVertical: 'center',
310
428
  textAlignHorizontal: 'center'
311
429
  };
@@ -338,18 +456,20 @@ async function handleToolCall(name, args) {
338
456
  if (!targetExists)
339
457
  throw new Error(`Target node ${args.targetId} not found`);
340
458
  const newEdge = {
341
- id: (0, crypto_1.randomUUID)(),
459
+ id: args.id || (0, crypto_1.randomUUID)(),
342
460
  sourceId: args.sourceId,
343
461
  targetId: args.targetId,
344
462
  sourcePointIndex: Number(args.sourcePointIndex),
345
463
  targetPointIndex: Number(args.targetPointIndex),
346
464
  label: args.label,
347
- style: 'line', // default
348
- routingType: 'straight', // default
465
+ style: args.style || 'line',
466
+ routingType: args.routingType || 'straight',
349
467
  color: args.color || '#000000',
350
468
  thickness: Math.max(0.005, Math.min(0.5, args.thickness ? Number(args.thickness) : 0.01)),
351
469
  fontSize: 20,
352
- borderStyle: 'solid'
470
+ borderStyle: args.borderStyle || 'solid',
471
+ terminationStart: args.terminationStart,
472
+ terminationEnd: args.terminationEnd
353
473
  };
354
474
  state.edges.push(newEdge);
355
475
  await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
@@ -420,6 +540,155 @@ async function handleToolCall(name, args) {
420
540
  await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
421
541
  return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
422
542
  }
543
+ case "create_uml_class": {
544
+ const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
545
+ const newNodeId = args.id || (0, crypto_1.randomUUID)();
546
+ // Standard size for class
547
+ const width = 3;
548
+ // dynamic height?
549
+ const attrCount = (args.attributes || []).length;
550
+ const methodCount = (args.methods || []).length;
551
+ const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
552
+ const height = Math.max(2, estimatedHeight);
553
+ // Auto-placement logic if X/Y not provided
554
+ let x = args.x !== undefined ? Number(args.x) : 0;
555
+ let z = args.y !== undefined ? Number(args.y) : 0;
556
+ if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
557
+ const lastNode = state.nodes[state.nodes.length - 1];
558
+ x = lastNode.x + 5;
559
+ z = lastNode.z;
560
+ }
561
+ const newNode = {
562
+ id: newNodeId,
563
+ x: x,
564
+ y: 0,
565
+ z: z,
566
+ width: width,
567
+ height: height,
568
+ label: args.className,
569
+ shape: "class",
570
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
571
+ backgroundColor: "#ffffff",
572
+ classData: {
573
+ attributes: args.attributes || [],
574
+ methods: args.methods || []
575
+ }
576
+ };
577
+ state.nodes.push(newNode);
578
+ await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
579
+ return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
580
+ }
581
+ case "create_db_table": {
582
+ const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
583
+ const newNodeId = args.id || (0, crypto_1.randomUUID)();
584
+ const cols = args.columns || [];
585
+ // Header + items
586
+ const estimatedHeight = 0.8 + cols.length * 0.4;
587
+ const height = Math.max(2, estimatedHeight);
588
+ const width = 3;
589
+ // Auto-placement logic if X/Y not provided
590
+ let x = args.x !== undefined ? Number(args.x) : 0;
591
+ let z = args.y !== undefined ? Number(args.y) : 0;
592
+ if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
593
+ const lastNode = state.nodes[state.nodes.length - 1];
594
+ x = lastNode.x + 5;
595
+ z = lastNode.z;
596
+ }
597
+ const newNode = {
598
+ id: newNodeId,
599
+ x: x,
600
+ y: 0,
601
+ z: z,
602
+ width: width,
603
+ height: height,
604
+ label: args.tableName,
605
+ shape: "db-table",
606
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
607
+ backgroundColor: "#ffffff",
608
+ dbTableData: {
609
+ columns: cols.map((c) => ({
610
+ name: c.name,
611
+ type: c.type,
612
+ isPk: !!c.isPk,
613
+ isFk: !!c.isFk
614
+ }))
615
+ }
616
+ };
617
+ state.nodes.push(newNode);
618
+ await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
619
+ return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
620
+ }
621
+ case "connect_entities": {
622
+ const state = await (0, fileHandler_1.readDiagramFile)(args.filePath);
623
+ // Find nodes by Label (tolerant search?)
624
+ const sourceNode = state.nodes.find(n => n.label === args.sourceName);
625
+ const targetNode = state.nodes.find(n => n.label === args.targetName);
626
+ if (!sourceNode)
627
+ throw new Error(`Source entity '${args.sourceName}' not found`);
628
+ if (!targetNode)
629
+ throw new Error(`Target entity '${args.targetName}' not found`);
630
+ let terminationStart = 'none';
631
+ let terminationEnd = 'none';
632
+ let style = 'line';
633
+ let borderStyle = 'solid';
634
+ // Map relationship to terminations
635
+ switch (args.relationType) {
636
+ case 'inheritance':
637
+ terminationEnd = 'triangle-empty';
638
+ break;
639
+ case 'implementation':
640
+ terminationEnd = 'triangle-empty';
641
+ borderStyle = 'dashed';
642
+ break;
643
+ case 'dependency':
644
+ terminationEnd = 'arrow';
645
+ borderStyle = 'dashed';
646
+ break;
647
+ case 'association':
648
+ terminationEnd = 'none'; // simple line? or arrow? normally association is simple line OR arrow
649
+ // Let's use arrow for directed association if implied?
650
+ // Usually "connect A to B" implies direction.
651
+ terminationEnd = 'arrow';
652
+ break;
653
+ case 'aggregation':
654
+ terminationStart = 'diamond-empty';
655
+ break;
656
+ case 'composition':
657
+ terminationStart = 'diamond-filled';
658
+ break;
659
+ case 'one-to-many':
660
+ terminationStart = 'crows-one';
661
+ terminationEnd = 'crows-many';
662
+ break;
663
+ case 'many-to-many':
664
+ terminationStart = 'crows-many';
665
+ terminationEnd = 'crows-many';
666
+ break;
667
+ case 'one-to-one':
668
+ terminationStart = 'crows-one';
669
+ terminationEnd = 'crows-one';
670
+ break;
671
+ }
672
+ const newEdge = {
673
+ id: (0, crypto_1.randomUUID)(),
674
+ sourceId: sourceNode.id,
675
+ targetId: targetNode.id,
676
+ sourcePointIndex: 2, // Bottom of source
677
+ targetPointIndex: 0, // Top of target (simple default)
678
+ label: args.label,
679
+ style: style,
680
+ routingType: 'orthogonal', // Semantic diagrams usually look better with orthogonal
681
+ color: '#000000',
682
+ thickness: 0.01,
683
+ fontSize: 20,
684
+ borderStyle: borderStyle,
685
+ terminationStart: terminationStart,
686
+ terminationEnd: terminationEnd
687
+ };
688
+ state.edges.push(newEdge);
689
+ await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
690
+ return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
691
+ }
423
692
  default:
424
693
  throw new Error(`Unknown tool: ${name}`);
425
694
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodraw-mcp-server",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
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
  }
@@ -116,7 +140,7 @@ export const toolDefinitions: Tool[] = [
116
140
  },
117
141
  {
118
142
  name: "add_directional_node",
119
- description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams.",
143
+ description: "Add a new node relative to an existing node and connect them. PRIMARY method for creating diagrams, including Class and Database diagrams. Supports 'columns' for DB tables.",
120
144
  inputSchema: {
121
145
  type: "object",
122
146
  properties: {
@@ -124,14 +148,90 @@ 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" },
158
+ columns: {
159
+ type: "array",
160
+ description: "Optional list of columns for Table nodes",
161
+ items: {
162
+ type: "object",
163
+ properties: {
164
+ name: { type: "string" },
165
+ type: { type: "string" },
166
+ isPk: { type: "boolean" },
167
+ isFk: { type: "boolean" }
168
+ },
169
+ required: ["name", "type"]
170
+ }
171
+ }
132
172
  },
133
173
  required: ["filePath", "sourceNodeId", "direction", "label"]
134
174
  }
175
+ },
176
+ {
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.",
179
+ inputSchema: {
180
+ type: "object",
181
+ properties: {
182
+ filePath: { type: "string" },
183
+ className: { type: "string" },
184
+ attributes: { type: "array", items: { type: "string" }, description: "List of attributes e.g. ['- id: int', '+ name: string']" },
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" }
189
+ },
190
+ required: ["filePath", "className"]
191
+ }
192
+ },
193
+ {
194
+ name: "create_db_table",
195
+ description: "Create a Database Entity (ERD Table). Use this for the FIRST table. For connected tables, use 'add_directional_node' with 'columns' to ensure correct relative positioning.",
196
+ inputSchema: {
197
+ type: "object",
198
+ properties: {
199
+ filePath: { type: "string" },
200
+ tableName: { type: "string" },
201
+ columns: {
202
+ type: "array",
203
+ items: {
204
+ type: "object",
205
+ properties: {
206
+ name: { type: "string" },
207
+ type: { type: "string" },
208
+ isPk: { type: "boolean" },
209
+ isFk: { type: "boolean" }
210
+ },
211
+ required: ["name", "type"]
212
+ }
213
+ },
214
+ x: { type: "number" },
215
+ y: { type: "number" },
216
+ id: { type: "string", description: "Optional explicit ID" }
217
+ },
218
+ required: ["filePath", "tableName", "columns"]
219
+ }
220
+ },
221
+ {
222
+ name: "connect_entities",
223
+ description: "Connect two entities semantically (e.g. Inherits, Composes)",
224
+ inputSchema: {
225
+ type: "object",
226
+ properties: {
227
+ filePath: { type: "string" },
228
+ sourceName: { type: "string", description: "Label of source node (e.g. Class Name)" },
229
+ targetName: { type: "string", description: "Label of target node" },
230
+ relationType: { type: "string", enum: ["association", "inheritance", "implementation", "dependency", "aggregation", "composition", "one-to-one", "one-to-many", "many-to-many"] },
231
+ label: { type: "string", description: "Optional edge label" }
232
+ },
233
+ required: ["filePath", "sourceName", "targetName", "relationType"]
234
+ }
135
235
  }
136
236
  ];
137
237
 
@@ -205,7 +305,7 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
205
305
  }
206
306
 
207
307
  const newNode: NodeData = {
208
- id: randomUUID(),
308
+ id: args.id || randomUUID(),
209
309
  x: x,
210
310
  y: 0.05,
211
311
  z: z,
@@ -215,6 +315,12 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
215
315
  shape: args.shape || "rectangle",
216
316
  layerId: args.layerId || state.activeLayerId,
217
317
  backgroundColor: args.color,
318
+ description: args.description,
319
+ tableData: args.tableData,
320
+ classData: (args.attributes || args.methods) ? {
321
+ attributes: args.attributes || [],
322
+ methods: args.methods || []
323
+ } : undefined,
218
324
  textAlignVertical: 'center',
219
325
  textAlignHorizontal: 'center'
220
326
  };
@@ -321,6 +427,18 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
321
427
  shape: args.shape || "rectangle",
322
428
  layerId: sourceNode.layerId, // Inherit layer
323
429
  backgroundColor: args.color || sourceNode.backgroundColor, // Inherit or new
430
+ classData: (args.attributes || args.methods) ? {
431
+ attributes: args.attributes || [],
432
+ methods: args.methods || []
433
+ } : undefined,
434
+ dbTableData: args.columns ? {
435
+ columns: args.columns.map((c: any) => ({
436
+ name: c.name,
437
+ type: c.type,
438
+ isPk: !!c.isPk,
439
+ isFk: !!c.isFk
440
+ }))
441
+ } : undefined,
324
442
  textAlignVertical: 'center',
325
443
  textAlignHorizontal: 'center'
326
444
  };
@@ -355,18 +473,20 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
355
473
  if (!targetExists) throw new Error(`Target node ${args.targetId} not found`);
356
474
 
357
475
  const newEdge: EdgeData = {
358
- id: randomUUID(),
476
+ id: args.id || randomUUID(),
359
477
  sourceId: args.sourceId,
360
478
  targetId: args.targetId,
361
479
  sourcePointIndex: Number(args.sourcePointIndex),
362
480
  targetPointIndex: Number(args.targetPointIndex),
363
481
  label: args.label,
364
- style: 'line', // default
365
- routingType: 'straight', // default
482
+ style: args.style || 'line',
483
+ routingType: args.routingType || 'straight',
366
484
  color: args.color || '#000000',
367
485
  thickness: Math.max(0.005, Math.min(0.5, args.thickness ? Number(args.thickness) : 0.01)),
368
486
  fontSize: 20,
369
- borderStyle: 'solid'
487
+ borderStyle: args.borderStyle || 'solid',
488
+ terminationStart: args.terminationStart,
489
+ terminationEnd: args.terminationEnd
370
490
  };
371
491
  state.edges.push(newEdge);
372
492
  await saveDiagramFile(args.filePath, state);
@@ -434,6 +554,170 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
434
554
  await saveDiagramFile(args.filePath, state);
435
555
  return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
436
556
  }
557
+ case "create_uml_class": {
558
+ const state = await readDiagramFile(args.filePath);
559
+ const newNodeId = args.id || randomUUID();
560
+
561
+ // Standard size for class
562
+ const width = 3;
563
+ // dynamic height?
564
+ const attrCount = (args.attributes || []).length;
565
+ const methodCount = (args.methods || []).length;
566
+ const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
567
+ const height = Math.max(2, estimatedHeight);
568
+
569
+ // Auto-placement logic if X/Y not provided
570
+ let x = args.x !== undefined ? Number(args.x) : 0;
571
+ let z = args.y !== undefined ? Number(args.y) : 0;
572
+
573
+ if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
574
+ const lastNode = state.nodes[state.nodes.length - 1];
575
+ x = lastNode.x + 5;
576
+ z = lastNode.z;
577
+ }
578
+
579
+ const newNode: NodeData = {
580
+ id: newNodeId,
581
+ x: x,
582
+ y: 0,
583
+ z: z,
584
+ width: width,
585
+ height: height,
586
+ label: args.className,
587
+ shape: "class",
588
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
589
+ backgroundColor: "#ffffff",
590
+ classData: {
591
+ attributes: args.attributes || [],
592
+ methods: args.methods || []
593
+ }
594
+ };
595
+
596
+ state.nodes.push(newNode);
597
+ await saveDiagramFile(args.filePath, state);
598
+ return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
599
+ }
600
+ case "create_db_table": {
601
+ const state = await readDiagramFile(args.filePath);
602
+ const newNodeId = args.id || randomUUID();
603
+
604
+ const cols = args.columns || [];
605
+ // Header + items
606
+ const estimatedHeight = 0.8 + cols.length * 0.4;
607
+ const height = Math.max(2, estimatedHeight);
608
+
609
+ const width = 3;
610
+
611
+ // Auto-placement logic if X/Y not provided
612
+ let x = args.x !== undefined ? Number(args.x) : 0;
613
+ let z = args.y !== undefined ? Number(args.y) : 0;
614
+
615
+ if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
616
+ const lastNode = state.nodes[state.nodes.length - 1];
617
+ x = lastNode.x + 5;
618
+ z = lastNode.z;
619
+ }
620
+
621
+ const newNode: NodeData = {
622
+ id: newNodeId,
623
+ x: x,
624
+ y: 0,
625
+ z: z,
626
+ width: width,
627
+ height: height,
628
+ label: args.tableName,
629
+ shape: "db-table",
630
+ layerId: state.layers[0]?.id || (state.nodes.length > 0 ? state.nodes[0].layerId : "default"),
631
+ backgroundColor: "#ffffff",
632
+ dbTableData: {
633
+ columns: cols.map((c: any) => ({
634
+ name: c.name,
635
+ type: c.type,
636
+ isPk: !!c.isPk,
637
+ isFk: !!c.isFk
638
+ }))
639
+ }
640
+ };
641
+
642
+ state.nodes.push(newNode);
643
+ await saveDiagramFile(args.filePath, state);
644
+ return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
645
+ }
646
+ case "connect_entities": {
647
+ const state = await readDiagramFile(args.filePath);
648
+
649
+ // Find nodes by Label (tolerant search?)
650
+ const sourceNode = state.nodes.find(n => n.label === args.sourceName);
651
+ const targetNode = state.nodes.find(n => n.label === args.targetName);
652
+
653
+ if (!sourceNode) throw new Error(`Source entity '${args.sourceName}' not found`);
654
+ if (!targetNode) throw new Error(`Target entity '${args.targetName}' not found`);
655
+
656
+ let terminationStart = 'none';
657
+ let terminationEnd = 'none';
658
+ let style: 'line' | 'arrow-source' | 'arrow-target' | 'arrow-both' = 'line';
659
+ let borderStyle = 'solid';
660
+
661
+ // Map relationship to terminations
662
+ switch (args.relationType) {
663
+ case 'inheritance':
664
+ terminationEnd = 'triangle-empty';
665
+ break;
666
+ case 'implementation':
667
+ terminationEnd = 'triangle-empty';
668
+ borderStyle = 'dashed';
669
+ break;
670
+ case 'dependency':
671
+ terminationEnd = 'arrow';
672
+ borderStyle = 'dashed';
673
+ break;
674
+ case 'association':
675
+ terminationEnd = 'none'; // simple line? or arrow? normally association is simple line OR arrow
676
+ // Let's use arrow for directed association if implied?
677
+ // Usually "connect A to B" implies direction.
678
+ terminationEnd = 'arrow';
679
+ break;
680
+ case 'aggregation':
681
+ terminationStart = 'diamond-empty';
682
+ break;
683
+ case 'composition':
684
+ terminationStart = 'diamond-filled';
685
+ break;
686
+ case 'one-to-many':
687
+ terminationStart = 'crows-one';
688
+ terminationEnd = 'crows-many';
689
+ break;
690
+ case 'many-to-many':
691
+ terminationStart = 'crows-many';
692
+ terminationEnd = 'crows-many';
693
+ break;
694
+ case 'one-to-one':
695
+ terminationStart = 'crows-one';
696
+ terminationEnd = 'crows-one';
697
+ break;
698
+ }
699
+
700
+ const newEdge: EdgeData = {
701
+ id: randomUUID(),
702
+ sourceId: sourceNode.id,
703
+ targetId: targetNode.id,
704
+ sourcePointIndex: 2, // Bottom of source
705
+ targetPointIndex: 0, // Top of target (simple default)
706
+ label: args.label,
707
+ style: style,
708
+ routingType: 'orthogonal', // Semantic diagrams usually look better with orthogonal
709
+ color: '#000000',
710
+ thickness: 0.01,
711
+ fontSize: 20,
712
+ borderStyle: borderStyle as any,
713
+ terminationStart: terminationStart as any,
714
+ terminationEnd: terminationEnd as any
715
+ };
716
+
717
+ state.edges.push(newEdge);
718
+ await saveDiagramFile(args.filePath, state);
719
+ return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
720
+ }
437
721
  default:
438
722
  throw new Error(`Unknown tool: ${name}`);
439
723
  }
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 {