drawio-mcp-server 1.0.2 → 1.0.3
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
|
@@ -135,7 +135,7 @@ The configuration is usually in: ~/.local/share/oterm/config.json
|
|
|
135
135
|
In order to control the Draw.io diagram, you need to install dedicated Browser Extension.
|
|
136
136
|
|
|
137
137
|
1. Open [Draw.io in your browser](https://app.diagrams.net/)
|
|
138
|
-
2. Install the Draw.io MCP Browser Extension from a web store
|
|
138
|
+
2. Install the Draw.io MCP Browser Extension from a web store or [use other means](https://github.com/lgazo/drawio-mcp-extension)
|
|
139
139
|
<p>
|
|
140
140
|
<a href="https://chrome.google.com/webstore/detail/drawio-mcp-extension/okdbbjbbccdhhfaefmcmekalmmdjjide">
|
|
141
141
|
<picture>
|
|
@@ -208,6 +208,12 @@ The Draw.io MCP server provides the following tools for programmatic diagram int
|
|
|
208
208
|
|
|
209
209
|
## Related Resources
|
|
210
210
|
|
|
211
|
+
[Troubleshooting](./TROUBLESHOOTING.md)
|
|
212
|
+
|
|
213
|
+
[Prompt examples](./docs/examples/index.md)
|
|
214
|
+
|
|
215
|
+
[Contributing](./CONTRIBUTING.md)
|
|
216
|
+
|
|
211
217
|
[Architecture](./ARCHITECTURE.md)
|
|
212
218
|
|
|
213
219
|
[Development](./DEVELOPMENT.md)
|
package/build/index.js
CHANGED
|
@@ -3,17 +3,28 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import EventEmitter from "node:events";
|
|
6
|
+
import { createServer } from "node:net";
|
|
6
7
|
import uWS from "uWebSockets.js";
|
|
7
8
|
import { bus_reply_stream, bus_request_stream, } from "./types.js";
|
|
8
9
|
import { create_bus } from "./emitter_bus.js";
|
|
9
10
|
import { default_tool } from "./tool.js";
|
|
10
11
|
import { nanoid_id_generator } from "./nanoid_id_generator.js";
|
|
11
12
|
import { create_logger } from "./mcp_console_logger.js";
|
|
13
|
+
const PORT = 3333;
|
|
12
14
|
const log = create_logger();
|
|
15
|
+
async function checkPortAvailable(port) {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const server = createServer();
|
|
18
|
+
server.listen(port, () => {
|
|
19
|
+
server.close(() => resolve(true));
|
|
20
|
+
});
|
|
21
|
+
server.on("error", () => resolve(false));
|
|
22
|
+
});
|
|
23
|
+
}
|
|
13
24
|
const emitter = new EventEmitter();
|
|
14
25
|
const conns = [];
|
|
15
26
|
const bus_to_ws_forwarder_listener = (event) => {
|
|
16
|
-
log.debug(`[bridge] received;
|
|
27
|
+
log.debug(`[bridge] received; forwarding message to #${conns.length} clients`, event);
|
|
17
28
|
for (let i = 0; i < conns.length; i++) {
|
|
18
29
|
try {
|
|
19
30
|
conns[i].send(JSON.stringify(event));
|
|
@@ -26,7 +37,7 @@ const bus_to_ws_forwarder_listener = (event) => {
|
|
|
26
37
|
emitter.on(bus_request_stream, bus_to_ws_forwarder_listener);
|
|
27
38
|
const ws_handler = {
|
|
28
39
|
open: (ws) => {
|
|
29
|
-
log.debug(
|
|
40
|
+
log.debug(`[ws_handler] A WebSocket client #${conns.length} connected, presumably MCP Extension!`);
|
|
30
41
|
conns.push(ws);
|
|
31
42
|
},
|
|
32
43
|
message: (ws, message, isBinary) => {
|
|
@@ -39,22 +50,34 @@ const ws_handler = {
|
|
|
39
50
|
emitter.emit(bus_reply_stream, json);
|
|
40
51
|
},
|
|
41
52
|
close: (ws, code, message) => {
|
|
42
|
-
log.debug("WebSocket closed");
|
|
53
|
+
log.debug("[ws_handler] WebSocket client closed");
|
|
43
54
|
//todo remove conn
|
|
44
55
|
},
|
|
45
56
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
log.debug("Listening to port 3333");
|
|
57
|
+
async function start_websocket_server() {
|
|
58
|
+
const isPortAvailable = await checkPortAvailable(PORT);
|
|
59
|
+
if (!isPortAvailable) {
|
|
60
|
+
console.error(`[start_websocket_server] Error: Port ${PORT} is already in use. Please stop the process using this port and try again.`);
|
|
61
|
+
process.exit(1);
|
|
52
62
|
}
|
|
53
|
-
|
|
63
|
+
const app = uWS
|
|
64
|
+
.App()
|
|
65
|
+
.ws("/*", ws_handler)
|
|
66
|
+
.listen(PORT, (token) => {
|
|
67
|
+
if (token) {
|
|
68
|
+
log.debug(`[start_websocket_server] Listening to port ${PORT}`);
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
console.error(`[start_websocket_server] Error: Failed to listen on port ${PORT}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return app;
|
|
76
|
+
}
|
|
54
77
|
// Create server instance
|
|
55
78
|
const server = new McpServer({
|
|
56
79
|
name: "drawio-mcp-server",
|
|
57
|
-
version: "1.0.
|
|
80
|
+
version: "1.0.3",
|
|
58
81
|
capabilities: {
|
|
59
82
|
resources: {},
|
|
60
83
|
tools: {},
|
|
@@ -175,6 +198,9 @@ server.tool(TOOL_add_cell_of_shape, "This tool allows you to add new vertex cell
|
|
|
175
198
|
.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;`"),
|
|
176
199
|
}, default_tool(TOOL_add_cell_of_shape, context));
|
|
177
200
|
async function main() {
|
|
201
|
+
log.debug("Draw.io MCP Server starting");
|
|
202
|
+
await start_websocket_server();
|
|
203
|
+
log.debug("Draw.io MCP Server WebSocket started");
|
|
178
204
|
const transport = new StdioServerTransport();
|
|
179
205
|
await server.connect(transport);
|
|
180
206
|
log.debug("Draw.io MCP Server running on stdio");
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
export function create_logger() {
|
|
2
2
|
return {
|
|
3
|
-
log: (level, message, data) => {
|
|
4
|
-
return
|
|
3
|
+
log: (level, message, ...data) => {
|
|
4
|
+
return data.length > 0
|
|
5
|
+
? console.error(`${level?.toUpperCase()}: ${message}`, ...data)
|
|
6
|
+
: console.error(`${level?.toUpperCase()}: ${message}`);
|
|
5
7
|
},
|
|
6
|
-
debug: (message, data) => {
|
|
7
|
-
return
|
|
8
|
+
debug: (message, ...data) => {
|
|
9
|
+
return data.length > 0
|
|
10
|
+
? console.error(`DEBUG: ${message}`, ...data)
|
|
11
|
+
: console.error(`DEBUG: ${message}`);
|
|
8
12
|
},
|
|
9
13
|
};
|
|
10
14
|
}
|
|
@@ -29,7 +29,7 @@ describe("create_logger", () => {
|
|
|
29
29
|
const testLevel = "info";
|
|
30
30
|
logger.log(testLevel, testMessage, testData);
|
|
31
31
|
expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
|
32
|
-
expect(mockConsoleError).toHaveBeenCalledWith(testMessage
|
|
32
|
+
expect(mockConsoleError).toHaveBeenCalledWith(`${testLevel.toUpperCase()}: ${testMessage}`, testData);
|
|
33
33
|
});
|
|
34
34
|
it("debug method should call console.error with message and data", () => {
|
|
35
35
|
const logger = create_logger();
|
|
@@ -37,15 +37,27 @@ describe("create_logger", () => {
|
|
|
37
37
|
const testData = { debug: true };
|
|
38
38
|
logger.debug(testMessage, testData);
|
|
39
39
|
expect(mockConsoleError).toHaveBeenCalledTimes(1);
|
|
40
|
-
expect(mockConsoleError).toHaveBeenCalledWith(testMessage
|
|
40
|
+
expect(mockConsoleError).toHaveBeenCalledWith(`DEBUG: ${testMessage}`, testData);
|
|
41
41
|
});
|
|
42
|
-
it("should handle
|
|
42
|
+
it("should handle no additional data parameters", () => {
|
|
43
43
|
const logger = create_logger();
|
|
44
44
|
const testMessage = "message without data";
|
|
45
45
|
logger.log("warn", testMessage);
|
|
46
|
-
expect(mockConsoleError).toHaveBeenCalledWith(testMessage
|
|
46
|
+
expect(mockConsoleError).toHaveBeenCalledWith(`WARN: ${testMessage}`);
|
|
47
47
|
mockConsoleError.mockClear();
|
|
48
48
|
logger.debug(testMessage);
|
|
49
|
-
expect(mockConsoleError).toHaveBeenCalledWith(testMessage
|
|
49
|
+
expect(mockConsoleError).toHaveBeenCalledWith(`DEBUG: ${testMessage}`);
|
|
50
|
+
});
|
|
51
|
+
it("should handle multiple data parameters", () => {
|
|
52
|
+
const logger = create_logger();
|
|
53
|
+
const testMessage = "message with multiple data";
|
|
54
|
+
const data1 = { key1: "value1" };
|
|
55
|
+
const data2 = { key2: "value2" };
|
|
56
|
+
const data3 = "string data";
|
|
57
|
+
logger.log("error", testMessage, data1, data2, data3);
|
|
58
|
+
expect(mockConsoleError).toHaveBeenCalledWith(`ERROR: ${testMessage}`, data1, data2, data3);
|
|
59
|
+
mockConsoleError.mockClear();
|
|
60
|
+
logger.debug(testMessage, data1, data2, data3);
|
|
61
|
+
expect(mockConsoleError).toHaveBeenCalledWith(`DEBUG: ${testMessage}`, data1, data2, data3);
|
|
50
62
|
});
|
|
51
63
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export function create_logger() {
|
|
2
2
|
return {
|
|
3
|
-
log: (level, message, data) => {
|
|
4
|
-
return console.log(message, data);
|
|
3
|
+
log: (level, message, ...data) => {
|
|
4
|
+
return console.log(message, ...data);
|
|
5
5
|
},
|
|
6
|
-
debug: (message, data) => {
|
|
7
|
-
return console.debug(message, data);
|
|
6
|
+
debug: (message, ...data) => {
|
|
7
|
+
return console.debug(message, ...data);
|
|
8
8
|
},
|
|
9
9
|
};
|
|
10
10
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
|
+
import { create_logger } from "./standard_console_logger.js";
|
|
3
|
+
describe("create_logger", () => {
|
|
4
|
+
let originalConsoleLog;
|
|
5
|
+
let originalConsoleDebug;
|
|
6
|
+
let mockConsoleLog;
|
|
7
|
+
let mockConsoleDebug;
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Save original console methods
|
|
10
|
+
originalConsoleLog = console.log;
|
|
11
|
+
originalConsoleDebug = console.debug;
|
|
12
|
+
// Create mocks
|
|
13
|
+
mockConsoleLog = jest.fn();
|
|
14
|
+
mockConsoleDebug = jest.fn();
|
|
15
|
+
console.log = mockConsoleLog;
|
|
16
|
+
console.debug = mockConsoleDebug;
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
// Restore original console methods
|
|
20
|
+
console.log = originalConsoleLog;
|
|
21
|
+
console.debug = originalConsoleDebug;
|
|
22
|
+
// Clear all mocks
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
it("should return a Logger object with log and debug methods", () => {
|
|
26
|
+
const logger = create_logger();
|
|
27
|
+
expect(logger).toBeDefined();
|
|
28
|
+
expect(typeof logger.log).toBe("function");
|
|
29
|
+
expect(typeof logger.debug).toBe("function");
|
|
30
|
+
});
|
|
31
|
+
it("log method should call console.log with message and data", () => {
|
|
32
|
+
const logger = create_logger();
|
|
33
|
+
const testMessage = "test message";
|
|
34
|
+
const testData = { key: "value" };
|
|
35
|
+
const testLevel = "info";
|
|
36
|
+
logger.log(testLevel, testMessage, testData);
|
|
37
|
+
expect(mockConsoleLog).toHaveBeenCalledTimes(1);
|
|
38
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(testMessage, testData);
|
|
39
|
+
});
|
|
40
|
+
it("debug method should call console.debug with message and data", () => {
|
|
41
|
+
const logger = create_logger();
|
|
42
|
+
const testMessage = "debug message";
|
|
43
|
+
const testData = { debug: true };
|
|
44
|
+
logger.debug(testMessage, testData);
|
|
45
|
+
expect(mockConsoleDebug).toHaveBeenCalledTimes(1);
|
|
46
|
+
expect(mockConsoleDebug).toHaveBeenCalledWith(testMessage, testData);
|
|
47
|
+
});
|
|
48
|
+
it("should handle no additional data parameters", () => {
|
|
49
|
+
const logger = create_logger();
|
|
50
|
+
const testMessage = "message without data";
|
|
51
|
+
logger.log("warn", testMessage);
|
|
52
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(testMessage);
|
|
53
|
+
mockConsoleLog.mockClear();
|
|
54
|
+
logger.debug(testMessage);
|
|
55
|
+
expect(mockConsoleDebug).toHaveBeenCalledWith(testMessage);
|
|
56
|
+
});
|
|
57
|
+
it("should ignore level parameter in log method", () => {
|
|
58
|
+
const logger = create_logger();
|
|
59
|
+
const testMessage = "test message";
|
|
60
|
+
const testData = { key: "value" };
|
|
61
|
+
// Test with different levels - should all behave the same
|
|
62
|
+
logger.log("error", testMessage, testData);
|
|
63
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(testMessage, testData);
|
|
64
|
+
mockConsoleLog.mockClear();
|
|
65
|
+
logger.log("warn", testMessage, testData);
|
|
66
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(testMessage, testData);
|
|
67
|
+
mockConsoleLog.mockClear();
|
|
68
|
+
logger.log("info", testMessage, testData);
|
|
69
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(testMessage, testData);
|
|
70
|
+
});
|
|
71
|
+
it("should handle multiple data parameters", () => {
|
|
72
|
+
const logger = create_logger();
|
|
73
|
+
const testMessage = "message with multiple data";
|
|
74
|
+
const data1 = { key1: "value1" };
|
|
75
|
+
const data2 = { key2: "value2" };
|
|
76
|
+
const data3 = "string data";
|
|
77
|
+
logger.log("info", testMessage, data1, data2, data3);
|
|
78
|
+
expect(mockConsoleLog).toHaveBeenCalledWith(testMessage, data1, data2, data3);
|
|
79
|
+
mockConsoleLog.mockClear();
|
|
80
|
+
logger.debug(testMessage, data1, data2, data3);
|
|
81
|
+
expect(mockConsoleDebug).toHaveBeenCalledWith(testMessage, data1, data2, data3);
|
|
82
|
+
});
|
|
83
|
+
});
|