drawio-mcp-server 1.2.1 → 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 +106 -1
- package/build/config.js +88 -0
- package/build/config.test.js +136 -0
- package/build/index.js +107 -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
|
|
|
@@ -279,6 +357,33 @@ The Draw.io MCP server provides the following tools for programmatic diagram int
|
|
|
279
357
|
- `text`: Optional text content
|
|
280
358
|
- `style`: Optional additional style properties
|
|
281
359
|
|
|
360
|
+
- **`set-cell-shape`**
|
|
361
|
+
Applies a library shape's style to an existing cell
|
|
362
|
+
*Parameters*:
|
|
363
|
+
- `cell_id`: ID of the cell whose appearance should change
|
|
364
|
+
- `shape_name`: Name of the library shape whose style should be applied
|
|
365
|
+
|
|
366
|
+
- **`set-cell-data`**
|
|
367
|
+
Stores or updates a custom attribute on a cell
|
|
368
|
+
*Parameters*:
|
|
369
|
+
- `cell_id`: ID of the cell to update
|
|
370
|
+
- `key`: Attribute name to set
|
|
371
|
+
- `value`: Attribute value (stored as a string internally)
|
|
372
|
+
|
|
373
|
+
- **`edit-cell`**
|
|
374
|
+
Updates an existing vertex/shape cell in place by ID
|
|
375
|
+
*Parameters*:
|
|
376
|
+
- `cell_id`: ID of the cell whose properties should change (required)
|
|
377
|
+
- `text`, `x`, `y`, `width`, `height`, `style`: Optional fields to update on the cell; omitted properties stay as-is
|
|
378
|
+
|
|
379
|
+
- **`edit-edge`**
|
|
380
|
+
Updates an existing edge connection between cells by ID
|
|
381
|
+
*Parameters*:
|
|
382
|
+
- `cell_id`: ID of the edge cell to update (required)
|
|
383
|
+
- `text`: Optional edge label text
|
|
384
|
+
- `source_id`, `target_id`: Optional IDs of new source/target cells
|
|
385
|
+
- `style`: Optional replacement style string
|
|
386
|
+
|
|
282
387
|
## Related Resources
|
|
283
388
|
|
|
284
389
|
[Troubleshooting](./TROUBLESHOOTING.md)
|
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
|
});
|
|
@@ -213,6 +234,68 @@ server.tool(TOOL_add_cell_of_shape, "This tool allows you to add new vertex cell
|
|
|
213
234
|
.optional()
|
|
214
235
|
.describe("Semi-colon separated list of Draw.io visual styles, in the form of `key=value`. Example: `whiteSpace=wrap;html=1;fillColor=#f5f5f5;strokeColor=#666666;`"),
|
|
215
236
|
}, default_tool(TOOL_add_cell_of_shape, context));
|
|
237
|
+
const TOOL_set_cell_shape = "set-cell-shape";
|
|
238
|
+
server.tool(TOOL_set_cell_shape, "Updates the visual style of an existing vertex cell to match a library shape by name.", {
|
|
239
|
+
cell_id: z
|
|
240
|
+
.string()
|
|
241
|
+
.describe("Identifier (`id` attribute) of the cell whose shape should change."),
|
|
242
|
+
shape_name: z
|
|
243
|
+
.string()
|
|
244
|
+
.describe("Name of the library shape whose style should be applied to the existing cell."),
|
|
245
|
+
}, default_tool(TOOL_set_cell_shape, context));
|
|
246
|
+
const TOOL_set_cell_data = "set-cell-data";
|
|
247
|
+
server.tool(TOOL_set_cell_data, "Sets or updates a custom attribute on an existing cell.", {
|
|
248
|
+
cell_id: z
|
|
249
|
+
.string()
|
|
250
|
+
.describe("Identifier (`id` attribute) of the cell to update with custom data."),
|
|
251
|
+
key: z.string().describe("Name of the attribute to set on the cell."),
|
|
252
|
+
value: z
|
|
253
|
+
.union([z.string(), z.number(), z.boolean()])
|
|
254
|
+
.describe("Value to store for the attribute. Non-string values are stringified before storage."),
|
|
255
|
+
}, default_tool(TOOL_set_cell_data, context));
|
|
256
|
+
const TOOL_edit_cell = "edit-cell";
|
|
257
|
+
server.tool(TOOL_edit_cell, "Update properties of an existing vertex/shape cell by its ID. Only provided fields are modified; unspecified properties remain unchanged.", {
|
|
258
|
+
cell_id: z
|
|
259
|
+
.string()
|
|
260
|
+
.describe("Identifier (`id` attribute) of the cell to update. Applies to vertex/shape cells."),
|
|
261
|
+
text: z
|
|
262
|
+
.string()
|
|
263
|
+
.optional()
|
|
264
|
+
.describe("Replace the cell's text/label content."),
|
|
265
|
+
x: z
|
|
266
|
+
.number()
|
|
267
|
+
.optional()
|
|
268
|
+
.describe("Set a new X-axis position for the cell."),
|
|
269
|
+
y: z
|
|
270
|
+
.number()
|
|
271
|
+
.optional()
|
|
272
|
+
.describe("Set a new Y-axis position for the cell."),
|
|
273
|
+
width: z.number().optional().describe("Set a new width for the cell."),
|
|
274
|
+
height: z.number().optional().describe("Set a new height for the cell."),
|
|
275
|
+
style: z
|
|
276
|
+
.string()
|
|
277
|
+
.optional()
|
|
278
|
+
.describe("Replace the cell's style string (semi-colon separated `key=value` pairs)."),
|
|
279
|
+
}, default_tool(TOOL_edit_cell, context));
|
|
280
|
+
const TOOL_edit_edge = "edit-edge";
|
|
281
|
+
server.tool(TOOL_edit_edge, "Update properties of an existing edge by its ID. Only provided fields are modified; unspecified properties remain unchanged.", {
|
|
282
|
+
cell_id: z
|
|
283
|
+
.string()
|
|
284
|
+
.describe("Identifier (`id` attribute) of the edge cell to update. The ID must reference an edge."),
|
|
285
|
+
text: z.string().optional().describe("Replace the edge's label text."),
|
|
286
|
+
source_id: z
|
|
287
|
+
.string()
|
|
288
|
+
.optional()
|
|
289
|
+
.describe("Reassign the edge's source terminal to a different cell ID."),
|
|
290
|
+
target_id: z
|
|
291
|
+
.string()
|
|
292
|
+
.optional()
|
|
293
|
+
.describe("Reassign the edge's target terminal to a different cell ID."),
|
|
294
|
+
style: z
|
|
295
|
+
.string()
|
|
296
|
+
.optional()
|
|
297
|
+
.describe("Replace the edge's style string (semi-colon separated `key=value` pairs)."),
|
|
298
|
+
}, default_tool(TOOL_edit_edge, context));
|
|
216
299
|
const Attributes = z.lazy(() => z
|
|
217
300
|
.array(z.union([
|
|
218
301
|
z.string(),
|
|
@@ -248,9 +331,22 @@ server.tool(TOOL_list_paged_model, "Retrieves a paginated view of all cells (ver
|
|
|
248
331
|
.default({}),
|
|
249
332
|
}, default_tool(TOOL_list_paged_model, context));
|
|
250
333
|
async function main() {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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}`);
|
|
254
350
|
const transport = new StdioServerTransport();
|
|
255
351
|
await server.connect(transport);
|
|
256
352
|
log.debug("Draw.io MCP Server running on stdio");
|