drawio-mcp-server 2.1.0 → 2.1.1

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
@@ -41,7 +41,7 @@ Experimental: integration with the **draw.io desktop (Electron) app** is in prog
41
41
 
42
42
  ## Requirements
43
43
 
44
- - **Node.js** (v20 or higher) - Runtime environment for the MCP server
44
+ - **Node.js** (v22 or higher; tested against v22 LTS and v24 LTS) - Runtime environment for the MCP server
45
45
  - **MCP client** - Claude Desktop, Claude Code, Zed, Codex, OpenCode, or any MCP-compatible host
46
46
 
47
47
  ### For Built-in Editor
package/build/index.js CHANGED
@@ -12,7 +12,7 @@ import { join } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { readFileSync, existsSync, statSync, readdirSync, realpathSync, } from "node:fs";
14
14
  import { WebSocket, WebSocketServer } from "ws";
15
- const VERSION = process.env.npm_package_version ?? "2.1.0";
15
+ const VERSION = process.env.npm_package_version ?? "2.1.1";
16
16
  import { buildConfig, defaultConfig, hasFlag, shouldShowHelp, getHttpFeatureConfig, } from "./config.js";
17
17
  import { installDesktopPlugin } from "./install-desktop-plugin.js";
18
18
  import { bus_reply_stream, bus_request_stream, } from "./types.js";
@@ -1,31 +1,83 @@
1
1
  import { stripSchemaRecursively } from "./strip-schema.js";
2
2
  /**
3
- * Creates a wrapped version of McpServer.tool that automatically strips
4
- * `$schema` from the inputSchema after tool registration.
3
+ * Wraps an `McpServer` so that the `tools/list` response strips the
4
+ * `$schema` key from every tool's `inputSchema` and `outputSchema`.
5
5
  *
6
- * The `$schema` key injected by zodToJsonSchema causes Claude Code to silently
7
- * drop tools because the `$` character fails Anthropic's validation regex
8
- * `^[a-zA-Z0-9_.-]{1,64}$`.
6
+ * Why this is necessary:
9
7
  *
10
- * @param server - The McpServer instance to wrap
11
- * @returns The same server instance, but with a modified tool() method
8
+ * - The MCP SDK lazily serializes tool schemas: it stores the Zod object
9
+ * verbatim at registration time and only converts to JSON Schema
10
+ * (via `zodToJsonSchema` / `z4mini.toJSONSchema`) when a client issues
11
+ * `tools/list`. The conversion emits a top-level
12
+ * `"$schema": "http://json-schema.org/draft-07/schema#"` field.
13
+ *
14
+ * - Claude Code's MCP ingest layer can't tolerate the `$` character in
15
+ * schema keys (Anthropic's parameter-name regex is
16
+ * `^[a-zA-Z0-9_.-]{1,64}$`). When `$schema` is present, the entire
17
+ * `properties` object is dropped, leaving tools effectively schemaless.
18
+ * The user can see the tool name but the LLM has no way to know which
19
+ * arguments to send, so calls fail at runtime.
20
+ *
21
+ * - A previous attempt at this fix tried to strip `$schema` from the
22
+ * stored `_registeredTools[name].inputSchema` via `setTimeout(0)`.
23
+ * That was a no-op against modern SDKs because the stored value at
24
+ * that point is the raw Zod object (which doesn't have `$schema`).
25
+ * The `$schema` key is added by `toJsonSchemaCompat` at serialization
26
+ * time, not at registration time.
27
+ *
28
+ * The fix here intercepts the `tools/list` request handler that the SDK
29
+ * registers internally on the first `server.tool(...)` call, and wraps
30
+ * it so the response runs through `stripSchemaRecursively` before being
31
+ * returned to the client.
12
32
  */
13
33
  export function createServerWithSchemaStripping(server) {
14
- // Store original tool method
15
34
  const originalTool = server.tool.bind(server);
16
- // Override tool method
17
- server.tool = function tool(name, description, inputSchema, handler) {
18
- // Call original tool method
19
- const result = originalTool(name, description, inputSchema, handler);
20
- // Strip $schema from the registered tool's inputSchema
21
- // This must be done after registration
22
- setTimeout(() => {
23
- const registeredTool = server._registeredTools?.[name];
24
- if (registeredTool?.inputSchema) {
25
- registeredTool.inputSchema = stripSchemaRecursively(registeredTool.inputSchema);
26
- }
27
- }, 0);
35
+ let patched = false;
36
+ server.tool = function tool(...args) {
37
+ const result = originalTool(...args);
38
+ // The SDK lazily registers the tools/list handler on the first
39
+ // server.tool() call (via setToolRequestHandlers). After that call
40
+ // returns, the handler is in place and we can wrap it.
41
+ if (!patched) {
42
+ patched = true;
43
+ patchToolsListHandler(server);
44
+ }
28
45
  return result;
29
46
  };
30
47
  return server;
31
48
  }
49
+ function patchToolsListHandler(server) {
50
+ // The McpServer wraps an inner Server (the low-level protocol layer).
51
+ // Its `_requestHandlers` Map stores per-method handlers keyed by the
52
+ // method name literal (e.g. "tools/list").
53
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
+ const innerServer = server.server;
55
+ const handlers = innerServer?._requestHandlers;
56
+ if (!handlers) {
57
+ // SDK internals changed; fail closed (no-op) so we don't crash the
58
+ // server. The tools will still work but schemas will leak `$schema`.
59
+ return;
60
+ }
61
+ const original = handlers.get("tools/list");
62
+ if (!original) {
63
+ return;
64
+ }
65
+ handlers.set("tools/list", async (...callArgs) => {
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ const response = await original(...callArgs);
68
+ if (response && Array.isArray(response.tools)) {
69
+ response.tools = response.tools.map(stripToolSchemas);
70
+ }
71
+ return response;
72
+ });
73
+ }
74
+ function stripToolSchemas(tool) {
75
+ const next = { ...tool };
76
+ if (next.inputSchema) {
77
+ next.inputSchema = stripSchemaRecursively(next.inputSchema);
78
+ }
79
+ if (next.outputSchema) {
80
+ next.outputSchema = stripSchemaRecursively(next.outputSchema);
81
+ }
82
+ return next;
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "drawio-mcp-server",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "description": "Provides Draw.io services to MCP Clients",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
@@ -34,11 +34,11 @@
34
34
  "@hono/node-server": "1.19.13",
35
35
  "@modelcontextprotocol/sdk": "1.29.0",
36
36
  "cachedir": "2.4.0",
37
- "hono": "4.12.14",
37
+ "hono": "4.12.23",
38
38
  "nanoid": "5.1.6",
39
39
  "node-forge": "1.4.0",
40
40
  "unzipper": "0.12.3",
41
- "ws": "8.18.3",
41
+ "ws": "8.21.0",
42
42
  "zod": "4.2.1"
43
43
  },
44
44
  "devDependencies": {