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