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.
- package/README.md +29 -3
- package/build/assets/downloader.js +16 -17
- package/build/config.js +253 -5
- package/build/config.test.js +449 -1
- package/build/emitter_bus.js +3 -0
- package/build/emitter_bus.test.js +7 -0
- package/build/index.capabilities.test.js +45 -0
- package/build/index.js +416 -82
- package/build/install-desktop-plugin.js +56 -0
- package/build/install-desktop-plugin.test.js +66 -0
- package/build/multi-transport.test.js +117 -0
- package/build/plugin/mcp-plugin.js +2180 -1105
- package/build/prefetch-assets.js +6 -5
- package/build/real-environment/document-targeting.test.js +124 -0
- package/build/real-environment/export-diagram.test.js +1 -1
- package/build/real-environment/harness.js +118 -31
- package/build/real-environment/import-mermaid.test.js +81 -0
- package/build/real-environment/pages-and-concurrency.test.js +448 -0
- package/build/real-environment/shapes.test.js +58 -0
- package/build/real-environment/tools.js +51 -6
- package/build/register-tool.js +31 -0
- package/build/request_queue.js +29 -0
- package/build/request_queue.test.js +98 -0
- package/build/stdio-transport-purity.test.js +88 -0
- package/build/strip-schema.js +61 -0
- package/build/tls/expiry.js +14 -0
- package/build/tls/expiry.test.js +54 -0
- package/build/tls/generate.js +123 -0
- package/build/tls/generate.test.js +115 -0
- package/build/tls/index.js +80 -0
- package/build/tls/index.test.js +141 -0
- package/build/tls/install-hint.js +45 -0
- package/build/tls/install-hint.test.js +32 -0
- package/build/tls/load.js +18 -0
- package/build/tls/load.test.js +40 -0
- package/build/tls/paths.js +30 -0
- package/build/tls/paths.test.js +53 -0
- package/build/tls/san.js +27 -0
- package/build/tls/san.test.js +72 -0
- package/build/tool-registry.test.js +823 -0
- package/build/tool.js +74 -15
- package/build/tool.test.js +250 -7
- package/build/tools/add-cell-of-shape.js +4 -2
- package/build/tools/add-edge.js +3 -1
- package/build/tools/add-rectangle.js +4 -2
- package/build/tools/copy-page.js +13 -0
- package/build/tools/create-layer.js +4 -2
- package/build/tools/create-page.js +8 -0
- package/build/tools/delete-cell-by-id.js +4 -2
- package/build/tools/edit-cell.js +3 -1
- package/build/tools/edit-edge.js +3 -1
- package/build/tools/export-diagram.js +7 -3
- package/build/tools/get-active-layer.js +4 -1
- package/build/tools/get-current-page.js +5 -0
- package/build/tools/get-selected-cell.js +4 -1
- package/build/tools/import-diagram.js +12 -2
- package/build/tools/import-mermaid.js +31 -0
- package/build/tools/index.js +14 -0
- package/build/tools/list-documents.js +17 -0
- package/build/tools/list-layers.js +4 -1
- package/build/tools/list-paged-model.js +4 -3
- package/build/tools/list-pages.js +5 -0
- package/build/tools/move-cell-to-layer.js +3 -1
- package/build/tools/rename-page.js +10 -0
- package/build/tools/set-active-layer.js +4 -2
- package/build/tools/set-cell-data.js +3 -1
- package/build/tools/set-cell-parent.js +3 -1
- package/build/tools/set-cell-shape.js +3 -1
- package/build/tools/shared.js +35 -0
- package/build/tools/shared.test.js +29 -0
- package/package.json +12 -4
package/build/config.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { parseExtensionPortValue, parseHttpPortValue, findArgValue, hasFlag, shouldShowHelp, parseConfig, buildConfig, parseTransports, } from "./config.js";
|
|
1
|
+
import { parseExtensionPortValue, parseHttpPortValue, findArgValue, hasFlag, shouldShowHelp, parseConfig, buildConfig, parseTransports, parseWebSocketUrlValue, parseHostValue, parseLoggerValue, envToArgs, defaultConfig, } from "./config.js";
|
|
2
2
|
describe("parseExtensionPortValue", () => {
|
|
3
3
|
test("valid port returns number", () => {
|
|
4
4
|
expect(parseExtensionPortValue("8080")).toBe(8080);
|
|
@@ -115,6 +115,9 @@ describe("parseConfig", () => {
|
|
|
115
115
|
httpPort: 3000,
|
|
116
116
|
transports: ["stdio"],
|
|
117
117
|
editorEnabled: false,
|
|
118
|
+
logger: "console",
|
|
119
|
+
tlsEnabled: false,
|
|
120
|
+
tlsAuto: false,
|
|
118
121
|
});
|
|
119
122
|
});
|
|
120
123
|
test("--extension-port flag sets custom port", () => {
|
|
@@ -123,6 +126,9 @@ describe("parseConfig", () => {
|
|
|
123
126
|
httpPort: 3000,
|
|
124
127
|
transports: ["stdio"],
|
|
125
128
|
editorEnabled: false,
|
|
129
|
+
logger: "console",
|
|
130
|
+
tlsEnabled: false,
|
|
131
|
+
tlsAuto: false,
|
|
126
132
|
});
|
|
127
133
|
});
|
|
128
134
|
test("-p flag sets custom port", () => {
|
|
@@ -131,6 +137,9 @@ describe("parseConfig", () => {
|
|
|
131
137
|
httpPort: 3000,
|
|
132
138
|
transports: ["stdio"],
|
|
133
139
|
editorEnabled: false,
|
|
140
|
+
logger: "console",
|
|
141
|
+
tlsEnabled: false,
|
|
142
|
+
tlsAuto: false,
|
|
134
143
|
});
|
|
135
144
|
});
|
|
136
145
|
test("--http-port flag sets custom port", () => {
|
|
@@ -139,6 +148,9 @@ describe("parseConfig", () => {
|
|
|
139
148
|
httpPort: 4242,
|
|
140
149
|
transports: ["stdio"],
|
|
141
150
|
editorEnabled: false,
|
|
151
|
+
logger: "console",
|
|
152
|
+
tlsEnabled: false,
|
|
153
|
+
tlsAuto: false,
|
|
142
154
|
});
|
|
143
155
|
});
|
|
144
156
|
test("both ports can be configured", () => {
|
|
@@ -147,6 +159,9 @@ describe("parseConfig", () => {
|
|
|
147
159
|
httpPort: 4242,
|
|
148
160
|
transports: ["stdio"],
|
|
149
161
|
editorEnabled: false,
|
|
162
|
+
logger: "console",
|
|
163
|
+
tlsEnabled: false,
|
|
164
|
+
tlsAuto: false,
|
|
150
165
|
});
|
|
151
166
|
});
|
|
152
167
|
test("help flag is ignored in config parsing", () => {
|
|
@@ -155,6 +170,9 @@ describe("parseConfig", () => {
|
|
|
155
170
|
httpPort: 3000,
|
|
156
171
|
transports: ["stdio"],
|
|
157
172
|
editorEnabled: false,
|
|
173
|
+
logger: "console",
|
|
174
|
+
tlsEnabled: false,
|
|
175
|
+
tlsAuto: false,
|
|
158
176
|
});
|
|
159
177
|
});
|
|
160
178
|
test("invalid port returns Error", () => {
|
|
@@ -183,6 +201,9 @@ describe("parseConfig", () => {
|
|
|
183
201
|
httpPort: 3000,
|
|
184
202
|
transports: ["stdio"],
|
|
185
203
|
editorEnabled: false,
|
|
204
|
+
logger: "console",
|
|
205
|
+
tlsEnabled: false,
|
|
206
|
+
tlsAuto: false,
|
|
186
207
|
});
|
|
187
208
|
});
|
|
188
209
|
test("short and long form both work, last wins", () => {
|
|
@@ -191,6 +212,9 @@ describe("parseConfig", () => {
|
|
|
191
212
|
httpPort: 3000,
|
|
192
213
|
transports: ["stdio"],
|
|
193
214
|
editorEnabled: false,
|
|
215
|
+
logger: "console",
|
|
216
|
+
tlsEnabled: false,
|
|
217
|
+
tlsAuto: false,
|
|
194
218
|
});
|
|
195
219
|
});
|
|
196
220
|
test("last http-port flag wins", () => {
|
|
@@ -199,6 +223,9 @@ describe("parseConfig", () => {
|
|
|
199
223
|
httpPort: 5000,
|
|
200
224
|
transports: ["stdio"],
|
|
201
225
|
editorEnabled: false,
|
|
226
|
+
logger: "console",
|
|
227
|
+
tlsEnabled: false,
|
|
228
|
+
tlsAuto: false,
|
|
202
229
|
});
|
|
203
230
|
});
|
|
204
231
|
test("sets single transport", () => {
|
|
@@ -207,6 +234,9 @@ describe("parseConfig", () => {
|
|
|
207
234
|
httpPort: 3000,
|
|
208
235
|
transports: ["stdio"],
|
|
209
236
|
editorEnabled: false,
|
|
237
|
+
logger: "console",
|
|
238
|
+
tlsEnabled: false,
|
|
239
|
+
tlsAuto: false,
|
|
210
240
|
});
|
|
211
241
|
});
|
|
212
242
|
test("sets multiple transports", () => {
|
|
@@ -215,6 +245,9 @@ describe("parseConfig", () => {
|
|
|
215
245
|
httpPort: 3000,
|
|
216
246
|
transports: ["stdio", "http"],
|
|
217
247
|
editorEnabled: false,
|
|
248
|
+
logger: "console",
|
|
249
|
+
tlsEnabled: false,
|
|
250
|
+
tlsAuto: false,
|
|
218
251
|
});
|
|
219
252
|
});
|
|
220
253
|
test("rejects unknown transport", () => {
|
|
@@ -227,6 +260,9 @@ describe("parseConfig", () => {
|
|
|
227
260
|
httpPort: 3000,
|
|
228
261
|
transports: ["stdio"],
|
|
229
262
|
editorEnabled: true,
|
|
263
|
+
logger: "console",
|
|
264
|
+
tlsEnabled: false,
|
|
265
|
+
tlsAuto: false,
|
|
230
266
|
});
|
|
231
267
|
});
|
|
232
268
|
test("-e flag enables editor", () => {
|
|
@@ -235,6 +271,9 @@ describe("parseConfig", () => {
|
|
|
235
271
|
httpPort: 3000,
|
|
236
272
|
transports: ["stdio"],
|
|
237
273
|
editorEnabled: true,
|
|
274
|
+
logger: "console",
|
|
275
|
+
tlsEnabled: false,
|
|
276
|
+
tlsAuto: false,
|
|
238
277
|
});
|
|
239
278
|
});
|
|
240
279
|
test("--editor false disables editor explicitly", () => {
|
|
@@ -243,6 +282,9 @@ describe("parseConfig", () => {
|
|
|
243
282
|
httpPort: 3000,
|
|
244
283
|
transports: ["stdio"],
|
|
245
284
|
editorEnabled: false,
|
|
285
|
+
logger: "console",
|
|
286
|
+
tlsEnabled: false,
|
|
287
|
+
tlsAuto: false,
|
|
246
288
|
});
|
|
247
289
|
});
|
|
248
290
|
test("--editor=true enables editor", () => {
|
|
@@ -251,6 +293,9 @@ describe("parseConfig", () => {
|
|
|
251
293
|
httpPort: 3000,
|
|
252
294
|
transports: ["stdio"],
|
|
253
295
|
editorEnabled: true,
|
|
296
|
+
logger: "console",
|
|
297
|
+
tlsEnabled: false,
|
|
298
|
+
tlsAuto: false,
|
|
254
299
|
});
|
|
255
300
|
});
|
|
256
301
|
test("--editor=false disables editor explicitly", () => {
|
|
@@ -259,13 +304,278 @@ describe("parseConfig", () => {
|
|
|
259
304
|
httpPort: 3000,
|
|
260
305
|
transports: ["stdio"],
|
|
261
306
|
editorEnabled: false,
|
|
307
|
+
logger: "console",
|
|
308
|
+
tlsEnabled: false,
|
|
309
|
+
tlsAuto: false,
|
|
262
310
|
});
|
|
263
311
|
});
|
|
264
312
|
});
|
|
313
|
+
describe("parseConfig --host", () => {
|
|
314
|
+
test("--host sets host in config", () => {
|
|
315
|
+
const result = parseConfig(["--host", "127.0.0.1"]);
|
|
316
|
+
expect(result).not.toBeInstanceOf(Error);
|
|
317
|
+
expect(result.host).toBe("127.0.0.1");
|
|
318
|
+
});
|
|
319
|
+
test("--host with IPv6 sets host in config", () => {
|
|
320
|
+
const result = parseConfig(["--host", "::1"]);
|
|
321
|
+
expect(result).not.toBeInstanceOf(Error);
|
|
322
|
+
expect(result.host).toBe("::1");
|
|
323
|
+
});
|
|
324
|
+
test("--host with invalid value returns Error", () => {
|
|
325
|
+
const result = parseConfig(["--host", "localhost"]);
|
|
326
|
+
expect(result).toBeInstanceOf(Error);
|
|
327
|
+
});
|
|
328
|
+
test("omitting --host leaves host undefined", () => {
|
|
329
|
+
const result = parseConfig([]);
|
|
330
|
+
expect(result).not.toBeInstanceOf(Error);
|
|
331
|
+
expect(result.host).toBeUndefined();
|
|
332
|
+
});
|
|
333
|
+
test("--host without value returns Error", () => {
|
|
334
|
+
const result = parseConfig(["--host"]);
|
|
335
|
+
expect(result).toBeInstanceOf(Error);
|
|
336
|
+
expect(result.message).toContain("--host flag requires an IP address");
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
describe("parseWebSocketUrlValue", () => {
|
|
340
|
+
test("accepts ws:// URL", () => {
|
|
341
|
+
expect(parseWebSocketUrlValue("ws://example.com:3333")).toBe("ws://example.com:3333");
|
|
342
|
+
});
|
|
343
|
+
test("accepts wss:// URL", () => {
|
|
344
|
+
expect(parseWebSocketUrlValue("wss://example.com/ws")).toBe("wss://example.com/ws");
|
|
345
|
+
});
|
|
346
|
+
test("rejects http:// URL", () => {
|
|
347
|
+
const result = parseWebSocketUrlValue("http://example.com");
|
|
348
|
+
expect(result).toBeInstanceOf(Error);
|
|
349
|
+
});
|
|
350
|
+
test("rejects empty string", () => {
|
|
351
|
+
const result = parseWebSocketUrlValue("");
|
|
352
|
+
expect(result).toBeInstanceOf(Error);
|
|
353
|
+
});
|
|
354
|
+
test("rejects undefined", () => {
|
|
355
|
+
const result = parseWebSocketUrlValue(undefined);
|
|
356
|
+
expect(result).toBeInstanceOf(Error);
|
|
357
|
+
});
|
|
358
|
+
test("rejects garbage", () => {
|
|
359
|
+
const result = parseWebSocketUrlValue("not a url");
|
|
360
|
+
expect(result).toBeInstanceOf(Error);
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
describe("parseLoggerValue", () => {
|
|
364
|
+
test("accepts console", () => {
|
|
365
|
+
expect(parseLoggerValue("console")).toBe("console");
|
|
366
|
+
});
|
|
367
|
+
test("accepts mcp-server", () => {
|
|
368
|
+
expect(parseLoggerValue("mcp-server")).toBe("mcp-server");
|
|
369
|
+
});
|
|
370
|
+
test("normalises legacy mcp_server to mcp-server", () => {
|
|
371
|
+
expect(parseLoggerValue("mcp_server")).toBe("mcp-server");
|
|
372
|
+
});
|
|
373
|
+
test("rejects unknown value", () => {
|
|
374
|
+
expect(parseLoggerValue("foo")).toBeInstanceOf(Error);
|
|
375
|
+
});
|
|
376
|
+
test("rejects undefined", () => {
|
|
377
|
+
expect(parseLoggerValue(undefined)).toBeInstanceOf(Error);
|
|
378
|
+
});
|
|
379
|
+
test("rejects empty string", () => {
|
|
380
|
+
expect(parseLoggerValue("")).toBeInstanceOf(Error);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
describe("parseConfig --logger", () => {
|
|
384
|
+
test("default logger is console", () => {
|
|
385
|
+
const result = parseConfig([]);
|
|
386
|
+
expect(result).not.toBeInstanceOf(Error);
|
|
387
|
+
expect(result.logger).toBe("console");
|
|
388
|
+
});
|
|
389
|
+
test("--logger console keeps default", () => {
|
|
390
|
+
const result = parseConfig(["--logger", "console"]);
|
|
391
|
+
expect(result.logger).toBe("console");
|
|
392
|
+
});
|
|
393
|
+
test("--logger mcp-server is accepted", () => {
|
|
394
|
+
const result = parseConfig(["--logger", "mcp-server"]);
|
|
395
|
+
expect(result.logger).toBe("mcp-server");
|
|
396
|
+
});
|
|
397
|
+
test("--logger mcp_server is normalised", () => {
|
|
398
|
+
const result = parseConfig(["--logger", "mcp_server"]);
|
|
399
|
+
expect(result.logger).toBe("mcp-server");
|
|
400
|
+
});
|
|
401
|
+
test("--logger without value returns Error", () => {
|
|
402
|
+
const result = parseConfig(["--logger"]);
|
|
403
|
+
expect(result).toBeInstanceOf(Error);
|
|
404
|
+
expect(result.message).toContain("--logger flag requires a mode");
|
|
405
|
+
});
|
|
406
|
+
test("--logger with invalid value returns Error", () => {
|
|
407
|
+
const result = parseConfig(["--logger", "syslog"]);
|
|
408
|
+
expect(result).toBeInstanceOf(Error);
|
|
409
|
+
expect(result.message).toContain('Invalid logger "syslog"');
|
|
410
|
+
});
|
|
411
|
+
test("last --logger wins", () => {
|
|
412
|
+
const result = parseConfig([
|
|
413
|
+
"--logger",
|
|
414
|
+
"mcp-server",
|
|
415
|
+
"--logger",
|
|
416
|
+
"console",
|
|
417
|
+
]);
|
|
418
|
+
expect(result.logger).toBe("console");
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
describe("defaultConfig", () => {
|
|
422
|
+
test("returns config with default logger", () => {
|
|
423
|
+
const config = defaultConfig();
|
|
424
|
+
expect(config.logger).toBe("console");
|
|
425
|
+
expect(config.extensionPort).toBe(3333);
|
|
426
|
+
expect(config.httpPort).toBe(3000);
|
|
427
|
+
expect(config.transports).toEqual(["stdio"]);
|
|
428
|
+
expect(config.editorEnabled).toBe(false);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
describe("parseHostValue", () => {
|
|
432
|
+
test("undefined input returns Error", () => {
|
|
433
|
+
expect(parseHostValue(undefined)).toBeInstanceOf(Error);
|
|
434
|
+
});
|
|
435
|
+
test("empty string returns Error", () => {
|
|
436
|
+
expect(parseHostValue("")).toBeInstanceOf(Error);
|
|
437
|
+
});
|
|
438
|
+
test("valid IPv4 loopback returns value", () => {
|
|
439
|
+
expect(parseHostValue("127.0.0.1")).toBe("127.0.0.1");
|
|
440
|
+
});
|
|
441
|
+
test("valid IPv4 wildcard returns value", () => {
|
|
442
|
+
expect(parseHostValue("0.0.0.0")).toBe("0.0.0.0");
|
|
443
|
+
});
|
|
444
|
+
test("valid IPv6 loopback returns value", () => {
|
|
445
|
+
expect(parseHostValue("::1")).toBe("::1");
|
|
446
|
+
});
|
|
447
|
+
test("valid IPv6 wildcard returns value", () => {
|
|
448
|
+
expect(parseHostValue("::")).toBe("::");
|
|
449
|
+
});
|
|
450
|
+
test("hostname string returns Error", () => {
|
|
451
|
+
expect(parseHostValue("localhost")).toBeInstanceOf(Error);
|
|
452
|
+
});
|
|
453
|
+
test("arbitrary string returns Error", () => {
|
|
454
|
+
expect(parseHostValue("not-an-ip")).toBeInstanceOf(Error);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
describe("parseConfig --websocket-url", () => {
|
|
458
|
+
test("accepts wss:// override", () => {
|
|
459
|
+
const result = parseConfig(["--websocket-url", "wss://example.com/ws"]);
|
|
460
|
+
expect(result).not.toBeInstanceOf(Error);
|
|
461
|
+
expect(result.webSocketUrl).toBe("wss://example.com/ws");
|
|
462
|
+
});
|
|
463
|
+
test("rejects http:// scheme", () => {
|
|
464
|
+
const result = parseConfig(["--websocket-url", "http://example.com"]);
|
|
465
|
+
expect(result).toBeInstanceOf(Error);
|
|
466
|
+
});
|
|
467
|
+
test("missing value returns Error", () => {
|
|
468
|
+
const result = parseConfig(["--websocket-url"]);
|
|
469
|
+
expect(result).toBeInstanceOf(Error);
|
|
470
|
+
expect(result.message).toContain("--websocket-url flag requires a URL");
|
|
471
|
+
});
|
|
472
|
+
test("last --websocket-url wins", () => {
|
|
473
|
+
const result = parseConfig([
|
|
474
|
+
"--websocket-url",
|
|
475
|
+
"ws://first.example/ws",
|
|
476
|
+
"--websocket-url",
|
|
477
|
+
"wss://second.example/ws",
|
|
478
|
+
]);
|
|
479
|
+
expect(result.webSocketUrl).toBe("wss://second.example/ws");
|
|
480
|
+
});
|
|
481
|
+
});
|
|
482
|
+
describe("envToArgs", () => {
|
|
483
|
+
test("empty env yields empty args", () => {
|
|
484
|
+
expect(envToArgs({})).toEqual([]);
|
|
485
|
+
});
|
|
486
|
+
test("maps DRAWIO_MCP_EXTENSION_PORT", () => {
|
|
487
|
+
expect(envToArgs({ DRAWIO_MCP_EXTENSION_PORT: "8080" })).toEqual([
|
|
488
|
+
"--extension-port",
|
|
489
|
+
"8080",
|
|
490
|
+
]);
|
|
491
|
+
});
|
|
492
|
+
test("maps DRAWIO_MCP_HTTP_PORT", () => {
|
|
493
|
+
expect(envToArgs({ DRAWIO_MCP_HTTP_PORT: "4242" })).toEqual([
|
|
494
|
+
"--http-port",
|
|
495
|
+
"4242",
|
|
496
|
+
]);
|
|
497
|
+
});
|
|
498
|
+
test("maps DRAWIO_MCP_TRANSPORT", () => {
|
|
499
|
+
expect(envToArgs({ DRAWIO_MCP_TRANSPORT: "stdio,http" })).toEqual([
|
|
500
|
+
"--transport",
|
|
501
|
+
"stdio,http",
|
|
502
|
+
]);
|
|
503
|
+
});
|
|
504
|
+
test("maps DRAWIO_MCP_EDITOR true/false", () => {
|
|
505
|
+
expect(envToArgs({ DRAWIO_MCP_EDITOR: "true" })).toEqual([
|
|
506
|
+
"--editor",
|
|
507
|
+
"true",
|
|
508
|
+
]);
|
|
509
|
+
expect(envToArgs({ DRAWIO_MCP_EDITOR: "false" })).toEqual([
|
|
510
|
+
"--editor",
|
|
511
|
+
"false",
|
|
512
|
+
]);
|
|
513
|
+
});
|
|
514
|
+
test("ignores invalid DRAWIO_MCP_EDITOR value", () => {
|
|
515
|
+
expect(envToArgs({ DRAWIO_MCP_EDITOR: "yes" })).toEqual([]);
|
|
516
|
+
});
|
|
517
|
+
test("maps DRAWIO_MCP_ASSET_PATH", () => {
|
|
518
|
+
expect(envToArgs({ DRAWIO_MCP_ASSET_PATH: "/tmp/assets" })).toEqual([
|
|
519
|
+
"--asset-path",
|
|
520
|
+
"/tmp/assets",
|
|
521
|
+
]);
|
|
522
|
+
});
|
|
523
|
+
test("maps DRAWIO_MCP_WEBSOCKET_URL", () => {
|
|
524
|
+
expect(envToArgs({ DRAWIO_MCP_WEBSOCKET_URL: "wss://example.com/ws" })).toEqual(["--websocket-url", "wss://example.com/ws"]);
|
|
525
|
+
});
|
|
526
|
+
test("ignores empty values", () => {
|
|
527
|
+
expect(envToArgs({
|
|
528
|
+
DRAWIO_MCP_EXTENSION_PORT: "",
|
|
529
|
+
DRAWIO_MCP_WEBSOCKET_URL: "",
|
|
530
|
+
})).toEqual([]);
|
|
531
|
+
});
|
|
532
|
+
test("maps multiple env vars together", () => {
|
|
533
|
+
expect(envToArgs({
|
|
534
|
+
DRAWIO_MCP_EXTENSION_PORT: "8080",
|
|
535
|
+
DRAWIO_MCP_WEBSOCKET_URL: "wss://example.com/ws",
|
|
536
|
+
DRAWIO_MCP_TRANSPORT: "http",
|
|
537
|
+
})).toEqual([
|
|
538
|
+
"--extension-port",
|
|
539
|
+
"8080",
|
|
540
|
+
"--transport",
|
|
541
|
+
"http",
|
|
542
|
+
"--websocket-url",
|
|
543
|
+
"wss://example.com/ws",
|
|
544
|
+
]);
|
|
545
|
+
});
|
|
546
|
+
test("DRAWIO_MCP_HOST maps to --host", () => {
|
|
547
|
+
const result = envToArgs({ DRAWIO_MCP_HOST: "0.0.0.0" });
|
|
548
|
+
expect(result).toEqual(["--host", "0.0.0.0"]);
|
|
549
|
+
});
|
|
550
|
+
test("missing DRAWIO_MCP_HOST produces no args", () => {
|
|
551
|
+
const result = envToArgs({});
|
|
552
|
+
expect(result).toEqual([]);
|
|
553
|
+
});
|
|
554
|
+
test("DRAWIO_MCP_LOGGER maps to --logger", () => {
|
|
555
|
+
expect(envToArgs({ DRAWIO_MCP_LOGGER: "mcp-server" })).toEqual([
|
|
556
|
+
"--logger",
|
|
557
|
+
"mcp-server",
|
|
558
|
+
]);
|
|
559
|
+
});
|
|
560
|
+
test("DRAWIO_MCP_LOGGER empty produces no args", () => {
|
|
561
|
+
expect(envToArgs({ DRAWIO_MCP_LOGGER: "" })).toEqual([]);
|
|
562
|
+
});
|
|
563
|
+
});
|
|
265
564
|
describe("buildConfig", () => {
|
|
266
565
|
const originalArgv = process.argv;
|
|
566
|
+
const originalEnv = process.env;
|
|
567
|
+
beforeEach(() => {
|
|
568
|
+
// Strip any host-set DRAWIO_MCP_* vars so they don't leak into tests
|
|
569
|
+
const clean = { ...originalEnv };
|
|
570
|
+
for (const key of Object.keys(clean)) {
|
|
571
|
+
if (key.startsWith("DRAWIO_MCP_"))
|
|
572
|
+
delete clean[key];
|
|
573
|
+
}
|
|
574
|
+
process.env = clean;
|
|
575
|
+
});
|
|
267
576
|
afterEach(() => {
|
|
268
577
|
process.argv = originalArgv;
|
|
578
|
+
process.env = originalEnv;
|
|
269
579
|
});
|
|
270
580
|
test("uses default config with empty args", () => {
|
|
271
581
|
process.argv = ["node", "script.js"];
|
|
@@ -275,6 +585,9 @@ describe("buildConfig", () => {
|
|
|
275
585
|
httpPort: 3000,
|
|
276
586
|
transports: ["stdio"],
|
|
277
587
|
editorEnabled: false,
|
|
588
|
+
logger: "console",
|
|
589
|
+
tlsEnabled: false,
|
|
590
|
+
tlsAuto: false,
|
|
278
591
|
});
|
|
279
592
|
});
|
|
280
593
|
test("parses custom port from argv", () => {
|
|
@@ -285,6 +598,9 @@ describe("buildConfig", () => {
|
|
|
285
598
|
httpPort: 3000,
|
|
286
599
|
transports: ["stdio"],
|
|
287
600
|
editorEnabled: false,
|
|
601
|
+
logger: "console",
|
|
602
|
+
tlsEnabled: false,
|
|
603
|
+
tlsAuto: false,
|
|
288
604
|
});
|
|
289
605
|
});
|
|
290
606
|
test("parses custom http port from argv", () => {
|
|
@@ -295,6 +611,9 @@ describe("buildConfig", () => {
|
|
|
295
611
|
httpPort: 4242,
|
|
296
612
|
transports: ["stdio"],
|
|
297
613
|
editorEnabled: false,
|
|
614
|
+
logger: "console",
|
|
615
|
+
tlsEnabled: false,
|
|
616
|
+
tlsAuto: false,
|
|
298
617
|
});
|
|
299
618
|
});
|
|
300
619
|
test("returns Error for invalid config", () => {
|
|
@@ -302,4 +621,133 @@ describe("buildConfig", () => {
|
|
|
302
621
|
const result = buildConfig();
|
|
303
622
|
expect(result).toBeInstanceOf(Error);
|
|
304
623
|
});
|
|
624
|
+
test("env var DRAWIO_MCP_WEBSOCKET_URL is honored", () => {
|
|
625
|
+
process.argv = ["node", "script.js"];
|
|
626
|
+
process.env = {
|
|
627
|
+
...originalEnv,
|
|
628
|
+
DRAWIO_MCP_WEBSOCKET_URL: "wss://env.example/ws",
|
|
629
|
+
};
|
|
630
|
+
const result = buildConfig();
|
|
631
|
+
expect(result.webSocketUrl).toBe("wss://env.example/ws");
|
|
632
|
+
});
|
|
633
|
+
test("CLI flag overrides env var (last-wins)", () => {
|
|
634
|
+
process.argv = [
|
|
635
|
+
"node",
|
|
636
|
+
"script.js",
|
|
637
|
+
"--websocket-url",
|
|
638
|
+
"wss://flag.example/ws",
|
|
639
|
+
];
|
|
640
|
+
process.env = {
|
|
641
|
+
...originalEnv,
|
|
642
|
+
DRAWIO_MCP_WEBSOCKET_URL: "wss://env.example/ws",
|
|
643
|
+
};
|
|
644
|
+
const result = buildConfig();
|
|
645
|
+
expect(result.webSocketUrl).toBe("wss://flag.example/ws");
|
|
646
|
+
});
|
|
647
|
+
test("env var DRAWIO_MCP_EXTENSION_PORT is honored", () => {
|
|
648
|
+
process.argv = ["node", "script.js"];
|
|
649
|
+
process.env = { ...originalEnv, DRAWIO_MCP_EXTENSION_PORT: "9090" };
|
|
650
|
+
const result = buildConfig();
|
|
651
|
+
expect(result.extensionPort).toBe(9090);
|
|
652
|
+
});
|
|
653
|
+
test("CLI port overrides env var port", () => {
|
|
654
|
+
process.argv = ["node", "script.js", "--extension-port", "7777"];
|
|
655
|
+
process.env = { ...originalEnv, DRAWIO_MCP_EXTENSION_PORT: "9090" };
|
|
656
|
+
const result = buildConfig();
|
|
657
|
+
expect(result.extensionPort).toBe(7777);
|
|
658
|
+
});
|
|
659
|
+
test("env var DRAWIO_MCP_LOGGER is honored", () => {
|
|
660
|
+
process.argv = ["node", "script.js"];
|
|
661
|
+
process.env = { ...originalEnv, DRAWIO_MCP_LOGGER: "mcp-server" };
|
|
662
|
+
const result = buildConfig();
|
|
663
|
+
expect(result.logger).toBe("mcp-server");
|
|
664
|
+
});
|
|
665
|
+
test("CLI --logger overrides DRAWIO_MCP_LOGGER", () => {
|
|
666
|
+
process.argv = ["node", "script.js", "--logger", "console"];
|
|
667
|
+
process.env = { ...originalEnv, DRAWIO_MCP_LOGGER: "mcp-server" };
|
|
668
|
+
const result = buildConfig();
|
|
669
|
+
expect(result.logger).toBe("console");
|
|
670
|
+
});
|
|
671
|
+
});
|
|
672
|
+
describe("TLS configuration", () => {
|
|
673
|
+
it("--tls alone sets tlsEnabled with no mode", () => {
|
|
674
|
+
const cfg = parseConfig(["--tls"]);
|
|
675
|
+
expect(cfg).not.toBeInstanceOf(Error);
|
|
676
|
+
if (cfg instanceof Error)
|
|
677
|
+
return;
|
|
678
|
+
expect(cfg.tlsEnabled).toBe(true);
|
|
679
|
+
expect(cfg.tlsAuto).toBe(false);
|
|
680
|
+
expect(cfg.tlsCert).toBeUndefined();
|
|
681
|
+
expect(cfg.tlsKey).toBeUndefined();
|
|
682
|
+
});
|
|
683
|
+
it("--tls --tls-cert X --tls-key Y configures manual mode", () => {
|
|
684
|
+
const cfg = parseConfig([
|
|
685
|
+
"--tls",
|
|
686
|
+
"--tls-cert",
|
|
687
|
+
"/c.pem",
|
|
688
|
+
"--tls-key",
|
|
689
|
+
"/k.pem",
|
|
690
|
+
]);
|
|
691
|
+
expect(cfg).not.toBeInstanceOf(Error);
|
|
692
|
+
if (cfg instanceof Error)
|
|
693
|
+
return;
|
|
694
|
+
expect(cfg.tlsEnabled).toBe(true);
|
|
695
|
+
expect(cfg.tlsCert).toBe("/c.pem");
|
|
696
|
+
expect(cfg.tlsKey).toBe("/k.pem");
|
|
697
|
+
expect(cfg.tlsAuto).toBe(false);
|
|
698
|
+
});
|
|
699
|
+
it("--tls --tls-auto configures auto mode", () => {
|
|
700
|
+
const cfg = parseConfig(["--tls", "--tls-auto"]);
|
|
701
|
+
expect(cfg).not.toBeInstanceOf(Error);
|
|
702
|
+
if (cfg instanceof Error)
|
|
703
|
+
return;
|
|
704
|
+
expect(cfg.tlsEnabled).toBe(true);
|
|
705
|
+
expect(cfg.tlsAuto).toBe(true);
|
|
706
|
+
});
|
|
707
|
+
it("--tls-dir captures override path", () => {
|
|
708
|
+
const cfg = parseConfig(["--tls", "--tls-auto", "--tls-dir", "/data/tls"]);
|
|
709
|
+
expect(cfg).not.toBeInstanceOf(Error);
|
|
710
|
+
if (cfg instanceof Error)
|
|
711
|
+
return;
|
|
712
|
+
expect(cfg.tlsDir).toBe("/data/tls");
|
|
713
|
+
});
|
|
714
|
+
it("--tls-cert without --tls-key is an error", () => {
|
|
715
|
+
expect(parseConfig(["--tls", "--tls-cert", "/c.pem"])).toBeInstanceOf(Error);
|
|
716
|
+
});
|
|
717
|
+
it("--tls-key without --tls-cert is an error", () => {
|
|
718
|
+
expect(parseConfig(["--tls", "--tls-key", "/k.pem"])).toBeInstanceOf(Error);
|
|
719
|
+
});
|
|
720
|
+
it("--tls-auto + --tls-cert is an error", () => {
|
|
721
|
+
expect(parseConfig([
|
|
722
|
+
"--tls",
|
|
723
|
+
"--tls-auto",
|
|
724
|
+
"--tls-cert",
|
|
725
|
+
"/c",
|
|
726
|
+
"--tls-key",
|
|
727
|
+
"/k",
|
|
728
|
+
])).toBeInstanceOf(Error);
|
|
729
|
+
});
|
|
730
|
+
it("--tls-cert without --tls is an error", () => {
|
|
731
|
+
expect(parseConfig(["--tls-cert", "/c", "--tls-key", "/k"])).toBeInstanceOf(Error);
|
|
732
|
+
});
|
|
733
|
+
it("envToArgs maps DRAWIO_MCP_TLS=true to --tls", () => {
|
|
734
|
+
expect(envToArgs({ DRAWIO_MCP_TLS: "true" })).toEqual(["--tls"]);
|
|
735
|
+
});
|
|
736
|
+
it("envToArgs maps DRAWIO_MCP_TLS_AUTO=true to --tls-auto", () => {
|
|
737
|
+
expect(envToArgs({ DRAWIO_MCP_TLS_AUTO: "true" })).toEqual(["--tls-auto"]);
|
|
738
|
+
});
|
|
739
|
+
it("envToArgs maps cert/key/dir env vars to flags", () => {
|
|
740
|
+
expect(envToArgs({
|
|
741
|
+
DRAWIO_MCP_TLS_CERT: "/c.pem",
|
|
742
|
+
DRAWIO_MCP_TLS_KEY: "/k.pem",
|
|
743
|
+
DRAWIO_MCP_TLS_DIR: "/data/tls",
|
|
744
|
+
})).toEqual([
|
|
745
|
+
"--tls-cert",
|
|
746
|
+
"/c.pem",
|
|
747
|
+
"--tls-key",
|
|
748
|
+
"/k.pem",
|
|
749
|
+
"--tls-dir",
|
|
750
|
+
"/data/tls",
|
|
751
|
+
]);
|
|
752
|
+
});
|
|
305
753
|
});
|
package/build/emitter_bus.js
CHANGED
|
@@ -62,4 +62,11 @@ describe("create_bus", () => {
|
|
|
62
62
|
expect(mockReply1).toHaveBeenCalledWith(matchingEvent);
|
|
63
63
|
expect(mockReply2).not.toHaveBeenCalled();
|
|
64
64
|
});
|
|
65
|
+
it("should allow callers to unsubscribe pending reply handlers", () => {
|
|
66
|
+
const mockReply = jest.fn();
|
|
67
|
+
const unsubscribe = bus.on_reply_from_extension("event1", mockReply);
|
|
68
|
+
unsubscribe();
|
|
69
|
+
emitter.emit(bus_reply_stream, { __event: "event1", data: "late" });
|
|
70
|
+
expect(mockReply).not.toHaveBeenCalled();
|
|
71
|
+
});
|
|
65
72
|
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
3
|
+
import { createDrawioMcpApp } from "./index.js";
|
|
4
|
+
import { defaultConfig } from "./config.js";
|
|
5
|
+
import { MemoryLogger } from "./real-environment/logger.js";
|
|
6
|
+
describe("createDrawioMcpApp capabilities", () => {
|
|
7
|
+
let app;
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
if (app)
|
|
10
|
+
await app.close();
|
|
11
|
+
});
|
|
12
|
+
it("does not advertise logging capability when logger is console", async () => {
|
|
13
|
+
app = createDrawioMcpApp({
|
|
14
|
+
config: { ...defaultConfig(), logger: "console" },
|
|
15
|
+
log: new MemoryLogger(),
|
|
16
|
+
});
|
|
17
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
18
|
+
const server = app.createMcpServer();
|
|
19
|
+
const client = new Client({ name: "cap-test", version: "1.0.0" });
|
|
20
|
+
await Promise.all([
|
|
21
|
+
server.connect(serverTransport),
|
|
22
|
+
client.connect(clientTransport),
|
|
23
|
+
]);
|
|
24
|
+
const capabilities = client.getServerCapabilities();
|
|
25
|
+
expect(capabilities?.logging).toBeUndefined();
|
|
26
|
+
await client.close();
|
|
27
|
+
});
|
|
28
|
+
it("advertises logging capability when logger is mcp-server", async () => {
|
|
29
|
+
app = createDrawioMcpApp({
|
|
30
|
+
config: { ...defaultConfig(), logger: "mcp-server" },
|
|
31
|
+
log: new MemoryLogger(),
|
|
32
|
+
});
|
|
33
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
34
|
+
const server = app.createMcpServer();
|
|
35
|
+
const client = new Client({ name: "cap-test", version: "1.0.0" });
|
|
36
|
+
await Promise.all([
|
|
37
|
+
server.connect(serverTransport),
|
|
38
|
+
client.connect(clientTransport),
|
|
39
|
+
]);
|
|
40
|
+
const capabilities = client.getServerCapabilities();
|
|
41
|
+
expect(capabilities?.logging).toBeDefined();
|
|
42
|
+
expect(capabilities?.logging).toEqual(expect.objectContaining({ setLevels: true }));
|
|
43
|
+
await client.close();
|
|
44
|
+
});
|
|
45
|
+
});
|