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