dodraw-mcp-server 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +175 -44
- 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 +114 -10
- 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
|
}
|
|
@@ -244,11 +250,11 @@ exports.toolDefinitions = [
|
|
|
244
250
|
}
|
|
245
251
|
}
|
|
246
252
|
];
|
|
247
|
-
async function handleToolCall(name, args) {
|
|
253
|
+
export async function handleToolCall(name, args) {
|
|
248
254
|
switch (name) {
|
|
249
255
|
case "create_new_diagram": {
|
|
250
256
|
// ... (existing code)
|
|
251
|
-
const defaultLayerId =
|
|
257
|
+
const defaultLayerId = randomUUID();
|
|
252
258
|
const initialState = {
|
|
253
259
|
nodes: [],
|
|
254
260
|
edges: [],
|
|
@@ -262,12 +268,12 @@ async function handleToolCall(name, args) {
|
|
|
262
268
|
gridSnappingEnabled: true,
|
|
263
269
|
gridSize: 0.5
|
|
264
270
|
};
|
|
265
|
-
await
|
|
271
|
+
await saveDiagramFile(args.filePath, initialState);
|
|
266
272
|
return { content: [{ type: "text", text: `Created new diagram at ${args.filePath}` }] };
|
|
267
273
|
}
|
|
268
274
|
case "read_diagram_structure": {
|
|
269
275
|
// ... (existing code)
|
|
270
|
-
const state = await
|
|
276
|
+
const state = await readDiagramFile(args.filePath);
|
|
271
277
|
return {
|
|
272
278
|
content: [{
|
|
273
279
|
type: "text",
|
|
@@ -284,7 +290,7 @@ async function handleToolCall(name, args) {
|
|
|
284
290
|
}
|
|
285
291
|
case "add_node": {
|
|
286
292
|
// ... (existing code)
|
|
287
|
-
const state = await
|
|
293
|
+
const state = await readDiagramFile(args.filePath);
|
|
288
294
|
// Auto-placement Logic
|
|
289
295
|
let x = 0;
|
|
290
296
|
let z = 0;
|
|
@@ -310,7 +316,7 @@ async function handleToolCall(name, args) {
|
|
|
310
316
|
}
|
|
311
317
|
}
|
|
312
318
|
const newNode = {
|
|
313
|
-
id: args.id ||
|
|
319
|
+
id: args.id || randomUUID(),
|
|
314
320
|
x: x,
|
|
315
321
|
y: 0.05,
|
|
316
322
|
z: z,
|
|
@@ -331,16 +337,16 @@ async function handleToolCall(name, args) {
|
|
|
331
337
|
};
|
|
332
338
|
console.error(`DEBUG: Adding node. Args: x=${args.x}, y=${args.y}. Computed: x=${newNode.x}, z=${newNode.z}`);
|
|
333
339
|
state.nodes.push(newNode);
|
|
334
|
-
await
|
|
340
|
+
await saveDiagramFile(args.filePath, state);
|
|
335
341
|
return { content: [{ type: "text", text: `Added node ${newNode.id} at (${newNode.x}, ${newNode.z})` }] };
|
|
336
342
|
}
|
|
337
343
|
case "add_directional_node": {
|
|
338
|
-
const state = await
|
|
344
|
+
const state = await readDiagramFile(args.filePath);
|
|
339
345
|
const sourceNode = state.nodes.find(n => n.id === args.sourceNodeId);
|
|
340
346
|
if (!sourceNode) {
|
|
341
347
|
throw new Error(`Source node with ID ${args.sourceNodeId} not found.`);
|
|
342
348
|
}
|
|
343
|
-
const newNodeId =
|
|
349
|
+
const newNodeId = randomUUID();
|
|
344
350
|
// 1. Calculate base position with tighter spacing (matching App interaction)
|
|
345
351
|
const INITIAL_SPACING = 2.0;
|
|
346
352
|
let dirX = 0;
|
|
@@ -438,7 +444,7 @@ async function handleToolCall(name, args) {
|
|
|
438
444
|
textAlignHorizontal: 'center'
|
|
439
445
|
};
|
|
440
446
|
const newEdge = {
|
|
441
|
-
id:
|
|
447
|
+
id: randomUUID(),
|
|
442
448
|
sourceId: sourceNode.id,
|
|
443
449
|
targetId: newNodeId,
|
|
444
450
|
sourcePointIndex: sourcePoint,
|
|
@@ -453,11 +459,11 @@ async function handleToolCall(name, args) {
|
|
|
453
459
|
};
|
|
454
460
|
state.nodes.push(newNode);
|
|
455
461
|
state.edges.push(newEdge);
|
|
456
|
-
await
|
|
462
|
+
await saveDiagramFile(args.filePath, state);
|
|
457
463
|
return { content: [{ type: "text", text: `Added node '${args.label}' (ID: ${newNodeId}) to the ${args.direction} of '${sourceNode.label}' and connected them.` }] };
|
|
458
464
|
}
|
|
459
465
|
case "add_edge": {
|
|
460
|
-
const state = await
|
|
466
|
+
const state = await readDiagramFile(args.filePath);
|
|
461
467
|
// Verify nodes exist
|
|
462
468
|
const sourceExists = state.nodes.find(n => n.id === args.sourceId);
|
|
463
469
|
const targetExists = state.nodes.find(n => n.id === args.targetId);
|
|
@@ -466,7 +472,7 @@ async function handleToolCall(name, args) {
|
|
|
466
472
|
if (!targetExists)
|
|
467
473
|
throw new Error(`Target node ${args.targetId} not found`);
|
|
468
474
|
const newEdge = {
|
|
469
|
-
id: args.id ||
|
|
475
|
+
id: args.id || randomUUID(),
|
|
470
476
|
sourceId: args.sourceId,
|
|
471
477
|
targetId: args.targetId,
|
|
472
478
|
sourcePointIndex: Number(args.sourcePointIndex),
|
|
@@ -482,11 +488,11 @@ async function handleToolCall(name, args) {
|
|
|
482
488
|
terminationEnd: args.terminationEnd
|
|
483
489
|
};
|
|
484
490
|
state.edges.push(newEdge);
|
|
485
|
-
await
|
|
491
|
+
await saveDiagramFile(args.filePath, state);
|
|
486
492
|
return { content: [{ type: "text", text: `Added edge ${newEdge.id}` }] };
|
|
487
493
|
}
|
|
488
494
|
case "update_node": {
|
|
489
|
-
const state = await
|
|
495
|
+
const state = await readDiagramFile(args.filePath);
|
|
490
496
|
const nodeIndex = state.nodes.findIndex(n => n.id === args.nodeId);
|
|
491
497
|
if (nodeIndex === -1) {
|
|
492
498
|
throw new Error(`Node ${args.nodeId} not found`);
|
|
@@ -503,12 +509,12 @@ async function handleToolCall(name, args) {
|
|
|
503
509
|
state.nodes[nodeIndex].height = Number(args.height);
|
|
504
510
|
if (args.backgroundColor)
|
|
505
511
|
state.nodes[nodeIndex].backgroundColor = args.backgroundColor;
|
|
506
|
-
await
|
|
512
|
+
await saveDiagramFile(args.filePath, state);
|
|
507
513
|
return { content: [{ type: "text", text: `Updated node ${args.nodeId}` }] };
|
|
508
514
|
}
|
|
509
515
|
case "add_layer": {
|
|
510
|
-
const state = await
|
|
511
|
-
const newLayerId =
|
|
516
|
+
const state = await readDiagramFile(args.filePath);
|
|
517
|
+
const newLayerId = randomUUID();
|
|
512
518
|
// Auto-positioning logic
|
|
513
519
|
let position = { x: 0, y: 0, z: 0 };
|
|
514
520
|
if (args.position) {
|
|
@@ -547,12 +553,12 @@ async function handleToolCall(name, args) {
|
|
|
547
553
|
visible: args.visible !== false
|
|
548
554
|
};
|
|
549
555
|
state.layers.push(newLayer);
|
|
550
|
-
await
|
|
556
|
+
await saveDiagramFile(args.filePath, state);
|
|
551
557
|
return { content: [{ type: "text", text: `Added layer '${args.name}' (ID: ${newLayerId}) at Y=${position.y}` }] };
|
|
552
558
|
}
|
|
553
559
|
case "create_uml_class": {
|
|
554
|
-
const state = await
|
|
555
|
-
const newNodeId = args.id ||
|
|
560
|
+
const state = await readDiagramFile(args.filePath);
|
|
561
|
+
const newNodeId = args.id || randomUUID();
|
|
556
562
|
// Standard size for class
|
|
557
563
|
const width = 3;
|
|
558
564
|
// dynamic height?
|
|
@@ -560,10 +566,64 @@ async function handleToolCall(name, args) {
|
|
|
560
566
|
const methodCount = (args.methods || []).length;
|
|
561
567
|
const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
|
|
562
568
|
const height = Math.max(2, estimatedHeight);
|
|
563
|
-
// Auto-placement logic
|
|
564
|
-
let x =
|
|
565
|
-
let z =
|
|
566
|
-
|
|
569
|
+
// Auto-placement logic (defaults to 0,0 for first node)
|
|
570
|
+
let x = 0;
|
|
571
|
+
let z = 0;
|
|
572
|
+
// Relative Placement Logic
|
|
573
|
+
if (args.referenceNodeId && args.direction) {
|
|
574
|
+
const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
|
|
575
|
+
if (!sourceNode)
|
|
576
|
+
throw new Error(`Reference node ${args.referenceNodeId} not found`);
|
|
577
|
+
const INITIAL_SPACING = 2.0;
|
|
578
|
+
let dirX = 0;
|
|
579
|
+
let dirZ = 0;
|
|
580
|
+
const srcW = Number(sourceNode.width) || 2;
|
|
581
|
+
const srcH = Number(sourceNode.height) || 1.5;
|
|
582
|
+
switch (args.direction) {
|
|
583
|
+
case "RIGHT":
|
|
584
|
+
dirX = 1;
|
|
585
|
+
break;
|
|
586
|
+
case "LEFT":
|
|
587
|
+
dirX = -1;
|
|
588
|
+
break;
|
|
589
|
+
case "DOWN":
|
|
590
|
+
dirZ = 1;
|
|
591
|
+
break;
|
|
592
|
+
case "UP":
|
|
593
|
+
dirZ = -1;
|
|
594
|
+
break;
|
|
595
|
+
}
|
|
596
|
+
x = sourceNode.x + (dirX * (srcW + INITIAL_SPACING));
|
|
597
|
+
z = sourceNode.z + (dirZ * (srcH + INITIAL_SPACING));
|
|
598
|
+
// Collision Avoidance reuse
|
|
599
|
+
const COLLISION_SPACING = 0.5;
|
|
600
|
+
const shiftStepX = srcW + COLLISION_SPACING;
|
|
601
|
+
const shiftStepZ = srcH + COLLISION_SPACING;
|
|
602
|
+
const newW = width;
|
|
603
|
+
const newH = height;
|
|
604
|
+
const checkCollision = (cx, cz) => {
|
|
605
|
+
return state.nodes.some(n => {
|
|
606
|
+
if (n.layerId !== sourceNode.layerId)
|
|
607
|
+
return false;
|
|
608
|
+
const dx = Math.abs(n.x - cx);
|
|
609
|
+
const dz = Math.abs(n.z - cz);
|
|
610
|
+
const otherW = Number(n.width) || 2;
|
|
611
|
+
const otherH = Number(n.height) || 1.5;
|
|
612
|
+
const combinedHalfWidth = (otherW + newW) / 2;
|
|
613
|
+
const combinedHalfHeight = (otherH + newH) / 2;
|
|
614
|
+
return dx < combinedHalfWidth && dz < combinedHalfHeight;
|
|
615
|
+
});
|
|
616
|
+
};
|
|
617
|
+
let iterations = 0;
|
|
618
|
+
while (checkCollision(x, z) && iterations < 50) {
|
|
619
|
+
iterations++;
|
|
620
|
+
if (dirX !== 0)
|
|
621
|
+
z += shiftStepZ;
|
|
622
|
+
else
|
|
623
|
+
x += shiftStepX;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else if (state.nodes.length > 0) {
|
|
567
627
|
const lastNode = state.nodes[state.nodes.length - 1];
|
|
568
628
|
x = lastNode.x + 5;
|
|
569
629
|
z = lastNode.z;
|
|
@@ -585,12 +645,83 @@ async function handleToolCall(name, args) {
|
|
|
585
645
|
}
|
|
586
646
|
};
|
|
587
647
|
state.nodes.push(newNode);
|
|
588
|
-
|
|
648
|
+
// Auto-Connect logic
|
|
649
|
+
if (args.connection && args.referenceNodeId) {
|
|
650
|
+
let termStart = 'none';
|
|
651
|
+
let termEnd = 'none';
|
|
652
|
+
let borderStyle = 'solid';
|
|
653
|
+
// UML Mapping
|
|
654
|
+
switch (args.connection.type) {
|
|
655
|
+
case 'inheritance':
|
|
656
|
+
termEnd = 'triangle-empty';
|
|
657
|
+
break;
|
|
658
|
+
case 'implementation':
|
|
659
|
+
termEnd = 'triangle-empty';
|
|
660
|
+
borderStyle = 'dashed';
|
|
661
|
+
break;
|
|
662
|
+
case 'dependency':
|
|
663
|
+
termEnd = 'arrow';
|
|
664
|
+
borderStyle = 'dashed';
|
|
665
|
+
break;
|
|
666
|
+
case 'association':
|
|
667
|
+
termEnd = 'arrow';
|
|
668
|
+
break;
|
|
669
|
+
case 'aggregation':
|
|
670
|
+
termStart = 'diamond-empty';
|
|
671
|
+
break;
|
|
672
|
+
case 'composition':
|
|
673
|
+
termStart = 'diamond-filled';
|
|
674
|
+
break;
|
|
675
|
+
}
|
|
676
|
+
// Points? Simple defaults based on direction
|
|
677
|
+
let sp = 0;
|
|
678
|
+
let tp = 0;
|
|
679
|
+
switch (args.direction) {
|
|
680
|
+
case "RIGHT":
|
|
681
|
+
sp = 1;
|
|
682
|
+
tp = 3;
|
|
683
|
+
break;
|
|
684
|
+
case "LEFT":
|
|
685
|
+
sp = 3;
|
|
686
|
+
tp = 1;
|
|
687
|
+
break;
|
|
688
|
+
case "DOWN":
|
|
689
|
+
sp = 2;
|
|
690
|
+
tp = 0;
|
|
691
|
+
break;
|
|
692
|
+
case "UP":
|
|
693
|
+
sp = 0;
|
|
694
|
+
tp = 2;
|
|
695
|
+
break;
|
|
696
|
+
default:
|
|
697
|
+
sp = 2;
|
|
698
|
+
tp = 0;
|
|
699
|
+
break;
|
|
700
|
+
}
|
|
701
|
+
const newEdge = {
|
|
702
|
+
id: randomUUID(),
|
|
703
|
+
sourceId: args.referenceNodeId,
|
|
704
|
+
targetId: newNodeId,
|
|
705
|
+
sourcePointIndex: sp,
|
|
706
|
+
targetPointIndex: tp,
|
|
707
|
+
label: args.connection.label,
|
|
708
|
+
style: 'line',
|
|
709
|
+
routingType: 'orthogonal',
|
|
710
|
+
color: '#000000',
|
|
711
|
+
thickness: 0.01,
|
|
712
|
+
fontSize: 20,
|
|
713
|
+
borderStyle: borderStyle,
|
|
714
|
+
terminationStart: termStart,
|
|
715
|
+
terminationEnd: termEnd
|
|
716
|
+
};
|
|
717
|
+
state.edges.push(newEdge);
|
|
718
|
+
}
|
|
719
|
+
await saveDiagramFile(args.filePath, state);
|
|
589
720
|
return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
|
|
590
721
|
}
|
|
591
722
|
case "create_db_table": {
|
|
592
|
-
const state = await
|
|
593
|
-
const newNodeId = args.id ||
|
|
723
|
+
const state = await readDiagramFile(args.filePath);
|
|
724
|
+
const newNodeId = args.id || randomUUID();
|
|
594
725
|
const cols = args.columns || [];
|
|
595
726
|
// Header + items
|
|
596
727
|
const estimatedHeight = 0.8 + cols.length * 0.4;
|
|
@@ -720,7 +851,7 @@ async function handleToolCall(name, args) {
|
|
|
720
851
|
break;
|
|
721
852
|
}
|
|
722
853
|
const newEdge = {
|
|
723
|
-
id:
|
|
854
|
+
id: randomUUID(),
|
|
724
855
|
sourceId: args.referenceNodeId,
|
|
725
856
|
targetId: newNodeId,
|
|
726
857
|
sourcePointIndex: sp,
|
|
@@ -737,11 +868,11 @@ async function handleToolCall(name, args) {
|
|
|
737
868
|
};
|
|
738
869
|
state.edges.push(newEdge);
|
|
739
870
|
}
|
|
740
|
-
await
|
|
871
|
+
await saveDiagramFile(args.filePath, state);
|
|
741
872
|
return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
|
|
742
873
|
}
|
|
743
874
|
case "connect_entities": {
|
|
744
|
-
const state = await
|
|
875
|
+
const state = await readDiagramFile(args.filePath);
|
|
745
876
|
// Find nodes by Label (tolerant search?)
|
|
746
877
|
const sourceNode = state.nodes.find(n => n.label === args.sourceName);
|
|
747
878
|
const targetNode = state.nodes.find(n => n.label === args.targetName);
|
|
@@ -792,7 +923,7 @@ async function handleToolCall(name, args) {
|
|
|
792
923
|
break;
|
|
793
924
|
}
|
|
794
925
|
const newEdge = {
|
|
795
|
-
id:
|
|
926
|
+
id: randomUUID(),
|
|
796
927
|
sourceId: sourceNode.id,
|
|
797
928
|
targetId: targetNode.id,
|
|
798
929
|
sourcePointIndex: 2, // Bottom of source
|
|
@@ -808,7 +939,7 @@ async function handleToolCall(name, args) {
|
|
|
808
939
|
terminationEnd: terminationEnd
|
|
809
940
|
};
|
|
810
941
|
state.edges.push(newEdge);
|
|
811
|
-
await
|
|
942
|
+
await saveDiagramFile(args.filePath, state);
|
|
812
943
|
return { content: [{ type: "text", text: `Connected '${args.sourceName}' to '${args.targetName}' via ${args.relationType}` }] };
|
|
813
944
|
}
|
|
814
945
|
default:
|
package/dist/src/types.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
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
|
}
|
|
@@ -578,11 +588,58 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
578
588
|
const estimatedHeight = 0.8 + (attrCount + methodCount) * 0.4;
|
|
579
589
|
const height = Math.max(2, estimatedHeight);
|
|
580
590
|
|
|
581
|
-
// Auto-placement logic
|
|
582
|
-
let x =
|
|
583
|
-
let z =
|
|
591
|
+
// Auto-placement logic (defaults to 0,0 for first node)
|
|
592
|
+
let x = 0;
|
|
593
|
+
let z = 0;
|
|
584
594
|
|
|
585
|
-
|
|
595
|
+
// Relative Placement Logic
|
|
596
|
+
if (args.referenceNodeId && args.direction) {
|
|
597
|
+
const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
|
|
598
|
+
if (!sourceNode) throw new Error(`Reference node ${args.referenceNodeId} not found`);
|
|
599
|
+
|
|
600
|
+
const INITIAL_SPACING = 2.0;
|
|
601
|
+
let dirX = 0;
|
|
602
|
+
let dirZ = 0;
|
|
603
|
+
const srcW = Number(sourceNode.width) || 2;
|
|
604
|
+
const srcH = Number(sourceNode.height) || 1.5;
|
|
605
|
+
|
|
606
|
+
switch (args.direction) {
|
|
607
|
+
case "RIGHT": dirX = 1; break;
|
|
608
|
+
case "LEFT": dirX = -1; break;
|
|
609
|
+
case "DOWN": dirZ = 1; break;
|
|
610
|
+
case "UP": dirZ = -1; break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
x = sourceNode.x + (dirX * (srcW + INITIAL_SPACING));
|
|
614
|
+
z = sourceNode.z + (dirZ * (srcH + INITIAL_SPACING));
|
|
615
|
+
|
|
616
|
+
// Collision Avoidance reuse
|
|
617
|
+
const COLLISION_SPACING = 0.5;
|
|
618
|
+
const shiftStepX = srcW + COLLISION_SPACING;
|
|
619
|
+
const shiftStepZ = srcH + COLLISION_SPACING;
|
|
620
|
+
const newW = width;
|
|
621
|
+
const newH = height;
|
|
622
|
+
|
|
623
|
+
const checkCollision = (cx: number, cz: number) => {
|
|
624
|
+
return state.nodes.some(n => {
|
|
625
|
+
if (n.layerId !== sourceNode.layerId) return false;
|
|
626
|
+
const dx = Math.abs(n.x - cx);
|
|
627
|
+
const dz = Math.abs(n.z - cz);
|
|
628
|
+
const otherW = Number(n.width) || 2;
|
|
629
|
+
const otherH = Number(n.height) || 1.5;
|
|
630
|
+
const combinedHalfWidth = (otherW + newW) / 2;
|
|
631
|
+
const combinedHalfHeight = (otherH + newH) / 2;
|
|
632
|
+
return dx < combinedHalfWidth && dz < combinedHalfHeight;
|
|
633
|
+
});
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
let iterations = 0;
|
|
637
|
+
while (checkCollision(x, z) && iterations < 50) {
|
|
638
|
+
iterations++;
|
|
639
|
+
if (dirX !== 0) z += shiftStepZ;
|
|
640
|
+
else x += shiftStepX;
|
|
641
|
+
}
|
|
642
|
+
} else if (state.nodes.length > 0) {
|
|
586
643
|
const lastNode = state.nodes[state.nodes.length - 1];
|
|
587
644
|
x = lastNode.x + 5;
|
|
588
645
|
z = lastNode.z;
|
|
@@ -606,6 +663,53 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
606
663
|
};
|
|
607
664
|
|
|
608
665
|
state.nodes.push(newNode);
|
|
666
|
+
|
|
667
|
+
// Auto-Connect logic
|
|
668
|
+
if (args.connection && args.referenceNodeId) {
|
|
669
|
+
let termStart = 'none';
|
|
670
|
+
let termEnd = 'none';
|
|
671
|
+
let borderStyle = 'solid';
|
|
672
|
+
|
|
673
|
+
// UML Mapping
|
|
674
|
+
switch (args.connection.type) {
|
|
675
|
+
case 'inheritance': termEnd = 'triangle-empty'; break;
|
|
676
|
+
case 'implementation': termEnd = 'triangle-empty'; borderStyle = 'dashed'; break;
|
|
677
|
+
case 'dependency': termEnd = 'arrow'; borderStyle = 'dashed'; break;
|
|
678
|
+
case 'association': termEnd = 'arrow'; break;
|
|
679
|
+
case 'aggregation': termStart = 'diamond-empty'; break;
|
|
680
|
+
case 'composition': termStart = 'diamond-filled'; break;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Points? Simple defaults based on direction
|
|
684
|
+
let sp = 0;
|
|
685
|
+
let tp = 0;
|
|
686
|
+
switch(args.direction) {
|
|
687
|
+
case "RIGHT": sp=1; tp=3; break;
|
|
688
|
+
case "LEFT": sp=3; tp=1; break;
|
|
689
|
+
case "DOWN": sp=2; tp=0; break;
|
|
690
|
+
case "UP": sp=0; tp=2; break;
|
|
691
|
+
default: sp=2; tp=0; break;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const newEdge: EdgeData = {
|
|
695
|
+
id: randomUUID(),
|
|
696
|
+
sourceId: args.referenceNodeId,
|
|
697
|
+
targetId: newNodeId,
|
|
698
|
+
sourcePointIndex: sp,
|
|
699
|
+
targetPointIndex: tp,
|
|
700
|
+
label: args.connection.label,
|
|
701
|
+
style: 'line',
|
|
702
|
+
routingType: 'orthogonal',
|
|
703
|
+
color: '#000000',
|
|
704
|
+
thickness: 0.01,
|
|
705
|
+
fontSize: 20,
|
|
706
|
+
borderStyle: borderStyle as any,
|
|
707
|
+
terminationStart: termStart as any,
|
|
708
|
+
terminationEnd: termEnd as any
|
|
709
|
+
};
|
|
710
|
+
state.edges.push(newEdge);
|
|
711
|
+
}
|
|
712
|
+
|
|
609
713
|
await saveDiagramFile(args.filePath, state);
|
|
610
714
|
return { content: [{ type: "text", text: `Created UML Class '${args.className}'` }] };
|
|
611
715
|
}
|
package/src/utils/fileHandler.ts
CHANGED