drawio-mcp-server 1.4.0 → 1.5.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
@@ -71,6 +71,49 @@ The server listens on port 3333 by default for WebSocket connections from the br
71
71
 
72
72
  **Note**: When using a custom port, ensure the browser extension is configured to connect to the same port.
73
73
 
74
+ ### HTTP Transport Port
75
+
76
+ The server can expose a streamable HTTP MCP transport on port 3000. Change this using the `--http-port` flag:
77
+
78
+ ```json
79
+ {
80
+ "mcpServers": {
81
+ "drawio": {
82
+ "command": "npx",
83
+ "args": ["-y", "drawio-mcp-server", "--transport", "http", "--http-port", "4000"]
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Transport Selection
90
+
91
+ By default only the stdio transport starts. Limit or combine transports with the `--transport` flag:
92
+
93
+ - `--transport stdio` – start only stdio (CLI-friendly)
94
+ - `--transport http` – start only the HTTP transport (for remote clients)
95
+ - `--transport stdio,http` – start both transports
96
+
97
+ ### Running the streamable HTTP transport
98
+
99
+ Use the streamable HTTP transport when you need to reach the MCP server over the network (for example from a remote agent runtime). The Draw.io browser extension is still required, and you must opt in to the HTTP transport.
100
+
101
+ 1. Start the server with HTTP enabled (optionally alongside stdio):
102
+
103
+ ```sh
104
+ npx -y drawio-mcp-server --transport http --http-port 3000
105
+ # or both: npx -y drawio-mcp-server --transport stdio,http --http-port 4000
106
+ ```
107
+
108
+ 2. Verify the health endpoint:
109
+
110
+ ```sh
111
+ curl http://localhost:3000/health
112
+ # { "status": "ok" }
113
+ ```
114
+
115
+ 3. Point your MCP client to the `/mcp` endpoint (`http://localhost:3000/mcp` by default). CORS is enabled for all origins so you can call it from a browser-based client as well.
116
+
74
117
  ## Installation
75
118
 
76
119
  ### Connecting with Claude Desktop
@@ -254,6 +297,61 @@ To use a custom extension port (e.g., 8080), add `"--extension-port", "8080"` to
254
297
  }
