drawio-mcp-server 1.8.0 → 2.0.4

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.
Files changed (49) hide show
  1. package/README.md +9 -5
  2. package/build/emitter_bus.js +2 -3
  3. package/build/index.js +243 -341
  4. package/build/multi-transport.test.js +160 -0
  5. package/build/plugin/mcp-plugin.js +853 -60
  6. package/build/prefetch-assets.js +11 -0
  7. package/build/real-environment/add-cell-of-shape.test.js +51 -0
  8. package/build/real-environment/add-edge.test.js +86 -0
  9. package/build/real-environment/assertions.js +18 -0
  10. package/build/real-environment/delete-cell-by-id.test.js +45 -0
  11. package/build/real-environment/edge-editing.test.js +70 -0
  12. package/build/real-environment/edit-cell.test.js +64 -0
  13. package/build/real-environment/export-diagram.test.js +473 -0
  14. package/build/real-environment/harness.js +176 -0
  15. package/build/real-environment/import-export.test.js +82 -0
  16. package/build/real-environment/layers-and-selection.test.js +70 -0
  17. package/build/real-environment/logger.js +25 -0
  18. package/build/real-environment/screenshot.js +46 -0
  19. package/build/real-environment/set-cell-parent.test.js +64 -0
  20. package/build/real-environment/shapes.test.js +153 -0
  21. package/build/real-environment/test-helpers.js +10 -0
  22. package/build/real-environment/tools.js +22 -0
  23. package/build/real-environment/types.js +1 -0
  24. package/build/tool.js +46 -0
  25. package/build/tools/add-cell-of-shape.js +42 -0
  26. package/build/tools/add-edge.js +33 -0
  27. package/build/tools/add-rectangle.js +41 -0
  28. package/build/tools/create-layer.js +8 -0
  29. package/build/tools/delete-cell-by-id.js +10 -0
  30. package/build/tools/edit-cell.js +28 -0
  31. package/build/tools/edit-edge.js +30 -0
  32. package/build/tools/export-diagram.js +96 -0
  33. package/build/tools/get-active-layer.js +5 -0
  34. package/build/tools/get-selected-cell.js +5 -0
  35. package/build/tools/get-shape-by-name.js +10 -0
  36. package/build/tools/get-shape-categories.js +5 -0
  37. package/build/tools/get-shapes-in-category.js +10 -0
  38. package/build/tools/import-diagram.js +22 -0
  39. package/build/tools/index.js +49 -0
  40. package/build/tools/list-layers.js +5 -0
  41. package/build/tools/list-paged-model.js +41 -0
  42. package/build/tools/move-cell-to-layer.js +11 -0
  43. package/build/tools/set-active-layer.js +8 -0
  44. package/build/tools/set-cell-data.js +14 -0
  45. package/build/tools/set-cell-parent.js +9 -0
  46. package/build/tools/set-cell-shape.js +13 -0
  47. package/build/tools/shared.js +7 -0
  48. package/build/tools/types.js +1 -0
  49. package/package.json +35 -33
