dodraw-mcp-server 0.1.5 → 0.1.6
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/tools/diagramTools.js +135 -3
- package/package.json +1 -1
- package/src/tools/diagramTools.ts +115 -3
|
@@ -192,7 +192,7 @@ exports.toolDefinitions = [
|
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
name: "create_db_table",
|
|
195
|
-
description: "Create a Database Entity (ERD Table).
|
|
195
|
+
description: "Create a Database Entity (ERD Table). Supports absolute positioning (x,y) OR relative positioning (referenceNodeId, direction). Can also auto-connect.",
|
|
196
196
|
inputSchema: {
|
|
197
197
|
type: "object",
|
|
198
198
|
properties: {
|
|
@@ -213,7 +213,19 @@ exports.toolDefinitions = [
|
|
|
213
213
|
},
|
|
214
214
|
x: { type: "number" },
|
|
215
215
|
y: { type: "number" },
|
|
216
|
-
id: { type: "string", description: "Optional explicit ID" }
|
|
216
|
+
id: { type: "string", description: "Optional explicit ID" },
|
|
217
|
+
referenceNodeId: { type: "string", description: "Optional. ID of an existing node to place this new table relative to." },
|
|
218
|
+
direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Optional. Direction to place relative to referenceNodeId." },
|
|
219
|
+
connection: {
|
|
220
|
+
type: "object",
|
|
221
|
+
description: "Optional. If provided, creates an edge connecting the reference node to this new table.",
|
|
222
|
+
properties: {
|
|
223
|
+
type: { type: "string", enum: ["one-to-one", "one-to-many", "many-to-many"], description: "Relationship type" },
|
|
224
|
+
label: { type: "string" },
|
|
225
|
+
direction: { type: "string", enum: ["source-to-target", "target-to-source", "bi-directional"], description: "Edge direction flow" }
|
|
226
|
+
},
|
|
227
|
+
required: ["type"]
|
|
228
|
+
}
|
|
217
229
|
},
|
|
218
230
|
required: ["filePath", "tableName", "columns"]
|
|
219
231
|
}
|
|
@@ -589,7 +601,62 @@ async function handleToolCall(name, args) {
|
|
|
589
601
|
// Auto-placement logic if X/Y not provided
|
|
590
602
|
let x = args.x !== undefined ? Number(args.x) : 0;
|
|
591
603
|
let z = args.y !== undefined ? Number(args.y) : 0;
|
|
592
|
-
|
|
604
|
+
// Relative Placement Logic
|
|
605
|
+
if (args.referenceNodeId && args.direction) {
|
|
606
|
+
const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
|
|
607
|
+
if (!sourceNode)
|
|
608
|
+
throw new Error(`Reference node ${args.referenceNodeId} not found`);
|
|
609
|
+
const INITIAL_SPACING = 2.0;
|
|
610
|
+
let dirX = 0;
|
|
611
|
+
let dirZ = 0;
|
|
612
|
+
const srcW = Number(sourceNode.width) || 2;
|
|
613
|
+
const srcH = Number(sourceNode.height) || 1.5;
|
|
614
|
+
switch (args.direction) {
|
|
615
|
+
case "RIGHT":
|
|
616
|
+
dirX = 1;
|
|
617
|
+
break;
|
|
618
|
+
case "LEFT":
|
|
619
|
+
dirX = -1;
|
|
620
|
+
break;
|
|
621
|
+
case "DOWN":
|
|
622
|
+
dirZ = 1;
|
|
623
|
+
break;
|
|
624
|
+
case "UP":
|
|
625
|
+
dirZ = -1;
|
|
626
|
+
break;
|
|
627
|
+
}
|
|
628
|
+
x = sourceNode.x + (dirX * (srcW + INITIAL_SPACING));
|
|
629
|
+
z = sourceNode.z + (dirZ * (srcH + INITIAL_SPACING));
|
|
630
|
+
// Collision Avoidance reuse
|
|
631
|
+
const COLLISION_SPACING = 0.5;
|
|
632
|
+
const shiftStepX = srcW + COLLISION_SPACING;
|
|
633
|
+
const shiftStepZ = srcH + COLLISION_SPACING;
|
|
634
|
+
const newW = width;
|
|
635
|
+
const newH = height;
|
|
636
|
+
const checkCollision = (cx, cz) => {
|
|
637
|
+
return state.nodes.some(n => {
|
|
638
|
+
if (n.layerId !== sourceNode.layerId)
|
|
639
|
+
return false;
|
|
640
|
+
const dx = Math.abs(n.x - cx);
|
|
641
|
+
const dz = Math.abs(n.z - cz);
|
|
642
|
+
const otherW = Number(n.width) || 2;
|
|
643
|
+
const otherH = Number(n.height) || 1.5;
|
|
644
|
+
const combinedHalfWidth = (otherW + newW) / 2;
|
|
645
|
+
const combinedHalfHeight = (otherH + newH) / 2;
|
|
646
|
+
return dx < combinedHalfWidth && dz < combinedHalfHeight;
|
|
647
|
+
});
|
|
648
|
+
};
|
|
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
|
+
}
|
|
658
|
+
else if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
|
|
659
|
+
// Fallback to "next to last" if no explicit relative info
|
|
593
660
|
const lastNode = state.nodes[state.nodes.length - 1];
|
|
594
661
|
x = lastNode.x + 5;
|
|
595
662
|
z = lastNode.z;
|
|
@@ -615,6 +682,71 @@ async function handleToolCall(name, args) {
|
|
|
615
682
|
}
|
|
616
683
|
};
|
|
617
684
|
state.nodes.push(newNode);
|
|
685
|
+
// Auto-Connect logic
|
|
686
|
+
if (args.connection && args.referenceNodeId) {
|
|
687
|
+
let termStart = 'none';
|
|
688
|
+
let termEnd = 'none';
|
|
689
|
+
// Simplified mapping for DB
|
|
690
|
+
if (args.connection.type === 'one-to-many') {
|
|
691
|
+
// Source (Reference) -> Target (New)
|
|
692
|
+
// If Reference is "Users" (1) and New is "Orders" (N)
|
|
693
|
+
// termStart = one, termEnd = many
|
|
694
|
+
termStart = 'crows-one';
|
|
695
|
+
termEnd = 'crows-many';
|
|
696
|
+
}
|
|
697
|
+
else if (args.connection.type === 'one-to-one') {
|
|
698
|
+
termStart = 'crows-one';
|
|
699
|
+
termEnd = 'crows-one';
|
|
700
|
+
}
|
|
701
|
+
else if (args.connection.type === 'many-to-many') {
|
|
702
|
+
termStart = 'crows-many';
|
|
703
|
+
termEnd = 'crows-many';
|
|
704
|
+
}
|
|
705
|
+
// Points? Simple defaults based on direction or just center-ish
|
|
706
|
+
// Let's use the logic from add_directional_node for cleaner ports if possible
|
|
707
|
+
// But simple 0-3 logic:
|
|
708
|
+
let sp = 0;
|
|
709
|
+
let tp = 0;
|
|
710
|
+
switch (args.direction) {
|
|
711
|
+
case "RIGHT":
|
|
712
|
+
sp = 1;
|
|
713
|
+
tp = 3;
|
|
714
|
+
break;
|
|
715
|
+
case "LEFT":
|
|
716
|
+
sp = 3;
|
|
717
|
+
tp = 1;
|
|
718
|
+
break;
|
|
719
|
+
case "DOWN":
|
|
720
|
+
sp = 2;
|
|
721
|
+
tp = 0;
|
|
722
|
+
break;
|
|
723
|
+
case "UP":
|
|
724
|
+
sp = 0;
|
|
725
|
+
tp = 2;
|
|
726
|
+
break;
|
|
727
|
+
default:
|
|
728
|
+
sp = 2;
|
|
729
|
+
tp = 0;
|
|
730
|
+
break;
|
|
731
|
+
}
|
|
732
|
+
const newEdge = {
|
|
733
|
+
id: (0, crypto_1.randomUUID)(),
|
|
734
|
+
sourceId: args.referenceNodeId,
|
|
735
|
+
targetId: newNodeId,
|
|
736
|
+
sourcePointIndex: sp,
|
|
737
|
+
targetPointIndex: tp,
|
|
738
|
+
label: args.connection.label,
|
|
739
|
+
style: 'line',
|
|
740
|
+
routingType: 'orthogonal',
|
|
741
|
+
color: '#000000',
|
|
742
|
+
thickness: 0.01,
|
|
743
|
+
fontSize: 20,
|
|
744
|
+
borderStyle: 'solid',
|
|
745
|
+
terminationStart: termStart,
|
|
746
|
+
terminationEnd: termEnd
|
|
747
|
+
};
|
|
748
|
+
state.edges.push(newEdge);
|
|
749
|
+
}
|
|
618
750
|
await (0, fileHandler_1.saveDiagramFile)(args.filePath, state);
|
|
619
751
|
return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
|
|
620
752
|
}
|
package/package.json
CHANGED
|
@@ -192,7 +192,8 @@ export const toolDefinitions: Tool[] = [
|
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
name: "create_db_table",
|
|
195
|
-
|
|
195
|
+
|
|
196
|
+
description: "Create a Database Entity (ERD Table). Supports absolute positioning (x,y) OR relative positioning (referenceNodeId, direction). Can also auto-connect.",
|
|
196
197
|
inputSchema: {
|
|
197
198
|
type: "object",
|
|
198
199
|
properties: {
|
|
@@ -213,7 +214,19 @@ export const toolDefinitions: Tool[] = [
|
|
|
213
214
|
},
|
|
214
215
|
x: { type: "number" },
|
|
215
216
|
y: { type: "number" },
|
|
216
|
-
id: { type: "string", description: "Optional explicit ID" }
|
|
217
|
+
id: { type: "string", description: "Optional explicit ID" },
|
|
218
|
+
referenceNodeId: { type: "string", description: "Optional. ID of an existing node to place this new table relative to." },
|
|
219
|
+
direction: { type: "string", enum: ["UP", "DOWN", "LEFT", "RIGHT"], description: "Optional. Direction to place relative to referenceNodeId." },
|
|
220
|
+
connection: {
|
|
221
|
+
type: "object",
|
|
222
|
+
description: "Optional. If provided, creates an edge connecting the reference node to this new table.",
|
|
223
|
+
properties: {
|
|
224
|
+
type: { type: "string", enum: ["one-to-one", "one-to-many", "many-to-many"], description: "Relationship type" },
|
|
225
|
+
label: { type: "string" },
|
|
226
|
+
direction: { type: "string", enum: ["source-to-target", "target-to-source", "bi-directional"], description: "Edge direction flow" }
|
|
227
|
+
},
|
|
228
|
+
required: ["type"]
|
|
229
|
+
}
|
|
217
230
|
},
|
|
218
231
|
required: ["filePath", "tableName", "columns"]
|
|
219
232
|
}
|
|
@@ -612,7 +625,55 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
612
625
|
let x = args.x !== undefined ? Number(args.x) : 0;
|
|
613
626
|
let z = args.y !== undefined ? Number(args.y) : 0;
|
|
614
627
|
|
|
615
|
-
|
|
628
|
+
// Relative Placement Logic
|
|
629
|
+
if (args.referenceNodeId && args.direction) {
|
|
630
|
+
const sourceNode = state.nodes.find(n => n.id === args.referenceNodeId);
|
|
631
|
+
if (!sourceNode) throw new Error(`Reference node ${args.referenceNodeId} not found`);
|
|
632
|
+
|
|
633
|
+
const INITIAL_SPACING = 2.0;
|
|
634
|
+
let dirX = 0;
|
|
635
|
+
let dirZ = 0;
|
|
636
|
+
const srcW = Number(sourceNode.width) || 2;
|
|
637
|
+
const srcH = Number(sourceNode.height) || 1.5;
|
|
638
|
+
|
|
639
|
+
switch (args.direction) {
|
|
640
|
+
case "RIGHT": dirX = 1; break;
|
|
641
|
+
case "LEFT": dirX = -1; break;
|
|
642
|
+
case "DOWN": dirZ = 1; break;
|
|
643
|
+
case "UP": dirZ = -1; break;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
x = sourceNode.x + (dirX * (srcW + INITIAL_SPACING));
|
|
647
|
+
z = sourceNode.z + (dirZ * (srcH + INITIAL_SPACING));
|
|
648
|
+
|
|
649
|
+
// Collision Avoidance reuse
|
|
650
|
+
const COLLISION_SPACING = 0.5;
|
|
651
|
+
const shiftStepX = srcW + COLLISION_SPACING;
|
|
652
|
+
const shiftStepZ = srcH + COLLISION_SPACING;
|
|
653
|
+
const newW = width;
|
|
654
|
+
const newH = height;
|
|
655
|
+
|
|
656
|
+
const checkCollision = (cx: number, cz: number) => {
|
|
657
|
+
return state.nodes.some(n => {
|
|
658
|
+
if (n.layerId !== sourceNode.layerId) return false;
|
|
659
|
+
const dx = Math.abs(n.x - cx);
|
|
660
|
+
const dz = Math.abs(n.z - cz);
|
|
661
|
+
const otherW = Number(n.width) || 2;
|
|
662
|
+
const otherH = Number(n.height) || 1.5;
|
|
663
|
+
const combinedHalfWidth = (otherW + newW) / 2;
|
|
664
|
+
const combinedHalfHeight = (otherH + newH) / 2;
|
|
665
|
+
return dx < combinedHalfWidth && dz < combinedHalfHeight;
|
|
666
|
+
});
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
let iterations = 0;
|
|
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) {
|
|
676
|
+
// Fallback to "next to last" if no explicit relative info
|
|
616
677
|
const lastNode = state.nodes[state.nodes.length - 1];
|
|
617
678
|
x = lastNode.x + 5;
|
|
618
679
|
z = lastNode.z;
|
|
@@ -640,6 +701,57 @@ export async function handleToolCall(name: string, args: any): Promise<any> {
|
|
|
640
701
|
};
|
|
641
702
|
|
|
642
703
|
state.nodes.push(newNode);
|
|
704
|
+
|
|
705
|
+
// Auto-Connect logic
|
|
706
|
+
if (args.connection && args.referenceNodeId) {
|
|
707
|
+
let termStart = 'none';
|
|
708
|
+
let termEnd = 'none';
|
|
709
|
+
// Simplified mapping for DB
|
|
710
|
+
if (args.connection.type === 'one-to-many') {
|
|
711
|
+
// Source (Reference) -> Target (New)
|
|
712
|
+
// If Reference is "Users" (1) and New is "Orders" (N)
|
|
713
|
+
// termStart = one, termEnd = many
|
|
714
|
+
termStart = 'crows-one';
|
|
715
|
+
termEnd = 'crows-many';
|
|
716
|
+
} else if (args.connection.type === 'one-to-one') {
|
|
717
|
+
termStart = 'crows-one';
|
|
718
|
+
termEnd = 'crows-one';
|
|
719
|
+
} else if (args.connection.type === 'many-to-many') {
|
|
720
|
+
termStart = 'crows-many';
|
|
721
|
+
termEnd = 'crows-many';
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Points? Simple defaults based on direction or just center-ish
|
|
725
|
+
// Let's use the logic from add_directional_node for cleaner ports if possible
|
|
726
|
+
// But simple 0-3 logic:
|
|
727
|
+
let sp = 0;
|
|
728
|
+
let tp = 0;
|
|
729
|
+
switch(args.direction) {
|
|
730
|
+
case "RIGHT": sp=1; tp=3; break;
|
|
731
|
+
case "LEFT": sp=3; tp=1; break;
|
|
732
|
+
case "DOWN": sp=2; tp=0; break;
|
|
733
|
+
case "UP": sp=0; tp=2; break;
|
|
734
|
+
default: sp=2; tp=0; break;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const newEdge: EdgeData = {
|
|
738
|
+
id: randomUUID(),
|
|
739
|
+
sourceId: args.referenceNodeId,
|
|
740
|
+
targetId: newNodeId,
|
|
741
|
+
sourcePointIndex: sp,
|
|
742
|
+
targetPointIndex: tp,
|
|
743
|
+
label: args.connection.label,
|
|
744
|
+
style: 'line',
|
|
745
|
+
routingType: 'orthogonal',
|
|
746
|
+
color: '#000000',
|
|
747
|
+
thickness: 0.01,
|
|
748
|
+
fontSize: 20,
|
|
749
|
+
borderStyle: 'solid',
|
|
750
|
+
terminationStart: termStart as any,
|
|
751
|
+
terminationEnd: termEnd as any
|
|
752
|
+
};
|
|
753
|
+
state.edges.push(newEdge);
|
|
754
|
+
}
|
|
643
755
|
await saveDiagramFile(args.filePath, state);
|
|
644
756
|
return { content: [{ type: "text", text: `Created DB Table '${args.tableName}'` }] };
|
|
645
757
|
}
|