drawio-mcp-server 1.3.0 → 1.4.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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Let's do some Vibe Diagramming with the most wide-spread diagramming tool called Draw.io (Diagrams.net).
4
4
 
5
- [![Build project](https://github.com/lgazo/drawio-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/lgazo/drawio-mcp-server/actions/workflows/ci.yml)
5
+ [![Discord channel](https://shields.io/static/v1?logo=discord&message=draw.io%20mcp&label=chat&color=5865F2&logoColor=white)](https://discord.gg/dM4PWdf42q) [![Build project](https://github.com/lgazo/drawio-mcp-server/actions/workflows/ci.yml/badge.svg)](https://github.com/lgazo/drawio-mcp-server/actions/workflows/ci.yml)
6
6
 
7
7
  ## Introduction
8
8
 
@@ -39,6 +39,38 @@ To use the Draw.io MCP server, you'll need:
39
39
 
40
40
  Note: The Draw.io desktop app or web version must be accessible to the system where the MCP server runs.
41
41
 
42
+ ## Configuration
43
+
44
+ ### WebSocket Port
45
+
46
+ The server listens on port 3333 by default for WebSocket connections from the browser extension. You can customize this port using the `--extension-port` or `-p` flag.
47
+
48
+ **Default behavior** (port 3333):
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "drawio": {
53
+ "command": "npx",
54
+ "args": ["-y", "drawio-mcp-server"]
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ **Custom port** (e.g., port 8080):
61
+ ```json
62
+ {
63
+ "mcpServers": {
64
+ "drawio": {
65
+ "command": "npx",
66
+ "args": ["-y", "drawio-mcp-server", "--extension-port", "8080"]
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Note**: When using a custom port, ensure the browser extension is configured to connect to the same port.
73
+
42
74
  ## Installation
43
75
 
44
76
  ### Connecting with Claude Desktop
@@ -86,6 +118,19 @@ Note: The Draw.io desktop app or web version must be accessible to the system wh
86
118
  ```
87
119
  </details>
88
120
 
121
+ To use a custom extension port (e.g., 8080), add `"--extension-port", "8080"` to the args array:
122
+
123
+ ```json
124
+ {
125
+ "mcpServers": {
126
+ "drawio": {
127
+ "command": "npx",
128
+ "args": ["-y", "drawio-mcp-server", "--extension-port", "8080"]
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
89
134
  4. Restart Claude Desktop
90
135
 
91
136
  ### Connecting with oterm
@@ -130,6 +175,19 @@ The configuration is usually in: ~/.local/share/oterm/config.json
130
175
  ```
131
176
  </details>
132
177
 
178
+ To use a custom extension port (e.g., 8080), add `"--extension-port", "8080"` to the args array:
179
+
180
+ ```json
181
+ {
182
+ "mcpServers": {
183
+ "drawio": {
184
+ "command": "npx",
185
+ "args": ["-y", "drawio-mcp-server", "--extension-port", "8080"]
186
+ }
187
+ }
188
+ }
189
+ ```
190
+
133
191
  ### Connect with Zed
134
192
 
135
193
  1. Open the Zed Preview application.
@@ -178,6 +236,24 @@ The configuration is usually in: ~/.local/share/oterm/config.json
178
236
  ```
179
237
  </details>
180
238
 
239
+ To use a custom extension port (e.g., 8080), add `"--extension-port", "8080"` to the args array:
240
+
241
+ ```json
242
+ {
243
+ /// The name of your MCP server
244
+ "drawio": {
245
+ "command": {
246
+ /// The path to the executable
247
+ "path": "npx",
248
+ /// The arguments to pass to the executable
249
+ "args": ["-y","drawio-mcp-server","--extension-port","8080"],
250
+ /// The environment variables to set for the executable
251
+ "env": {}
252
+ }
253
+ }
254
+ }
255
+ ```
256
+
181
257
  ### Browser Extension Setup
182
258
 
183
259
  In order to control the Draw.io diagram, you need to install dedicated Browser Extension.
@@ -198,6 +274,8 @@ In order to control the Draw.io diagram, you need to install dedicated Browser E
198
274
  </p>
199
275
  3. Ensure it is connected, the Extension icon should indicate green signal overlay <img alt="Extension connected" src="https://raw.githubusercontent.com/lgazo/drawio-mcp-extension/refs/heads/main/public/icon/logo_connected_32.png" />
200
276
 
277
+ **Important**: If you configured the MCP server to use a custom port (not 3333), you must configure the browser extension to use the same port. See the extension documentation for port configuration instructions.
278
+
201
279
 
202
280
  ## Sponsoring
203
281
 
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Default configuration values
3
+ */
4
+ const DEFAULT_CONFIG = {
5
+ extensionPort: 3333,
6
+ };
7
+ /**
8
+ * Valid port range
9
+ */
10
+ const PORT_RANGE = {
11
+ min: 1,
12
+ max: 65535,
13
+ };
14
+ /**
15
+ * Parse extension port value from string - pure function
16
+ */
17
+ export const parseExtensionPortValue = (value) => {
18
+ if (!value) {
19
+ return new Error("--extension-port flag requires a port number");
20
+ }
21
+ const port = parseInt(value, 10);
22
+ if (isNaN(port)) {
23
+ return new Error(`Invalid port number "${value}". Port must be a number`);
24
+ }
25
+ if (port < PORT_RANGE.min || port > PORT_RANGE.max) {
26
+ return new Error(`Invalid port number "${value}". Port must be between ${PORT_RANGE.min} and ${PORT_RANGE.max}`);
27
+ }
28
+ return port;
29
+ };
30
+ /**
31
+ * Find argument value by flag name - pure function
32
+ */
33
+ export const findArgValue = (args, ...flags) => {
34
+ const index = args.findIndex((arg) => flags.includes(arg));
35
+ return index !== -1 ? args[index + 1] : undefined;
36
+ };
37
+ /**
38
+ * Check if any flag exists in arguments - pure function
39
+ */
40
+ export const hasFlag = (args, ...flags) => {
41
+ return args.some((arg) => flags.includes(arg));
42
+ };
43
+ /**
44
+ * Check if help was requested - pure function
45
+ */
46
+ export const shouldShowHelp = (args) => {
47
+ return hasFlag(args, "--help", "-h");
48
+ };
49
+ /**
50
+ * Parse command line arguments into configuration object
51
+ * Pure function - no side effects, deterministic output
52
+ */
53
+ export const parseConfig = (args) => {
54
+ // Walk arguments so repeated flags allow "last wins" semantics
55
+ let portValue;
56
+ for (let i = 0; i < args.length; i += 1) {
57
+ const arg = args[i];
58
+ if (arg === "--extension-port" || arg === "-p") {
59
+ const nextValue = args[i + 1];
60
+ if (nextValue === undefined) {
61
+ return new Error("--extension-port flag requires a port number");
62
+ }
63
+ portValue = nextValue;
64
+ i += 1; // Skip the value we just consumed
65
+ }
66
+ }
67
+ if (portValue !== undefined) {
68
+ const extensionPort = parseExtensionPortValue(portValue);
69
+ if (extensionPort instanceof Error) {
70
+ return extensionPort;
71
+ }
72
+ return {
73
+ ...DEFAULT_CONFIG,
74
+ extensionPort,
75
+ };
76
+ }
77
+ // Return default configuration
78
+ return DEFAULT_CONFIG;
79
+ };
80
+ /**
81
+ * Build configuration from process.argv
82
+ * This is the main entry point for configuration
83
+ * Returns Error for invalid config, or ServerConfig
84
+ */
85
+ export const buildConfig = () => {
86
+ const args = process.argv.slice(2);
87
+ return parseConfig(args);
88
+ };
@@ -0,0 +1,136 @@
1
+ import { parseExtensionPortValue, findArgValue, hasFlag, shouldShowHelp, parseConfig, buildConfig, } from "./config.js";
2
+ describe("parseExtensionPortValue", () => {
3
+ test("valid port returns number", () => {
4
+ expect(parseExtensionPortValue("8080")).toBe(8080);
5
+ });
6
+ test("undefined input returns Error", () => {
7
+ expect(parseExtensionPortValue(undefined)).toBeInstanceOf(Error);
8
+ });
9
+ test("non-numeric string returns Error", () => {
10
+ expect(parseExtensionPortValue("abc")).toBeInstanceOf(Error);
11
+ });
12
+ test("out of range (too low) returns Error", () => {
13
+ expect(parseExtensionPortValue("0")).toBeInstanceOf(Error);
14
+ });
15
+ test("out of range (too high) returns Error", () => {
16
+ expect(parseExtensionPortValue("70000")).toBeInstanceOf(Error);
17
+ });
18
+ test("port 1 is valid", () => {
19
+ expect(parseExtensionPortValue("1")).toBe(1);
20
+ });
21
+ test("port 65535 is valid", () => {
22
+ expect(parseExtensionPortValue("65535")).toBe(65535);
23
+ });
24
+ });
25
+ describe("findArgValue", () => {
26
+ test("finds value after flag", () => {
27
+ expect(findArgValue(["--port", "8080", "--help"], "--port")).toBe("8080");
28
+ });
29
+ test("returns undefined when flag not found", () => {
30
+ expect(findArgValue(["--help"], "--port")).toBeUndefined();
31
+ });
32
+ test("returns undefined when flag is last argument", () => {
33
+ expect(findArgValue(["--port"], "--port")).toBeUndefined();
34
+ });
35
+ test("finds value with short flag", () => {
36
+ expect(findArgValue(["-p", "8080"], "-p")).toBe("8080");
37
+ });
38
+ test("works with readonly array", () => {
39
+ const args = ["--port", "8080"];
40
+ expect(findArgValue(args, "--port")).toBe("8080");
41
+ });
42
+ });
43
+ describe("hasFlag", () => {
44
+ test("returns true when flag exists", () => {
45
+ expect(hasFlag(["--help", "--port", "8080"], "--help")).toBe(true);
46
+ });
47
+ test("returns false when flag does not exist", () => {
48
+ expect(hasFlag(["--port", "8080"], "--help")).toBe(false);
49
+ });
50
+ test("returns true with short flag", () => {
51
+ expect(hasFlag(["-h"], "-h")).toBe(true);
52
+ });
53
+ test("works with multiple flags", () => {
54
+ expect(hasFlag(["-h"], "-h", "--help")).toBe(true);
55
+ });
56
+ test("works with readonly array", () => {
57
+ const args = ["--help"];
58
+ expect(hasFlag(args, "--help")).toBe(true);
59
+ });
60
+ });
61
+ describe("shouldShowHelp", () => {
62
+ test("returns true for --help", () => {
63
+ expect(shouldShowHelp(["--help"])).toBe(true);
64
+ });
65
+ test("returns true for -h", () => {
66
+ expect(shouldShowHelp(["-h"])).toBe(true);
67
+ });
68
+ test("returns false for no help flag", () => {
69
+ expect(shouldShowHelp(["--extension-port", "8080"])).toBe(false);
70
+ });
71
+ test("returns false for empty args", () => {
72
+ expect(shouldShowHelp([])).toBe(false);
73
+ });
74
+ });
75
+ describe("parseConfig", () => {
76
+ test("no args returns default config", () => {
77
+ expect(parseConfig([])).toEqual({ extensionPort: 3333 });
78
+ });
79
+ test("--extension-port flag sets custom port", () => {
80
+ expect(parseConfig(["--extension-port", "8080"])).toEqual({
81
+ extensionPort: 8080,
82
+ });
83
+ });
84
+ test("-p flag sets custom port", () => {
85
+ expect(parseConfig(["-p", "8080"])).toEqual({ extensionPort: 8080 });
86
+ });
87
+ test("help flag is ignored in config parsing", () => {
88
+ expect(parseConfig(["--help"])).toEqual({ extensionPort: 3333 });
89
+ });
90
+ test("invalid port returns Error", () => {
91
+ const result = parseConfig(["--extension-port", "abc"]);
92
+ expect(result).toBeInstanceOf(Error);
93
+ expect(result.message).toContain("Invalid port number");
94
+ });
95
+ test("missing port value returns Error", () => {
96
+ const result = parseConfig(["--extension-port"]);
97
+ expect(result).toBeInstanceOf(Error);
98
+ expect(result.message).toContain("--extension-port flag requires a port number");
99
+ });
100
+ test("out of range port returns Error", () => {
101
+ const result = parseConfig(["--extension-port", "70000"]);
102
+ expect(result).toBeInstanceOf(Error);
103
+ expect(result.message).toContain("Invalid port number");
104
+ });
105
+ test("multiple --extension-port flags uses last one", () => {
106
+ expect(parseConfig(["--extension-port", "8080", "--extension-port", "9090"])).toEqual({
107
+ extensionPort: 9090,
108
+ });
109
+ });
110
+ test("short and long form both work, last wins", () => {
111
+ expect(parseConfig(["--extension-port", "8080", "-p", "9090"])).toEqual({
112
+ extensionPort: 9090,
113
+ });
114
+ });
115
+ });
116
+ describe("buildConfig", () => {
117
+ const originalArgv = process.argv;
118
+ afterEach(() => {
119
+ process.argv = originalArgv;
120
+ });
121
+ test("uses default config with empty args", () => {
122
+ process.argv = ["node", "script.js"];
123
+ const result = buildConfig();
124
+ expect(result).toEqual({ extensionPort: 3333 });
125
+ });
126
+ test("parses custom port from argv", () => {
127
+ process.argv = ["node", "script.js", "--extension-port", "8080"];
128
+ const result = buildConfig();
129
+ expect(result).toEqual({ extensionPort: 8080 });
130
+ });
131
+ test("returns Error for invalid config", () => {
132
+ process.argv = ["node", "script.js", "--extension-port", "abc"];
133
+ const result = buildConfig();
134
+ expect(result).toBeInstanceOf(Error);
135
+ });
136
+ });
package/build/index.js CHANGED
@@ -5,13 +5,34 @@ import { z } from "zod";
5
5
  import EventEmitter from "node:events";
6
6
  import { createServer } from "node:net";
7
7
  import uWS from "uWebSockets.js";
8
+ import { buildConfig, shouldShowHelp } from "./config.js";
8
9
  import { bus_reply_stream, bus_request_stream, } from "./types.js";
9
10
  import { create_bus } from "./emitter_bus.js";
10
11
  import { default_tool } from "./tool.js";
11
12
  import { nanoid_id_generator } from "./nanoid_id_generator.js";
12
13
  import { create_logger as create_console_logger } from "./mcp_console_logger.js";
13
14
  import { create_logger as create_server_logger, validLogLevels, } from "./mcp_server_logger.js";
14
- const PORT = 3333;
15
+ /**
16
+ * Display help message and exit
17
+ */
18
+ function showHelp() {
19
+ console.log(`
20
+ Draw.io MCP Server
21
+
22
+ Usage: drawio-mcp-server [options]
23
+
24
+ Options:
25
+ --extension-port, -p <number> WebSocket server port for browser extension (default: 3333)
26
+ --help, -h Show this help message
27
+
28
+ Examples:
29
+ drawio-mcp-server # Use default extension port 3333
30
+ drawio-mcp-server --extension-port 8080 # Use custom extension port 8080
31
+ drawio-mcp-server -p 8080 # Short form
32
+ `);
33
+ process.exit(0);
34
+ }
35
+ // No PORT constant needed - using dynamic config
15
36
  async function checkPortAvailable(port) {
16
37
  return new Promise((resolve) => {
17
38
  const server = createServer();
@@ -55,21 +76,21 @@ const ws_handler = {
55
76
  //todo remove conn
56
77
  },
57
78
  };
58
- async function start_websocket_server() {
59
- const isPortAvailable = await checkPortAvailable(PORT);
79
+ async function start_websocket_server(extensionPort) {
80
+ const isPortAvailable = await checkPortAvailable(extensionPort);
60
81
  if (!isPortAvailable) {
61
- console.error(`[start_websocket_server] Error: Port ${PORT} is already in use. Please stop the process using this port and try again.`);
82
+ console.error(`[start_websocket_server] Error: Port ${extensionPort} is already in use. Please stop the process using this port and try again.`);
62
83
  process.exit(1);
63
84
  }
64
85
  const app = uWS
65
86
  .App()
66
87
  .ws("/*", ws_handler)
67
- .listen(PORT, (token) => {
88
+ .listen(extensionPort, (token) => {
68
89
  if (token) {
69
- log.debug(`[start_websocket_server] Listening to port ${PORT}`);
90
+ log.debug(`[start_websocket_server] Listening to port ${extensionPort}`);
70
91
  }
71
92
  else {
72
- console.error(`[start_websocket_server] Error: Failed to listen on port ${PORT}`);
93
+ console.error(`[start_websocket_server] Error: Failed to listen on port ${extensionPort}`);
73
94
  process.exit(1);
74
95
  }
75
96
  });
@@ -92,7 +113,7 @@ if (logger_type === "mcp_server") {
92
113
  // Create server instance
93
114
  const server = new McpServer({
94
115
  name: "drawio-mcp-server",
95
- version: "1.2.1",
116
+ version: "1.4.0",
96
117
  }, {
97
118
  capabilities,
98
119
  });
@@ -310,9 +331,22 @@ server.tool(TOOL_list_paged_model, "Retrieves a paginated view of all cells (ver
310
331
  .default({}),
311
332
  }, default_tool(TOOL_list_paged_model, context));
312
333
  async function main() {
313
- log.debug("Draw.io MCP Server starting");
314
- await start_websocket_server();
315
- log.debug("Draw.io MCP Server WebSocket started");
334
+ // Check if help was requested (before parsing config)
335
+ if (shouldShowHelp(process.argv.slice(2))) {
336
+ showHelp();
337
+ // never returns
338
+ }
339
+ // Build configuration from command line args
340
+ const configResult = buildConfig();
341
+ // Handle errors from configuration parsing
342
+ if (configResult instanceof Error) {
343
+ console.error(`Error: ${configResult.message}`);
344
+ process.exit(1);
345
+ }
346
+ const config = configResult;
347
+ log.debug(`Draw.io MCP Server starting (WebSocket extension port: ${config.extensionPort})`);
348
+ await start_websocket_server(config.extensionPort);
349
+ log.debug(`Draw.io MCP Server WebSocket started on extension port ${config.extensionPort}`);
316
350
  const transport = new StdioServerTransport();
317
351
  await server.connect(transport);
318
352
  log.debug("Draw.io MCP Server running on stdio");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drawio-mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Provides Draw.io services to MCP Clients",
5
5
  "type": "module",
6
6
  "main": "index.js",