@modudraft/mcp 0.1.1 → 0.2.0

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.
Files changed (2) hide show
  1. package/dist/index.js +263 -25
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -14599,7 +14599,11 @@ var archetypes_default = {
14599
14599
  suggestions: ["10GB", "100GB", "1TB", "10TB"]
14600
14600
  },
14601
14601
  { key: "indexes", label: "Indexes", type: "text" },
14602
- { key: "connection-pool", label: "Connection Pool", type: "number" }
14602
+ { key: "connection-pool", label: "Connection Pool", type: "number" },
14603
+ { key: "database-name", label: "Database Name", type: "text" },
14604
+ { key: "host", label: "Host", type: "text" },
14605
+ { key: "port", label: "Port", type: "number" },
14606
+ { key: "schema", label: "Schema", type: "text" }
14603
14607
  ]
14604
14608
  },
14605
14609
  queue: {
@@ -14675,7 +14679,14 @@ var archetypes_default = {
14675
14679
  label: "CPU",
14676
14680
  type: "text",
14677
14681
  suggestions: ["0.25 vCPU", "1 vCPU", "4 vCPU", "16 vCPU"]
14678
- }
14682
+ },
14683
+ {
14684
+ key: "region",
14685
+ label: "Region",
14686
+ type: "text",
14687
+ suggestions: ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]
14688
+ },
14689
+ { key: "hostname", label: "Hostname", type: "text" }
14679
14690
  ]
14680
14691
  },
14681
14692
  cache: {
@@ -14713,7 +14724,9 @@ var archetypes_default = {
14713
14724
  label: "Size",
14714
14725
  type: "text",
14715
14726
  suggestions: ["512MB", "2GB", "8GB", "64GB"]
14716
- }
14727
+ },
14728
+ { key: "host", label: "Host", type: "text" },
14729
+ { key: "port", label: "Port", type: "number" }
14717
14730
  ]
14718
14731
  },
14719
14732
  gateway: {
@@ -14798,6 +14811,13 @@ var archetypes_default = {
14798
14811
  label: "Region Replication",
14799
14812
  type: "enum",
14800
14813
  options: ["single-region", "multi-region"]
14814
+ },
14815
+ { key: "bucket-name", label: "Bucket Name", type: "text" },
14816
+ {
14817
+ key: "region",
14818
+ label: "Region",
14819
+ type: "text",
14820
+ suggestions: ["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1"]
14801
14821
  }
14802
14822
  ]
14803
14823
  },
@@ -16187,6 +16207,18 @@ var tools_default = {
16187
16207
  iconSlug: "fluentbit",
16188
16208
  brandColor: "#49BDA5"
16189
16209
  },