255
298
  ```
256
299
 
300
+ ### Connecting with Codex
301
+
302
+ Edit the configuration usually located in: ~/.codex/config.toml
303
+
304
+ <details>
305
+ <summary>Using <code>npm</code></summary>
306
+
307
+ ```toml
308
+ [mcp_servers.drawio]
309
+ command = "npx"
310
+ args = [
311
+ "-y",
312
+ "drawio-mcp-server"
313
+ ]
314
+ ```
315
+ </details>
316
+
317
+ <details>
318
+ <summary>Using <code>pnpm</code></summary>
319
+
320
+ ```toml
321
+ [mcp_servers.drawio]
322
+ command = "pnpm"
323
+ args = [
324
+ "dlx",
325
+ "drawio-mcp-server"
326
+ ]
327
+ ```
328
+ </details>
329
+
330
+ To use a custom extension port (e.g., 8080), add `"--extension-port", "8080"` to the args array:
331
+
332
+ <details>
333
+ <summary>Using <code>npm</code></summary>
334
+
335
+ ```toml
336
+ [mcp_servers.drawio]
337
+ command = "npx"
338
+ args = [
339
+ "-y",
340
+ "drawio-mcp-server",
341
+ "--extension-port",
342
+ "8080"
343
+ ]
344
+ ```
345
+ </details>
346
+
347
+ To connect to a locally running MCP with Streamable HTTP transport:
348
+
349
+ ```toml
350
+ [mcp_servers.drawio]
351
+ url = "http://localhost:3000/mcp"
352
+ ```
353
+
354
+
257
355
  ### Browser Extension Setup
258
356
 
259
357
  In order to control the Draw.io diagram, you need to install dedicated Browser Extension.
package/build/config.js CHANGED
@@ -3,6 +3,8 @@
3
3
  */
4
4
  const DEFAULT_CONFIG = {
5
5
  extensionPort: 3333,
6
+ httpPort: 3000,
7
+ transports: ["stdio"],
6
8
  };
7
9
  /**
8
10
  * Valid port range
@@ -27,6 +29,45 @@ export const parseExtensionPortValue = (value) => {
27
29
  }
28
30
  return port;
29
31
  };
32
+ /**
33
+ * Parse http port value from string - pure function
34
+ */
35
+ export const parseHttpPortValue = (value) => {
36
+ if (!value) {
37
+ return new Error("--http-port flag requires a port number");
38
+ }
39
+ const port = parseInt(value, 10);
40
+ if (isNaN(port)) {
41
+ return new Error(`Invalid port number "${value}". Port must be a number`);
42
+ }
43
+ if (port < PORT_RANGE.min || port > PORT_RANGE.max) {
44
+ return new Error(`Invalid port number "${value}". Port must be between ${PORT_RANGE.min} and ${PORT_RANGE.max}`);
45
+ }
46
+ return port;
47
+ };
48
+ export const parseTransports = (values) => {
49
+ if (!values || values.length === 0) {
50
+ return DEFAULT_CONFIG.transports;
51
+ }
52
+ const normalized = values
53
+ .flatMap((value) => value.split(","))
54
+ .map((value) => value.trim().toLowerCase())
55
+ .filter((value) => value.length > 0);
56
+ if (normalized.length === 0) {
57
+ return new Error("At least one transport must be specified");
58
+ }
59
+ const validTransports = [];
60
+ for (const value of normalized) {
61
+ if (value === "stdio" || value === "http") {
62
+ validTransports.push(value);
63
+ }
64
+ else {
65
+ return new Error(`Invalid transport "${value}". Supported transports: stdio, http`);
66
+ }
67
+ }
68
+ // Remove duplicates while preserving order
69
+ return Array.from(new Set(validTransports));
70
+ };
30
71
  /**
31
72
  * Find argument value by flag name - pure function
32
73
  */
@@ -53,6 +94,9 @@ export const shouldShowHelp = (args) => {
53
94
  export const parseConfig = (args) => {
54
95
  // Walk arguments so repeated flags allow "last wins" semantics
55
96
  let portValue;
97
+ let httpPortValue;
98
+ let parsedHttpPort;
99
+ let transportValues;
56
100
  for (let i = 0; i < args.length; i += 1) {
57
101
  const arg = args[i];
58
102
  if (arg === "--extension-port" || arg === "-p") {
@@ -63,19 +107,66 @@ export const parseConfig = (args) => {
63
107
  portValue = nextValue;
64
108
  i += 1; // Skip the value we just consumed
65
109
  }
110
+ else if (arg === "--http-port") {
111
+ const nextValue = args[i + 1];
112
+ if (nextValue === undefined) {
113
+ return new Error("--http-port flag requires a port number");
114
+ }
115
+ httpPortValue = nextValue;
116
+ i += 1;
117
+ }
118
+ else if (arg === "--transport") {
119
+ const nextValue = args[i + 1];
120
+ if (nextValue === undefined) {
121
+ return new Error("--transport flag requires a transport name");
122
+ }
123
+ transportValues = [nextValue];
124
+ i += 1;
125
+ }
126
+ }
127
+ if (httpPortValue !== undefined) {
128
+ const httpPort = parseHttpPortValue(httpPortValue);
129
+ if (httpPort instanceof Error) {
130
+ return httpPort;
131
+ }
132
+ parsedHttpPort = httpPort;
66
133
  }
67
134
  if (portValue !== undefined) {
68
135
  const extensionPort = parseExtensionPortValue(portValue);
69
136
  if (extensionPort instanceof Error) {
70
137
  return extensionPort;
71
138
  }
139
+ const transports = parseTransports(transportValues);
140
+ if (transports instanceof Error) {
141
+ return transports;
142
+ }
72
143
  return {
73
144
  ...DEFAULT_CONFIG,
74
145
  extensionPort,
146
+ httpPort: parsedHttpPort !== undefined ? parsedHttpPort : DEFAULT_CONFIG.httpPort,
147
+ transports,
75
148
  };
76
149
  }
150
+ if (httpPortValue !== undefined) {
151
+ const transports = parseTransports(transportValues);
152
+ if (transports instanceof Error) {
153
+ return transports;
154
+ }
155
+ return {
156
+ ...DEFAULT_CONFIG,
157
+ httpPort: parsedHttpPort,
158
+ transports,
159
+ };
160
+ }
161
+ const transports = parseTransports(transportValues);
162
+ if (transports instanceof Error) {
163
+ return transports;
164
+ }
77
165
  // Return default configuration
78
- return DEFAULT_CONFIG;
166
+ return {
167
+ ...DEFAULT_CONFIG,
168
+ transports,
169
+ };
79
170
  };
80
171
  /**
81
172
  * Build configuration from process.argv
@@ -1,4 +1,4 @@
1
- import { parseExtensionPortValue, findArgValue, hasFlag, shouldShowHelp, parseConfig, buildConfig, } from "./config.js";
1
+ import { parseExtensionPortValue, parseHttpPortValue, findArgValue, hasFlag, shouldShowHelp, parseConfig, buildConfig, parseTransports, } from "./config.js";
2
2
  describe("parseExtensionPortValue", () => {
3
3
  test("valid port returns number", () => {
4
4
  expect(parseExtensionPortValue("8080")).toBe(8080);
@@ -22,6 +22,42 @@ describe("parseExtensionPortValue", () => {
22
22
  expect(parseExtensionPortValue("65535")).toBe(65535);
23
23
  });
24
24
  });
25
+ describe("parseHttpPortValue", () => {
26
+ test("valid port returns number", () => {
27
+ expect(parseHttpPortValue("3000")).toBe(3000);
28
+ });
29
+ test("undefined input returns Error", () => {
30
+ expect(parseHttpPortValue(undefined)).toBeInstanceOf(Error);
31
+ });
32
+ test("non-numeric string returns Error", () => {
33
+ expect(parseHttpPortValue("abc")).toBeInstanceOf(Error);
34
+ });
35
+ test("out of range returns Error", () => {
36
+ expect(parseHttpPortValue("70000")).toBeInstanceOf(Error);
37
+ });
38
+ });
39
+ describe("parseTransports", () => {
40
+ test("returns default when undefined", () => {
41
+ expect(parseTransports(undefined)).toEqual(["stdio"]);
42
+ });
43
+ test("parses single transport", () => {
44
+ expect(parseTransports(["stdio"])).toEqual(["stdio"]);
45
+ });
46
+ test("parses comma separated list", () => {
47
+ expect(parseTransports(["stdio,http"])).toEqual(["stdio", "http"]);
48
+ });
49
+ test("deduplicates transports", () => {
50
+ expect(parseTransports(["stdio", "stdio"])).toEqual(["stdio"]);
51
+ });
52
+ test("rejects empty string", () => {
53
+ const result = parseTransports([""]);
54
+ expect(result).toBeInstanceOf(Error);
55
+ });
56
+ test("rejects unknown transport", () => {
57
+ const result = parseTransports(["foo"]);
58
+ expect(result).toBeInstanceOf(Error);
59
+ });
60
+ });
25
61
  describe("findArgValue", () => {
26
62
  test("finds value after flag", () => {
27
63
  expect(findArgValue(["--port", "8080", "--help"], "--port")).toBe("8080");
@@ -74,18 +110,46 @@ describe("shouldShowHelp", () => {
74
110
  });
75
111
  describe("parseConfig", () => {
76
112
  test("no args returns default config", () => {
77
- expect(parseConfig([])).toEqual({ extensionPort: 3333 });
113
+ expect(parseConfig([])).toEqual({
114
+ extensionPort: 3333,
115
+ httpPort: 3000,
116
+ transports: ["stdio"],
117
+ });
78
118
  });
79
119
  test("--extension-port flag sets custom port", () => {
80
120
  expect(parseConfig(["--extension-port", "8080"])).toEqual({
81
121
  extensionPort: 8080,
122
+ httpPort: 3000,
123
+ transports: ["stdio"],
82
124
  });
83
125
  });
84
126
  test("-p flag sets custom port", () => {
85
- expect(parseConfig(["-p", "8080"])).toEqual({ extensionPort: 8080 });
127
+ expect(parseConfig(["-p", "8080"])).toEqual({
128
+ extensionPort: 8080,
129
+ httpPort: 3000,
130
+ transports: ["stdio"],
131
+ });
132
+ });
133
+ test("--http-port flag sets custom port", () => {
134
+ expect(parseConfig(["--http-port", "4242"])).toEqual({
135
+ extensionPort: 3333,
136
+ httpPort: 4242,
137
+ transports: ["stdio"],
138
+ });
139
+ });
140
+ test("both ports can be configured", () => {
141
+ expect(parseConfig(["--extension-port", "8080", "--http-port", "4242"])).toEqual({
142
+ extensionPort: 8080,
143
+ httpPort: 4242,
144
+ transports: ["stdio"],
145
+ });
86
146
  });
87
147
  test("help flag is ignored in config parsing", () => {
88
- expect(parseConfig(["--help"])).toEqual({ extensionPort: 3333 });
148
+ expect(parseConfig(["--help"])).toEqual({
149
+ extensionPort: 3333,
150
+ httpPort: 3000,
151
+ transports: ["stdio"],
152
+ });
89
153
  });
90
154
  test("invalid port returns Error", () => {
91
155
  const result = parseConfig(["--extension-port", "abc"]);
@@ -97,6 +161,11 @@ describe("parseConfig", () => {
97
161
  expect(result).toBeInstanceOf(Error);
98
162
  expect(result.message).toContain("--extension-port flag requires a port number");
99
163
  });
164
+ test("missing http port value returns Error", () => {
165
+ const result = parseConfig(["--http-port"]);
166
+ expect(result).toBeInstanceOf(Error);
167
+ expect(result.message).toContain("--http-port flag requires a port number");
168
+ });
100
169
  test("out of range port returns Error", () => {
101
170
  const result = parseConfig(["--extension-port", "70000"]);
102
171
  expect(result).toBeInstanceOf(Error);
@@ -105,13 +174,42 @@ describe("parseConfig", () => {
105
174
  test("multiple --extension-port flags uses last one", () => {
106
175
  expect(parseConfig(["--extension-port", "8080", "--extension-port", "9090"])).toEqual({
107
176
  extensionPort: 9090,
177
+ httpPort: 3000,
178
+ transports: ["stdio"],
108
179
  });
109
180
  });
110
181
  test("short and long form both work, last wins", () => {
111
182
  expect(parseConfig(["--extension-port", "8080", "-p", "9090"])).toEqual({
112
183
  extensionPort: 9090,
184
+ httpPort: 3000,
185
+ transports: ["stdio"],
186
+ });
187
+ });
188
+ test("last http-port flag wins", () => {
189
+ expect(parseConfig(["--http-port", "4000", "--http-port", "5000"])).toEqual({
190
+ extensionPort: 3333,
191
+ httpPort: 5000,
192
+ transports: ["stdio"],
193
+ });
194
+ });
195
+ test("sets single transport", () => {
196
+ expect(parseConfig(["--transport", "stdio"])).toEqual({
197
+ extensionPort: 3333,
198
+ httpPort: 3000,
199
+ transports: ["stdio"],
113
200
  });
114
201
  });
202
+ test("sets multiple transports", () => {
203
+ expect(parseConfig(["--transport", "stdio,http"])).toEqual({
204
+ extensionPort: 3333,
205
+ httpPort: 3000,
206
+ transports: ["stdio", "http"],
207
+ });
208
+ });
209
+ test("rejects unknown transport", () => {
210
+ const result = parseConfig(["--transport", "foo"]);
211
+ expect(result).toBeInstanceOf(Error);
212
+ });
115
213
  });
116
214
  describe("buildConfig", () => {
117
215
  const originalArgv = process.argv;
@@ -121,12 +219,29 @@ describe("buildConfig", () => {
121
219
  test("uses default config with empty args", () => {
122
220
  process.argv = ["node", "script.js"];
123
221
  const result = buildConfig();
124
- expect(result).toEqual({ extensionPort: 3333 });
222
+ expect(result).toEqual({
223
+ extensionPort: 3333,
224
+ httpPort: 3000,
225
+ transports: ["stdio"],
226
+ });
125
227
  });
126
228
  test("parses custom port from argv", () => {
127
229
  process.argv = ["node", "script.js", "--extension-port", "8080"];
128
230
  const result = buildConfig();
129
- expect(result).toEqual({ extensionPort: 8080 });
231
+ expect(result).toEqual({
232
+ extensionPort: 8080,
233
+ httpPort: 3000,
234
+ transports: ["stdio"],
235
+ });
236
+ });
237
+ test("parses custom http port from argv", () => {
238
+ process.argv = ["node", "script.js", "--http-port", "4242"];
239
+ const result = buildConfig();
240
+ expect(result).toEqual({
241
+ extensionPort: 3333,
242
+ httpPort: 4242,
243
+ transports: ["stdio"],
244
+ });
130
245
  });
131
246
  test("returns Error for invalid config", () => {
132
247
  process.argv = ["node", "script.js", "--extension-port", "abc"];
package/build/index.js CHANGED
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
5
+ import { serve } from "@hono/node-server";
4
6
  import { z } from "zod";
7
+ import { Hono } from "hono";
8
+ import { cors } from "hono/cors";
5
9
  import EventEmitter from "node:events";
6
10
  import { createServer } from "node:net";
7
- import uWS from "uWebSockets.js";
11
+ import { WebSocket, WebSocketServer } from "ws";
8
12
  import { buildConfig, shouldShowHelp } from "./config.js";
9
13
  import { bus_reply_stream, bus_request_stream, } from "./types.js";
10
14
  import { create_bus } from "./emitter_bus.js";
@@ -43,58 +47,63 @@ async function checkPortAvailable(port) {
43
47
  });
44
48
  }
45
49
  const emitter = new EventEmitter();
46
- const conns = [];
50
+ const conns = new Set();
47
51
  const bus_to_ws_forwarder_listener = (event) => {
48
- log.debug(`[bridge] received; forwarding message to #${conns.length} clients`, event);
49
- for (let i = 0; i < conns.length; i++) {
52
+ log.debug(`[bridge] received; forwarding message to #${conns.size} clients`, event);
53
+ for (const ws of [...conns]) {
54
+ if (ws.readyState !== WebSocket.OPEN) {
55
+ conns.delete(ws);
56
+ continue;
57
+ }
50
58
  try {
51
- conns[i].send(JSON.stringify(event));
59
+ ws.send(JSON.stringify(event));
52
60
  }
53
61
  catch (e) {
54
- log.debug(`[bridge] error forwarding request at conn = ${i}`);
62
+ log.debug("[bridge] error forwarding request", e);
63
+ conns.delete(ws);
55
64
  }
56
65
  }
57
66
  };
