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.
@@ -192,7 +192,7 @@ exports.toolDefinitions = [
192
192
  },
193
193
  {
194
194
  name: "create_db_table",
195
- description: "Create a Database Entity (ERD Table). Use this for the FIRST table. For connected tables, use 'add_directional_node' with 'columns' to ensure correct relative positioning.",
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
- x: { type: "number" },
215
- y: { type: "number" },
216
- id: { type: "string", description: "Optional explicit ID" }
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 if X/Y not provided
590
- let x = args.x !== undefined ? Number(args.x) : 0;
591
- let z = args.y !== undefined ? Number(args.y) : 0;
592
- if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodraw-mcp-server",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "MCP server for DoDraw",
5
5
  "main": "dist/src/index.js",
6
6
  "bin": {
@@ -192,7 +192,8 @@ export const toolDefinitions: Tool[] = [
192
192
  },
193
193
  {
194
194
  name: "create_db_table",
195
- description: "Create a Database Entity (ERD Table). Use this for the FIRST table. For connected tables, use 'add_directional_node' with 'columns' to ensure correct relative positioning.",
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
- x: { type: "number" },
215
- y: { type: "number" },
216
- id: { type: "string", description: "Optional explicit ID" }
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 if X/Y not provided
612
- let x = args.x !== undefined ? Number(args.x) : 0;
613
- let z = args.y !== undefined ? Number(args.y) : 0;
623
+ // Auto-placement logic (defaults to 0,0 for first node)
624
+ let x = 0;
625
+ let z = 0;
614
626
 
615
- if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
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
  }