16210
+ "elastic-apm": {
16211
+ archetype: "observability",
16212
+ label: "Elastic APM",
16213
+ iconSlug: "elasticstack",
16214
+ brandColor: "#005571"
16215
+ },
16216
+ logstash: {
16217
+ archetype: "observability",
16218
+ label: "Logstash",
16219
+ iconSlug: "logstash",
16220
+ brandColor: "#005571"
16221
+ },
16190
16222
  digitalocean: {
16191
16223
  archetype: "compute",
16192
16224
  label: "DigitalOcean",
@@ -18354,9 +18386,114 @@ function importDiagram(json2) {
18354
18386
  }
18355
18387
 
18356
18388
  // src/tools.ts
18389
+ var DESCRIPTOR_WORDS = /* @__PURE__ */ new Set([
18390
+ "bucket",
18391
+ "cluster",
18392
+ "database",
18393
+ "db",
18394
+ "server",
18395
+ "service",
18396
+ "cache",
18397
+ "queue",
18398
+ "store",
18399
+ "storage",
18400
+ "topic",
18401
+ "stream",
18402
+ "instance",
18403
+ "container",
18404
+ "pod",
18405
+ "node",
18406
+ "system",
18407
+ "platform",
18408
+ "layer",
18409
+ "tier",
18410
+ "component",
18411
+ "infrastructure",
18412
+ "provider",
18413
+ "engine"
18414
+ ]);
18415
+ var ALIASES = {
18416
+ postgres: "postgresql",
18417
+ pg: "postgresql",
18418
+ mongo: "mongodb",
18419
+ k8s: "kubernetes",
18420
+ kube: "kubernetes",
18421
+ rabbit: "rabbitmq",
18422
+ otel: "opentelemetry",
18423
+ elasticapm: "elastic-apm",
18424
+ "elastic-apm": "elastic-apm",
18425
+ elk: "kibana",
18426
+ mssql: "sqlserver",
18427
+ "sql-server": "sqlserver",
18428
+ "sql server": "sqlserver",
18429
+ aurora: "aurora",
18430
+ "cloud sql": "cloudsql",
18431
+ "big table": "bigtable",
18432
+ cosmos: "cosmosdb",
18433
+ "cosmos db": "cosmosdb",
18434
+ dynamo: "dynamodb",
18435
+ "dynamo db": "dynamodb",
18436
+ redis: "redis",
18437
+ "aws s3": "s3",
18438
+ "s3 bucket": "s3",
18439
+ "google cloud storage": "gcs",
18440
+ "gcs bucket": "gcs",
18441
+ "azure blob": "azureblob",
18442
+ "azure blob storage": "azureblob",
18443
+ lambda: "lambda",
18444
+ "cloud functions": "cloudfunctions",
18445
+ "cloud run": "cloudrun",
18446
+ "github actions": "githubactions",
18447
+ "google gemini": "googlegemini",
18448
+ "open ai": "openai",
18449
+ "hugging face": "huggingface"
18450
+ };
18451
+ function tryResolveByName(name) {
18452
+ const slug = name.replace(/[^a-z0-9]/g, "");
18453
+ const aliasKey = ALIASES[slug] ?? ALIASES[name];
18454
+ if (aliasKey && tools[aliasKey]) {
18455
+ return { archetype: tools[aliasKey].archetype, toolKey: aliasKey };
18456
+ }
18457
+ if (tools[slug]) return { archetype: tools[slug].archetype, toolKey: slug };
18458
+ for (const [key, tool] of Object.entries(tools)) {
18459
+ if (tool.label.toLowerCase() === name) return { archetype: tool.archetype, toolKey: key };
18460
+ }
18461
+ return null;
18462
+ }
18463
+ function resolveEntity(rawName) {
18464
+ const lower = rawName.toLowerCase().trim();
18465
+ const direct = tryResolveByName(lower);
18466
+ if (direct) return direct;
18467
+ const stripped = lower.split(/\s+/).filter((w2) => !DESCRIPTOR_WORDS.has(w2)).join(" ").trim();
18468
+ if (stripped && stripped !== lower) {
18469
+ const fromStripped = tryResolveByName(stripped);
18470
+ if (fromStripped) return fromStripped;
18471
+ }
18472
+ for (const word of lower.split(/\s+/)) {
18473
+ if (word.length < 2) continue;
18474
+ const m = tryResolveByName(word);
18475
+ if (m) return m;
18476
+ }
18477
+ for (const [key, tool] of Object.entries(tools)) {
18478
+ const tLabel = tool.label.toLowerCase();
18479
+ if (tLabel.length > 3 && lower.includes(tLabel)) {
18480
+ return { archetype: tool.archetype, toolKey: key };
18481
+ }
18482
+ }
18483
+ const archSlug = lower.replace(/[^a-z0-9]/g, "");
18484
+ if (archetypes[archSlug]) {
18485
+ return { archetype: archSlug, toolKey: archetypes[archSlug].defaultTool };
18486
+ }
18487
+ for (const [key, arch] of Object.entries(archetypes)) {
18488
+ if (arch.label.toLowerCase() === lower) {
18489
+ return { archetype: key, toolKey: arch.defaultTool };
18490
+ }
18491
+ }
18492
+ return null;
18493
+ }
18357
18494
  var getDiagram = {
18358
18495
  name: "get_diagram",
18359
- description: "Return the current diagram state: all nodes and edges with their properties.",
18496
+ description: "Return the current diagram state: all nodes and edges with their properties, including any metadata.",
18360
18497
  inputSchema: { type: "object", properties: {} },
18361
18498
  handler: () => {
18362
18499
  const { nodes, edges, title } = getState();
@@ -18364,12 +18501,14 @@ var getDiagram = {
18364
18501
  title,
18365
18502
  nodeCount: nodes.length,
18366
18503
  edgeCount: edges.length,
18367
- nodes: nodes.map((n) => ({
18368
- id: n.id,
18369
- type: n.type,
18370
- position: n.position,
18371
- data: n.data
18372
- })),
18504
+ nodes: nodes.map((n) => {
18505
+ const base = { id: n.id, type: n.type, position: n.position, data: n.data };
18506
+ if (n.type === "sysNode") {
18507
+ const d = n.data;
18508
+ return { ...base, metadata: d.customProperties ?? {} };
18509
+ }
18510
+ return base;
18511
+ }),
18373
18512
  edges: edges.map((e) => ({
18374
18513
  id: e.id,
18375
18514
  source: e.source,
@@ -18381,14 +18520,17 @@ var getDiagram = {
18381
18520
  };
18382
18521
  var addNodeTool = {
18383
18522
  name: "add_node",
18384
- description: "Add a system component node to the diagram. Use list_archetypes to see valid archetypes, list_tools to see valid tools.",
18523
+ description: "Add a system component node to the diagram. Prefer passing 'name' (e.g. 'Sentry', 'GCS bucket', 'Elastic APM', 'PostgreSQL') \u2014 the server will automatically resolve the correct archetype and tool. Use list_archetypes / list_tools if you need explicit control. Pass 'metadata' for structured properties like schema, port, host, region, etc. Use list_archetypes to see well-known attribute keys per archetype.",
18385
18524
  inputSchema: {
18386
18525
  type: "object",
18387
- required: ["archetype"],
18388
18526
  properties: {
18527
+ name: {
18528
+ type: "string",
18529
+ description: "Natural name of the technology (e.g. 'Sentry', 'GCS bucket', 'Elastic APM', 'Redis cache', 'PostgreSQL'). Auto-resolves to the correct archetype and tool. Takes precedence over 'archetype'/'tool' when provided."
18530
+ },
18389
18531
  archetype: {
18390
18532
  type: "string",
18391
- description: "Archetype key e.g. 'database', 'queue', 'compute'"
18533
+ description: "Archetype key e.g. 'database', 'queue', 'compute'. Required if 'name' is not given."
18392
18534
  },
18393
18535
  tool: {
18394
18536
  type: "string",
@@ -18396,28 +18538,59 @@ var addNodeTool = {
18396
18538
  },
18397
18539
  label: {
18398
18540
  type: "string",
18399
- description: "Display label for the node. Defaults to the tool or archetype label."
18541
+ description: "Display label. Defaults to the resolved tool's proper name (e.g. 'Google Cloud Storage')."
18542
+ },
18543
+ metadata: {
18544
+ type: "object",
18545
+ description: "Structured key-value metadata for this node. Use list_archetypes to see well-known keys (e.g. 'schema', 'host', 'port', 'database-name' for databases; 'region', 'hostname' for compute; 'bucket-name', 'region' for storage). Any string key is accepted.",
18546
+ additionalProperties: { type: "string" }
18400
18547
  },
18401
18548
  x: { type: "number", description: "Canvas X position (default 0)" },
18402
18549
  y: { type: "number", description: "Canvas Y position (default 0)" }
18403
18550
  }
18404
18551
  },
18405
18552
  handler: (args) => {
18406
- const archetypeKey = String(args.archetype);
18553
+ let archetypeKey;
18554
+ let toolKey;
18555
+ if (args.name) {
18556
+ const resolved = resolveEntity(String(args.name));
18557
+ if (!resolved) {
18558
+ throw new Error(
18559
+ `Could not resolve "${args.name}" to a known technology. Call list_tools or list_archetypes to browse available options, then pass 'archetype' and 'tool' explicitly.`
18560
+ );
18561
+ }
18562
+ archetypeKey = resolved.archetype;
18563
+ toolKey = args.tool ? String(args.tool) : resolved.toolKey;
18564
+ } else if (args.archetype) {
18565
+ archetypeKey = String(args.archetype);
18566
+ const arch2 = getArchetype(archetypeKey);
18567
+ if (!arch2) {
18568
+ throw new Error(`Unknown archetype "${archetypeKey}". Call list_archetypes to see valid values.`);
18569
+ }
18570
+ toolKey = args.tool ? String(args.tool) : arch2.defaultTool;
18571
+ } else {
18572
+ throw new Error("Provide either 'name' (e.g. 'Sentry') or 'archetype'.");
18573
+ }
18407
18574
  const arch = getArchetype(archetypeKey);
18408
- if (!arch) throw new Error(`Unknown archetype "${archetypeKey}". Call list_archetypes to see valid values.`);
18409
- const toolKey = args.tool ? String(args.tool) : arch.defaultTool;
18575
+ if (!arch) throw new Error(`Unknown archetype "${archetypeKey}".`);
18410
18576
  const toolDef = getTool(toolKey);
18411
18577
  if (!toolDef) throw new Error(`Unknown tool "${toolKey}". Call list_tools to see valid values.`);
18412
18578
  const label = args.label ? String(args.label) : toolDef.label ?? arch.label;
18579
+ const customProperties = args.metadata ? args.metadata : void 0;
18413
18580
  const node = {
18414
18581
  id: genId("node"),
18415
18582
  type: "sysNode",
18416
18583
  position: { x: Number(args.x ?? 0), y: Number(args.y ?? 0) },
18417
- data: { archetype: archetypeKey, concreteTool: toolKey, label }
18584
+ data: { archetype: archetypeKey, concreteTool: toolKey, label, customProperties }
18418
18585
  };
18419
18586
  addNode(node);
18420
- return { id: node.id, type: node.type, data: node.data, position: node.position };
18587
+ return {
18588
+ id: node.id,
18589
+ type: node.type,
18590
+ data: node.data,
18591
+ position: node.position,
18592
+ metadata: customProperties ?? {}
18593
+ };
18421
18594
  }
18422
18595
  };
18423
18596
  var addNoteTool = {
@@ -18487,7 +18660,7 @@ var addBoundaryTool = {
18487
18660
  };
18488
18661
  var updateNodeTool = {
18489
18662
  name: "update_node",
18490
- description: "Update a node's label, tool, or position.",
18663
+ description: "Update a node's label, tool, position, or metadata.",
18491
18664
  inputSchema: {
18492
18665
  type: "object",
18493
18666
  required: ["id"],
@@ -18496,7 +18669,12 @@ var updateNodeTool = {
18496
18669
  label: { type: "string" },
18497
18670
  tool: { type: "string", description: "New tool key (sysNode only)" },
18498
18671
  x: { type: "number" },
18499
- y: { type: "number" }
18672
+ y: { type: "number" },
18673
+ metadata: {
18674
+ type: "object",
18675
+ description: "Metadata key-value pairs to merge into this node's properties. Pass an empty string value to delete a key. Existing keys not mentioned are preserved.",
18676
+ additionalProperties: { type: "string" }
18677
+ }
18500
18678
  }
18501
18679
  },
18502
18680
  handler: (args) => {
@@ -18517,12 +18695,64 @@ var updateNodeTool = {
18517
18695
  if (!getTool(String(args.tool))) throw new Error(`Unknown tool "${args.tool}"`);
18518
18696
  dataPatch.concreteTool = String(args.tool);
18519
18697
  }
18698
+ if (args.metadata !== void 0) {
18699
+ const existing = node.data.customProperties ?? {};
18700
+ const incoming = args.metadata;
18701
+ const merged = { ...existing };
18702
+ for (const [k2, v2] of Object.entries(incoming)) {
18703
+ if (v2 === "" || v2 === null) delete merged[k2];
18704
+ else merged[k2] = String(v2);
18705
+ }
18706
+ dataPatch.customProperties = Object.keys(merged).length ? merged : void 0;
18707
+ }
18520
18708
  if (Object.keys(dataPatch).length) patch.data = { ...node.data, ...dataPatch };
18521
18709
  } else if (args.label !== void 0) {
18522
18710
  patch.data = { ...node.data, text: String(args.label) };
18523
18711
  }
18524
18712
  const updated = updateNode(id, patch);
18525
- return { id: updated.id, data: updated.data, position: updated.position };
18713
+ const updatedData = updated.data;
18714
+ return {
18715
+ id: updated.id,
18716
+ data: updated.data,
18717
+ position: updated.position,
18718
+ metadata: updatedData.customProperties ?? {}
18719
+ };
18720
+ }
18721
+ };
18722
+ var setNodeMetadataTool = {
18723
+ name: "set_node_metadata",
18724
+ description: "Set, update, or delete individual metadata fields on a sysNode. Merges into existing metadata \u2014 unmentioned keys are preserved. Pass an empty string to delete a key. Use list_archetypes to see well-known attribute keys for each archetype (e.g. 'schema', 'host', 'port', 'database-name' for databases).",
18725
+ inputSchema: {
18726
+ type: "object",
18727
+ required: ["id", "metadata"],
18728
+ properties: {
18729
+ id: { type: "string", description: "Node ID" },
18730
+ metadata: {
18731
+ type: "object",
18732
+ description: "Key-value pairs to set. Empty string removes the key. Well-known keys per archetype: database \u2192 schema, host, port, database-name, replication, consistency; cache \u2192 host, port, eviction, ttl; compute \u2192 region, hostname, replicas, language; storage \u2192 bucket-name, region, class; observability \u2192 signal, retention, sampling.",
18733
+ additionalProperties: { type: "string" }
18734
+ }
18735
+ }
18736
+ },
18737
+ handler: (args) => {
18738
+ const id = String(args.id);
18739
+ const node = getNode(id);
18740
+ if (!node) throw new Error(`Node "${id}" not found`);
18741
+ if (node.type !== "sysNode") {
18742
+ throw new Error(`Metadata is only supported on sysNode nodes (type is "${node.type}")`);
18743
+ }
18744
+ const existing = node.data.customProperties ?? {};
18745
+ const incoming = args.metadata;
18746
+ const merged = { ...existing };
18747
+ for (const [k2, v2] of Object.entries(incoming)) {
18748
+ if (v2 === "" || v2 === null) delete merged[k2];
18749
+ else merged[k2] = String(v2);
18750
+ }
18751
+ const customProperties = Object.keys(merged).length ? merged : void 0;
18752
+ const updated = updateNode(id, {
18753
+ data: { ...node.data, customProperties }
18754
+ });
18755
+ return { id: updated.id, metadata: updated.data.customProperties ?? {} };
18526
18756
  }
18527
18757
  };
18528
18758
  var deleteNodeTool = {
@@ -18654,12 +18884,19 @@ var layoutDiagramTool = {
18654
18884
  };
18655
18885
  var listArchetypes = {
18656
18886
  name: "list_archetypes",
18657
- description: "List all available node archetypes (database, queue, compute, etc.) with their default tools.",
18887
+ description: "List all available node archetypes with their default tools and well-known metadata attributes. Use attribute keys with add_node/set_node_metadata to attach structured metadata.",
18658
18888
  inputSchema: { type: "object", properties: {} },
18659
18889
  handler: () => Object.entries(archetypes).map(([key, a]) => ({
18660
18890
  key,
18661
18891
  label: a.label,
18662
- defaultTool: a.defaultTool
18892
+ defaultTool: a.defaultTool,
18893
+ attributes: (a.attributes ?? []).map((attr) => ({
18894
+ key: attr.key,
18895
+ label: attr.label,
18896
+ type: attr.type,
18897
+ ...attr.type === "enum" ? { options: attr.options } : {},
18898
+ ..."suggestions" in attr && attr.suggestions ? { suggestions: attr.suggestions } : {}
18899
+ }))
18663
18900
  }))
18664
18901
  };
18665
18902
  var listTools = {
@@ -18670,7 +18907,7 @@ var listTools = {
18670
18907
  properties: {
18671
18908
  archetype: {
18672
18909
  type: "string",
18673
- description: "Filter by archetype key. Omit to list all 190+ tools."
18910
+ description: "Filter by archetype key. Omit to list all tools."
18674
18911
  }
18675
18912
  }
18676
18913
  },
@@ -18692,6 +18929,7 @@ var ALL_TOOLS = [
18692
18929
  addNoteTool,
18693
18930
  addBoundaryTool,
18694
18931
  updateNodeTool,
18932
+ setNodeMetadataTool,
18695
18933
  deleteNodeTool,
18696
18934
  addEdgeTool,
18697
18935
  deleteEdgeTool,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modudraft/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP server for Modudraft — create and edit system-design diagrams via AI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,7 @@
24
24
  "@modelcontextprotocol/sdk": "^1.12.0"
25
25
  },
26
26
  "devDependencies": {
27
- "@modudraft/core": ">=0.1.5",
27
+ "@modudraft/core": ">=0.1.6",
28
28
  "@types/node": "^22.0.0",
29
29
  "tsup": "^8.5.0",
30
30
  "typescript": "~5.8.0"