58
67
  emitter.on(bus_request_stream, bus_to_ws_forwarder_listener);
59
- const ws_handler = {
60
- maxPayloadLength: 128 * 1024,
61
- open: (ws) => {
62
- log.debug(`[ws_handler] A WebSocket client #${conns.length} connected, presumably MCP Extension!`);
63
- conns.push(ws);
64
- },
65
- message: (ws, message, isBinary) => {
66
- // ws.send(message, isBinary);
67
- const decoder = new TextDecoder();
68
- const str = decoder.decode(message);
69
- const json = JSON.parse(str);
70
- log.debug(`[ws] received from Extension`, json);
71
- // const event_name = message.__event;
72
- emitter.emit(bus_reply_stream, json);
73
- },
74
- close: (ws, code, message) => {
75
- log.debug(`[ws_handler] WebSocket client closed with code ${code}`);
76
- //todo remove conn
77
- },
78
- };
79
68
  async function start_websocket_server(extensionPort) {
69
+ log.debug(`Draw.io MCP Server starting (WebSocket extension port: ${extensionPort})`);
80
70
  const isPortAvailable = await checkPortAvailable(extensionPort);
81
71
  if (!isPortAvailable) {
82
72
  console.error(`[start_websocket_server] Error: Port ${extensionPort} is already in use. Please stop the process using this port and try again.`);
83
73
  process.exit(1);
84
74
  }
85
- const app = uWS
86
- .App()
87
- .ws("/*", ws_handler)
88
- .listen(extensionPort, (token) => {
89
- if (token) {
90
- log.debug(`[start_websocket_server] Listening to port ${extensionPort}`);
91
- }
92
- else {
93
- console.error(`[start_websocket_server] Error: Failed to listen on port ${extensionPort}`);
94
- process.exit(1);
95
- }
75
+ const server = new WebSocketServer({ port: extensionPort });
76
+ server.on("connection", (ws) => {
77
+ log.debug(`[ws_handler] A WebSocket client #${conns.size} connected, presumably MCP Extension!`);
78
+ conns.add(ws);
79
+ ws.on("message", (data) => {
80
+ const str = typeof data === "string" ? data : data.toString();
81
+ try {
82
+ const json = JSON.parse(str);
83
+ log.debug(`[ws] received from Extension`, json);
84
+ emitter.emit(bus_reply_stream, json);
85
+ }
86
+ catch (error) {
87
+ log.debug(`[ws] failed to parse message`, error);
88
+ }
89
+ });
90
+ ws.on("close", (code) => {
91
+ conns.delete(ws);
92
+ log.debug(`[ws_handler] WebSocket client closed with code ${code}`);
93
+ });
94
+ ws.on("error", (error) => {
95
+ log.debug(`[ws_handler] WebSocket client error`, error);
96
+ conns.delete(ws);
97
+ });
98
+ });
99
+ server.on("listening", () => {
100
+ log.debug(`[start_websocket_server] Listening to port ${extensionPort}`);
101
+ });
102
+ server.on("error", (error) => {
103
+ console.error(`[start_websocket_server] Error: Failed to listen on port ${extensionPort}`, error);
104
+ process.exit(1);
96
105
  });
97
- return app;
106
+ return server;
98
107
  }
99
108
  const logger_type = process.env.LOGGER_TYPE;
100
109
  let capabilities = {
@@ -303,7 +312,8 @@ const Attributes = z.lazy(() => z
303
312
  ]))
304
313
  .refine((arr) => arr.length === 0 || typeof arr[0] === "string", {
305
314
  message: "If not empty, the first element must be a string operator",
306
- }));
315
+ })
316
+ .default([]));
307
317
  const TOOL_list_paged_model = "list-paged-model";
