@sealab/mcp-server 1.0.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 (48) hide show
  1. package/PROPOSED-CHANGES-INSERTION-POINTS.md +220 -0
  2. package/SEALAB_MCP_DOCUMENTATION.md +1136 -0
  3. package/dist/client/api-client.js +44 -0
  4. package/dist/index.js +42 -0
  5. package/dist/tools/canvas.js +446 -0
  6. package/dist/tools/catalog.js +95 -0
  7. package/dist/tools/configuration-info.js +299 -0
  8. package/dist/tools/configuration.js +32 -0
  9. package/dist/tools/orders.js +1267 -0
  10. package/dist/tools/saved-settings.js +271 -0
  11. package/package.json +32 -0
  12. package/resources/tooltips/backPanel.txt +17 -0
  13. package/resources/tooltips/backPanelMaterial.txt +29 -0
  14. package/resources/tooltips/caseEdge.txt +18 -0
  15. package/resources/tooltips/caseMaterial.txt +31 -0
  16. package/resources/tooltips/depth.txt +11 -0
  17. package/resources/tooltips/drawerType.txt +12 -0
  18. package/resources/tooltips/edgeBandingType.txt +18 -0
  19. package/resources/tooltips/excludeFronts.txt +5 -0
  20. package/resources/tooltips/frontEdge.txt +18 -0
  21. package/resources/tooltips/frontMaterial.txt +35 -0
  22. package/resources/tooltips/gapBottom.txt +2 -0
  23. package/resources/tooltips/gapCenter.txt +2 -0
  24. package/resources/tooltips/gapLeft.txt +15 -0
  25. package/resources/tooltips/gapRight.txt +15 -0
  26. package/resources/tooltips/gapTop.txt +2 -0
  27. package/resources/tooltips/height.txt +6 -0
  28. package/resources/tooltips/hingePlate.txt +11 -0
  29. package/resources/tooltips/includeLegLevelers.txt +8 -0
  30. package/resources/tooltips/jointMethod.txt +7 -0
  31. package/resources/tooltips/leftCornerWidth.txt +2 -0
  32. package/resources/tooltips/numOfShelves.txt +6 -0
  33. package/resources/tooltips/positionName.txt +3 -0
  34. package/resources/tooltips/rightCornerDepth.txt +2 -0
  35. package/resources/tooltips/topDrwrHeight.txt +8 -0
  36. package/resources/tooltips/width.txt +5 -0
  37. package/src/client/api-client.ts +37 -0
  38. package/src/index.ts +52 -0
  39. package/src/tools/canvas.ts +442 -0
  40. package/src/tools/catalog.test.ts +61 -0
  41. package/src/tools/catalog.ts +80 -0
  42. package/src/tools/configuration-info.ts +274 -0
  43. package/src/tools/configuration.test.ts +43 -0
  44. package/src/tools/configuration.ts +25 -0
  45. package/src/tools/orders.test.ts +260 -0
  46. package/src/tools/orders.ts +1229 -0
  47. package/src/tools/saved-settings.ts +241 -0
  48. package/tsconfig.json +15 -0
