@hiabhee/archdraw-mcp-server 1.0.2 → 1.0.5

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 (79) hide show
  1. package/dist/index.js +5 -742
  2. package/dist/index.js.map +1 -1
  3. package/dist/lib/checkpoints.d.ts +4 -0
  4. package/dist/lib/checkpoints.d.ts.map +1 -1
  5. package/dist/lib/checkpoints.js +14 -1
  6. package/dist/lib/checkpoints.js.map +1 -1
  7. package/dist/lib/diagram-state.d.ts +1 -0
  8. package/dist/lib/diagram-state.d.ts.map +1 -1
  9. package/dist/lib/diagram-state.js +5 -2
  10. package/dist/lib/diagram-state.js.map +1 -1
  11. package/dist/lib/http.d.ts +12 -0
  12. package/dist/lib/http.d.ts.map +1 -1
  13. package/dist/lib/http.js +11 -1
  14. package/dist/lib/http.js.map +1 -1
  15. package/dist/lib/preview-links.d.ts +20 -0
  16. package/dist/lib/preview-links.d.ts.map +1 -0
  17. package/dist/lib/preview-links.js +22 -0
  18. package/dist/lib/preview-links.js.map +1 -0
  19. package/dist/lib/schema.js +2 -2
  20. package/dist/lib/schema.js.map +1 -1
  21. package/dist/server.d.ts +32 -34
  22. package/dist/server.d.ts.map +1 -1
  23. package/dist/server.js +739 -141
  24. package/dist/server.js.map +1 -1
  25. package/dist/tools/apply-template.d.ts +1 -0
  26. package/dist/tools/apply-template.d.ts.map +1 -1
  27. package/dist/tools/apply-template.js +16 -8
  28. package/dist/tools/apply-template.js.map +1 -1
  29. package/dist/tools/export-diagram.d.ts +1 -1
  30. package/dist/tools/export-diagram.d.ts.map +1 -1
  31. package/dist/tools/export-diagram.js +6 -6
  32. package/dist/tools/export-diagram.js.map +1 -1
  33. package/dist/tools/fix-layout.js +2 -2
  34. package/dist/tools/fix-layout.js.map +1 -1
  35. package/dist/tools/generate-diagram.d.ts +2 -0
  36. package/dist/tools/generate-diagram.d.ts.map +1 -1
  37. package/dist/tools/generate-diagram.js +29 -211
  38. package/dist/tools/generate-diagram.js.map +1 -1
  39. package/dist/tools/read-me.js +1 -1
  40. package/dist/tools/update-diagram.d.ts +1 -0
  41. package/dist/tools/update-diagram.d.ts.map +1 -1
  42. package/dist/tools/update-diagram.js +5 -4
  43. package/dist/tools/update-diagram.js.map +1 -1
  44. package/dist/types/index.d.ts +0 -31
  45. package/dist/types/index.d.ts.map +1 -1
  46. package/dist/ui/diagram-widget.d.ts +3 -0
  47. package/dist/ui/diagram-widget.d.ts.map +1 -0
  48. package/dist/ui/diagram-widget.html +282 -0
  49. package/dist/ui/diagram-widget.js +4 -0
  50. package/dist/ui/diagram-widget.js.map +1 -0
  51. package/package.json +15 -4
  52. package/dist/lib/__tests__/constants.test.d.ts +0 -2
  53. package/dist/lib/__tests__/constants.test.d.ts.map +0 -1
  54. package/dist/lib/__tests__/constants.test.js +0 -47
  55. package/dist/lib/__tests__/constants.test.js.map +0 -1
  56. package/dist/lib/__tests__/diagram-state.test.d.ts +0 -2
  57. package/dist/lib/__tests__/diagram-state.test.d.ts.map +0 -1
  58. package/dist/lib/__tests__/diagram-state.test.js +0 -82
  59. package/dist/lib/__tests__/diagram-state.test.js.map +0 -1
  60. package/dist/lib/__tests__/elk-runner.test.d.ts +0 -2
  61. package/dist/lib/__tests__/elk-runner.test.d.ts.map +0 -1
  62. package/dist/lib/__tests__/elk-runner.test.js +0 -87
  63. package/dist/lib/__tests__/elk-runner.test.js.map +0 -1
  64. package/dist/lib/elk-runner.d.ts +0 -29
  65. package/dist/lib/elk-runner.d.ts.map +0 -1
  66. package/dist/lib/elk-runner.js +0 -532
  67. package/dist/lib/elk-runner.js.map +0 -1
  68. package/dist/lib/prompt-builder.d.ts +0 -14
  69. package/dist/lib/prompt-builder.d.ts.map +0 -1
  70. package/dist/lib/prompt-builder.js +0 -133
  71. package/dist/lib/prompt-builder.js.map +0 -1
  72. package/dist/tools/__tests__/export-diagram.test.d.ts +0 -2
  73. package/dist/tools/__tests__/export-diagram.test.d.ts.map +0 -1
  74. package/dist/tools/__tests__/export-diagram.test.js +0 -40
  75. package/dist/tools/__tests__/export-diagram.test.js.map +0 -1
  76. package/dist/tools/__tests__/update-diagram.test.d.ts +0 -2
  77. package/dist/tools/__tests__/update-diagram.test.d.ts.map +0 -1
  78. package/dist/tools/__tests__/update-diagram.test.js +0 -98
  79. package/dist/tools/__tests__/update-diagram.test.js.map +0 -1
package/dist/server.js CHANGED
@@ -1,287 +1,885 @@
1
+ import { randomUUID } from 'node:crypto';
1
2
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
4
  import { generateDiagram } from './tools/generate-diagram.js';
4
5
  import { fixLayout } from './tools/fix-layout.js';
5
6
  import { listNodeTypes } from './tools/list-nodes.js';
6
7
  import { applyTemplate, getAvailableTemplates } from './tools/apply-template.js';
7
8
  import { getReadMe } from './tools/read-me.js';
8
- import { getDiagramState } from './lib/diagram-state.js';
9
+ import { clearDiagramState, getDiagramState, hasDiagramState, setDiagramState } from './lib/diagram-state.js';
10
+ import { clearCheckpoints, exportCheckpoints, importCheckpoints } from './lib/checkpoints.js';
11
+ import { runWithMcpContext } from './lib/http.js';
12
+ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
9
13
  import { validateDiagram } from './tools/validate-diagram.js';
10
14
  import { updateDiagram } from './tools/update-diagram.js';
11
15
  import { saveCheckpoint } from './tools/save-checkpoint.js';
12
16
  import { loadCheckpoint } from './tools/load-checkpoint.js';
13
17
  import { exportDiagram } from './tools/export-diagram.js';