308
318
  server.tool(TOOL_list_paged_model, "Retrieves a paginated view of all cells (vertices and edges) in the current Draw.io diagram. This tool provides access to the complete model data with essential fields only, sanitized to remove circular dependencies and excessive data. It allows to filter based on multiple criteria and attribute boolean logic. Useful for programmatic inspection of diagram structure without overwhelming response sizes.", {
309
319
  page: z
@@ -322,14 +332,45 @@ server.tool(TOOL_list_paged_model, "Retrieves a paginated view of all cells (ver
322
332
  .enum(["edge", "vertex", "object", "layer", "group"])
323
333
  .optional()
324
334
  .describe("Filter by cell type: 'edge' for connection lines, 'vertex' for vertices/shapes, 'object' for any cell type, 'layer' for layer cells, 'group' for grouped cells"),
325
- attributes: Attributes.optional()
326
- .describe('Boolean logic array expressions for filtering cell attributes. Format: ["and" | "or", ...expressions] or ["equal", key, value]. Matches against cell attributes and parsed style properties.')
327
- .default([]),
335
+ attributes: Attributes.optional().describe('Boolean logic array expressions for filtering cell attributes. Format: ["and" | "or", ...expressions] or ["equal", key, value]. Matches against cell attributes and parsed style properties.'),
328
336
  })
329
337
  .optional()
330
338
  .describe("Optional filter criteria to apply to cells before pagination")
331
339
  .default({}),
332
340
  }, default_tool(TOOL_list_paged_model, context));
