drawio-mcp-server 2.0.4 → 2.1.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 (71) hide show
  1. package/README.md +29 -3
  2. package/build/assets/downloader.js +16 -17
  3. package/build/config.js +253 -5
  4. package/build/config.test.js +449 -1
  5. package/build/emitter_bus.js +3 -0
  6. package/build/emitter_bus.test.js +7 -0
  7. package/build/index.capabilities.test.js +45 -0
  8. package/build/index.js +416 -82
  9. package/build/install-desktop-plugin.js +56 -0
  10. package/build/install-desktop-plugin.test.js +66 -0
  11. package/build/multi-transport.test.js +117 -0
  12. package/build/plugin/mcp-plugin.js +2180 -1105
  13. package/build/prefetch-assets.js +6 -5
  14. package/build/real-environment/document-targeting.test.js +124 -0
  15. package/build/real-environment/export-diagram.test.js +1 -1
  16. package/build/real-environment/harness.js +118 -31
  17. package/build/real-environment/import-mermaid.test.js +81 -0
  18. package/build/real-environment/pages-and-concurrency.test.js +448 -0
  19. package/build/real-environment/shapes.test.js +58 -0
  20. package/build/real-environment/tools.js +51 -6
  21. package/build/register-tool.js +31 -0
  22. package/build/request_queue.js +29 -0
  23. package/build/request_queue.test.js +98 -0
  24. package/build/stdio-transport-purity.test.js +88 -0
  25. package/build/strip-schema.js +61 -0
  26. package/build/tls/expiry.js +14 -0
  27. package/build/tls/expiry.test.js +54 -0
  28. package/build/tls/generate.js +123 -0
  29. package/build/tls/generate.test.js +115 -0
  30. package/build/tls/index.js +80 -0
  31. package/build/tls/index.test.js +141 -0
  32. package/build/tls/install-hint.js +45 -0
  33. package/build/tls/install-hint.test.js +32 -0
  34. package/build/tls/load.js +18 -0
  35. package/build/tls/load.test.js +40 -0
  36. package/build/tls/paths.js +30 -0
  37. package/build/tls/paths.test.js +53 -0
  38. package/build/tls/san.js +27 -0
  39. package/build/tls/san.test.js +72 -0
  40. package/build/tool-registry.test.js +823 -0
  41. package/build/tool.js +74 -15
  42. package/build/tool.test.js +250 -7
  43. package/build/tools/add-cell-of-shape.js +4 -2
  44. package/build/tools/add-edge.js +3 -1
  45. package/build/tools/add-rectangle.js +4 -2
  46. package/build/tools/copy-page.js +13 -0
  47. package/build/tools/create-layer.js +4 -2
  48. package/build/tools/create-page.js +8 -0
  49. package/build/tools/delete-cell-by-id.js +4 -2
  50. package/build/tools/edit-cell.js +3 -1
  51. package/build/tools/edit-edge.js +3 -1
  52. package/build/tools/export-diagram.js +7 -3
  53. package/build/tools/get-active-layer.js +4 -1
  54. package/build/tools/get-current-page.js +5 -0
  55. package/build/tools/get-selected-cell.js +4 -1
  56. package/build/tools/import-diagram.js +12 -2
  57. package/build/tools/import-mermaid.js +31 -0
  58. package/build/tools/index.js +14 -0
  59. package/build/tools/list-documents.js +17 -0
  60. package/build/tools/list-layers.js +4 -1
  61. package/build/tools/list-paged-model.js +4 -3
  62. package/build/tools/list-pages.js +5 -0
  63. package/build/tools/move-cell-to-layer.js +3 -1
  64. package/build/tools/rename-page.js +10 -0
  65. package/build/tools/set-active-layer.js +4 -2
  66. package/build/tools/set-cell-data.js +3 -1
  67. package/build/tools/set-cell-parent.js +3 -1
  68. package/build/tools/set-cell-shape.js +3 -1
  69. package/build/tools/shared.js +35 -0
  70. package/build/tools/shared.test.js +29 -0
  71. package/package.json +12 -4
package/README.md CHANGED
@@ -9,11 +9,21 @@ Let's do some Vibe Diagramming with the most wide-spread diagramming tool called
9
9
 
10
10
  ## Key Highlights
11
11
 