14
18
  import { GenerateDiagramInputSchema, FixLayoutInputSchema, ListNodeTypesInputSchema, ApplyTemplateInputSchema, UpdateDiagramInputSchema, SaveCheckpointInputSchema, LoadCheckpointInputSchema, ExportDiagramInputSchema, } from './lib/schema.js';
19
+ import { DIAGRAM_WIDGET_HTML, DIAGRAM_WIDGET_URI } from './ui/diagram-widget.js';
20
+ import { formatPreviewLinks } from './lib/preview-links.js';
15
21
  const TOOLS = [
16
22
  {
17
23
  name: 'generate_diagram',
18
- description: `Generate React Flow nodes and edges from Mermaid code or structured JSON.
24
+ title: 'Generate an ArchDraw diagram',
25
+ description: `Generate a rich architecture diagram with groups, labeled edges, and visual hierarchy. Compatible chat clients display the result as an inline interactive preview; text-only clients receive a concise summary and the ArchDraw site link.
19
26
 
20
- **RECOMMENDED**: Pass the 'mermaid' string containing valid Mermaid graph syntax. The server will use the robust Web UI pipeline to parse, style, and lay out the diagram automatically.
27
+ **ALWAYS show the returned preview link (shareUrl) and edit link (diagramUrl) to the user in your reply** — the diagram is visual and the link is the only way users of text-only clients can see it. Links are created by default in the same response (no extra call needed); they are unguessable, view-by-link, and expire after 7 days.
21
28
 
22
- **IMPORTANT**: When this tool returns a 'diagramUrl', you MUST tell the user to open that URL in their browser.`,
29
+ **INPUT MODES — choose ONE**:
30
+
31
+ **Mode 1 (RECOMMENDED): Mermaid code** — pass a \`graph LR\` / \`graph TD\` diagram in \`mermaid\`.
32
+ This runs the same parser + layout engine the web editor uses, so output is guaranteed to match the product.
33
+ Example:
34
+ graph LR
35
+ subgraph client["Client Tier"]
36
+ Web[Web App]
37
+ Mobile[Mobile App]
38
+ end
39
+ subgraph edge["API Gateway Tier"]
40
+ GW[API Gateway]
41
+ end
42
+ subgraph data["Data Tier"]
43
+ DB[("PostgreSQL")]
44
+ end
45
+ Web --> GW
46
+ Mobile --> GW
47
+ GW --> DB
48
+ Use \`direction\` to pick LR (default) or TD. Any graph \`graph TD\` implies top-down.
49
+ The web parser runs Mermaid → React Flow → Dagre layout, so positions come out clean automatically.
50
+ Optional directives like \`%% archdraw-shape: {"id":"<id>","shape":"shield"}\` are supported for non-native shapes.
51
+
52
+ **Mode 2 (compatibility): Structured JSON** — pass \`nodes\` and \`edges\` arrays (schemas below). ArchDraw converts this input to Mermaid and uses the same editor pipeline.
53
+
54
+ **ALWAYS ALSO PASS** (when you have them):
55
+ - \`userPrompt\`: the user's request verbatim — node subtitles must reflect any tech stack they named.
56
+ - \`techStack\`: explicit list of technologies (["Next.js", "PostgreSQL 15", "Redis"]).
57
+ - \`customFeatures\`: features the user explicitly asked for.
58
+
59
+ **Architecture guidance, not hard rules:** use groups and concise labels where they help comprehension. Model only the components and flows supported by the user’s context. Gateways, event brokers, direct client streams, and domain-specific services are valid when the architecture calls for them.
60
+
61
+ **Privacy:** pass \`publish:false\` only if the user asks to keep the diagram private; then no link is created and the diagram stays in this MCP session. Retain and resend \`workingSessionId\` from the result when working on more than one diagram in parallel.
62
+
63
+ **WORKFLOW**:
64
+ 1. Identify the user’s actual domain and constraints
65
+ 2. Model only supported components and relationships
66
+ 3. Optionally call list_node_types to find icon names
67
+ 4. Design the Mermaid graph: groups (subgraphs) first, then children inside them
68
+ 5. Design edges: use correct communicationType, audit Auth Service edges, verify no security bypass
69
+ 6. **EDGE AUDIT (mandatory)**: Before calling this tool, scan every edge you planned:
70
+ - Does any edge have a client-tier node as the TARGET? DELETE IT (unless both source and target are client tier)
71
+ - Does any single node receive >45% of all edges? That node is a hub -- redistribute connections
72
+ - Do all edges flow LEFT->RIGHT (lower tier -> higher tier)? If not, fix direction.
73
+ 7. Call this tool
74
+
75
+ **NODE FIELDS (JSON mode)**:
76
+ - id (required): snake_case unique ID
77
+ - label (required): 1-3 word display name
78
+ - tier (required): client | edge | compute | async | data | external | observe
79
+ - subtitle (required): specific description of what this service does
80
+ - isGroup: true = swimlane container wrapping child nodes
81
+ - parentId: ID of parent group (places this node inside that group)
82
+ - groupColor: hex background tint for group containers
83
+ - icon: lucide icon name (box, server, database, zap, shield, globe, etc.)
84
+ - accentColor: override highlight color (15 palette options)
85
+ - status: healthy | warning | error | unknown
86
+ - width/height: pixel size (groups: 400-800px wide, 200-400px tall)
87
+
88
+ **EDGE FIELDS (JSON mode)**:
89
+ - source, target (required): node IDs
90
+ - communicationType: sync | async | stream | event | dep
91
+ - label: required for async/stream/event — name the message or event
92
+ - pathType: Smoothstep | bezier | step | straight
93
+
94
+ **TIER COLORS**:
95
+ - client: #64748b (slate) — browsers, mobile apps
96
+ - edge: #6366f1 (indigo) — API gateways, load balancers, CDN
97
+ - compute: #0d9488 (teal) — microservices, workers, APIs
98
+ - async: #d97706 (amber) — queues, event buses, Kafka
99
+ - data: #3b82f6 (blue) — databases, caches, object storage
100
+ - external: #8b5cf6 (violet) — third-party APIs, payment gateways
101
+ - observe: #6b7280 (gray) — monitoring, logging, tracing`,
23
102
  inputSchema: {
24
103
  type: 'object',
25
104
  properties: {
26
105
  mermaid: {
27
106
  type: 'string',
28
- description: 'Valid Mermaid graph code representing the architecture (e.g. graph LR \\n subgraph ... end)'
107
+ description: 'RECOMMENDED: Mermaid code for the diagram (graph LR or graph TD with subgraphs). Uses the web pipeline parser + Dagre layout.',
29
108
  },
30
109
  nodes: {
31
110
  type: 'array',
111
+ description: 'Legacy JSON mode (only if not using mermaid). Array of nodes. RULES: (1) every node needs id+label+tier+subtitle, (2) include at least one isGroup:true node, (3) use parentId to nest nodes inside groups, (4) use accentColor to visually differentiate nodes in the same tier.',
32
112
  items: {
33
113
  type: 'object',
34
114
  properties: {
35
- id: { type: 'string' },
36
- label: { type: 'string' },
37
- tier: { type: 'string' },
38
- layer: { type: 'string' },
39
- subtitle: { type: 'string' },
40
- icon: { type: 'string' },
41
- tierColor: { type: 'string' },
42
- width: { type: 'number' },
43
- height: { type: 'number' },
115
+ id: { type: 'string', description: 'Unique snake_case identifier (e.g. "api_gateway", "postgres_db")' },
116
+ label: { type: 'string', description: 'Short display name (1-3 words)' },
117
+ tier: { type: 'string', description: 'Tier: client | edge | compute | async | data | external | observe' },
118
+ layer: { type: 'string', description: 'Alias for tier' },
119
+ subtitle: { type: 'string', description: 'REQUIRED: Specific description (e.g. "PostgreSQL 15, stores orders and users")' },
120
+ isGroup: { type: 'boolean', description: 'true = swimlane container. Use to cluster related services.' },
121
+ parentId: { type: 'string', description: 'ID of parent group — places this node inside that group' },
122
+ groupColor: { type: 'string', description: 'Background hex for group containers (e.g. "#0f172a")' },
123
+ icon: { type: 'string', description: 'Lucide icon name (server, database, zap, globe, shield, cpu, activity, box, cloud, layers, user, lock, wifi)' },
124
+ tierColor: { type: 'string', description: 'Override tier color hex' },
125
+ accentColor: { type: 'string', description: 'Highlight color: #3b82f6 #0ea5e9 #06b6d4 #14b8a6 #22c55e #f59e0b #f97316 #ef4444 #ec4899 #6b7280 #f43f5e #a855f7 #84cc16 #fb923c' },
126
+ status: { type: 'string', enum: ['healthy', 'warning', 'error', 'unknown'], description: 'Status dot shown on node' },
127
+ width: { type: 'number', description: 'Width px. Groups: 400-800. Nodes: 160-260.' },
128
+ height: { type: 'number', description: 'Height px. Groups: 200-400. Nodes: 60-90.' },
129
+ shape: { type: 'string', enum: ['rectangle', 'diamond', 'ellipse', 'hexagon'], description: 'Node shape' },
44
130
  },
45
- required: ['label'],
131
+ required: ['id', 'label', 'tier', 'subtitle'],
46
132
  },
47
133
  },
48
134
  edges: {
49
135
  type: 'array',
136
+ description: 'Legacy JSON mode (only if not using mermaid). Array of edges. REQUIRED. Add labels to all async/event/stream edges. Do not connect data tier directly to client tier.',
50
137
  items: {
51
138
  type: 'object',
52
139
  properties: {
53
- id: { type: 'string' },
54
- source: { type: 'string' },
55
- target: { type: 'string' },
56
- communicationType: { type: 'string', enum: ['sync', 'async', 'stream', 'event', 'dep'] },
57
- pathType: { type: 'string', enum: ['smooth', 'Smoothstep', 'bezier', 'step', 'straight'] },
58
- label: { type: 'string' },
140
+ id: { type: 'string', description: 'Unique edge identifier' },
141
+ source: { type: 'string', description: 'Source node ID' },
142
+ target: { type: 'string', description: 'Target node ID' },
143
+ communicationType: {
144
+ type: 'string',
145
+ enum: ['sync', 'async', 'stream', 'event', 'dep'],
146
+ default: 'sync',
147
+ description: 'sync=REST/gRPC, async=queue/kafka, stream=WebSocket/SSE, event=pub-sub, dep=dependency',
148
+ },
149
+ pathType: {
150
+ type: 'string',
151
+ enum: ['smooth', 'Smoothstep', 'bezier', 'step', 'straight'],
152
+ default: 'Smoothstep',
153
+ description: 'Edge path style',
154
+ },
155
+ label: { type: 'string', description: 'Edge label. REQUIRED for async/stream/event. Describe the message or event name.' },
59
156
  },
60
- required: ['source', 'target'],
157
+ required: ['source', 'target', 'label'],
61
158
  },
62
159
  },
63
- direction: { type: 'string', enum: ['RIGHT', 'DOWN', 'LEFT', 'UP'] },
160
+ direction: {
161
+ type: 'string',
162
+ enum: ['RIGHT', 'DOWN', 'LEFT', 'UP'],
163
+ default: 'RIGHT',
164
+ description: 'Layout direction: RIGHT (LR, default), DOWN (TB for pipelines)',
165
+ },
166
+ label: { type: 'string', description: 'Diagram title' },
167
+ diagramDescription: { type: 'string', description: 'One-sentence description of what this architecture does' },
168
+ userPrompt: { type: 'string', description: 'The user\'s original request verbatim. Subtitle content must reflect any tech stack/services the user named.' },
169
+ techStack: { type: 'array', items: { type: 'string' }, description: 'Explicit technologies/services extracted from the prompt (e.g. ["Next.js", "PostgreSQL 15"]).' },
170
+ customFeatures: { type: 'array', items: { type: 'string' }, description: 'Custom features/components the user explicitly requested.' },
171
+ publish: { type: 'boolean', default: true, description: 'Create a preview link (default true). Set false only when the user asks to keep the diagram private.' },
172
+ workingSessionId: { type: 'string', description: 'Opaque local workspace key. Resend it to keep parallel diagrams isolated.' },
173
+ },
174
+ required: [],
175
+ },
176
+ outputSchema: {
177
+ type: 'object',
178
+ properties: {
179
+ success: { type: 'boolean' },
180
+ nodes: { type: 'array', items: { type: 'object' } },
181
+ edges: { type: 'array', items: { type: 'object' } },
182
+ diagramUrl: { type: 'string' },
183
+ shareUrl: { type: 'string' },
184
+ sessionId: { type: 'string' },
185
+ workingSessionId: { type: 'string' },
64
186
  label: { type: 'string' },
65
- diagramDescription: { type: 'string' },
66
- userPrompt: { type: 'string' },
67
- techStack: { type: 'array', items: { type: 'string' } },
68
- customFeatures: { type: 'array', items: { type: 'string' } },
187
+ metadata: {
188
+ type: 'object',
189
+ properties: {
190
+ nodeCount: { type: 'number' },
191
+ edgeCount: { type: 'number' },
192
+ layoutAlgorithm: { type: 'string' },
193
+ direction: { type: 'string' },
194
+ groupCount: { type: 'number' },
195
+ },
196
+ },
197
+ errors: { type: 'array', items: { type: 'string' } },
69
198
  },
199
+ required: ['success', 'nodes', 'edges', 'workingSessionId', 'metadata'],
200
+ },
201
+ annotations: { title: 'Generate an ArchDraw diagram', openWorldHint: true },
202
+ _meta: {
203
+ ui: { resourceUri: DIAGRAM_WIDGET_URI },
204
+ 'openai/toolInvocation/invoking': 'Generating diagram…',
205
+ 'openai/toolInvocation/invoked': 'Diagram preview ready',
70
206
  },
71
207
  },
72
208
  {
73
209
  name: 'fix_layout',
74
- description: 'Apply ELK auto-layout algorithm to existing nodes and edges.',
210
+ description: `Apply the same Mermaid → Dagre auto-layout pipeline used by the ArchDraw editor.
211
+
212
+ **USE WHEN**: User provides nodes/edges that need automatic positioning
213
+ **INPUT**: Nodes with their tier assignments, plus edges between them
214
+ **OUTPUT**: Returns nodes with x,y positions computed by the editor pipeline
215
+
216
+ This tool does NOT generate or modify nodes/edges - it only computes optimal positions.
217
+
218
+ **INPUT**:
219
+ - nodes: Array of nodes (id, label, layer/tier required)
220
+ - edges: Array of connections between nodes
221
+ - direction: Layout direction (RIGHT is default, means left-to-right)`,
75
222
  inputSchema: {
76
223
  type: 'object',
77
224
  properties: {
78
225
  nodes: {
79
226
  type: 'array',
227
+ description: 'Array of nodes with layer assignments',
80
228
  items: {
81
229
  type: 'object',
82
230
  properties: {
83
- id: { type: 'string' },
84
- label: { type: 'string' },
85
- layer: { type: 'string' },
86
- width: { type: 'number' },
87
- height: { type: 'number' },
231
+ id: { type: 'string', description: 'Unique node identifier' },
232
+ label: { type: 'string', description: 'Node label/name' },
233
+ layer: { type: 'string', description: 'Tier layer: client, edge, compute, async, data, observe, external' },
234
+ width: { type: 'number', default: 180, description: 'Node width' },
235
+ height: { type: 'number', default: 70, description: 'Node height' },
88
236
  },
89
237
  required: ['id', 'label', 'layer'],
90
238
  },
91
239
  },
92
240
  edges: {
93
241
  type: 'array',
242
+ description: 'Array of edges connecting the nodes',
94
243
  items: {
95
244
  type: 'object',
96
245
  properties: {
97
- id: { type: 'string' },
98
- source: { type: 'string' },
99
- target: { type: 'string' },
100
- communicationType: { type: 'string' },
246
+ id: { type: 'string', description: 'Unique edge identifier' },
247
+ source: { type: 'string', description: 'Source node ID' },
248
+ target: { type: 'string', description: 'Target node ID' },
249
+ communicationType: {
250
+ type: 'string',
251
+ enum: ['sync', 'async', 'stream', 'event', 'dep'],
252
+ default: 'sync',
253
+ description: 'Communication type between nodes',
254
+ },
101
255
  },
102
256
  required: ['source', 'target'],
103
257
  },
104
258
  },
105
- direction: { type: 'string', enum: ['RIGHT', 'DOWN', 'LEFT', 'UP'] },
259
+ direction: {
260
+ type: 'string',
261
+ enum: ['RIGHT', 'DOWN', 'LEFT', 'UP'],
262
+ default: 'RIGHT',
263
+ description: 'Layout direction',
264
+ },
106
265
  },
107
266
  required: ['nodes'],
108
267
  },
109
268
  },
110
269
  {
111
270
  name: 'list_node_types',
112
- description: 'List all available pre-made component types from the ArchDraw component library.',
271
+ description: `List all available pre-made component types from the ArchDraw component library.
272
+
273
+ **USE WHEN**: You want to see what components are available before generating a diagram
274
+ **OUTPUT**: Categorized list of 150+ component types with their icons and descriptions
275
+
276
+ **FILTERING**:
277
+ - category: Filter by category name (e.g., "AI / ML", "Data Storage", "Compute", "Messaging & Events")
278
+ - search: Search by label, description, or category
279
+ - limit: Max results (default 50, max 200)
280
+
281
+ **CATEGORIES INCLUDE**:
282
+ - Client & Entry (CDN, Load Balancer, API Gateway)
283
+ - Compute (API Server, Auth Service, Workers, Containers)
284
+ - AI / ML (LLM Models, Vector DB, Embedding Service, RAG)
285
+ - Data Storage (PostgreSQL, MongoDB, Redis, S3)
286
+ - Messaging & Events (Kafka, RabbitMQ, SQS)
287
+ - Observability (Prometheus, Grafana, ELK Stack)
288
+ - External Services (Stripe, Twilio, SendGrid)`,
113
289
  inputSchema: {
114
290
  type: 'object',
115
291
  properties: {
116
- category: { type: 'string' },
117
- search: { type: 'string' },
118
- limit: { type: 'number', default: 50 },
292
+ category: {
293
+ type: 'string',
294
+ description: 'Filter by category name (e.g., "AI / ML", "Data Storage", "Compute")',
295
+ },
296
+ search: {
297
+ type: 'string',
298
+ description: 'Search by label, description, or category',
299
+ },
300
+ limit: {
301
+ type: 'number',
302
+ default: 50,
303
+ minimum: 1,
304
+ maximum: 200,
305
+ description: 'Maximum number of results',
306
+ },
119
307
  },
120
308
  },
121
309
  },
122
310
  {
123
311
  name: 'apply_template',
124
- description: 'Apply a pre-built architecture template.',
312
+ title: 'Apply an ArchDraw template',
313
+ description: `Apply a pre-built architecture template with pre-defined nodes and edges. Compatible chat clients display the result as an inline preview.
314
+
315
+ **USE WHEN**: User wants a common architecture pattern (e-commerce, chat app, rideshare, etc.)
316
+ **OUTPUT**: Full diagram with positioned nodes and edges, plus a preview link (shareUrl) and edit link (diagramUrl) created by default. ALWAYS show those links to the user. Pass publish:false only if the user asks to keep it private.
317
+
318
+ **AVAILABLE TEMPLATES**:
319
+ - archflow: ArchDraw's own architecture (modern SaaS)
320
+ - chatgpt: LLM chat app with RAG, vector DB, streaming
321
+ - instagram: Social platform with Kafka, media storage, search
322
+ - rideshare: Uber-like with real-time tracking and dynamic pricing
323
+ - ecommerce: Full e-commerce with cart, payments, order management
324
+
325
+ **CUSTOMIZATIONS**:
326
+ - renameNodes: Map of node ID to new label
327
+ - addNodes: Additional nodes to include`,
125
328
  inputSchema: {
126
329
  type: 'object',
127
330
  properties: {
128
- templateId: { type: 'string' },
129
- customizations: { type: 'object' },
331
+ templateId: {
332
+ type: 'string',
333
+ description: 'Template identifier: archflow, chatgpt, instagram, rideshare, ecommerce',
334
+ },
335
+ customizations: {
336
+ type: 'object',
337
+ description: 'Optional customizations to apply',
338
+ properties: {
339
+ renameNodes: {
340
+ type: 'object',
341
+ description: 'Map of node ID to new label',
342
+ additionalProperties: { type: 'string' },
343
+ },
344
+ addNodes: {
345
+ type: 'array',
346
+ description: 'Additional nodes to add',
347
+ items: {
348
+ type: 'object',
349
+ properties: {
350
+ id: { type: 'string' },
351
+ label: { type: 'string' },
352
+ category: { type: 'string' },
353
+ color: { type: 'string' },
354
+ icon: { type: 'string' },
355
+ },
356
+ },
357
+ },
358
+ },
359
+ },
360
+ publish: { type: 'boolean', default: true, description: 'Create a preview link (default true). Set false only when the user asks to keep the diagram private.' },
361
+ workingSessionId: { type: 'string', description: 'Opaque local workspace key for isolating parallel diagrams.' },
130
362
  },
131
363
  required: ['templateId'],
132
364
  },
365
+ _meta: {
366
+ ui: { resourceUri: DIAGRAM_WIDGET_URI },
367
+ 'openai/toolInvocation/invoking': 'Loading template…',
368
+ 'openai/toolInvocation/invoked': 'Diagram preview ready',
369
+ },
133
370
  },
134
371
  {
135
372
  name: 'list_templates',
136
- description: 'List all available architecture templates.',
137
- inputSchema: { type: 'object', properties: {} },
373
+ description: 'List all available architecture templates with their descriptions',
374
+ inputSchema: {
375
+ type: 'object',
376
+ properties: {},
377
+ },
138
378
  },
139
379
  {
140
380
  name: 'read_me',
141
- description: 'Returns ArchDraw reference guide.',
142
- inputSchema: { type: 'object', properties: {} },
381
+ description: `Returns a compact LLM-optimized reference guide for ArchDraw — tiers, node types,
382
+ edge communication types, layout rules, and best practices. Call this FIRST before generating
383
+ any diagram to produce accurate, professional output.`,
384
+ inputSchema: {
385
+ type: 'object',
386
+ properties: {},
387
+ },
143
388
  },
144
389
  {
145
390
  name: 'get_diagram_state',
146
- description: 'Returns the current diagram nodes and edges.',
147
- inputSchema: { type: 'object', properties: {} },
391
+ description: `Returns the current diagram's nodes and edges as structured JSON.
392
+ Call this before update_diagram to read what's on the canvas.
393
+ Optionally pass a sessionId to load that diagram first (e.g. a link the user opened).`,
394
+ inputSchema: {
395
+ type: 'object',
396
+ properties: {
397
+ workingSessionId: { type: 'string', description: 'The workingSessionId returned by generate_diagram / apply_template. Required on the hosted server; resend it on every call about that diagram.' },
398
+ sessionId: { type: 'string', description: 'Optional session ID to load before returning state (e.g. from a diagramUrl the user shared).' },
399
+ },
400
+ },
148
401
  },
149
402
  {
150
403
  name: 'update_diagram',
151
- description: 'Surgically modifies an existing diagram.',
404
+ description: `Surgically modifies an existing diagram. Add nodes, remove nodes,
405
+ add edges, remove edges, or update node properties — without regenerating the whole diagram.`,
152
406
  inputSchema: {
153
407
  type: 'object',
154
408
  properties: {
155
- addNodes: { type: 'array' },
156
- removeNodeIds: { type: 'array' },
157
- addEdges: { type: 'array' },
158
- removeEdgeIds: { type: 'array' },
159
- updateNodes: { type: 'array' },
409
+ workingSessionId: { type: 'string', description: 'The workingSessionId returned by generate_diagram / apply_template. Required on the hosted server; resend it on every call about that diagram.' },
410
+ addNodes: {
411
+ type: 'array',
412
+ description: 'Nodes to add',
413
+ items: {
414
+ type: 'object',
415
+ properties: {
416
+ id: { type: 'string', description: 'Unique node identifier' },
417
+ label: { type: 'string', description: 'Display name' },
418
+ tier: { type: 'string', description: 'Tier: client, edge, compute, async, data, observe, external' },
419
+ subtitle: { type: 'string', description: 'Optional short description' },
420
+ icon: { type: 'string', description: 'Optional icon name' },
421
+ },
422
+ required: ['id', 'label', 'tier'],
423
+ },
424
+ },
425
+ removeNodeIds: {
426
+ type: 'array',
427
+ description: 'Node IDs to remove',
428
+ items: { type: 'string' },
429
+ },
430
+ addEdges: {
431
+ type: 'array',
432
+ description: 'Edges to add',
433
+ items: {
434
+ type: 'object',
435
+ properties: {
436
+ source: { type: 'string', description: 'Source node ID' },
437
+ target: { type: 'string', description: 'Target node ID' },
438
+ communicationType: { type: 'string', enum: ['sync', 'async', 'stream', 'event', 'dep'] },
439
+ label: { type: 'string', description: 'Optional edge label' },
440
+ },
441
+ required: ['source', 'target'],
442
+ },
443
+ },
444
+ removeEdgeIds: {
445
+ type: 'array',
446
+ description: 'Edge IDs to remove',
447
+ items: { type: 'string' },
448
+ },
449
+ updateNodes: {
450
+ type: 'array',
451
+ description: 'Nodes to update',
452
+ items: {
453
+ type: 'object',
454
+ properties: {
455
+ id: { type: 'string', description: 'Node ID to update' },
456
+ label: { type: 'string', description: 'New label' },
457
+ subtitle: { type: 'string', description: 'New subtitle' },
458
+ tier: { type: 'string', description: 'New tier' },
459
+ },
460
+ required: ['id'],
461
+ },
462
+ },
160
463
  },
161
464
  },
162
465
  },
163
466
  {
164
467
  name: 'validate_diagram',
165
- description: 'Analyses the current diagram and returns issues.',
166
- inputSchema: { type: 'object', properties: {} },
468
+ description: `Analyses the current diagram and returns a list of structural issues
469
+ and improvement suggestions. Use after generating to catch problems before rendering.`,
470
+ inputSchema: {
471
+ type: 'object',
472
+ properties: {
473
+ workingSessionId: { type: 'string', description: 'The workingSessionId returned by generate_diagram / apply_template. Required on the hosted server; resend it on every call about that diagram.' },
474
+ },
475
+ },
167
476
  },
168
477
  {
169
478
  name: 'save_checkpoint',
170
- description: 'Saves the current diagram state.',
479
+ description: `Saves the current diagram state with a named label so it can be restored later.
480
+ Useful for multi-turn workflows: save before a major change, restore if needed.`,
171
481
  inputSchema: {
172
482
  type: 'object',
173
483
  properties: {
174
- name: { type: 'string' },
175
- description: { type: 'string' },
484
+ workingSessionId: { type: 'string', description: 'The workingSessionId returned by generate_diagram / apply_template. Required on the hosted server; resend it on every call about that diagram.' },
485
+ name: { type: 'string', description: 'Checkpoint name (e.g. "before-refactor")' },
486
+ description: { type: 'string', description: 'Optional description' },
176
487
  },
177
488
  required: ['name'],
178
489
  },
179
490
  },
180
491
  {
181
492
  name: 'load_checkpoint',
182
- description: 'Restores a previously saved diagram state.',
493
+ description: `Restores a previously saved diagram state by name.`,
183
494
  inputSchema: {
184
495
  type: 'object',
185
496
  properties: {
186
- name: { type: 'string' },
187
- listAvailable: { type: 'boolean' },
497
+ workingSessionId: { type: 'string', description: 'The workingSessionId returned by generate_diagram / apply_template. Required on the hosted server; resend it on every call about that diagram.' },
498
+ name: { type: 'string', description: 'Checkpoint name to restore' },
499
+ listAvailable: { type: 'boolean', description: 'If true, list all checkpoints without restoring' },
188
500
  },
189
501
  required: ['name'],
190
502
  },
191
503
  },
192
504
  {
193
505
  name: 'export_diagram',
194
- description: 'Export the current diagram in various formats.',
506
+ description: `Export the current diagram in various formats.
507
+
508
+ **USE WHEN**: User wants to save or download the diagram
509
+ **OUTPUT**: Returns the diagram data or instructions for image export
510
+
511
+ **FORMATS**:
512
+ - json: Returns raw nodes and edges as JSON (recommended for saving)
513
+ - png: Returns instructions to export as PNG from the editor
514
+ - svg: Returns instructions to export as SVG from the editor
515
+
516
+ **REQUIREMENT**: You must have a diagram loaded (generate or apply a template first).`,
195
517
  inputSchema: {
196
518
  type: 'object',
197
519
  properties: {
198
- sessionId: { type: 'string' },
199
- format: { type: 'string', enum: ['json', 'png', 'svg'] },
520
+ workingSessionId: { type: 'string', description: 'The workingSessionId returned by generate_diagram / apply_template. Required on the hosted server; resend it on every call about that diagram.' },
521
+ sessionId: { type: 'string', description: 'Session ID from a previous diagram operation. Omit to use the most recent session.' },
522
+ format: { type: 'string', enum: ['json', 'png', 'svg'], default: 'json', description: 'Export format' },
200
523
  },
201
- required: ['sessionId'],
524
+ required: [],
202
525
  },
203
526
  },
204
527
  ];
