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.
@@ -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: {
@@ -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
- if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dodraw-mcp-server",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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: {
@@ -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
- if (args.x === undefined && args.y === undefined && state.nodes.length > 0) {
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
  }