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 +79 -1
- package/build/config.js +88 -0
- package/build/config.test.js +136 -0
- package/build/index.js +45 -11
- package/package.json +1 -1
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
|
-
[](https://github.com/lgazo/drawio-mcp-server/actions/workflows/ci.yml)
|
|
5
|
+
[](https://discord.gg/dM4PWdf42q) [](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
|
|
package/build/config.js
ADDED
|
@@ -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
|
-
|
|
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(
|
|
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 ${
|
|
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(
|
|
88
|
+
.listen(extensionPort, (token) => {
|
|
68
89
|
if (token) {
|
|
69
|
-
log.debug(`[start_websocket_server] Listening to 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 ${
|
|
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.
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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");
|