205
- export function createMCPServer() {
206
- const server = new Server({ name: 'archdraw-mcp-server', version: '1.0.0' }, { capabilities: { tools: {} } });
207
- server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
208
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
209
- const { name, arguments: args } = request.params;
528
+ export const SERVER_VERSION = '1.0.5';
529
+ const STATEFUL_TOOLS = new Set([
530
+ 'generate_diagram',
531
+ 'apply_template',
532
+ 'get_diagram_state',
533
+ 'update_diagram',
534
+ 'validate_diagram',
535
+ 'save_checkpoint',
536
+ 'load_checkpoint',
537
+ 'export_diagram',
538
+ ]);
539
+ const SESSION_CREATING_TOOLS = new Set(['generate_diagram', 'apply_template', 'get_diagram_state']);
540
+ const WORKING_SESSION_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
541
+ function errorResult(text) {
542
+ return { content: [{ type: 'text', text }], isError: true };
543
+ }
544
+ /**
545
+ * Pick the working session for a call. Stdio keeps the historical 'default'
546
+ * fallback (one client per process). With a session store the process is
547
+ * shared by everyone, so ids must be explicit UUIDs — a missing id on a
548
+ * creating tool mints a fresh one; on any other stateful tool it's an error.
549
+ */
550
+ function resolveWorkingSession(name, args, hosted) {
551
+ const raw = typeof args?.workingSessionId === 'string' ? args.workingSessionId.trim() : '';
552
+ if (!hosted) {
553
+ if (raw)
554
+ return { id: raw };
555
+ return { id: name === 'generate_diagram' || name === 'apply_template' ? randomUUID() : 'default' };
556
+ }
557
+ if (raw) {
558
+ return WORKING_SESSION_RE.test(raw) ? { id: raw.toLowerCase() } : { error: 'workingSessionId must be the UUID returned by generate_diagram or apply_template.' };
559
+ }
560
+ if (!STATEFUL_TOOLS.has(name))
561
+ return { id: '' };
562
+ const canCreate = SESSION_CREATING_TOOLS.has(name) && (name !== 'get_diagram_state' || typeof args?.sessionId === 'string');
563
+ if (canCreate)
564
+ return { id: randomUUID() };
565
+ return { error: `${name} needs the workingSessionId returned by generate_diagram or apply_template. Pass it on every call about that diagram.` };
566
+ }
567
+ async function withWorkingSession(store, id, run) {
568
+ const snapshot = await store.load(id);
569
+ if (snapshot?.diagram)
570
+ setDiagramState(snapshot.diagram, id);
571
+ if (snapshot?.checkpoints?.length)
572
+ importCheckpoints(snapshot.checkpoints, id);
573
+ try {
574
+ return await run();
575
+ }
576
+ finally {
210
577
  try {
211
- let result;
212
- switch (name) {
213
- case 'generate_diagram': {
214
- const input = GenerateDiagramInputSchema.parse(args);
215
- result = await generateDiagram(input);
216
- if (result && typeof result === 'object' && 'success' in result && result.success) {
217
- const { setDiagramState } = await import('./lib/diagram-state.js');
218
- setDiagramState({ nodes: result.nodes, edges: result.edges });
219
- }
220
- break;
221
- }
222
- case 'fix_layout': {
223
- const input = FixLayoutInputSchema.parse(args);
224
- result = await fixLayout(input);
225
- break;
226
- }
227
- case 'list_node_types': {
228
- const input = ListNodeTypesInputSchema.parse(args ?? {});
229
- result = await listNodeTypes(input);
230
- break;
231
- }
232
- case 'apply_template': {
233
- const input = ApplyTemplateInputSchema.parse(args);
234
- result = await applyTemplate(input);
235
- if (result && typeof result === 'object' && 'success' in result && result.success) {
236
- const { setDiagramState } = await import('./lib/diagram-state.js');
237
- setDiagramState({ nodes: result.nodes, edges: result.edges });
238
- }
239
- break;
240
- }
241
- case 'list_templates':
242
- result = { templates: getAvailableTemplates() };
243
- break;
244
- case 'read_me':
245
- result = getReadMe();
246
- break;
247
- case 'get_diagram_state':
248
- result = getDiagramState();
249
- break;
250
- case 'update_diagram': {
251
- const input = UpdateDiagramInputSchema.parse(args);
252
- result = await updateDiagram(input);
253
- break;
254
- }
255
- case 'validate_diagram':
256
- result = await validateDiagram();
257
- break;
258
- case 'save_checkpoint': {
259
- const input = SaveCheckpointInputSchema.parse(args);
260
- result = await saveCheckpoint(input);
261
- break;
262
- }
263
- case 'load_checkpoint': {
264
- const input = LoadCheckpointInputSchema.parse(args);
265
- result = await loadCheckpoint(input);
266
- break;
267
- }
268
- case 'export_diagram': {
269
- const input = ExportDiagramInputSchema.parse(args);
270
- result = await exportDiagram(input);
271
- break;
578
+ const checkpoints = exportCheckpoints(id);
579
+ if (hasDiagramState(id) || checkpoints.length) {
580
+ await store.save(id, { diagram: hasDiagramState(id) ? getDiagramState(id) : null, checkpoints });
581
+ }
582
+ }
583
+ finally {
584
+ clearDiagramState(id);
585
+ clearCheckpoints(id);
586
+ }
587
+ }
588
+ }
589
+ async function dispatchTool(name, args, workingSession) {
590
+ switch (name) {
591
+ case 'generate_diagram': {
592
+ const input = GenerateDiagramInputSchema.parse(args);
593
+ const result = await generateDiagram(input);
594
+ if (result.success) {
595
+ setDiagramState({ nodes: result.nodes, edges: result.edges, sessionId: result.sessionId, updateToken: result.updateToken }, workingSession);
596
+ }
597
+ const structuredContent = {
598
+ success: result.success,
599
+ nodes: result.nodes,
600
+ edges: result.edges,
601
+ ...(result.diagramUrl ? { diagramUrl: result.diagramUrl } : {}),
602
+ ...(result.shareUrl ? { shareUrl: result.shareUrl } : {}),
603
+ ...(result.sessionId ? { sessionId: result.sessionId } : {}),
604
+ workingSessionId: workingSession,
605
+ label: input.label || 'ArchDraw diagram',
606
+ metadata: result.metadata,
607
+ ...(result.errors?.length ? { errors: result.errors } : {}),
608
+ };
609
+ const summary = result.message || `Diagram generated with ${result.metadata.nodeCount} nodes and ${result.metadata.edgeCount} connections.`;
610
+ const text = result.success
611
+ ? `${formatPreviewLinks({ ...result, privateByRequest: input.publish === false })}\n\n${summary}`
612
+ : `Diagram generation failed: ${(result.errors || ['Unknown error']).join('; ')}`;
613
+ return {
614
+ structuredContent: { ...structuredContent, ...(result.linkError ? { linkError: result.linkError } : {}) },
615
+ content: [{ type: 'text', text }],
616
+ };
617
+ }
618
+ case 'fix_layout': {
619
+ const input = FixLayoutInputSchema.parse(args);
620
+ const result = await fixLayout(input);
621
+ return {
622
+ content: [
623
+ {
624
+ type: 'text',
625
+ text: JSON.stringify(result, null, 2),
626
+ },
627
+ ],
628
+ };
629
+ }
630
+ case 'list_node_types': {
631
+ const input = ListNodeTypesInputSchema.parse(args ?? {});
632
+ const result = await listNodeTypes(input);
633
+ return {
634
+ content: [
635
+ {
636
+ type: 'text',
637
+ text: JSON.stringify(result, null, 2),
638
+ },
639
+ ],
640
+ };
641
+ }
642
+ case 'apply_template': {
643
+ const input = ApplyTemplateInputSchema.parse(args);
644
+ const result = await applyTemplate(input);
645
+ if (result.success) {
646
+ setDiagramState({ nodes: result.nodes, edges: result.edges, sessionId: result.sessionId, updateToken: result.updateToken }, workingSession);
647
+ }
648
+ const structuredContent = {
649
+ success: result.success,
650
+ nodes: result.nodes,
651
+ edges: result.edges,
652
+ ...(result.diagramUrl ? { diagramUrl: result.diagramUrl } : {}),
653
+ ...(result.shareUrl ? { shareUrl: result.shareUrl } : {}),
654
+ ...(result.sessionId ? { sessionId: result.sessionId } : {}),
655
+ workingSessionId: workingSession,
656
+ label: input.templateId,
657
+ metadata: {
658
+ nodeCount: result.nodes.length,
659
+ edgeCount: result.edges.length,
660
+ groupCount: result.nodes.filter(node => node.data?.isGroup).length,
661
+ layoutAlgorithm: 'template',
662
+ },
663
+ ...(result.errors?.length ? { errors: result.errors } : {}),
664
+ };
665
+ const text = result.success
666
+ ? `${formatPreviewLinks({ ...result, privateByRequest: input.publish === false })}\n\n${result.message || `${input.templateId} template is ready.`}`
667
+ : `Template failed: ${(result.errors || ['Unknown error']).join('; ')}`;
668
+ return {
669
+ structuredContent: { ...structuredContent, ...(result.linkError ? { linkError: result.linkError } : {}) },
670
+ content: [{ type: 'text', text }],
671
+ };
672
+ }
673
+ case 'list_templates': {
674
+ const templates = getAvailableTemplates();
675
+ return {
676
+ content: [
677
+ {
678
+ type: 'text',
679
+ text: JSON.stringify({ templates }, null, 2),
680
+ },
681
+ ],
682
+ };
683
+ }
684
+ case 'read_me': {
685
+ const readmeContent = getReadMe();
686
+ return {
687
+ content: [
688
+ {
689
+ type: 'text',
690
+ text: readmeContent,
691
+ },
692
+ ],
693
+ };
694
+ }
695
+ case 'get_diagram_state': {
696
+ const { sessionId } = (args ?? {});
697
+ if (typeof sessionId === 'string' && sessionId.trim().length > 0) {
698
+ const { syncDiagramStateFromSession } = await import('./lib/diagram-state.js');
699
+ const synced = await syncDiagramStateFromSession(sessionId.trim(), undefined, workingSession);
700
+ if (!synced) {
701
+ return {
702
+ content: [
703
+ {
704
+ type: 'text',
705
+ text: JSON.stringify({ error: `Could not load session '${sessionId}'. It may not exist or the frontend is unreachable.` }, null, 2),
706
+ },
707
+ ],
708
+ isError: true,
709
+ };
272
710
  }
273
- default:
274
- return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
275
711
  }
276
- return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
712
+ const state = getDiagramState(workingSession);
713
+ return {
714
+ content: [
715
+ {
716
+ type: 'text',
717
+ text: JSON.stringify(state.nodes.length === 0 && state.edges.length === 0
718
+ ? { nodes: [], edges: [], workingSessionId: workingSession, message: 'No diagram loaded yet.' }
719
+ : { ...state, updateToken: undefined, workingSessionId: workingSession }, null, 2),
720
+ },
721
+ ],
722
+ };
277
723
  }
278
- catch (error) {
724
+ case 'update_diagram': {
725
+ const input = UpdateDiagramInputSchema.parse(args);
726
+ const result = await updateDiagram(input, workingSession);
727
+ if (result.success) {
728
+ const previous = getDiagramState(workingSession);
729
+ setDiagramState({ nodes: result.nodes, edges: result.edges, sessionId: result.sessionId, updateToken: previous.updateToken }, workingSession);
730
+ }
731
+ const details = JSON.stringify({
732
+ success: result.success,
733
+ message: result.message,
734
+ error: result.error,
735
+ changes: result.changes,
736
+ diagramUrl: result.diagramUrl,
737
+ shareUrl: result.shareUrl,
738
+ sessionId: result.sessionId,
739
+ workingSessionId: workingSession,
740
+ note: 'Raw nodes and edges omitted for brevity.',
741
+ }, null, 2);
742
+ // The published link is updated in place — resurface it so the
743
+ // user can see the change without asking for the link again.
744
+ const linkBlock = result.success && (result.shareUrl || result.diagramUrl)
745
+ ? `${formatPreviewLinks(result)}\n\n`
746
+ : '';
747
+ return {
748
+ content: [{ type: 'text', text: `${linkBlock}${details}` }],
749
+ };
750
+ }
751
+ case 'validate_diagram': {
752
+ const result = await validateDiagram(workingSession);
753
+ return {
754
+ content: [
755
+ {
756
+ type: 'text',
757
+ text: JSON.stringify(result, null, 2),
758
+ },
759
+ ],
760
+ };
761
+ }
762
+ case 'save_checkpoint': {
763
+ const input = SaveCheckpointInputSchema.parse(args);
764
+ const result = await saveCheckpoint(input, workingSession);
279
765
  return {
280
- content: [{ type: 'text', text: error instanceof Error ? error.message : String(error) }],
766
+ content: [
767
+ {
768
+ type: 'text',
769
+ text: JSON.stringify(result, null, 2),
770
+ },
771
+ ],
772
+ };
773
+ }
774
+ case 'load_checkpoint': {
775
+ const input = LoadCheckpointInputSchema.parse(args);
776
+ const result = await loadCheckpoint(input, workingSession);
777
+ return {
778
+ content: [
779
+ {
780
+ type: 'text',
781
+ text: JSON.stringify(result, null, 2),
782
+ },
783
+ ],
784
+ };
785
+ }
786
+ case 'export_diagram': {
787
+ const input = ExportDiagramInputSchema.parse(args);
788
+ const result = await exportDiagram(input, workingSession);
789
+ return {
790
+ content: [
791
+ {
792
+ type: 'text',
793
+ text: JSON.stringify(result, null, 2),
794
+ },
795
+ ],
796
+ };
797
+ }
798
+ default:
799
+ return {
800
+ content: [
801
+ {
802
+ type: 'text',
803
+ text: `Unknown tool: ${name}`,
804
+ },
805
+ ],
281
806
  isError: true,
282
807
  };
808
+ }
809
+ }
810
+ export function createArchDrawMCPServer(options = {}) {
811
+ const server = new Server({
812
+ name: 'archdraw-mcp-server',
813
+ version: SERVER_VERSION,
814
+ }, {
815
+ capabilities: {
816
+ tools: {},
817
+ resources: {},
818
+ },
819
+ });
820
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
821
+ return { tools: TOOLS };
822
+ });
823
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
824
+ resources: [{
825
+ uri: DIAGRAM_WIDGET_URI,
826
+ name: 'ArchDraw diagram preview',
827
+ description: 'Inline diagram preview and prompt form for generating another ArchDraw diagram in the chat.',
828
+ mimeType: 'text/html;profile=mcp-app',
829
+ }],
830
+ }));
831
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
832
+ if (request.params.uri !== DIAGRAM_WIDGET_URI) {
833
+ throw new Error(`Unknown resource: ${request.params.uri}`);
834
+ }
835
+ return {
836
+ contents: [{
837
+ uri: DIAGRAM_WIDGET_URI,
838
+ mimeType: 'text/html;profile=mcp-app',
839
+ text: DIAGRAM_WIDGET_HTML,
840
+ _meta: { ui: { prefersBorder: true } },
841
+ }],
842
+ };
843
+ });
844
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
845
+ const { name, arguments: args } = request.params;
846
+ const store = options.sessionStore;
847
+ // Hosts can multiplex calls, so agents retain and resend this opaque key
848
+ // to isolate parallel diagrams.
849
+ const resolved = resolveWorkingSession(name, args, Boolean(store));
850
+ if ('error' in resolved)
851
+ return errorResult(resolved.error);
852
+ const workingSession = resolved.id;
853
+ try {
854
+ if (store && workingSession) {
855
+ return await withWorkingSession(store, workingSession, () => dispatchTool(name, args, workingSession));
856
+ }
857
+ return await dispatchTool(name, args, workingSession);
858
+ }
859
+ catch (error) {
860
+ return errorResult(error instanceof Error ? error.message : String(error));
283
861
  }
284
862
  });
285
863
  return server;
286
864
  }
865
+ /**
866
+ * Serve one stateless Streamable HTTP request (POST JSON-RPC in, JSON out).
867
+ * Used by the hosted endpoint; `context` scopes the API base / fetch the tools
868
+ * use to this request only.
869
+ */
870
+ export async function handleMcpHttpRequest(request, options = {}) {
871
+ const server = createArchDrawMCPServer(options);
872
+ const transport = new WebStandardStreamableHTTPServerTransport({
873
+ sessionIdGenerator: undefined,
874
+ enableJsonResponse: true,
875
+ });
876
+ await server.connect(transport);
877
+ try {
878
+ return await runWithMcpContext(options.context ?? {}, () => transport.handleRequest(request));
879
+ }
880
+ finally {
881
+ await server.close();
882
+ }
883
+ }
884
+ export { runWithMcpContext };
287
885
  //# sourceMappingURL=server.js.map