341
+ async function start_stdio_transport() {
342
+ const transport = new StdioServerTransport();
343
+ await server.connect(transport);
344
+ log.debug(`Draw.io MCP Server STDIO transport active`);
345
+ }
346
+ async function start_streamable_http_transport(http_port) {
347
+ // Create a stateless transport (no options = no session management)
348
+ const transport = new WebStandardStreamableHTTPServerTransport();
349
+ // Create the Hono app
350
+ const app = new Hono();
351
+ // Enable CORS for all origins
352
+ app.use("*", cors({
353
+ origin: "*",
354
+ allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
355
+ allowHeaders: [
356
+ "Content-Type",
357
+ "mcp-session-id",
358
+ "Last-Event-ID",
359
+ "mcp-protocol-version",
360
+ ],
361
+ exposeHeaders: ["mcp-session-id", "mcp-protocol-version"],
362
+ }));
363
+ app.get("/health", (c) => c.json({ status: server.isConnected() ? "ok" : "mcp not ready" }));
364
+ app.all("/mcp", (c) => transport.handleRequest(c.req.raw));
365
+ await server.connect(transport);
366
+ serve({
367
+ fetch: app.fetch,
368
+ port: http_port,
369
+ });
370
+ log.debug(`Draw.io MCP Server Streamable HTTP transport active`);
371
+ log.debug(`Health check: http://localhost:${http_port}/health`);
372
+ log.debug(`MCP endpoint: http://localhost:${http_port}/mcp`);
373
+ }
333
374
  async function main() {
334
375
  // Check if help was requested (before parsing config)
335
376
  if (shouldShowHelp(process.argv.slice(2))) {
@@ -344,12 +385,14 @@ async function main() {
344
385
  process.exit(1);
345
386
  }
346
387
  const config = configResult;
347
- log.debug(`Draw.io MCP Server starting (WebSocket extension port: ${config.extensionPort})`);
348
388
  await start_websocket_server(config.extensionPort);
349
- log.debug(`Draw.io MCP Server WebSocket started on extension port ${config.extensionPort}`);
350
- const transport = new StdioServerTransport();
351
- await server.connect(transport);
352
- log.debug("Draw.io MCP Server running on stdio");
389
+ if (config.transports.indexOf("stdio") > -1) {
390
+ await start_stdio_transport();
391
+ }
392
+ if (config.transports.indexOf("http") > -1) {
393
+ start_streamable_http_transport(config.httpPort);
394
+ }
395
+ log.debug(`Draw.io MCP Server running on ${config.transports}`);
353
396
  }
354
397
  main().catch((error) => {
355
398
  log.debug("Fatal error in main():", error);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drawio-mcp-server",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Provides Draw.io services to MCP Clients",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -27,22 +27,25 @@
27
27
  "author": "Ladislav Gazo",
28
28
  "license": "MIT",
29
29
  "dependencies": {
30
- "@modelcontextprotocol/sdk": "1.17.5",
31
- "nanoid": "5.1.5",
32
- "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.52.0",
33
- "zod": "3.24.3"
30
+ "@hono/node-server": "1.19.7",
31
+ "@modelcontextprotocol/sdk": "1.25.1",
32
+ "hono": "4.11.1",
33
+ "nanoid": "5.1.6",
34
+ "ws": "8.18.3",
35
+ "zod": "4.2.1"
34
36
  },
35
37
  "devDependencies": {
36
- "@jest/globals": "30.1.2",
38
+ "@jest/globals": "30.2.0",
37
39
  "@types/jest": "30.0.0",
38
- "@types/node": "24.3.1",
39
- "globals": "16.3.0",
40
- "jest": "30.1.3",
41
- "jest-environment-node": "30.1.2",
42
- "prettier": "3.5.3",
43
- "rimraf": "6.0.1",
44
- "ts-jest": "29.4.1",
45
- "typescript": "5.9.2"
40
+ "@types/node": "25.0.3",
41
+ "@types/ws": "^8.18.1",
42
+ "globals": "16.5.0",
43
+ "jest": "30.2.0",
44
+ "jest-environment-node": "30.2.0",
45
+ "prettier": "3.7.4",
46
+ "rimraf": "6.1.2",
47
+ "ts-jest": "29.4.6",
48
+ "typescript": "5.9.3"
46
49
  },
47
50
  "scripts": {
48
51
  "build": "tsc",