drawio-mcp-server 1.0.1 → 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 (**still pending review**) or [use other means](https://github.com/lgazo/drawio-mcp-extension)
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
@@ -1,18 +1,30 @@
1
+ #!/usr/bin/env node
1
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
4
  import { z } from "zod";
4
5
  import EventEmitter from "node:events";
6
+ import { createServer } from "node:net";
5
7
  import uWS from "uWebSockets.js";
6
8
  import { bus_reply_stream, bus_request_stream, } from "./types.js";
7
9
  import { create_bus } from "./emitter_bus.js";
8
10
  import { default_tool } from "./tool.js";
9
11
  import { nanoid_id_generator } from "./nanoid_id_generator.js";
10
12
  import { create_logger } from "./mcp_console_logger.js";
13
+ const PORT = 3333;
11
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
+ }
12
24
  const emitter = new EventEmitter();
13
25
  const conns = [];
14
26
  const bus_to_ws_forwarder_listener = (event) => {
15
- log.debug(`[bridge] received; passing to #${conns.length}`, event);
27
+ log.debug(`[bridge] received; forwarding message to #${conns.length} clients`, event);
16
28
  for (let i = 0; i < conns.length; i++) {
17
29
  try {
18
30
  conns[i].send(JSON.stringify(event));
@@ -25,7 +37,7 @@ const bus_to_ws_forwarder_listener = (event) => {
25
37
  emitter.on(bus_request_stream, bus_to_ws_forwarder_listener);
26
38
  const ws_handler = {
27
39
  open: (ws) => {
28
- log.debug("A WebSocket connected!");
40
+ log.debug(`[ws_handler] A WebSocket client #${conns.length} connected, presumably MCP Extension!`);
29
41
  conns.push(ws);
30
42
  },
31
43
  message: (ws, message, isBinary) => {
@@ -38,22 +50,34 @@ const ws_handler = {
38
50
  emitter.emit(bus_reply_stream, json);
39
51
  },
40
52
  close: (ws, code, message) => {
41
- log.debug("WebSocket closed");
53
+ log.debug("[ws_handler] WebSocket client closed");
42
54
  //todo remove conn
43
55
  },
44
56
  };
45
- const app = uWS
46
- .App()
47
- .ws("/*", ws_handler)
48
- .listen(3333, (token) => {
49
- if (token) {
50
- 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);
51
62
  }
52
- });
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
+ }
53
77
  // Create server instance
54
78
  const server = new McpServer({
55
79
  name: "drawio-mcp-server",
56
- version: "1.0.1",
80
+ version: "1.0.3",
57
81
  capabilities: {
58
82
  resources: {},
59
83
  tools: {},
@@ -174,6 +198,9 @@ server.tool(TOOL_add_cell_of_shape, "This tool allows you to add new vertex cell
174
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;`"),
175
199
  }, default_tool(TOOL_add_cell_of_shape, context));
176
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");
177
204
  const transport = new StdioServerTransport();
178
205
  await server.connect(transport);
179
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 console.error(message, data);
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 console.error(message, data);
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, testData);
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, testData);
40
+ expect(mockConsoleError).toHaveBeenCalledWith(`DEBUG: ${testMessage}`, testData);
41
41
  });
42
- it("should handle undefined data parameter", () => {
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, undefined);
46
+ expect(mockConsoleError).toHaveBeenCalledWith(`WARN: ${testMessage}`);
47
47
  mockConsoleError.mockClear();
48
48
  logger.debug(testMessage);
49
- expect(mockConsoleError).toHaveBeenCalledWith(testMessage, undefined);
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
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drawio-mcp-server",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Provides Draw.io services to MCP Clients",
5
5
  "type": "module",
6
6
  "main": "index.js",