@@ -0,0 +1,11 @@
1
+ import { ensureAssets } from "./assets/index.js";
2
+ async function main() {
3
+ const { assetRoot } = await ensureAssets({}, (message) => {
4
+ console.log(message);
5
+ });
6
+ console.log(`Assets ready at ${assetRoot}`);
7
+ }
8
+ main().catch((error) => {
9
+ console.error("Failed to prefetch draw.io assets", error);
10
+ process.exit(1);
11
+ });
@@ -0,0 +1,51 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
2
+ import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCells, resetDiagram, } from "./harness.js";
3
+ import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
4
+ import { callToolJson } from "./tools.js";
5
+ describe("real environment/add-cell-of-shape", () => {
6
+ let context;
7
+ beforeAll(async () => {
8
+ context = await createRealEnvironmentContext();
9
+ }, 180000);
10
+ afterAll(async () => {
11
+ await disposeRealEnvironmentContext(context);
12
+ });
13
+ it("adds a cell via MCP and verifies the live diagram state", async () => {
14
+ await resetDiagram(context);
15
+ context.browserMessages.length = 0;
16
+ const logCountBefore = context.logger.entries.length;
17
+ const beforeCells = await getCells(context.page);
18
+ const { payload } = await callToolJson(context, "add-cell-of-shape", {
19
+ shape_name: "rectangle",
20
+ text: "MCP rectangle",
21
+ x: 180,
22
+ y: 140,
23
+ width: 160,
24
+ height: 90,
25
+ style: "fillColor=#dae8fc;strokeColor=#6c8ebf;",
26
+ });
27
+ expect(payload.success).toBe(true);
28
+ expect(payload.result.id).toBeTruthy();
29
+ await context.page.waitForFunction((id) => {
30
+ const ui = window.ui;
31
+ const graph = ui?.editor?.graph;
32
+ const cell = graph?.getModel?.().getCell?.(id);
33
+ return Boolean(cell);
34
+ }, payload.result.id);
35
+ await withVerificationScreenshot(context, "add-cell-of-shape", "before-live-state-verification", async () => {
36
+ const afterCells = await getCells(context.page);
37
+ expect(afterCells).toHaveLength(beforeCells.length + 1);
38
+ const insertedCell = afterCells.find((cell) => cell.id === payload.result.id);
39
+ expect(insertedCell).toBeDefined();
40
+ expect(insertedCell?.value).toBe("MCP rectangle");
41
+ expect(insertedCell?.x).toBe(180);
42
+ expect(insertedCell?.y).toBe(140);
43
+ expect(insertedCell?.width).toBe(160);
44
+ expect(insertedCell?.height).toBe(90);
45
+ expect(insertedCell?.style).toContain("fillColor=#dae8fc");
46
+ expect(insertedCell?.style).toContain("strokeColor=#6c8ebf");
47
+ });
48
+ await expectNoBrowserErrors(context, "add-cell-of-shape");
49
+ await expectNoServerErrors(context, "add-cell-of-shape", logCountBefore);
50
+ }, 180000);
51
+ });
@@ -0,0 +1,86 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
2
+ import { createRealEnvironmentContext, disposeRealEnvironmentContext, resetDiagram, } from "./harness.js";
3
+ import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
4
+ import { callToolJson } from "./tools.js";
5
+ describe("real environment/add-edge", () => {
6
+ let context;
7
+ beforeAll(async () => {
8
+ context = await createRealEnvironmentContext();
9
+ }, 180000);
10
+ afterAll(async () => {
11
+ await disposeRealEnvironmentContext(context);
12
+ });
13
+ it("adds an edge through MCP and verifies the live diagram state", async () => {
14
+ await resetDiagram(context);
15
+ context.browserMessages.length = 0;
16
+ const logCountBefore = context.logger.entries.length;
17
+ const { payload: source } = await callToolJson(context, "add-cell-of-shape", {
18
+ shape_name: "rectangle",
19
+ text: "Source",
20
+ x: 100,
21
+ y: 140,
22
+ width: 120,
23
+ height: 70,
24
+ });
25
+ const { payload: target } = await callToolJson(context, "add-cell-of-shape", {
26
+ shape_name: "rectangle",
27
+ text: "Target",
28
+ x: 360,
29
+ y: 140,
30
+ width: 120,
31
+ height: 70,
32
+ });
33
+ expect(source.success).toBe(true);
34
+ expect(target.success).toBe(true);
35
+ const { payload } = await callToolJson(context, "add-edge", {
36
+ source_id: source.result.id,
37
+ target_id: target.result.id,
38
+ text: "connects to",
39
+ style: "endArrow=classic;strokeColor=#b85450;html=1;rounded=0;",
40
+ });
41
+ expect(payload.success).toBe(true);
42
+ expect(payload.result.id).toBeTruthy();
43
+ await context.page.waitForFunction(({ edgeId, sourceId, targetId, }) => {
44
+ const ui = window.ui;
45
+ const graph = ui?.editor?.graph;
46
+ const edge = graph?.getModel?.().getCell?.(edgeId);
47
+ return (edge &&
48
+ edge.edge === true &&
49
+ edge.source?.id === sourceId &&
50
+ edge.target?.id === targetId &&
51
+ edge.value === "connects to" &&
52
+ String(edge.style || "").includes("strokeColor=#b85450"));
53
+ }, {
54
+ edgeId: payload.result.id,
55
+ sourceId: source.result.id,
56
+ targetId: target.result.id,
57
+ });
58
+ await withVerificationScreenshot(context, "add-edge", "before-live-state-verification", async () => {
59
+ const edge = await context.page.evaluate((edgeId) => {
60
+ const ui = window.ui;
61
+ const graph = ui?.editor?.graph;
62
+ const cell = graph?.getModel?.().getCell?.(edgeId);
63
+ if (!cell) {
64
+ return null;
65
+ }
66
+ return {
67
+ id: String(cell.id),
68
+ edge: Boolean(cell.edge),
69
+ value: typeof cell.value === "string" ? cell.value : "",
70
+ style: String(cell.style || ""),
71
+ sourceId: cell.source?.id ? String(cell.source.id) : null,
72
+ targetId: cell.target?.id ? String(cell.target.id) : null,
73
+ };
74
+ }, payload.result.id);
75
+ expect(edge).toBeDefined();
76
+ expect(edge).not.toBeNull();
77
+ expect(edge?.edge).toBe(true);
78
+ expect(edge?.value).toBe("connects to");
79
+ expect(edge?.sourceId).toBe(source.result.id);
80
+ expect(edge?.targetId).toBe(target.result.id);
81
+ expect(edge?.style).toContain("strokeColor=#b85450");
82
+ });
83
+ await expectNoBrowserErrors(context, "add-edge");
84
+ await expectNoServerErrors(context, "add-edge", logCountBefore);
85
+ }, 180000);
86
+ });
@@ -0,0 +1,18 @@
1
+ import { expect } from "@jest/globals";
2
+ import { captureVerificationArtifact } from "./screenshot.js";
3
+ import { browserErrors } from "./harness.js";
4
+ export async function withVerificationScreenshot(context, testName, stepName, verify) {
5
+ await captureVerificationArtifact(context.artifactRunDir, context.page, testName, stepName);
6
+ return await verify();
7
+ }
8
+ export async function expectNoBrowserErrors(context, testName) {
9
+ return withVerificationScreenshot(context, testName, "before-browser-log-verification", () => {
10
+ expect(browserErrors(context)).toEqual([]);
11
+ });
12
+ }
13
+ export async function expectNoServerErrors(context, testName, logCountBefore) {
14
+ return withVerificationScreenshot(context, testName, "before-server-log-verification", () => {
15
+ const serverErrors = context.logger.errors().slice(logCountBefore);
16
+ expect(serverErrors).toEqual([]);
17
+ });
18
+ }
@@ -0,0 +1,45 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
2
+ import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCells, resetDiagram, } from "./harness.js";
3
+ import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
4
+ import { callToolJson } from "./tools.js";
5
+ describe("real environment/delete-cell-by-id", () => {
6
+ let context;
7
+ beforeAll(async () => {
8
+ context = await createRealEnvironmentContext();
9
+ }, 180000);
10
+ afterAll(async () => {
11
+ await disposeRealEnvironmentContext(context);
12
+ });
13
+ it("deletes a cell through MCP and verifies the live diagram state", async () => {
14
+ await resetDiagram(context);
15
+ context.browserMessages.length = 0;
16
+ const logCountBefore = context.logger.entries.length;
17
+ const { payload: createdPayload } = await callToolJson(context, "add-cell-of-shape", {
18
+ shape_name: "rectangle",
19
+ text: "Delete me",
20
+ x: 260,
21
+ y: 160,
22
+ width: 120,
23
+ height: 70,
24
+ });
25
+ expect(createdPayload.success).toBe(true);
26
+ const beforeDelete = await getCells(context.page);
27
+ expect(beforeDelete.some((cell) => cell.id === createdPayload.result.id)).toBe(true);
28
+ const { payload } = await callToolJson(context, "delete-cell-by-id", {
29
+ cell_id: createdPayload.result.id,
30
+ });
31
+ expect(payload.success).toBe(true);
32
+ await context.page.waitForFunction((id) => {
33
+ const ui = window.ui;
34
+ const graph = ui?.editor?.graph;
35
+ return !graph?.getModel?.().getCell?.(id);
36
+ }, createdPayload.result.id);
37
+ await withVerificationScreenshot(context, "delete-cell-by-id", "before-live-state-verification", async () => {
38
+ const afterDelete = await getCells(context.page);
39
+ expect(afterDelete.some((cell) => cell.id === createdPayload.result.id)).toBe(false);
40
+ expect(afterDelete).toHaveLength(beforeDelete.length - 1);
41
+ });
42
+ await expectNoBrowserErrors(context, "delete-cell-by-id");
43
+ await expectNoServerErrors(context, "delete-cell-by-id", logCountBefore);
44
+ }, 180000);
45
+ });
@@ -0,0 +1,70 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
2
+ import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCellById, resetDiagram, } from "./harness.js";
3
+ import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
4
+ import { callToolJson } from "./tools.js";
5
+ import { expectToolSuccess } from "./test-helpers.js";
6
+ describe("real environment/edge editing", () => {
7
+ let context;
8
+ beforeAll(async () => {
9
+ context = await createRealEnvironmentContext();
10
+ }, 180000);
11
+ afterAll(async () => {
12
+ await disposeRealEnvironmentContext(context);
13
+ });
14
+ it("covers edge editing with target change, style change, and waypoints", async () => {
15
+ await resetDiagram(context);
16
+ context.browserMessages.length = 0;
17
+ const logCountBefore = context.logger.entries.length;
18
+ const { payload: source } = await callToolJson(context, "add-rectangle", {
19
+ x: 120,
20
+ y: 110,
21
+ width: 150,
22
+ height: 90,
23
+ text: "Source",
24
+ });
25
+ const { payload: target } = await callToolJson(context, "add-rectangle", {
26
+ x: 360,
27
+ y: 110,
28
+ width: 150,
29
+ height: 90,
30
+ text: "Target",
31
+ });
32
+ const { payload: alternateTarget } = await callToolJson(context, "add-rectangle", {
33
+ x: 620,
34
+ y: 110,
35
+ width: 150,
36
+ height: 90,
37
+ text: "Alternate target",
38
+ });
39
+ expectToolSuccess(source);
40
+ expectToolSuccess(target);
41
+ expectToolSuccess(alternateTarget);
42
+ const { payload: initialEdge } = await callToolJson(context, "add-edge", {
43
+ source_id: source.result.id,
44
+ target_id: target.result.id,
45
+ text: "initial edge",
46
+ });
47
+ expectToolSuccess(initialEdge);
48
+ await callToolJson(context, "edit-edge", {
49
+ cell_id: initialEdge.result.id,
50
+ text: "edited edge",
51
+ target_id: alternateTarget.result.id,
52
+ style: "endArrow=classic;strokeColor=#9673a6;html=1;rounded=0;",
53
+ points: [{ x: 500, y: 70 }],
54
+ });
55
+ await withVerificationScreenshot(context, "edge-editing", "before-live-state-verification", async () => {
56
+ const edge = await getCellById(context.page, initialEdge.result.id);
57
+ expect(edge).not.toBeNull();
58
+ expect(edge?.edge).toBe(true);
59
+ expect(edge?.value).toBe("edited edge");
60
+ expect(edge?.sourceId).toBe(source.result.id);
61
+ expect(edge?.targetId).toBe(alternateTarget.result.id);
62
+ expect(edge?.style).toContain("strokeColor=#9673a6");
63
+ expect(edge?.points).toHaveLength(1);
64
+ expect(edge?.points[0]?.x).toBe(500);
65
+ expect(edge?.points[0]?.y).toBe(70);
66
+ });
67
+ await expectNoBrowserErrors(context, "edge-editing");
68
+ await expectNoServerErrors(context, "edge-editing", logCountBefore);
69
+ }, 180000);
70
+ });
@@ -0,0 +1,64 @@
1
+ import { afterAll, beforeAll, describe, expect, it } from "@jest/globals";
2
+ import { createRealEnvironmentContext, disposeRealEnvironmentContext, getCells, resetDiagram, } from "./harness.js";
3
+ import { expectNoBrowserErrors, expectNoServerErrors, withVerificationScreenshot, } from "./assertions.js";
4
+ import { callToolJson } from "./tools.js";
5
+ describe("real environment/edit-cell", () => {
6
+ let context;
7
+ beforeAll(async () => {
8
+ context = await createRealEnvironmentContext();
9
+ }, 180000);
10
+ afterAll(async () => {
11
+ await disposeRealEnvironmentContext(context);
12
+ });
13
+ it("edits an existing cell through MCP and verifies the live diagram state", async () => {
14
+ await resetDiagram(context);
15
+ context.browserMessages.length = 0;
16
+ const logCountBefore = context.logger.entries.length;
17
+ const { payload: created } = await callToolJson(context, "add-cell-of-shape", {
18
+ shape_name: "rectangle",
19
+ text: "Original",
20
+ x: 100,
21
+ y: 120,
22
+ width: 120,
23
+ height: 70,
24
+ style: "fillColor=#ffffff;strokeColor=#000000;",
25
+ });
26
+ expect(created.success).toBe(true);
27
+ const { payload } = await callToolJson(context, "edit-cell", {
28
+ cell_id: created.result.id,
29
+ text: "Edited via MCP",
30
+ x: 260,
31
+ y: 210,
32
+ width: 180,
33
+ height: 95,
34
+ style: "fillColor=#d5e8d4;strokeColor=#82b366;",
35
+ });
36
+ expect(payload.success).toBe(true);
37
+ await context.page.waitForFunction((id) => {
38
+ const ui = window.ui;
39
+ const graph = ui?.editor?.graph;
40
+ const cell = graph?.getModel?.().getCell?.(id);
41
+ return (cell &&
42
+ cell.value === "Edited via MCP" &&
43
+ cell.geometry?.x === 260 &&
44
+ cell.geometry?.y === 210 &&
45
+ cell.geometry?.width === 180 &&
46
+ cell.geometry?.height === 95 &&
47
+ String(cell.style || "").includes("fillColor=#d5e8d4"));
48
+ }, created.result.id);
49
+ await withVerificationScreenshot(context, "edit-cell", "before-live-state-verification", async () => {
50
+ const cells = await getCells(context.page);
51
+ const editedCell = cells.find((cell) => cell.id === created.result.id);
52
+ expect(editedCell).toBeDefined();
53
+ expect(editedCell?.value).toBe("Edited via MCP");
54
+ expect(editedCell?.x).toBe(260);
55
+ expect(editedCell?.y).toBe(210);
56
+ expect(editedCell?.width).toBe(180);
57
+ expect(editedCell?.height).toBe(95);
58
+ expect(editedCell?.style).toContain("fillColor=#d5e8d4");
59
+ expect(editedCell?.style).toContain("strokeColor=#82b366");
60
+ });
61
+ await expectNoBrowserErrors(context, "edit-cell");
62
+ await expectNoServerErrors(context, "edit-cell", logCountBefore);
63
+ }, 180000);
64
+ });