drawio-mcp-server 2.1.1 → 2.3.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 +21 -68
- package/build/assets/auto-refresh.js +21 -0
- package/build/assets/auto-refresh.test.js +54 -0
- package/build/assets/downloader.js +4 -0
- package/build/assets/version.js +38 -0
- package/build/assets/version.test.js +26 -0
- package/build/documents-changed-broadcast.test.js +123 -0
- package/build/drawio-compat/log-report.js +21 -0
- package/build/drawio-compat/log-report.test.js +43 -0
- package/build/drawio-compat/matrix.js +14 -0
- package/build/index.js +152 -11
- package/build/install/config-io.js +21 -0
- package/build/install/config-io.test.js +37 -0
- package/build/install/hosts/claude-code.js +37 -0
- package/build/install/hosts/claude-code.test.js +47 -0
- package/build/install/hosts/claude-desktop.js +45 -0
- package/build/install/hosts/claude-desktop.test.js +57 -0
- package/build/install/hosts/codex.js +171 -0
- package/build/install/hosts/codex.test.js +124 -0
- package/build/install/hosts/index.js +15 -0
- package/build/install/hosts/opencode.js +48 -0
- package/build/install/hosts/opencode.test.js +60 -0
- package/build/install/hosts/zed.js +37 -0
- package/build/install/hosts/zed.test.js +47 -0
- package/build/install/index.js +170 -0
- package/build/install/index.test.js +115 -0
- package/build/install/install.integration.test.js +103 -0
- package/build/install/types.js +1 -0
- package/build/multi-transport.test.js +1 -0
- package/build/plugin/mcp-plugin.js +406 -89
- package/build/real-environment/import-export.test.js +107 -0
- package/build/stdio-shutdown.test.js +177 -0
- package/build/tool-registry.test.js +57 -0
- package/build/tools/index.js +4 -0
- package/build/tools/save-document.js +5 -0
- package/build/tools/set-document-title.js +12 -0
- package/build/vendored/compat/index.js +35 -0
- package/package.json +15 -10
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "@jest/globals";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { createServer } from "node:net";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import WebSocket from "ws";
|
|
7
|
+
async function getFreePort() {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const srv = createServer();
|
|
10
|
+
srv.once("error", reject);
|
|
11
|
+
srv.listen(0, "127.0.0.1", () => {
|
|
12
|
+
const addr = srv.address();
|
|
13
|
+
if (addr && typeof addr === "object") {
|
|
14
|
+
const port = addr.port;
|
|
15
|
+
srv.close(() => resolve(port));
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
srv.close(() => reject(new Error("could not resolve free port")));
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function isPortFree(port, host = "127.0.0.1") {
|
|
24
|
+
return new Promise((resolve) => {
|
|
25
|
+
const srv = createServer();
|
|
26
|
+
srv.once("error", () => resolve(false));
|
|
27
|
+
srv.listen({ port, host }, () => {
|
|
28
|
+
srv.close(() => resolve(true));
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function waitForPortHeld(port, host, timeoutMs) {
|
|
33
|
+
const deadline = Date.now() + timeoutMs;
|
|
34
|
+
while (Date.now() < deadline) {
|
|
35
|
+
const free = await isPortFree(port, host);
|
|
36
|
+
if (!free)
|
|
37
|
+
return true;
|
|
38
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
async function waitForExit(proc, timeoutMs) {
|
|
43
|
+
return new Promise((resolve) => {
|
|
44
|
+
const timer = setTimeout(() => resolve(null), timeoutMs);
|
|
45
|
+
proc.once("exit", (code) => {
|
|
46
|
+
clearTimeout(timer);
|
|
47
|
+
resolve(code ?? 0);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
52
|
+
const SERVER_BIN = join(here, "index.js");
|
|
53
|
+
const HOST = "127.0.0.1";
|
|
54
|
+
function spawnServer(extensionPort, httpPort, transport = "stdio") {
|
|
55
|
+
return spawn(process.execPath, [
|
|
56
|
+
SERVER_BIN,
|
|
57
|
+
"--transport",
|
|
58
|
+
transport,
|
|
59
|
+
"--extension-port",
|
|
60
|
+
String(extensionPort),
|
|
61
|
+
"--http-port",
|
|
62
|
+
String(httpPort),
|
|
63
|
+
"--host",
|
|
64
|
+
HOST,
|
|
65
|
+
], { stdio: ["pipe", "pipe", "pipe"] });
|
|
66
|
+
}
|
|
67
|
+
describe("graceful shutdown releases WebSocket port", () => {
|
|
68
|
+
let proc;
|
|
69
|
+
afterEach(async () => {
|
|
70
|
+
if (proc && !proc.killed) {
|
|
71
|
+
proc.kill("SIGKILL");
|
|
72
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
73
|
+
}
|
|
74
|
+
proc = undefined;
|
|
75
|
+
});
|
|
76
|
+
it("releases extension port after SIGTERM", async () => {
|
|
77
|
+
const extensionPort = await getFreePort();
|
|
78
|
+
const httpPort = await getFreePort();
|
|
79
|
+
proc = spawnServer(extensionPort, httpPort);
|
|
80
|
+
const portTaken = await waitForPortHeld(extensionPort, HOST, 5000);
|
|
81
|
+
expect(portTaken).toBe(true);
|
|
82
|
+
proc.kill("SIGTERM");
|
|
83
|
+
const exitCode = await waitForExit(proc, 5000);
|
|
84
|
+
expect(exitCode).not.toBeNull();
|
|
85
|
+
const free = await isPortFree(extensionPort, HOST);
|
|
86
|
+
expect(free).toBe(true);
|
|
87
|
+
}, 20000);
|
|
88
|
+
it("releases extension port after SIGINT", async () => {
|
|
89
|
+
const extensionPort = await getFreePort();
|
|
90
|
+
const httpPort = await getFreePort();
|
|
91
|
+
proc = spawnServer(extensionPort, httpPort);
|
|
92
|
+
const portTaken = await waitForPortHeld(extensionPort, HOST, 5000);
|
|
93
|
+
expect(portTaken).toBe(true);
|
|
94
|
+
proc.kill("SIGINT");
|
|
95
|
+
const exitCode = await waitForExit(proc, 5000);
|
|
96
|
+
expect(exitCode).not.toBeNull();
|
|
97
|
+
const free = await isPortFree(extensionPort, HOST);
|
|
98
|
+
expect(free).toBe(true);
|
|
99
|
+
}, 20000);
|
|
100
|
+
it("releases HTTP and extension ports after SIGTERM in http transport", async () => {
|
|
101
|
+
const extensionPort = await getFreePort();
|
|
102
|
+
const httpPort = await getFreePort();
|
|
103
|
+
proc = spawnServer(extensionPort, httpPort, "http");
|
|
104
|
+
const extTaken = await waitForPortHeld(extensionPort, HOST, 5000);
|
|
105
|
+
const httpTaken = await waitForPortHeld(httpPort, HOST, 5000);
|
|
106
|
+
expect(extTaken).toBe(true);
|
|
107
|
+
expect(httpTaken).toBe(true);
|
|
108
|
+
proc.kill("SIGTERM");
|
|
109
|
+
const exitCode = await waitForExit(proc, 5000);
|
|
110
|
+
expect(exitCode).not.toBeNull();
|
|
111
|
+
expect(await isPortFree(extensionPort, HOST)).toBe(true);
|
|
112
|
+
expect(await isPortFree(httpPort, HOST)).toBe(true);
|
|
113
|
+
}, 20000);
|
|
114
|
+
it("releases extension port after SIGINT with a live WebSocket client", async () => {
|
|
115
|
+
const extensionPort = await getFreePort();
|
|
116
|
+
const httpPort = await getFreePort();
|
|
117
|
+
proc = spawnServer(extensionPort, httpPort);
|
|
118
|
+
const portTaken = await waitForPortHeld(extensionPort, HOST, 5000);
|
|
119
|
+
expect(portTaken).toBe(true);
|
|
120
|
+
const ws = new WebSocket(`ws://${HOST}:${extensionPort}`);
|
|
121
|
+
await new Promise((resolve, reject) => {
|
|
122
|
+
ws.once("open", () => resolve());
|
|
123
|
+
ws.once("error", reject);
|
|
124
|
+
});
|
|
125
|
+
proc.kill("SIGINT");
|
|
126
|
+
const exitCode = await waitForExit(proc, 5000);
|
|
127
|
+
expect(exitCode).not.toBeNull();
|
|
128
|
+
try {
|
|
129
|
+
ws.terminate();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
// ignore
|
|
133
|
+
}
|
|
134
|
+
const free = await isPortFree(extensionPort, HOST);
|
|
135
|
+
expect(free).toBe(true);
|
|
136
|
+
}, 20000);
|
|
137
|
+
it("keeps the HTTP port free in stdio-only mode", async () => {
|
|
138
|
+
const extensionPort = await getFreePort();
|
|
139
|
+
const httpPort = await getFreePort();
|
|
140
|
+
proc = spawnServer(extensionPort, httpPort);
|
|
141
|
+
const portTaken = await waitForPortHeld(extensionPort, HOST, 5000);
|
|
142
|
+
expect(portTaken).toBe(true);
|
|
143
|
+
// Give any (wrongly) eager HTTP listener time to bind before asserting.
|
|
144
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
145
|
+
expect(await isPortFree(httpPort, HOST)).toBe(true);
|
|
146
|
+
}, 20000);
|
|
147
|
+
it("exits fatally when the HTTP port cannot be bound in http transport", async () => {
|
|
148
|
+
const extensionPort = await getFreePort();
|
|
149
|
+
// Occupy the HTTP port so the server's bind attempt fails.
|
|
150
|
+
const blocker = createServer();
|
|
151
|
+
await new Promise((resolve) => {
|
|
152
|
+
blocker.listen({ port: 0, host: HOST }, () => resolve());
|
|
153
|
+
});
|
|
154
|
+
const takenPort = blocker.address().port;
|
|
155
|
+
proc = spawnServer(extensionPort, takenPort, "http");
|
|
156
|
+
let stderr = "";
|
|
157
|
+
proc.stderr.on("data", (chunk) => {
|
|
158
|
+
stderr += chunk.toString();
|
|
159
|
+
});
|
|
160
|
+
const exitCode = await waitForExit(proc, 10000);
|
|
161
|
+
expect(exitCode).toBe(1);
|
|
162
|
+
expect(stderr).toContain("Failed to bind HTTP server");
|
|
163
|
+
blocker.close();
|
|
164
|
+
}, 20000);
|
|
165
|
+
it("releases extension port when stdio host closes stdin pipe", async () => {
|
|
166
|
+
const extensionPort = await getFreePort();
|
|
167
|
+
const httpPort = await getFreePort();
|
|
168
|
+
proc = spawnServer(extensionPort, httpPort);
|
|
169
|
+
const portTaken = await waitForPortHeld(extensionPort, HOST, 5000);
|
|
170
|
+
expect(portTaken).toBe(true);
|
|
171
|
+
proc.stdin.end();
|
|
172
|
+
const exitCode = await waitForExit(proc, 5000);
|
|
173
|
+
expect(exitCode).not.toBeNull();
|
|
174
|
+
const free = await isPortFree(extensionPort, HOST);
|
|
175
|
+
expect(free).toBe(true);
|
|
176
|
+
}, 20000);
|
|
177
|
+
});
|
|
@@ -20,6 +20,8 @@ describe("shared tool registry", () => {
|
|
|
20
20
|
"create-page",
|
|
21
21
|
"copy-page",
|
|
22
22
|
"rename-page",
|
|
23
|
+
"set-document-title",
|
|
24
|
+
"save-document",
|
|
23
25
|
"import-mermaid",
|
|
24
26
|
]));
|
|
25
27
|
});
|
|
@@ -38,6 +40,9 @@ describe("shared tool registry", () => {
|
|
|
38
40
|
expect(registry.get("list-paged-model")?.params.has("target_document")).toBe(true);
|
|
39
41
|
expect(registry.get("rename-page")?.params.has("page")).toBe(true);
|
|
40
42
|
expect(registry.get("rename-page")?.params.has("target_document")).toBe(true);
|
|
43
|
+
expect(registry.get("set-document-title")?.params.has("title")).toBe(true);
|
|
44
|
+
expect(registry.get("set-document-title")?.params.has("target_document")).toBe(true);
|
|
45
|
+
expect(registry.get("save-document")?.params.has("target_document")).toBe(true);
|
|
41
46
|
expect(registry.get("copy-page")?.params.has("page")).toBe(true);
|
|
42
47
|
expect(registry.get("copy-page")?.params.has("target_document")).toBe(true);
|
|
43
48
|
expect(registry.get("import-mermaid")?.params.has("target_page")).toBe(true);
|
|
@@ -45,6 +50,58 @@ describe("shared tool registry", () => {
|
|
|
45
50
|
expect(registry.get("get-shape-by-name")?.params.has("target_page")).toBe(false);
|
|
46
51
|
expect(registry.get("get-shape-by-name")?.params.has("target_document")).toBe(true);
|
|
47
52
|
});
|
|
53
|
+
it("renames the current document while preserving its file extension", async () => {
|
|
54
|
+
await activateDocument();
|
|
55
|
+
const toolDefinitions = await loadToolDefinitions();
|
|
56
|
+
const registry = new Map(toolDefinitions.map((definition) => [definition.name, definition]));
|
|
57
|
+
const handler = registry.get("set-document-title")?.handler;
|
|
58
|
+
expect(handler).toBeDefined();
|
|
59
|
+
let title = "Untitled Diagram.drawio";
|
|
60
|
+
const rename = jest.fn((nextTitle, success, _error) => {
|
|
61
|
+
title = nextTitle;
|
|
62
|
+
success();
|
|
63
|
+
});
|
|
64
|
+
const ui = {
|
|
65
|
+
getCurrentFile: jest.fn(() => ({
|
|
66
|
+
getTitle: () => title,
|
|
67
|
+
rename,
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
await expect(handler?.(ui, {
|
|
71
|
+
target_document: { id: "doc-1" },
|
|
72
|
+
title: "Architecture",
|
|
73
|
+
})).resolves.toEqual({
|
|
74
|
+
previous_title: "Untitled Diagram.drawio",
|
|
75
|
+
title: "Architecture.drawio",
|
|
76
|
+
});
|
|
77
|
+
expect(rename).toHaveBeenCalledWith("Architecture.drawio", expect.any(Function), expect.any(Function));
|
|
78
|
+
});
|
|
79
|
+
it("triggers the editor's existing save action", async () => {
|
|
80
|
+
await activateDocument();
|
|
81
|
+
const toolDefinitions = await loadToolDefinitions();
|
|
82
|
+
const registry = new Map(toolDefinitions.map((definition) => [definition.name, definition]));
|
|
83
|
+
const handler = registry.get("save-document")?.handler;
|
|
84
|
+
expect(handler).toBeDefined();
|
|
85
|
+
const save = jest.fn();
|
|
86
|
+
const ui = {
|
|
87
|
+
actions: {
|
|
88
|
+
get: jest.fn((name) => name === "save" ? { funct: save } : null),
|
|
89
|
+
},
|
|
90
|
+
getCurrentFile: jest.fn(() => ({
|
|
91
|
+
getTitle: () => "Architecture.drawio",
|
|
92
|
+
getMode: () => "device",
|
|
93
|
+
})),
|
|
94
|
+
};
|
|
95
|
+
expect(handler?.(ui, {
|
|
96
|
+
target_document: { id: "doc-1" },
|
|
97
|
+
})).toEqual({
|
|
98
|
+
triggered: true,
|
|
99
|
+
title: "Architecture.drawio",
|
|
100
|
+
mode: "device",
|
|
101
|
+
});
|
|
102
|
+
expect(ui.actions.get).toHaveBeenCalledWith("save");
|
|
103
|
+
expect(save).toHaveBeenCalledTimes(1);
|
|
104
|
+
});
|
|
48
105
|
it("classifies background-safe and UI-bound page tools in the shared registry", async () => {
|
|
49
106
|
const toolDefinitions = await loadToolDefinitions();
|
|
50
107
|
const registry = new Map(toolDefinitions.map((definition) => [definition.name, definition]));
|
package/build/tools/index.js
CHANGED
|
@@ -22,10 +22,12 @@ import { registerListPagesTool } from "./list-pages.js";
|
|
|
22
22
|
import { registerListPagedModelTool } from "./list-paged-model.js";
|
|
23
23
|
import { registerMoveCellToLayerTool } from "./move-cell-to-layer.js";
|
|
24
24
|
import { registerRenamePageTool } from "./rename-page.js";
|
|
25
|
+
import { registerSaveDocumentTool } from "./save-document.js";
|
|
25
26
|
import { registerSetActiveLayerTool } from "./set-active-layer.js";
|
|
26
27
|
import { registerSetCellDataTool } from "./set-cell-data.js";
|
|
27
28
|
import { registerSetCellParentTool } from "./set-cell-parent.js";
|
|
28
29
|
import { registerSetCellShapeTool } from "./set-cell-shape.js";
|
|
30
|
+
import { registerSetDocumentTitleTool } from "./set-document-title.js";
|
|
29
31
|
const registrars = [
|
|
30
32
|
registerGetSelectedCellTool,
|
|
31
33
|
registerAddRectangleTool,
|
|
@@ -55,6 +57,8 @@ const registrars = [
|
|
|
55
57
|
registerCreatePageTool,
|
|
56
58
|
registerCopyPageTool,
|
|
57
59
|
registerRenamePageTool,
|
|
60
|
+
registerSetDocumentTitleTool,
|
|
61
|
+
registerSaveDocumentTool,
|
|
58
62
|
];
|
|
59
63
|
export function registerTools(...args) {
|
|
60
64
|
for (const register of registrars) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { default_tool } from "../tool.js";
|
|
2
|
+
export const TOOL_save_document = "save-document";
|
|
3
|
+
export const registerSaveDocumentTool = (server, context) => {
|
|
4
|
+
server.tool(TOOL_save_document, "Triggers Draw.io's existing File > Save action for the current document. The active storage provider may display an authentication, conflict, or Save As prompt that requires user interaction.", {}, default_tool(TOOL_save_document, context, { queue: true }));
|
|
5
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { default_tool } from "../tool.js";
|
|
3
|
+
export const TOOL_set_document_title = "set-document-title";
|
|
4
|
+
export const registerSetDocumentTitleTool = (server, context) => {
|
|
5
|
+
server.tool(TOOL_set_document_title, "Renames the current Draw.io document through its active storage provider while preserving the existing file extension when one is present.", {
|
|
6
|
+
title: z
|
|
7
|
+
.string()
|
|
8
|
+
.trim()
|
|
9
|
+
.min(1)
|
|
10
|
+
.describe("New document title, with or without the existing extension"),
|
|
11
|
+
}, default_tool(TOOL_set_document_title, context, { queue: true }));
|
|
12
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const SEMVER_HEAD = /^(\d+)\.(\d+)\.(\d+)/;
|
|
2
|
+
export function parseVersion(raw) {
|
|
3
|
+
const m = SEMVER_HEAD.exec(raw);
|
|
4
|
+
if (!m)
|
|
5
|
+
return null;
|
|
6
|
+
return [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
7
|
+
}
|
|
8
|
+
export function compareVersion(a, b) {
|
|
9
|
+
for (let i = 0; i < 3; i++) {
|
|
10
|
+
if (a[i] < b[i])
|
|
11
|
+
return -1;
|
|
12
|
+
if (a[i] > b[i])
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
export function isBelowFloor(v, floor) {
|
|
18
|
+
const parsed = parseVersion(floor);
|
|
19
|
+
if (!parsed)
|
|
20
|
+
return false;
|
|
21
|
+
return compareVersion(v, parsed) < 0;
|
|
22
|
+
}
|
|
23
|
+
export function isInRange(v, r) {
|
|
24
|
+
const min = parseVersion(r.min);
|
|
25
|
+
if (!min)
|
|
26
|
+
return false;
|
|
27
|
+
if (compareVersion(v, min) < 0)
|
|
28
|
+
return false;
|
|
29
|
+
if (r.maxExclusive === null)
|
|
30
|
+
return true;
|
|
31
|
+
const max = parseVersion(r.maxExclusive);
|
|
32
|
+
if (!max)
|
|
33
|
+
return true;
|
|
34
|
+
return compareVersion(v, max) < 0;
|
|
35
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "drawio-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Provides Draw.io services to MCP Clients",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/index.js",
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
"url": "git+https://github.com/lgazo/drawio-mcp-server.git"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@hono/node-server": "1.19.
|
|
35
|
-
"@modelcontextprotocol/sdk": "1.
|
|
34
|
+
"@hono/node-server": "1.19.17",
|
|
35
|
+
"@modelcontextprotocol/sdk": "1.30.0",
|
|
36
36
|
"cachedir": "2.4.0",
|
|
37
|
-
"hono": "4.
|
|
38
|
-
"nanoid": "5.1.
|
|
37
|
+
"hono": "4.13.5",
|
|
38
|
+
"nanoid": "5.1.16",
|
|
39
39
|
"node-forge": "1.4.0",
|
|
40
40
|
"unzipper": "0.12.3",
|
|
41
41
|
"ws": "8.21.0",
|
|
@@ -45,30 +45,35 @@
|
|
|
45
45
|
"@biomejs/biome": "2.4.13",
|
|
46
46
|
"@jest/globals": "30.2.0",
|
|
47
47
|
"@playwright/test": "1.59.1",
|
|
48
|
+
"@types/diff": "8.0.0",
|
|
48
49
|
"@types/jest": "30.0.0",
|
|
49
50
|
"@types/node": "25.0.3",
|
|
50
51
|
"@types/node-forge": "1.3.11",
|
|
51
52
|
"@types/unzipper": "0.10.11",
|
|
52
53
|
"@types/ws": "8.18.1",
|
|
53
54
|
"concurrently": "9.1.2",
|
|
55
|
+
"diff": "8.0.4",
|
|
54
56
|
"esbuild": "0.25.12",
|
|
55
57
|
"globals": "16.5.0",
|
|
56
58
|
"jest": "30.2.0",
|
|
57
59
|
"jest-environment-node": "30.2.0",
|
|
60
|
+
"jsonc-parser": "3.3.1",
|
|
58
61
|
"prettier": "3.7.4",
|
|
59
62
|
"rimraf": "6.1.3",
|
|
63
|
+
"smol-toml": "1.8.0",
|
|
60
64
|
"ts-jest": "29.4.9",
|
|
61
65
|
"typescript": "5.9.3",
|
|
62
66
|
"drawio-mcp-dev-proxy": "1.0.0",
|
|
63
|
-
"drawio-mcp-plugin": "2.
|
|
67
|
+
"drawio-mcp-plugin": "2.3.0"
|
|
64
68
|
},
|
|
65
69
|
"scripts": {
|
|
66
|
-
"
|
|
70
|
+
"vendor:compat": "rimraf src/vendored/compat && mkdir -p src/vendored/compat && cp ../drawio-mcp-compat/src/index.ts src/vendored/compat/index.ts",
|
|
71
|
+
"build": "pnpm run vendor:compat && rimraf build && tsc && mkdir -p build/plugin && cp node_modules/drawio-mcp-plugin/dist/mcp-plugin.js build/plugin/mcp-plugin.js",
|
|
67
72
|
"ci": "pnpm install --frozen-lockfile",
|
|
68
|
-
"dev": "tsc --watch",
|
|
69
|
-
"dev:server": "concurrently --kill-others-on-fail -n tsc,node -c cyan,green \"tsc --watch --preserveWatchOutput\" \"node --watch --watch-path=build build/index.js\"",
|
|
73
|
+
"dev": "pnpm run vendor:compat && tsc --watch",
|
|
74
|
+
"dev:server": "pnpm run vendor:compat && concurrently --kill-others-on-fail -n tsc,node -c cyan,green \"tsc --watch --preserveWatchOutput\" \"node --watch --watch-path=build build/index.js\"",
|
|
70
75
|
"inspect": "HOST=0.0.0.0 pnpx @modelcontextprotocol/inspector --config ./inspector.json --server drawio",
|
|
71
|
-
"lint": "biome check src/ && tsc --noEmit",
|
|
76
|
+
"lint": "pnpm run vendor:compat && biome check src/ && tsc --noEmit",
|
|
72
77
|
"lint:fix": "biome check --write src/",
|
|
73
78
|
"prefetch-assets": "node build/prefetch-assets.js",
|
|
74
79
|
"format": "prettier --write \"**/*.ts\"",
|