@@ -0,0 +1,1136 @@
1
+ # Sealab MCP Server — Full Documentation
2
+
3
+ ## Overview
4
+
5
+ The Sealab MCP Server is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that exposes the Sealab cabinetry platform to AI assistants. It allows an AI agent to browse the Sealab cabinet catalog, configure individual articles, build orders or saved carts, manage saved configuration presets, and interact with the drawing tool canvas (cabinet placement coordinates, background images, and calibration metadata).
6
+
7
+ The server runs as a long-lived Node.js process and communicates with MCP clients over **stdio** (standard input/output). All cabinet data is fetched from the live Sealab REST API at `https://thesealab.com/api/mcp/v1`.
8
+
9
+ ---
10
+
11
+ ## Architecture
12
+
13
+ ```
14
+ MCP Client (e.g. Claude Code)
15
+ │ stdio (JSON-RPC)
16
+
17
+ src/index.ts ← MCP server entry point
18
+
19
+ ├── src/tools/catalog.ts
20
+ ├── src/tools/configuration.ts
21
+ ├── src/tools/configuration-info.ts
22
+ ├── src/tools/orders.ts
23
+ ├── src/tools/saved-settings.ts
24
+ └── src/tools/canvas.ts
25
+
26
+
27
+ src/client/api-client.ts ← Axios HTTP client
28
+
29
+
30
+ https://thesealab.com/api/mcp/v1
31
+ ```
32
+
33
+ ### Tech Stack
34
+
35
+ | Component | Technology |
36
+ |---|---|
37
+ | Language | TypeScript 5.3+ |
38
+ | Runtime | Node.js ≥ 18 |
39
+ | MCP SDK | `@modelcontextprotocol/sdk` v1.x |
40
+ | HTTP client | Axios 1.6+ |
41
+ | Schema/validation | Zod 3.22+ |
42
+ | Schema → JSON Schema | `zod-to-json-schema` |
43
+ | File uploads | `form-data` |
44
+ | Build | TypeScript compiler (`tsc`) |
45
+ | Tests | Vitest |
46
+
47
+ ### Entry Point (`src/index.ts`)
48
+
49
+ Instantiates an MCP `Server` named `sealab` (version `1.0.0`) with `tools` capability. Registers two request handlers:
50
+ - `ListToolsRequestSchema` — returns all tool names, descriptions, and JSON Schemas (converted from Zod schemas via `zodToJsonSchema`)
51
+ - `CallToolRequestSchema` — finds the matching tool, validates input with Zod `safeParse`, calls the handler, and returns the result as a text content block
52
+
53
+ All tool results are returned as plain text strings. Errors are returned as text (not thrown), so the MCP client always receives a readable message.
54
+
55
+ ---
56
+
57
+ ## Authentication & Configuration
58
+
59
+ ### Environment Variables
60
+
61
+ | Variable | Required | Default | Purpose |
62
+ |---|---|---|---|
63
+ | `SEALAB_API_KEY` | **Yes** | — | API key sent as `X-API-Key` header on every request. Server throws on startup if missing. |
64
+ | `SEALAB_API_URL` | No | `https://thesealab.com` | Base URL for the Sealab API. Can be overridden for local/staging environments. |
65
+
66
+ All API requests target `{SEALAB_API_URL}/api/mcp/v1` with a `10,000 ms` timeout.
67
+
68
+ ### Error Handling (`src/client/api-client.ts`)
69
+
70
+ `handleAxiosError` maps HTTP status codes to typed `McpApiError` instances:
71
+ - `401` → `"Invalid API key"`
72
+ - `404` → message from API response body
73
+ - `422` → message from API response body
74
+ - Other → `"API error: {message}"`
75
+ - Network failure → `"Unable to reach Sealab API — check SEALAB_API_URL"`
76
+
77
+ ---
78
+
79
+ ## Tools Reference
80
+
81
+ The server exposes **35 tools** across 6 modules.
82
+
83
+ ---
84
+
85
+ ### Module 1: Catalog (`src/tools/catalog.ts`)
86
+
87
+ Tools for browsing and retrieving cabinet articles from the Sealab catalog.
88
+
89
+ ---
90
+
91
+ #### `search_catalog`
92
+
93
+ Search the Sealab cabinetry catalog by keyword, filter tags, and/or dimension ranges.
94
+
95
+ **Input parameters:**
96
+
97
+ | Parameter | Type | Required | Description |
98
+ |---|---|---|---|
99
+ | `q` | string | No | Keyword search query |
100
+ | `tags` | string[] | No | Filter by tag (e.g. `BASE`, `WALL`, `PANTRY`, `TALL`) |
101
+ | `minH` | number | No | Minimum height in inches |
102
+ | `maxH` | number | No | Maximum height in inches |
103
+ | `minW` | number | No | Minimum width in inches |
104
+ | `maxW` | number | No | Maximum width in inches |
105
+ | `minD` | number | No | Minimum depth in inches |
106
+ | `maxD` | number | No | Maximum depth in inches |
107
+
108
+ **API call:** `GET /catalog/search?q=...&tags=BASE&tags=WALL&minH=...`
109
+
110
+ Tags are serialized without array brackets (`tags=BASE&tags=WALL`, not `tags[]=`).
111
+
112
+ **Returns:** JSON array of matching articles, or `"No articles found matching your search."` if empty.
113
+
114
+ ---
115
+
116
+ #### `get_article`
117
+
118
+ Get full details for a single cabinet article by its serial number.
119
+
120
+ **Input parameters:**
121
+
122
+ | Parameter | Type | Required | Description |
123
+ |---|---|---|---|
124
+ | `serialNumber` | string | Yes | Article serial number (e.g. `B30`) |
125
+
126
+ **API call:** `GET /catalog/{serialNumber}`
127
+
128
+ **Returns:** JSON object with full article details.
129
+
130
+ ---
131
+
132
+ #### `list_filter_tags`
133
+
134
+ List all available filter tag categories in the catalog.
135
+
136
+ **Input parameters:** None
137
+
138
+ **API call:** `GET /catalog/filter-tags`
139
+
140
+ **Returns:** Newline-separated list of tags (e.g. `BASE`, `WALL`, `PANTRY`, `TALL`, `Leg_Levelers`, etc.).
141
+
142
+ ---
143
+
144
+ ### Module 2: Configuration (`src/tools/configuration.ts`)
145
+
146
+ Low-level access to raw article configuration flags.
147
+
148
+ ---
149
+
150
+ #### `get_article_configuration`
151
+
152
+ Get the raw feature flags and dimension ranges for a cabinet article. Returns boolean flags such as `hasCase`, `hasFront`, `doors`, `drawers`, `adjShelves`, `gapControl`, `jointControl`, `cornerVariables`, etc., along with `heightRange`, `widthRange`, `depthRange`.
153
+
154
+ **When to use:** Only when you need raw boolean flags without guidance text. For guided configuration, use `get_article_context` instead.
155
+
156
+ **Input parameters:**
157
+
158
+ | Parameter | Type | Required | Description |
159
+ |---|---|---|---|
160
+ | `serialNumber` | string | Yes | Article serial number |
161
+
162
+ **API call:** `GET /catalog/{serialNumber}/configuration`
163
+
164
+ **Returns:** JSON object of feature flags and dimension ranges.
165
+
166
+ ---
167
+
168
+ ### Module 3: Configuration Info (`src/tools/configuration-info.ts`)
169
+
170
+ Higher-level tools for guided article configuration. These are the primary tools used in the ordering workflow.
171
+
172
+ **Applicable configuration fields** (derived from feature flags):
173
+
174
+ | Field | Triggered by flag |
175
+ |---|---|
176
+ | `positionName`, `height`, `width`, `depth` | Always included |
177
+ | `caseMaterial` | `hasCase = true` or `hasMatCaseThin = true` |
178
+ | `frontMaterial` | `hasFront = true` |
179
+ | `excludeFronts` | `hasCase = true` AND `hasFront = true` |
180
+ | `backPanelMaterial`, `backPanel` | `matBack = true` |
181
+ | `caseEdge` | `caseEdge = true` |
182
+ | `frontEdge` | `frontEdge = true` |
183
+ | `hingePlate` | `doors = true` |
184
+ | `drawerType` | `drawers = true` |
185
+ | `topDrwrHeight` | `topDrwrHeight = true` |
186
+ | `numOfShelves` | `adjShelves = true` |
187
+ | `gapTop`, `gapBottom`, `gapLeft`, `gapRight`, `gapCenter` | `gapControl = true` |
188
+ | `jointMethod` | `jointControl = true` |
189
+ | `leftCornerWidth`, `rightCornerDepth` | `cornerVariables = true` |
190
+ | `edgeBandingType` | Serial number starts with `LP_SP` or `LP_GP` |
191
+ | `includeLegLevelers` | `filterTags` contains `"Leg_Levelers"` |
192
+
193
+ ---
194
+
195
+ #### `get_article_context`
196
+
197
+ Load all configuration context for a cabinet article in a single call. Fetches the configuration, determines which fields apply to this specific article, and returns dimension ranges plus the full guidance text for each applicable field (read from `.txt` tooltip files on disk).
198
+
199
+ **Input parameters:**
200
+
201
+ | Parameter | Type | Required | Description |
202
+ |---|---|---|---|
203
+ | `serialNumber` | string | Yes | Article serial number |
204
+
205
+ **API call:** `GET /catalog/{serialNumber}/configuration`
206
+
207
+ **Returns:** Plain-text block containing:
208
+ - Dimension ranges (height / width / depth)
209
+ - All applicable configuration fields with their guidance descriptions
210
+ - Embedded instructions directing the AI to present options to the user, ask for explicit choices, and NOT auto-select or assume values
211
+
212
+ **Intended workflow:**
213
+ 1. Call `get_article_context` immediately when a user selects an article
214
+ 2. Call `get_article_options` to get valid option values
215
+ 3. Present both to the user, ask for all choices explicitly
216
+ 4. Only proceed to create order/cart once all fields are confirmed
217
+
218
+ ---
219
+
220
+ #### `get_article_options`
221
+
222
+ Fetch the valid selectable values for all applicable configuration fields for a cabinet article.
223
+
224
+ **Input parameters:**
225
+
226
+ | Parameter | Type | Required | Description |
227
+ |---|---|---|---|
228
+ | `serialNumber` | string | Yes | Article serial number |
229
+
230
+ **API call:** `GET /catalog/{serialNumber}/options`
231
+
232
+ **Returns:** Plain-text block organized by option group:
233
+ - `MATERIALS` — valid strings for `caseMaterial`, `frontMaterial`, `backPanelMaterial`, `innerCaseMaterial`
234
+ - `EDGEBANDING` — valid strings for `caseEdge`, `frontEdge`
235
+ - `JOINT METHOD` — valid strings for `jointMethod`
236
+ - `DRAWER TYPE` — valid strings for `drawerType`
237
+ - `HINGE PLATE` — valid strings for `hingePlate`
238
+ - `BACK PANEL METHOD` — valid strings for `backPanel`
239
+ - `NUMBER OF ADJUSTABLE SHELVES` — valid strings for `numOfShelves`
240
+
241
+ Only fields applicable to the article are included. Values returned are the **exact strings** the order system expects.
242
+
243
+ ---
244
+
245
+ #### `get_configuration_info`
246
+
247
+ Get full guidance text for a single configuration field. Reads and strips HTML from the corresponding `.txt` file in `resources/tooltips/`.
248
+
249
+ **Input parameters:**
250
+
251
+ | Parameter | Type | Required | Description |
252
+ |---|---|---|---|
253
+ | `field` | enum | Yes | One of the 25 valid field names (see list below) |
254
+
255
+ **Valid field names:** `backPanel`, `backPanelMaterial`, `caseEdge`, `caseMaterial`, `depth`, `drawerType`, `edgeBandingType`, `excludeFronts`, `frontEdge`, `frontMaterial`, `gapBottom`, `gapCenter`, `gapLeft`, `gapRight`, `gapTop`, `height`, `hingePlate`, `includeLegLevelers`, `jointMethod`, `leftCornerWidth`, `numOfShelves`, `positionName`, `rightCornerDepth`, `topDrwrHeight`, `width`
256
+
257
+ **Returns:** Plain-text guidance for the specified field. Use only when the user explicitly asks for more detail about a specific option.
258
+
259
+ ---
260
+
261
+ ### Module 4: Orders (`src/tools/orders.ts`)
262
+
263
+ The largest and most complex module. Handles order creation, saved carts, order revisions, and BOM reporting.
264
+
265
+ ---
266
+
267
+ #### Article Item Schema
268
+
269
+ Every article in an order or cart uses the following fields. All string values for materials, edgebanding, joint method, etc. must be the exact strings returned by `get_article_options`.
270
+
271
+ **Identification and dimensions:**
272
+
273
+ | Field | Type | Required | Description |
274
+ |---|---|---|---|
275
+ | `serialNumber` | string | Yes | Article serial number (cabinet type) |
276
+ | `positionName` | string | Yes | Unique label within the order. Max 25 chars, no dashes (underscores OK). For qty > 1, must include `_01` suffix — server increments it per unit (e.g. `DR_B2_01` qty=3 → `DR_B2_01`, `DR_B2_02`, `DR_B2_03`). |
277
+ | `quantity` | integer ≥ 1 | Yes | Number of this cabinet to order |
278
+ | `height` | number | Yes | Height in inches |
279
+ | `width` | number | Yes | Width in inches |
280
+ | `depth` | number | Yes | Depth in inches |
281
+
282
+ **Materials:**
283
+
284
+ | Field | Type | Required | Description |
285
+ |---|---|---|---|
286
+ | `caseMaterial` | string | Conditional | Case/box material. Exact string from `get_article_options`. |
287
+ | `frontMaterial` | string | Conditional | Door/drawer front material. Omit when `excludeFronts` is true. |
288
+ | `innerCaseMaterial` | string | Conditional | Inner case material. Only for articles with `hasMatCaseThin = true`. |
289
+ | `backPanelMaterial` | string | Conditional | Back panel material. |
290
+
291
+ **Edgebanding:**
292
+
293
+ | Field | Type | Required | Description |
294
+ |---|---|---|---|
295
+ | `caseEdge` | string | Conditional | Case edgebanding. Standard articles only. |
296
+ | `frontEdge` | string | Conditional | Front edgebanding. Standard articles only. |
297
+ | `edgeBandingType` | string | Conditional | Edgebanding for LP_SP/LP_GP series only. Applied to all four edge positions. |
298
+
299
+ **Configuration options:**
300
+
301
+ | Field | Type | Required | Description |
302
+ |---|---|---|---|
303
+ | `drawerType` | string | Conditional | Drawer box construction type. |
304
+ | `jointMethod` | string | Conditional | Case joinery method. |
305
+ | `hingePlate` | string | Conditional | Hinge plate type. |
306
+ | `backPanel` | string | Conditional | Back panel attachment method. |
307
+ | `numOfShelves` | string | Conditional | Number of adjustable shelves (e.g. `"3"`, `"0"`, `"Parametric Shelves"`). |
308
+
309
+ **Gap control** (only for articles with `gapControl = true`):
310
+
311
+ | Field | Type | Valid Values |
312
+ |---|---|---|
313
+ | `gapTop` | string | `"0"` (flush), `"0.0625"` (1/16"), `"0.125"` (1/8") |
314
+ | `gapBottom` | string | same |
315
+ | `gapLeft` | string | same |
316
+ | `gapRight` | string | same |
317
+ | `gapCenter` | string | same (gap between adjacent doors/drawers) |
318
+
319
+ **Gap default behavior:** If an article has `gapControl = true` and any gap field is omitted, the server client-side auto-applies `"0.125"` (1/8") as a default. This is done by fetching the article configuration and filling in missing gap fields before submitting to the API.
320
+
321
+ **Other options:**
322
+
323
+ | Field | Type | Required | Description |
324
+ |---|---|---|---|
325
+ | `topDrwrHeightValue` | string | Conditional | Custom top drawer height in inches (e.g. `"12"`). Valid range: 4–28. Only for articles with `topDrwrHeight` feature. |
326
+ | `leftCornerWidth` | number | Conditional | Left corner width in inches. Corner cabinets with `cornerVariables = true` only. |
327
+ | `rightCornerDepth` | number | Conditional | Right corner depth in inches. Corner cabinets only. |
328
+ | `excludeFronts` | boolean | No | When true, fronts excluded from order. Do not also set `frontMaterial`. |
329
+ | `includeLegLevelers` | boolean | No | When true, leg levelers included. Only valid for articles with `Leg_Levelers` filter tag. |
330
+ | `settingsName` | string | Conditional | **Required** when any configuration values were taken from a saved setting. The exact name of the saved setting (e.g. `"Modern Euro"`). Ensures database traceability. ARTICLE-LEVEL only — never pass at order level. |
331
+
332
+ **Coordinates (optional):**
333
+
334
+ If an architectural drawing is provided, coordinates can be supplied to place cabinets in the CAD file. If omitted, cabinets are placed in a fallback row.
335
+
336
+ | Field | Type | Description |
337
+ |---|---|---|
338
+ | `x` | number | Center X on plan in decimal inches (see coordinate convention below) |
339
+ | `y` | number | Center Y on plan in decimal inches |
340
+ | `z` | number | Height from floor in decimal inches. 0 for base cabinets. Stacked units: sum of all cabinet heights below. |
341
+ | `rotation` | number | Plan orientation in degrees (clockwise). 0 = north wall, 90 = east wall, 180 = south wall, 270 = west wall. |
342
+
343
+ **Coordinate convention for `create_order` / `create_saved_cart`:**
344
+
345
+ The coordinates passed at order creation represent CENTER X and CENTER Y directly — the server stores them as-is (no conversion applied).
346
+
347
+ | Wall | x formula | y formula |
348
+ |---|---|---|
349
+ | North wall (rotation=0) | `D_adj_west + (sum of widths to the left) + this_width/2` | `room_depth - depth/2` |
350
+ | South wall (rotation=180) | `D_adj_west + (sum of widths to the left) + this_width/2` | `depth/2` |
351
+ | East wall (rotation=90) | `room_width - depth/2` (fixed for all on this wall) | `room_depth - D_adj_north - (sum of preceding widths) - this_width/2` |
352
+ | West wall (rotation=270) | `depth/2` (fixed) | same formula as east wall |
353
+
354
+ - `D_adj_west` = depth of west-wall cabinets if present, else 0 (applies to north/south wall x only)
355
+ - `D_adj_north` = depth of north-wall base cabinets if present, else 0
356
+ - Upper cabinets front-flush with base: `x` or `y` offset from base by `(base_depth - upper_depth)`
357
+ - East/west wall upper cabinets: y = same as the base cabinet y (no formula). Front-flush is an x change only.
358
+
359
+ ---
360
+
361
+ #### `list_orders`
362
+
363
+ List orders on the account with pagination.
364
+
365
+ **Input parameters:**
366
+
367
+ | Parameter | Type | Default | Description |
368
+ |---|---|---|---|
369
+ | `limit` | integer 1–100 | 20 | Max results |
370
+ | `offset` | integer ≥ 0 | 0 | Pagination offset |
371
+
372
+ **API call:** `GET /orders?limit=...&offset=...`
373
+
374
+ **Returns:** JSON array of orders, or `"No orders found."` if empty.
375
+
376
+ ---
377
+
378
+ #### `get_order`
379
+
380
+ Get full details and current status for a specific order.
381
+
382
+ **Input parameters:**
383
+
384
+ | Parameter | Type | Required | Description |
385
+ |---|---|---|---|
386
+ | `orderId` | string | Yes | Order ID (e.g. `ORD-001`) |
387
+
388
+ **API call:** `GET /orders/{orderId}`
389
+
390
+ **Returns:** JSON object with order details including status, date, price, projectName, purchaseOrder, and articles array. Each article has `positionName` (unique identifier), `serialNumber`, `quantity`, `height`, `width`, `depth`.
391
+
392
+ **Important:** Use `positionName` (not `serialNumber`) to identify specific cabinets, as multiple cabinets can share the same serial number.
393
+
394
+ ---
395
+
396
+ #### `get_order_breakdown`
397
+
398
+ Get the full Bill of Materials (BOM) for a placed order.
399
+
400
+ **Input parameters:**
401
+
402
+ | Parameter | Type | Required | Description |
403
+ |---|---|---|---|
404
+ | `orderId` | string | Yes | Order ID |
405
+
406
+ **API call:** `GET /orders/{orderId}/breakdown`
407
+
408
+ **Returns:** Formatted plain-text BOM report containing:
409
+ - Number of cabinets
410
+ - Materials: square footage per material type + estimated sheet count (based on 32 sqft/sheet × 1.2 waste factor)
411
+ - Edgebanding: linear footage per edgebanding type
412
+ - Hardware quantities
413
+ - Drawer boxes with dimensions (W × H × D)
414
+ - Stretchable purchased parts with footage
415
+ - Door panels with dimensions
416
+ - Drawer fronts with dimensions
417
+ - Blind fronts with dimensions
418
+
419
+ ---
420
+
421
+ #### `get_position_breakdown`
422
+
423
+ Get detailed breakdown for a single cabinet position within an order.
424
+
425
+ **Input parameters:**
426
+
427
+ | Parameter | Type | Required | Description |
428
+ |---|---|---|---|
429
+ | `orderId` | string | Yes | Order ID |
430
+ | `positionName` | string | Yes | Position name (e.g. `"Base"`, `"Upper.1"`) |
431
+
432
+ **API call:** `GET /orders/{orderId}/position/{positionName}/breakdown`
433
+
434
+ **Returns:** JSON object with materials (sqft), edgebanding, hardware, and drawer boxes for this specific cabinet.
435
+
436
+ ---
437
+
438
+ #### `update_order_options`
439
+
440
+ Update order-level options on a placed order without creating a new version. Changes are persisted immediately and broadcast to the frontend in real time.
441
+
442
+ **Input parameters:**
443
+
444
+ | Parameter | Type | Required | Description |
445
+ |---|---|---|---|
446
+ | `orderId` | string | Yes | Order ID to update |
447
+ | `includeDrawerboxes` | boolean | No | Include drawer boxes. Omit to leave unchanged. |
448
+ | `includeAssembly` | boolean | No | Include assembly. Omit to leave unchanged. |
449
+ | `includeHardware` | boolean | No | Include hardware. Omit to leave unchanged. |
450
+ | `includeFinishing` | boolean | No | Include finishing. Omit to leave unchanged. |
451
+ | `finishingType` | `"Matte"` \| `"Satin"` \| `"Primed"` | Conditional | Required when `includeFinishing` is true. |
452
+ | `finishingColor` | string | Conditional | Required when `includeFinishing` is true (e.g. `"White"`, `"Black"`, `"Natural"`). |
453
+ | `specialInstructions` | string | No | Special instructions. Pass `""` to clear. |
454
+ | `notes` | string | No | Internal notes. Pass `""` to clear. |
455
+ | `projectName` | string | No | Project name. |
456
+ | `purchaseOrder` | string | No | Purchase order number. |
457
+
458
+ **API call:** `PATCH /orders/{orderId}/options`
459
+
460
+ Only fields explicitly provided are sent. Omitted fields are unchanged.
461
+
462
+ **Returns:** JSON of the full updated order options.
463
+
464
+ ---
465
+
466
+ #### `create_order`
467
+
468
+ Place a new cabinet order. Creates a completely new order with a new order ID.
469
+
470
+ **Input parameters:**
471
+
472
+ | Parameter | Type | Required | Description |
473
+ |---|---|---|---|
474
+ | `articles` | ArticleItem[] | Yes | Line items to order (see article schema above) |
475
+ | `projectName` | string | Yes | Project name |
476
+ | `purchaseOrder` | string | Yes | Purchase order number |
477
+ | `projectAddress` | object | No | `{projectAddress1, projectAddress2, projectCity, projectState, projectZipcode}` |
478
+ | `includeDrawerboxes` | boolean | No | Include drawer boxes |
479
+ | `includeAssembly` | boolean | No | Include assembly |
480
+ | `includeHardware` | boolean | No | Include hardware |
481
+ | `includeFinishing` | boolean | No | Include finishing |
482
+ | `finishingType` | `"Matte"` \| `"Satin"` \| `"Primed"` | Conditional | Required if `includeFinishing` is true |
483
+ | `finishingColor` | string | Conditional | Required if `includeFinishing` is true |
484
+ | `specialInstructions` | string | No | Special instructions |
485
+ | `savedCartId` | string | No | tempOrderId of a saved cart whose canvas metadata should be copied to the new order (preserves drawing tool calibration) |
486
+
487
+ **Pre-submission client-side processing:**
488
+ 1. Duplicate `positionName` check — returns error if duplicates detected
489
+ 2. Apply saved setting preset values to each article (user-provided values take precedence)
490
+ 3. Apply gap defaults (`"0.125"`) to gap fields for articles with `gapControl = true`
491
+
492
+ **API call:** `POST /orders`
493
+
494
+ **Returns:** `"Order created successfully. Order ID: {orderId}"`
495
+
496
+ **Required pre-call confirmations from user:** projectName, purchaseOrder, all add-on flags, all article-level configuration fields. The AI must never auto-fill, assume, or default any field.
497
+
498
+ ---
499
+
500
+ #### `create_saved_cart`
501
+
502
+ Save a NEW cabinet configuration as a saved cart for user review before committing to an order. Same input schema as `create_order`.
503
+
504
+ **Use when:** Creating a brand new order from scratch. NOT for editing existing orders.
505
+
506
+ **Pre-submission client-side processing:** Same as `create_order` (duplicate check, preset application, gap defaults).
507
+
508
+ **API call:** `POST /saved-carts`
509
+
510
+ **Returns:** `"Saved cart created successfully. Reference ID: {tempOrderId}. Please inform the user that they can log in to their Sealab account at thesealab.com and navigate to Saved Carts to review their cabinet configuration before placing the order."`
511
+
512
+ ---
513
+
514
+ #### `list_saved_carts`
515
+
516
+ List all saved carts for the authenticated user.
517
+
518
+ **Input parameters:** None
519
+
520
+ **API call:** `GET /saved-carts`
521
+
522
+ **Returns:** Summary list of carts, each showing: `tempOrderId`, project name, PO number, article count, date, and whether it is an edit cart (revision of an existing order) with its `sourceOrderId`.
523
+
524
+ ---
525
+
526
+ #### `get_saved_cart`
527
+
528
+ Read the current state of a saved cart including all articles and their configurations.
529
+
530
+ **Input parameters:**
531
+
532
+ | Parameter | Type | Required | Description |
533
+ |---|---|---|---|
534
+ | `tempOrderId` | string | Yes | Saved cart reference ID (plain numeric string, e.g. `"901787"`) |
535
+
536
+ **API call:** `GET /saved-carts/{tempOrderId}`
537
+
538
+ **Returns:** Formatted plain-text summary of the cart: project name, PO, and for each article: positionName, displayName, serialNumber, dimensions, all material/edge/config fields, gaps, corner dimensions, and preset name.
539
+
540
+ **Important:** Call this before making edits — users may have updated the cart directly in the Sealab web portal.
541
+
542
+ ---
543
+
544
+ #### `add_articles_to_cart`
545
+
546
+ Add new articles to an existing saved cart.
547
+
548
+ **Input parameters:**
549
+
550
+ | Parameter | Type | Required | Description |
551
+ |---|---|---|---|
552
+ | `tempOrderId` | string | Yes | Saved cart reference ID (plain numeric string) |
553
+ | `articles` | ArticleItem[] | Yes | Articles to add. Accepts native JSON array or JSON string. |
554
+
555
+ **Pre-submission processing:** Same as `create_order` (duplicate check within the batch, preset application, gap defaults).
556
+
557
+ **API call:** `POST /saved-carts/{tempOrderId}/articles`
558
+
559
+ **Returns:** `"Added {N} article(s) to saved cart {tempOrderId}. Total cart ID: {tempOrderId}"`
560
+
561
+ ---
562
+
563
+ #### `update_saved_cart_articles`
564
+
565
+ Update the configuration of one or more existing articles in a saved cart. Only the fields that have changed need to be sent — `positionName` is required to identify each article; all other fields are optional. Can also change the cabinet type by patching `serialNumber`.
566
+
567
+ **Input parameters:**
568
+
569
+ | Parameter | Type | Required | Description |
570
+ |---|---|---|---|
571
+ | `tempOrderId` | string | Yes | Saved cart reference ID |
572
+ | `articles` | UpdateArticleItem[] | Yes | Articles to update. Each must have `positionName`; all other fields optional. Accepts native JSON array or JSON string. |
573
+
574
+ Note: The PATCH update does NOT include coordinate fields (x, y, z, rotation) — those are updated via `update_cabinet_coordinates` or `patch_cabinet_coordinates`.
575
+
576
+ **Pre-submission processing:** Applies saved setting preset values where `settingsName` is specified.
577
+
578
+ **API call:** `PATCH /saved-carts/{tempOrderId}/articles`
579
+
580
+ **Returns:** `"Updated {N} article(s) in saved cart {tempOrderId}."`
581
+
582
+ ---
583
+
584
+ #### `delete_saved_cart_articles`
585
+
586
+ Remove one or more articles from a saved cart by `positionName`.
587
+
588
+ **Input parameters:**
589
+
590
+ | Parameter | Type | Required | Description |
591
+ |---|---|---|---|
592
+ | `tempOrderId` | string | Yes | Saved cart reference ID |
593
+ | `positionNames` | string[] | Yes | Position names to delete. Accepts native JSON array or JSON string. |
594
+
595
+ **API call:** `DELETE /saved-carts/{tempOrderId}/articles`
596
+
597
+ **Returns:** `"Deleted {N} article(s) from saved cart {tempOrderId}: [{names}]."`
598
+
599
+ ---
600
+
601
+ #### `create_edit_cart`
602
+
603
+ Create a saved cart from an EXISTING placed order for editing. Copies all articles and order settings from the source order into a new saved cart. Sets `sourceOrderId` and `isEditCart` flags required for versioned submission.
604
+
605
+ **Use when:** User wants to edit or revise a previously placed order (e.g. "edit order 429308").
606
+
607
+ **Input parameters:**
608
+
609
+ | Parameter | Type | Required | Description |
610
+ |---|---|---|---|
611
+ | `orderId` | string | Yes | Order ID to create edit cart from |
612
+
613
+ **API call:** `POST /saved-carts/{orderId}/create-edit-cart`
614
+
615
+ **Returns:** `"Edit cart created successfully. Reference ID: {tempOrderId}. Source order: {sourceOrderId}. Inform the user that they can log in to their Sealab account at thesealab.com and navigate to Saved Carts to review their cabinet configuration before submitting the revised order."`
616
+
617
+ ---
618
+
619
+ #### `submit_cart_as_version`
620
+
621
+ Submit a saved edit cart as a versioned order revision (e.g. `ORDER123_v2`). Copies canvas data, coordinates, and permissions from the source order, applies modified articles, then deletes the saved cart.
622
+
623
+ **Use ONLY when:** The cart was created via `create_edit_cart` (has `sourceOrderId` set).
624
+
625
+ **Input parameters:**
626
+
627
+ | Parameter | Type | Required | Description |
628
+ |---|---|---|---|
629
+ | `tempOrderId` | string | Yes | Saved cart reference ID (plain numeric string — no prefix like `"SavedCart_"`) |
630
+ | `projectName` | string | No | Overrides saved cart value if provided |
631
+ | `purchaseOrder` | string | No | Overrides saved cart value if provided |
632
+ | `includeDrawerboxes` | boolean | No | |
633
+ | `includeAssembly` | boolean | No | |
634
+ | `includeHardware` | boolean | No | |
635
+ | `includeFinishing` | boolean | No | |
636
+ | `finishingType` | `"Matte"` \| `"Satin"` \| `"Primed"` | Conditional | Required if `includeFinishing` is true |
637
+ | `finishingColor` | string | Conditional | Required if `includeFinishing` is true |
638
+ | `specialInstructions` | string | No | |
639
+ | `projectAddress` | object | No | `{projectAddress1, projectAddress2, projectCity, projectState, projectZipcode}` |
640
+
641
+ **API call:** `POST /saved-carts/{tempOrderId}/submit-as-version`
642
+
643
+ **Returns:** `"Order version created successfully. New order ID: {newOrderId} (version {versionNumber}). The saved cart has been deleted. Inform the user that they will receive an email notification and must log in to complete payment for the revised order."`
644
+
645
+ ---
646
+
647
+ ### Module 5: Saved Settings (`src/tools/saved-settings.ts`)
648
+
649
+ Saved settings are reusable configuration presets that store material, edge, joint, drawer, hinge, and shelf selections. They can be applied to articles via the `settingsName` field, reducing repetitive data entry.
650
+
651
+ **Saved setting fields:**
652
+
653
+ | Field | Type | Description |
654
+ |---|---|---|
655
+ | `savedSettingsId` | number | Auto-assigned ID |
656
+ | `name` | string | Descriptive name (e.g. `"Modern Euro"`, `"Traditional"`) |
657
+ | `frontMaterial` | string | Front material specification |
658
+ | `caseMaterial` | string | Case material specification |
659
+ | `backPanelMaterial` | string | Back panel material |
660
+ | `backPanel` | string | Back panel type |
661
+ | `drawerType` | string | Drawer type |
662
+ | `jointMethod` | string | Joint method |
663
+ | `caseEdge` | string | Case edge treatment |
664
+ | `frontEdge` | string | Front edge treatment |
665
+ | `numOfShelves` | string | Number of shelves |
666
+ | `bottomPanelConnector` | string | Bottom panel connector (e.g. `"Leg Levelers"`) |
667
+ | `topDrwrHeightValue` | string | Top drawer height value |
668
+ | `hingePlate` | string | Hinge plate type |
669
+ | `includeLegLevelers` | string | Include leg levelers flag |
670
+ | `isPreset` | number | `1` = public preset available to all users, `0` = user-specific |
671
+
672
+ **Usage rule:** When values from a saved setting are used to configure any article, the `settingsName` field MUST be set on that article object to the exact name of the saved setting. This is an article-level field — never pass at the order level.
673
+
674
+ ---
675
+
676
+ #### `get_saved_settings_presets`
677
+
678
+ Get all public preset saved settings (available to all users).
679
+
680
+ **Input parameters:** None
681
+
682
+ **API call:** `GET /saved-settings/presets`
683
+
684
+ **Returns:** JSON array of preset saved settings, or `"No preset saved settings found."` if empty.
685
+
686
+ ---
687
+
688
+ #### `get_my_saved_settings`
689
+
690
+ Get all saved settings for the currently authenticated user — both personal settings and public presets.
691
+
692
+ **Input parameters:** None
693
+
694
+ **API call:** `GET /saved-settings/user`
695
+
696
+ **Returns:** JSON array of saved settings, or `"No saved settings found for your account."` if empty.
697
+
698
+ ---
699
+
700
+ #### `get_saved_setting_by_id`
701
+
702
+ Get a specific saved setting by its numeric ID.
703
+
704
+ **Input parameters:**
705
+
706
+ | Parameter | Type | Required | Description |
707
+ |---|---|---|---|
708
+ | `id` | number | Yes | Saved setting ID |
709
+
710
+ **API call:** `GET /saved-settings/{id}`
711
+
712
+ **Authorization:** Presets available to all authenticated users; user settings available only to the owner.
713
+
714
+ **Returns:** JSON object for the saved setting.
715
+
716
+ ---
717
+
718
+ #### `create_saved_setting`
719
+
720
+ Create a new saved setting for the current user.
721
+
722
+ **Input parameters:** All saved setting fields except `savedSettingsId` and `isPreset` (optional boolean — `true` creates a public preset, requires admin privileges).
723
+
724
+ **API call:** `POST /saved-settings/user`
725
+
726
+ **Returns:** `"Saved setting created successfully. ID: {savedSettingsId}"`
727
+
728
+ ---
729
+
730
+ #### `update_saved_setting`
731
+
732
+ Update an existing saved setting. Can only update the user's own settings (not presets).
733
+
734
+ **Input parameters:** All saved setting fields. `savedSettingsId` is required.
735
+
736
+ **API call:** `PUT /saved-settings/{savedSettingsId}`
737
+
738
+ **Returns:** `"Saved setting updated successfully. ID: {savedSettingsId}"`
739
+
740
+ ---
741
+
742
+ #### `delete_saved_setting`
743
+
744
+ Delete a saved setting. Can only delete the user's own settings.
745
+
746
+ **Input parameters:**
747
+
748
+ | Parameter | Type | Required | Description |
749
+ |---|---|---|---|
750
+ | `id` | number | Yes | Saved setting ID to delete |
751
+
752
+ **API call:** `DELETE /saved-settings/{id}`
753
+
754
+ **Returns:** `"Saved setting {id} deleted successfully."`
755
+
756
+ ---
757
+
758
+ ### Module 6: Canvas (`src/tools/canvas.ts`)
759
+
760
+ Tools for managing the Sealab Drawing Tool — the web-based canvas where users position cabinets on floor plan and elevation views. Supports both placed orders and saved carts.
761
+
762
+ #### Canvas Types
763
+
764
+ | Canvas Type | Description |
765
+ |---|---|
766
+ | `plan` | Floor plan view (overhead) |
767
+ | `north` | North elevation view |
768
+ | `south` | South elevation view |
769
+ | `east` | East elevation view |
770
+ | `west` | West elevation view |
771
+
772
+ ---
773
+
774
+ #### Canvas Coordinate System (for `update_cabinet_coordinates` and `patch_cabinet_coordinates`)
775
+
776
+ The coordinate system used by the canvas tools differs slightly from the `create_order` / `create_saved_cart` coordinate system. Coordinates are stored as **back-edge** values (not center) for the depth axis.
777
+
778
+ | Coordinate | Meaning |
779
+ |---|---|
780
+ | `x` | **Back-edge X** in decimal inches (absolute room coordinate: 0 = west wall face) |
781
+ | `y` | **Back-edge Y** in decimal inches (absolute room coordinate: 0 = south wall face, room_depth = north wall face) |
782
+ | `z` | Height from floor in decimal inches (0 for base cabinets on floor) |
783
+ | `rotation` | Wall orientation in degrees clockwise (0=north, 90=east, 180=south, 270=west) |
784
+
785
+ **x formulas by wall:**
786
+
787
+ | Wall | Formula |
788
+ |---|---|
789
+ | North/South | `x = D_adj_west + (sum of widths of cabinets to the left) + this_width/2` |
790
+ | West wall base | `x = 0` (back against west wall) |
791
+ | West wall upper (front-flush) | `x = base_depth - upper_depth` |
792
+ | East wall base | `x = room_width` (back against east wall) |
793
+ | East wall upper (front-flush) | `x = base_x - (base_depth - upper_depth)` |
794
+
795
+ **y formulas by wall:**
796
+
797
+ | Wall | Formula |
798
+ |---|---|
799
+ | South wall base | `y = 0` (back against south wall) |
800
+ | South wall upper (front-flush) | `y = base_depth - upper_depth` |
801
+ | North wall base | `y = room_depth` (back against north wall) |
802
+ | North wall upper (front-flush) | `y = base_y - (base_depth - upper_depth)` |
803
+ | East/West wall | `y = room_depth - D_adj_north - (sum of widths of all preceding cabinets) - this_width/2` |
804
+ | East/West upper | `y = same as base cabinet on that position` (front-flush is x change only) |
805
+
806
+ **Multi-quantity articles:** When an article was added with `quantity > 1`, the server expands it into separate articles with incrementing suffixes (e.g. `DR_B2_01` qty=3 → `DR_B2_01`, `DR_B2_02`, `DR_B2_03`). Every expanded position name must have its own coordinate entry.
807
+
808
+ ---
809
+
810
+ #### `get_drawing_tool_state`
811
+
812
+ Retrieve the full drawing tool state for an order in a single call.
813
+
814
+ **Input parameters:**
815
+
816
+ | Parameter | Type | Required | Description |
817
+ |---|---|---|---|
818
+ | `orderId` | string | Yes | Order ID |
819
+
820
+ **API call:** `GET /canvas/{orderId}`
821
+
822
+ **Returns:** JSON with three sections:
823
+ 1. `metadata` — canvas scale, calibration, Z0 floor references, elevation visibility, active canvases, and all other drawing tool state
824
+ 2. `images` — S3 keys for background and snapshot images per canvas type
825
+ 3. `coordinates` — all cabinet positions keyed by positionName
826
+
827
+ Returns 404 if no drawing tool state exists yet for this order.
828
+
829
+ ---
830
+
831
+ #### `get_canvas_metadata`
832
+
833
+ Retrieve canvas metadata only (without images or coordinates).
834
+
835
+ **Input parameters:**
836
+
837
+ | Parameter | Type | Required | Description |
838
+ |---|---|---|---|
839
+ | `orderId` | string | Yes | Order ID |
840
+
841
+ **API call:** `GET /canvas/{orderId}/metadata`
842
+
843
+ **Returns:** JSON containing: `activeCanvases`, `canvasMeta` (scale/position/zoom per canvas type), `z0References` (floor calibration per elevation), `verticalCalibration`, `elevationVisibility`, `calibrationOffsets`, `itemRotations`, `fixedElevationPositions`, `elevationCalibrated`, `globalElevationOffsets`, `calibratedCabinets`.
844
+
845
+ ---
846
+
847
+ #### `update_canvas_metadata`
848
+
849
+ Replace all canvas metadata fields for an order. All existing metadata is overwritten. Fields not included are set to null.
850
+
851
+ **Input parameters:** `orderId` + any subset of these metadata fields:
852
+
853
+ | Field | Type | Description |
854
+ |---|---|---|
855
+ | `canvasMeta` | object | Per-canvas display metadata keyed by canvas type. Each entry: scale (ratio, unit), position (x, y), rotation, zoom, backgroundImage (S3 URL). |
856
+ | `z0References` | object | Floor reference line calibration per elevation. Each entry: imagePercentFromBottom, originalImageDimensions, realWorldHeight, timestamp. |
857
+ | `verticalCalibration` | object | Vertical height mapping calibration per elevation. Each entry: scale, offset, unit, timestamp. |
858
+ | `elevationVisibility` | object | Item visibility per elevation, keyed by itemId. Each value maps elevation name to boolean. |
859
+ | `calibrationOffsets` | object | Coordinate positioning offsets per canvas type. Each entry: x, y offset values in pixels. |
860
+ | `itemRotations` | object | Item-specific rotation values keyed by itemId. Values are rotation in degrees. |
861
+ | `activeCanvases` | string[] | List of active canvas types (`"plan"`, `"north"`, `"south"`, `"east"`, `"west"`). |
862
+ | `fixedElevationPositions` | object | Pinned cabinet pixel positions after global calibration, keyed by elevation then positionName. |
863
+ | `elevationCalibrated` | object | Global calibration status per elevation (boolean values). |
864
+ | `globalElevationOffsets` | object | Coordinate system transformations per elevation (maps plan coordinates to elevation coordinates). Each entry: x, y offset in inches. |
865
+ | `calibratedCabinets` | object | Which cabinets have been globally calibrated per elevation, keyed by elevation then positionName. |
866
+
867
+ **API call:** `PUT /canvas/{orderId}/metadata`
868
+
869
+ **Returns:** JSON of updated metadata.
870
+
871
+ ---
872
+
873
+ #### `get_canvas_images`
874
+
875
+ Retrieve canvas image records for an order.
876
+
877
+ **Input parameters:**
878
+
879
+ | Parameter | Type | Required | Description |
880
+ |---|---|---|---|
881
+ | `orderId` | string | Yes | Order ID |
882
+
883
+ **API call:** `GET /canvas/{orderId}/images`
884
+
885
+ **Returns:** JSON array of entries, each with: `canvasType` (plan/north/south/east/west), `imageType` (background/snapshot), `s3Key` (S3 path to the image file).
886
+
887
+ ---
888
+
889
+ #### `get_cabinet_coordinates`
890
+
891
+ Retrieve all stored cabinet coordinates for an order or saved cart.
892
+
893
+ **Input parameters:**
894
+
895
+ | Parameter | Type | Required | Description |
896
+ |---|---|---|---|
897
+ | `orderId` | string | Yes | Order ID or saved cart tempOrderId |
898
+
899
+ **API call:** `GET /canvas/{orderId}/coordinates`
900
+
901
+ **Returns:** JSON of all cabinet coordinates. Each entry has: `positionName`, `x` (back-edge X), `y` (back-edge Y), `z` (height from floor), `rotation`. Use these values directly in `update_cabinet_coordinates` for round-trip operations.
902
+
903
+ ---
904
+
905
+ #### `update_cabinet_coordinates`
906
+
907
+ Replace ALL cabinet coordinates for an order or saved cart. All existing coordinates are deleted and replaced with the provided list. To remove a cabinet's coordinates, omit it from the list.
908
+
909
+ **Recommended workflow for saved carts:**
910
+ 1. `create_edit_cart` or use existing saved cart tempOrderId
911
+ 2. `update_cabinet_coordinates(tempOrderId, [...])` ← set coordinates on the cart BEFORE submitting
912
+ 3. `submit_cart_as_version` — coordinates stored on the cart are written to the XML at submit time
913
+
914
+ **Input parameters:**
915
+
916
+ | Parameter | Type | Required | Description |
917
+ |---|---|---|---|
918
+ | `orderId` | string | Yes | Order ID or saved cart tempOrderId |
919
+ | `coordinates` | CoordinateItem[] | Yes | Full replacement list. Each item: positionName, x, y, z, rotation. |
920
+
921
+ **API call:** `PUT /canvas/{orderId}/coordinates`
922
+
923
+ **Returns:** JSON of stored coordinates.
924
+
925
+ ---
926
+
927
+ #### `patch_cabinet_coordinates`
928
+
929
+ Partially update coordinates for specific cabinets only. Other cabinets keep their existing coordinates unchanged.
930
+
931
+ **Use when:** Repositioning one or a few specific cabinets without disturbing the rest of the layout.
932
+
933
+ **Use `update_cabinet_coordinates` instead when:** Setting coordinates for the first time, or removing cabinets' coordinates.
934
+
935
+ **Input parameters:**
936
+
937
+ | Parameter | Type | Required | Description |
938
+ |---|---|---|---|
939
+ | `orderId` | string | Yes | Order ID or saved cart tempOrderId |
940
+ | `coordinates` | CoordinateItem[] (min 1) | Yes | Cabinets to update. Only these positionNames are affected. |
941
+
942
+ **API call:** `PATCH /canvas/{orderId}/coordinates`
943
+
944
+ **Returns:** JSON with `patched` count (positions sent), `total` (all stored coordinates), and the full updated coordinate list. The frontend is notified in real time via WebSocket.
945
+
946
+ ---
947
+
948
+ #### `upload_canvas_background_image`
949
+
950
+ Upload a background image for a specific canvas from a local file path. The existing background image for that canvas type is automatically removed from S3 and replaced. Canvas defaults (scale 1, zoom 1, position 0/0) are initialized for the uploaded canvas type, and it is added to `activeCanvases`. The drawing tool frontend is notified in real time via WebSocket.
951
+
952
+ **Input parameters:**
953
+
954
+ | Parameter | Type | Required | Description |
955
+ |---|---|---|---|
956
+ | `orderId` | string | Yes | Order ID |
957
+ | `canvasType` | `"plan"` \| `"north"` \| `"south"` \| `"east"` \| `"west"` | Yes | Which canvas view to upload to |
958
+ | `imagePath` | string | Yes | Absolute local filesystem path to the image file. Supported formats: `.png`, `.jpg`, `.jpeg`, `.webp`. |
959
+
960
+ The MCP server reads and encodes the file itself — do not convert it before passing.
961
+
962
+ **API call:** `POST /canvas/{orderId}/images/{canvasType}/background` (multipart/form-data)
963
+
964
+ **Returns:** JSON of the uploaded image record.
965
+
966
+ ---
967
+
968
+ ## Internal Behaviors
969
+
970
+ ### `flexArray` Helper (`orders.ts`)
971
+
972
+ Array fields in order/cart tools accept either a native JSON array or a JSON string representing an array. This handles the behavior of some MCP clients (including Claude Code) that validate tool arguments against the JSON Schema before sending — a plain `z.array()` schema causes the client to reject string values. The `flexArray` helper generates a `{ anyOf: [array, string] }` schema so strings pass client-side validation, and are parsed and re-validated server-side.
973
+
974
+ ### Saved Setting Application (`orders.ts`)
975
+
976
+ When an article has a `settingsName` set, the server client-side fetches that saved setting (from user settings first, then public presets) and merges its values as defaults into the article. User-explicitly-provided values always take precedence. The following fields are mapped from the saved setting: `caseMaterial`, `frontMaterial`, `backPanelMaterial`, `backPanel`, `drawerType`, `jointMethod`, `caseEdge`, `frontEdge`, `hingePlate`, `numOfShelves`, `topDrwrHeightValue`, `includeLegLevelers`.
977
+
978
+ ### Settings Cache (`orders.ts`)
979
+
980
+ Fetched saved settings are cached in memory with a **5-minute TTL** (`SETTINGS_CACHE_TTL_MS = 300,000 ms`) to avoid redundant API calls within a session. The cache handles updates made from the web portal — entries older than the TTL are re-fetched on next use.
981
+
982
+ ### Duplicate `positionName` Guard
983
+
984
+ `create_order`, `create_saved_cart`, `add_articles_to_cart`, and `update_saved_cart_articles` all perform a client-side check for duplicate `positionName` values within the submitted articles before calling the API. If duplicates are found, a descriptive error is returned immediately without making an API call.
985
+
986
+ ### Gap Default Application (`orders.ts`)
987
+
988
+ For articles that have `gapControl = true` (determined by fetching `GET /catalog/{serialNumber}/configuration`), any gap field (`gapTop`, `gapBottom`, `gapLeft`, `gapRight`, `gapCenter`) that is not provided by the user is automatically set to `"0.125"` (1/8 inch). This is applied in `create_order`, `create_saved_cart`, and `add_articles_to_cart`.
989
+
990
+ ---
991
+
992
+ ## Tooltip Resources (`resources/tooltips/`)
993
+
994
+ Twenty-five `.txt` files containing HTML-formatted guidance text for each configurable field. These are read at runtime by `get_article_context` (to build the full context block) and `get_configuration_info` (for single-field deep dives). HTML is stripped to plain text before returning.
995
+
996
+ | File | Field |
997
+ |---|---|
998
+ | `backPanel.txt` | Back panel attachment method |
999
+ | `backPanelMaterial.txt` | Back panel material |
1000
+ | `caseEdge.txt` | Case edgebanding |
1001
+ | `caseMaterial.txt` | Case/box material |
1002
+ | `depth.txt` | Cabinet depth |
1003
+ | `drawerType.txt` | Drawer box construction type (undermount glides only) |
1004
+ | `edgeBandingType.txt` | Edgebanding type (LP_SP/LP_GP series) |
1005
+ | `excludeFronts.txt` | Exclude fronts flag |
1006
+ | `frontEdge.txt` | Front edgebanding |
1007
+ | `frontMaterial.txt` | Door/drawer front material |
1008
+ | `gapBottom.txt` | Bottom reveal gap |
1009
+ | `gapCenter.txt` | Center gap between adjacent doors/drawers |
1010
+ | `gapLeft.txt` | Left reveal gap |
1011
+ | `gapRight.txt` | Right reveal gap |
1012
+ | `gapTop.txt` | Top reveal gap |
1013
+ | `height.txt` | Cabinet height |
1014
+ | `hingePlate.txt` | Hinge plate type (Cross Plate / Inline Plate, Euro-style cup hinges) |
1015
+ | `includeLegLevelers.txt` | Leg levelers flag |
1016
+ | `jointMethod.txt` | Case joinery method (biscuit joints, Lamello #20, biscuits ~5" apart) |
1017
+ | `leftCornerWidth.txt` | Left corner width (corner cabinets) |
1018
+ | `numOfShelves.txt` | Number of adjustable shelves |
1019
+ | `positionName.txt` | Position name field guidance |
1020
+ | `rightCornerDepth.txt` | Right corner depth (corner cabinets) |
1021
+ | `topDrwrHeight.txt` | Top drawer height |
1022
+ | `width.txt` | Cabinet width |
1023
+
1024
+ ---
1025
+
1026
+ ## Build and Development
1027
+
1028
+ ### Scripts (`package.json`)
1029
+
1030
+ | Script | Command | Purpose |
1031
+ |---|---|---|
1032
+ | `build` | `tsc` | Compile TypeScript to `dist/` |
1033
+ | `start` | `node dist/index.js` | Run compiled server |
1034
+ | `dev` | `ts-node src/index.ts` | Run directly from TypeScript (no compile step) |
1035
+ | `test` | `vitest run` | Run test suite once |
1036
+ | `test:watch` | `vitest` | Run tests in watch mode |
1037
+
1038
+ ### Binary
1039
+
1040
+ The package exposes `sealab-mcp-server` as a CLI binary pointing to `dist/index.js`.
1041
+
1042
+ ### TypeScript Configuration (`tsconfig.json`)
1043
+
1044
+ - Target: `ES2022`
1045
+ - Module: `commonjs`
1046
+ - Strict mode enabled
1047
+ - Output: `dist/`
1048
+ - Source root: `src/`
1049
+ - Test files (`*.test.ts`) excluded from compilation
1050
+
1051
+ ---
1052
+
1053
+ ## API Surface Summary
1054
+
1055
+ All endpoints target `{SEALAB_API_URL}/api/mcp/v1`:
1056
+
1057
+ | Endpoint | Method | Tool |
1058
+ |---|---|---|
1059
+ | `/catalog/search` | GET | `search_catalog` |
1060
+ | `/catalog/{sn}` | GET | `get_article` |
1061
+ | `/catalog/filter-tags` | GET | `list_filter_tags` |
1062
+ | `/catalog/{sn}/configuration` | GET | `get_article_configuration`, `get_article_context`, internal (gap/preset logic) |
1063
+ | `/catalog/{sn}/options` | GET | `get_article_options` |
1064
+ | `/orders` | GET | `list_orders` |
1065
+ | `/orders` | POST | `create_order` |
1066
+ | `/orders/{id}` | GET | `get_order` |
1067
+ | `/orders/{id}/breakdown` | GET | `get_order_breakdown` |
1068
+ | `/orders/{id}/position/{pos}/breakdown` | GET | `get_position_breakdown` |
1069
+ | `/orders/{id}/options` | PATCH | `update_order_options` |
1070
+ | `/saved-carts` | GET | `list_saved_carts` |
1071
+ | `/saved-carts` | POST | `create_saved_cart` |
1072
+ | `/saved-carts/{id}` | GET | `get_saved_cart` |
1073
+ | `/saved-carts/{id}/articles` | POST | `add_articles_to_cart` |
1074
+ | `/saved-carts/{id}/articles` | PATCH | `update_saved_cart_articles` |
1075
+ | `/saved-carts/{id}/articles` | DELETE | `delete_saved_cart_articles` |
1076
+ | `/saved-carts/{id}/create-edit-cart` | POST | `create_edit_cart` |
1077
+ | `/saved-carts/{id}/submit-as-version` | POST | `submit_cart_as_version` |
1078
+ | `/saved-settings/presets` | GET | `get_saved_settings_presets`, internal (preset lookup) |
1079
+ | `/saved-settings/user` | GET | `get_my_saved_settings`, internal (preset lookup) |
1080
+ | `/saved-settings/user` | POST | `create_saved_setting` |
1081
+ | `/saved-settings/{id}` | GET | `get_saved_setting_by_id` |
1082
+ | `/saved-settings/{id}` | PUT | `update_saved_setting` |
1083
+ | `/saved-settings/{id}` | DELETE | `delete_saved_setting` |
1084
+ | `/canvas/{id}` | GET | `get_drawing_tool_state` |
1085
+ | `/canvas/{id}/metadata` | GET | `get_canvas_metadata` |
1086
+ | `/canvas/{id}/metadata` | PUT | `update_canvas_metadata` |
1087
+ | `/canvas/{id}/images` | GET | `get_canvas_images` |
1088
+ | `/canvas/{id}/coordinates` | GET | `get_cabinet_coordinates` |
1089
+ | `/canvas/{id}/coordinates` | PUT | `update_cabinet_coordinates` |
1090
+ | `/canvas/{id}/coordinates` | PATCH | `patch_cabinet_coordinates` |
1091
+ | `/canvas/{id}/images/{type}/background` | POST (multipart) | `upload_canvas_background_image` |
1092
+
1093
+ ---
1094
+
1095
+ ## Proposed Future Enhancements (`PROPOSED-CHANGES-INSERTION-POINTS.md`)
1096
+
1097
+ The following changes are documented as proposals (not yet implemented, as of the file's 2026-03-27 date):
1098
+
1099
+ | # | Change | Status | Priority |
1100
+ |---|---|---|---|
1101
+ | 1 | Add coordinate acknowledgment (echo stored coordinates) to `create_order` / `create_saved_cart` response | Not implemented | P1 |
1102
+ | 2 | Add `lastModifiedAt` timestamp to canvas coordinate entries | Not implemented | P1 |
1103
+ | 3 | Add `diff_cabinet_coordinates` tool for efficient change detection | Not implemented | P2 |
1104
+ | 4 | Add `get_cabinet_coordinates_batch` for reading coordinates across multiple orders at once | Not implemented | P2 |
1105
+ | 5 | Add JSDoc block documenting the coordinate convention in `orders.ts` | Not implemented | P0 |
1106
+
1107
+ These changes are motivated by an integration with "Openclaw" — a proposal engine that extracts cabinet insertion points from architectural drawings. The proposals are all additive (no breaking changes) and several require backend API modifications in addition to MCP tool changes.
1108
+
1109
+ ---
1110
+
1111
+ ## Intended Usage Workflow
1112
+
1113
+ ### Standard New Order Flow
1114
+
1115
+ 1. **Browse catalog** — `search_catalog` + `list_filter_tags` to find candidate articles
1116
+ 2. **Select article** — `get_article` to see full details
1117
+ 3. **Load context** — `get_article_context` to understand which fields apply and get guidance
1118
+ 4. **Load options** — `get_article_options` to get valid string values for each field
1119
+ 5. **Collect user input** — present all applicable options, ask user to confirm every value
1120
+ 6. **Save as cart** — `create_saved_cart` with all confirmed values (user reviews at thesealab.com)
1121
+ 7. **Place order** — `create_order` once user is satisfied (or submit cart separately)
1122
+
1123
+ ### Order Editing Flow
1124
+
1125
+ 1. **Find the order** — `list_orders` or `get_order`
1126
+ 2. **Create edit cart** — `create_edit_cart(orderId)` → returns `tempOrderId`
1127
+ 3. **Review current state** — `get_saved_cart(tempOrderId)`
1128
+ 4. **Modify articles** — `update_saved_cart_articles` / `add_articles_to_cart` / `delete_saved_cart_articles`
1129
+ 5. **Update coordinates** — `update_cabinet_coordinates(tempOrderId, ...)` BEFORE submitting
1130
+ 6. **Submit revision** — `submit_cart_as_version(tempOrderId, ...)`
1131
+
1132
+ ### Configuration Presets Flow
1133
+
1134
+ 1. **Browse presets** — `get_saved_settings_presets` or `get_my_saved_settings`
1135
+ 2. **Apply to article** — include preset field values in article AND set `settingsName` to the preset's exact name
1136
+ 3. **Server merges** — the MCP server fetches the preset client-side and fills in any fields not explicitly provided