12
+ - Enable Draw.io MCP in IFrames ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
13
+ - AWS, GCP, Azure, Cisco19, and CiscoSafe stencils auto-discovered at runtime from drawio's sidebar ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
14
+ - Multi-document targeting with `list-documents` and `target_document` selectors for multi-tab workflows ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
15
+ - Multi-page targeting with required `target_page` selectors for page-scoped tools ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
16
+ - Per-document FIFO serialization for live operations, so multiple agents can work on different files safely ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
17
+ - Page management tools: `list-pages`, `get-current-page`, `create-page`, `copy-page`, `rename-page` ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
18
+ - Import, embed, or expand [Mermaid](https://mermaid.js.org/) diagrams ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
19
+ - Firefox support is back, TLS mode is necessary ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
20
+ - Server supports TLS mode and optionally generates self-signed certificates ![v2.1.0](https://img.shields.io/badge/v2.1.0-blue)
21
+
12
22
  - Import and export diagrams from/to XML, SVG (with embedded XML), or PNG (with embedded XML) ![v2.0.0](https://img.shields.io/badge/v2.0.0-blue)
13
23
  - Edge geometry control with waypoints and automatic self-connector routing ![v2.0.0](https://img.shields.io/badge/v2.0.0-blue)
14
24
  - Parent-child relationships for nested shapes and grouping ![v2.0.0](https://img.shields.io/badge/v2.0.0-blue)
15
25
  - Unified Server and Extension in the same mono-repo ![v2.0.0](https://img.shields.io/badge/v2.0.0-blue)
16
- - Built-in Draw.io editor - no browser extension required ![v1.8.0](https://img.shields.io/badge/v1.8.0-blue)
26
+ - Built-in Draw.io editor - no browser extension required
17
27
  - MCP server that lets AI agents control Draw.io diagrams
18
28
  - Programmatic diagram creation, inspection, and modification via MCP tools
19
29
  - Layer management for complex diagrams
@@ -27,6 +37,8 @@ Two ways to use:
27
37
  1. **Built-in editor** - Server hosts Draw.io directly, accessible in your browser
28
38
  2. **Browser extension** - Connect to Draw.io running in your browser via extension
29
39
 
40
+ Experimental: integration with the **draw.io desktop (Electron) app** is in progress but currently blocked by an upstream CSP issue — see [DESKTOP.md](./DESKTOP.md).
41
+
30
42
  ## Requirements
31
43
 
32
44
  - **Node.js** (v20 or higher) - Runtime environment for the MCP server
@@ -134,6 +146,8 @@ Example prompts you can try:
134
146
 
135
147
  > "Create an event-driven architecture diagram showing a message queue with producers, consumers, and three backend services"
136
148
 
149
+ > "Create a three-page event-driven architecture diagram. Use three agents in parallel for service topology, message flow, and retry/failure handling, with each agent assigned to a separate target page."
150
+
137
151
  > "Draw a CRUD API diagram with a database, API gateway, and four microservices with their endpoints"
138
152
 
139
153
  > "Add a new layer called 'Background' and move all decorative elements to it, then create a new layer for annotations"
@@ -144,9 +158,13 @@ Your AI assistant can now control the diagram using MCP tools.
144
158
 
145
159
  The server provides MCP tools for:
146
160
 
147
- - **Diagram inspection** - read shapes, layers, and cell properties
148
- - **Diagram modification** - add/edit/delete shapes, edges, and labels
161
+ - **Document discovery** - list connected Draw.io document instances and route later calls to a specific tab/file instance
162
+ - **Diagram inspection** - read shapes, pages, layers, and cell properties
163
+ - **Diagram modification** - add/edit/delete shapes, edges, and labels on a target page
164
+ - **Page management** - list pages, inspect the current page, create pages, copy pages, and rename pages without forcing a visible page switch on supported runtimes
149
165
  - **Layer management** - create, switch, and organize layers
166
+ - **Vendor shape coverage** - AWS, GCP, Azure, Cisco19, and CiscoSafe stencils auto-discovered at runtime from drawio's sidebar, so agents can place icons like `mxgraph.gcp2.cloud_run` or `mxgraph.cisco19.router` without hand-curated catalogs
167
+ - **Built-in TLS** — opt-in HTTPS + WSS with manual cert/key or auto-generated self-signed material via a per-user local CA. See [CONFIG.md → TLS](./CONFIG.md#tls-https--wss).
150
168
 
151
169
  See [Tools Reference](./TOOLS.md) for the complete list of available tools.
152
170
 
@@ -179,6 +197,12 @@ Configuration without `--editor`:
179
197
 
180
198
  See the [extension documentation](./packages/drawio-mcp-extension/README.md) for more details.
181
199
 
200
+ ## Experimental: Draw.io Desktop
201
+
202
+ Integration with the [draw.io desktop](https://github.com/jgraph/drawio-desktop) (Electron) app is **experimental** and currently blocked end-to-end by an upstream CSP issue. The plugin loads inside draw.io desktop, but its WebSocket connection back to the MCP server is rejected by draw.io's hard-coded `connect-src 'self'` policy.
203
+
204
+ See [DESKTOP.md](./DESKTOP.md) for the full setup steps and the current limitation.
205
+
182
206
  ## Related Resources
183
207
 
184
208
  [Configuration](./CONFIG.md) - CLI flags and advanced options
@@ -195,6 +219,8 @@ See the [extension documentation](./packages/drawio-mcp-extension/README.md) for
195
219
 
196
220
  [Development](./DEVELOPMENT.md)
197
221
 
222
+ [Draw.io Desktop (experimental)](./DESKTOP.md) - install path and known CSP limitation
223
+
198
224
  ## Star History
199
225
 
200
226
  <a href="https://star-history.com/#lgazo/drawio-mcp-server&Date">
@@ -43,7 +43,7 @@ export async function extractWar(warPath, extractDir) {
43
43
  extract.on("error", reject);
44
44
  });
45
45
  }
46
- export function cleanupExtractedFiles(extractDir) {
46
+ export function cleanupExtractedFiles(extractDir, log) {
47
47
  const webappDir = join(extractDir, "webapp");
48
48
  const pathsToRemove = [
49
49
  join(webappDir, "WEB-INF"),
@@ -53,44 +53,43 @@ export function cleanupExtractedFiles(extractDir) {
53
53
  if (existsSync(path)) {
54
54
  try {
55
55
  rmSync(path, { recursive: true, force: true });
56
- console.log(`Removed: ${path}`);
56
+ log.log("info", `Removed: ${path}`);
57
57
  }
58
58
  catch (err) {
59
- console.warn(`Failed to remove ${path}:`, err);
59
+ log.log("warning", `Failed to remove ${path}:`, err);
60
60
  }
61
61
  }
62
62
  }
63
63
  }
64
- export async function downloadAndExtractAssets(targetDir, onProgress) {
65
- const progress = onProgress || console.log;
66
- progress("Fetching draw.io release info...");
64
+ export async function downloadAndExtractAssets(targetDir, log) {
65
+ log.log("info", "Fetching draw.io release info...");
67
66
  const warUrl = await getLatestWarUrl();
68
67
  const warPath = join(targetDir, "draw.war");
69
- progress(`Downloading draw.war from ${warUrl}...`);
68
+ log.log("info", `Downloading draw.war from ${warUrl}...`);
70
69
  await downloadFile(warUrl, warPath);
71
- progress("Download complete.");
70
+ log.log("info", "Download complete.");
72
71
  const webappDir = join(targetDir, "webapp");
73
- progress("Extracting archive...");
72
+ log.log("info", "Extracting archive...");
74
73
  await extractWar(warPath, webappDir);
75
- progress("Extraction complete.");
76
- progress("Cleaning up unnecessary files...");
77
- cleanupExtractedFiles(targetDir);
74
+ log.log("info", "Extraction complete.");
75
+ log.log("info", "Cleaning up unnecessary files...");
76
+ cleanupExtractedFiles(targetDir, log);
78
77
  // Remove the WAR file
79
78
  try {
80
79
  rmSync(warPath, { force: true });
81
80
  }
82
81
  catch (err) {
83
- console.warn("Failed to remove WAR file:", err);
82
+ log.log("warning", "Failed to remove WAR file:", err);
84
83
  }
85
- progress("Assets ready!");
84
+ log.log("info", "Assets ready!");
86
85
  }
87
- export async function ensureAssets(config, onProgress) {
86
+ export async function ensureAssets(config, log) {
88
87
  const { getCacheDir, getAssetRoot, assetsExist } = await import("./manager.js");
89
88
  const cacheDir = getCacheDir(config.assetPath);
90
89
  const assetRoot = getAssetRoot(config);
91
90
  if (!assetsExist(config)) {
92
- console.log(`Assets not found in ${assetRoot}. Downloading...`);
93
- await downloadAndExtractAssets(cacheDir, onProgress);
91
+ log.log("info", `Assets not found in ${assetRoot}. Downloading...`);
92
+ await downloadAndExtractAssets(cacheDir, log);
94
93
  }
95
94
  return { assetRoot, isLocal: true };
96
95
  }
package/build/config.js CHANGED
@@ -1,6 +1,4 @@
1
- /**
2
- * Application configuration interface
3
- */
1
+ import { isIP } from "node:net";
4
2
  /**
5
3
  * Default configuration values
6
4
  */
@@ -9,6 +7,9 @@ const DEFAULT_CONFIG = {
9
7
  httpPort: 3000,
10
8
  transports: ["stdio"],
11
9
  editorEnabled: false,
10
+ logger: "console",
11
+ tlsEnabled: false,
12
+ tlsAuto: false,
12
13
  };
13
14
  /**
14
15
  * Valid port range
@@ -49,6 +50,54 @@ export const parseHttpPortValue = (value) => {
49
50
  }
50
51
  return port;
51
52
  };
53
+ /**
54
+ * Parse WebSocket URL value - pure function
55
+ * Accepts ws:// or wss:// URLs only
56
+ */
57
+ export const parseWebSocketUrlValue = (value) => {
58
+ if (!value) {
59
+ return new Error("--websocket-url flag requires a URL");
60
+ }
61
+ let parsed;
62
+ try {
63
+ parsed = new URL(value);
64
+ }
65
+ catch {
66
+ return new Error(`Invalid WebSocket URL "${value}"`);
67
+ }
68
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
69
+ return new Error(`Invalid WebSocket URL "${value}". Protocol must be ws:// or wss://`);
70
+ }
71
+ return value;
72
+ };
73
+ /**
74
+ * Parse host value - pure function
75
+ * Accepts IPv4 or IPv6 addresses only (no hostnames)
76
+ */
77
+ export const parseHostValue = (value) => {
78
+ if (!value) {
79
+ return new Error("--host flag requires an IP address");
80
+ }
81
+ if (isIP(value) === 0) {
82
+ return new Error(`Invalid host "${value}". Must be a valid IPv4 or IPv6 address`);
83
+ }
84
+ return value;
85
+ };
86
+ /**
87
+ * Parse logger mode value - pure function
88
+ * Accepts "console" or "mcp-server". For convenience the legacy underscore
89
+ * form "mcp_server" is normalised to "mcp-server".
90
+ */
91
+ export const parseLoggerValue = (value) => {
92
+ if (!value) {
93
+ return new Error("--logger flag requires a mode (console|mcp-server)");
94
+ }
95
+ const normalized = value === "mcp_server" ? "mcp-server" : value;
96
+ if (normalized === "console" || normalized === "mcp-server") {
97
+ return normalized;
98
+ }
99
+ return new Error(`Invalid logger "${value}". Must be one of: console, mcp-server`);
100
+ };
52
101
  export const parseTransports = (values) => {
53
102
  if (!values || values.length === 0) {
54
103
  return DEFAULT_CONFIG.transports;
@@ -103,6 +152,14 @@ export const parseConfig = (args) => {
103
152
  let transportValues;
104
153
  let editorEnabled = false;
105
154
  let assetPath;
155
+ let webSocketUrlValue;
156
+ let hostValue;
157
+ let loggerValue;
158
+ let tlsEnabled = false;
159
+ let tlsAuto = false;
160
+ let tlsCert;
161
+ let tlsKey;
162
+ let tlsDir;
106
163
  for (let i = 0; i < args.length; i += 1) {
107
164
  const arg = args[i];
108
165
  if (arg === "--extension-port" || arg === "-p") {
@@ -154,6 +211,84 @@ export const parseConfig = (args) => {
154
211
  assetPath = nextValue;
155
212
  i += 1;
156
213
  }
214
+ else if (arg === "--websocket-url") {
215
+ const nextValue = args[i + 1];
216
+ if (nextValue === undefined) {
217
+ return new Error("--websocket-url flag requires a URL");
218
+ }
219
+ webSocketUrlValue = nextValue;
220
+ i += 1;
221
+ }
222
+ else if (arg === "--host") {
223
+ const nextValue = args[i + 1];
224
+ if (nextValue === undefined) {
225
+ return new Error("--host flag requires an IP address");
226
+ }
227
+ hostValue = nextValue;
228
+ i += 1;
229
+ }
230
+ else if (arg === "--logger") {
231
+ const nextValue = args[i + 1];
232
+ if (nextValue === undefined) {
233
+ return new Error("--logger flag requires a mode (console|mcp-server)");
234
+ }
235
+ loggerValue = nextValue;
236
+ i += 1;
237
+ }
238
+ else if (arg === "--tls") {
239
+ tlsEnabled = true;
240
+ }
241
+ else if (arg === "--tls-auto") {
242
+ tlsAuto = true;
243
+ }
244
+ else if (arg === "--tls-cert") {
245
+ const nextValue = args[i + 1];
246
+ if (nextValue === undefined) {
247
+ return new Error("--tls-cert flag requires a path");
248
+ }
249
+ tlsCert = nextValue;
250
+ i += 1;
251
+ }
252
+ else if (arg === "--tls-key") {
253
+ const nextValue = args[i + 1];
254
+ if (nextValue === undefined) {
255
+ return new Error("--tls-key flag requires a path");
256
+ }
257
+ tlsKey = nextValue;
258
+ i += 1;
259
+ }
260
+ else if (arg === "--tls-dir") {
261
+ const nextValue = args[i + 1];
262
+ if (nextValue === undefined) {
263
+ return new Error("--tls-dir flag requires a directory path");
264
+ }
265
+ tlsDir = nextValue;
266
+ i += 1;
267
+ }
268
+ }
269
+ let logger = DEFAULT_CONFIG.logger;
270
+ if (loggerValue !== undefined) {
271
+ const parsed = parseLoggerValue(loggerValue);
272
+ if (parsed instanceof Error) {
273
+ return parsed;
274
+ }
275
+ logger = parsed;
276
+ }
277
+ let webSocketUrl;
278
+ if (webSocketUrlValue !== undefined) {
279
+ const parsed = parseWebSocketUrlValue(webSocketUrlValue);
280
+ if (parsed instanceof Error) {
281
+ return parsed;
282
+ }
283
+ webSocketUrl = parsed;
284
+ }
285
+ let host;
286
+ if (hostValue !== undefined) {
287
+ const parsed = parseHostValue(hostValue);
288
+ if (parsed instanceof Error) {
289
+ return parsed;
290
+ }
291
+ host = parsed;
157
292
  }
158
293
  if (httpPortValue !== undefined) {
159
294
  const httpPort = parseHttpPortValue(httpPortValue);
@@ -162,6 +297,21 @@ export const parseConfig = (args) => {
162
297
  }
163
298
  parsedHttpPort = httpPort;
164
299
  }
300
+ // TLS validation
301
+ if ((tlsCert || tlsKey || tlsAuto || tlsDir) && !tlsEnabled) {
302
+ return new Error("TLS sub-flags (--tls-cert, --tls-key, --tls-auto, --tls-dir) require --tls");
303
+ }
304
+ if (tlsEnabled) {
305
+ if (tlsCert && !tlsKey) {
306
+ return new Error("--tls-cert requires --tls-key");
307
+ }
308
+ if (tlsKey && !tlsCert) {
309
+ return new Error("--tls-key requires --tls-cert");
310
+ }
311
+ if (tlsAuto && (tlsCert || tlsKey)) {
312
+ return new Error("Cannot combine --tls-auto with --tls-cert/--tls-key. Pick one mode.");
313
+ }
314
+ }
165
315
  if (portValue !== undefined) {
166
316
  const extensionPort = parseExtensionPortValue(portValue);
167
317
  if (extensionPort instanceof Error) {
@@ -178,6 +328,14 @@ export const parseConfig = (args) => {
178
328
  transports,
179
329
  editorEnabled,
180
330
  assetPath,
331
+ webSocketUrl,
332
+ host,
333
+ logger,
334
+ tlsEnabled,
335
+ tlsAuto,
336
+ tlsCert,
337
+ tlsKey,
338
+ tlsDir,
181
339
  };
182
340
  }
183
341
  if (httpPortValue !== undefined) {
@@ -191,6 +349,14 @@ export const parseConfig = (args) => {
191
349
  transports,
192
350
  editorEnabled,
193
351
  assetPath,
352
+ webSocketUrl,
353
+ host,
354
+ logger,
355
+ tlsEnabled,
356
+ tlsAuto,
357
+ tlsCert,
358
+ tlsKey,
359
+ tlsDir,
194
360
  };
195
361
  }
196
362
  const transports = parseTransports(transportValues);
@@ -203,16 +369,98 @@ export const parseConfig = (args) => {
203
369
  transports,
204
370
  editorEnabled,
205
371
  assetPath,
372
+ webSocketUrl,
373
+ host,
374
+ logger,
375
+ tlsEnabled,
376
+ tlsAuto,
377
+ tlsCert,
378
+ tlsKey,
379
+ tlsDir,
206
380
  };
207
381
  };
382
+ /**
383
+ * Map known DRAWIO_MCP_* environment variables to argv tokens.
384
+ * Pure function - injected env in, argv out.
385
+ *
386
+ * Result is concatenated BEFORE real CLI args so that parseConfig's
387
+ * last-wins semantics give CLI flags precedence over env vars.
388
+ */
389
+ export const envToArgs = (env) => {
390
+ const out = [];
391
+ const extPort = env.DRAWIO_MCP_EXTENSION_PORT;
392
+ if (extPort && extPort.length > 0) {
393
+ out.push("--extension-port", extPort);
394
+ }
395
+ const httpPort = env.DRAWIO_MCP_HTTP_PORT;
396
+ if (httpPort && httpPort.length > 0) {
397
+ out.push("--http-port", httpPort);
398
+ }
399
+ const transport = env.DRAWIO_MCP_TRANSPORT;
400
+ if (transport && transport.length > 0) {
401
+ out.push("--transport", transport);
402
+ }
403
+ const editor = env.DRAWIO_MCP_EDITOR;
404
+ if (editor && editor.length > 0) {
405
+ const normalized = editor.toLowerCase();
406
+ if (normalized === "true" || normalized === "false") {
407
+ out.push("--editor", normalized);
408
+ }
409
+ }
410
+ const assetPath = env.DRAWIO_MCP_ASSET_PATH;
411
+ if (assetPath && assetPath.length > 0) {
412
+ out.push("--asset-path", assetPath);
413
+ }
414
+ const wsUrl = env.DRAWIO_MCP_WEBSOCKET_URL;
415
+ if (wsUrl && wsUrl.length > 0) {
416
+ out.push("--websocket-url", wsUrl);
417
+ }
418
+ const host = env.DRAWIO_MCP_HOST;
419
+ if (host && host.length > 0) {
420
+ out.push("--host", host);
421
+ }
422
+ const logger = env.DRAWIO_MCP_LOGGER;
423
+ if (logger && logger.length > 0) {
424
+ out.push("--logger", logger);
425
+ }
426
+ const tls = env.DRAWIO_MCP_TLS;
427
+ if (tls && tls.toLowerCase() === "true")
428
+ out.push("--tls");
429
+ const tlsAuto = env.DRAWIO_MCP_TLS_AUTO;
430
+ if (tlsAuto && tlsAuto.toLowerCase() === "true")
431
+ out.push("--tls-auto");
432
+ const tlsCert = env.DRAWIO_MCP_TLS_CERT;
433
+ if (tlsCert && tlsCert.length > 0)
434
+ out.push("--tls-cert", tlsCert);
435
+ const tlsKey = env.DRAWIO_MCP_TLS_KEY;
436
+ if (tlsKey && tlsKey.length > 0)
437
+ out.push("--tls-key", tlsKey);
438
+ const tlsDir = env.DRAWIO_MCP_TLS_DIR;
439
+ if (tlsDir && tlsDir.length > 0)
440
+ out.push("--tls-dir", tlsDir);
441
+ return out;
442
+ };
208
443
  /**
209
444
  * Build configuration from process.argv
210
445
  * This is the main entry point for configuration
211
446
  * Returns Error for invalid config, or ServerConfig
212
447
  */
213
448
  export const buildConfig = () => {
214
- const args = process.argv.slice(2);
215
- return parseConfig(args);
449
+ const envArgs = envToArgs(process.env);
450
+ const cliArgs = process.argv.slice(2);
451
+ return parseConfig([...envArgs, ...cliArgs]);
452
+ };
453
+ /**
454
+ * Convenience: parse an empty argument list and return the default
455
+ * ServerConfig. Used by test harnesses that need a config object but do not
456
+ * exercise the parser.
457
+ */
458
+ export const defaultConfig = () => {
459
+ const result = parseConfig([]);
460
+ if (result instanceof Error) {
461
+ throw result;
462
+ }
463
+ return result;
216
464
  };
217
465
  export function getHttpFeatureConfig(config) {
218
466
  return {