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.
Files changed (38) hide show
  1. package/README.md +21 -68
  2. package/build/assets/auto-refresh.js +21 -0
  3. package/build/assets/auto-refresh.test.js +54 -0
  4. package/build/assets/downloader.js +4 -0
  5. package/build/assets/version.js +38 -0
  6. package/build/assets/version.test.js +26 -0
  7. package/build/documents-changed-broadcast.test.js +123 -0
  8. package/build/drawio-compat/log-report.js +21 -0
  9. package/build/drawio-compat/log-report.test.js +43 -0
  10. package/build/drawio-compat/matrix.js +14 -0
  11. package/build/index.js +152 -11
  12. package/build/install/config-io.js +21 -0
  13. package/build/install/config-io.test.js +37 -0
  14. package/build/install/hosts/claude-code.js +37 -0
  15. package/build/install/hosts/claude-code.test.js +47 -0
  16. package/build/install/hosts/claude-desktop.js +45 -0
  17. package/build/install/hosts/claude-desktop.test.js +57 -0
  18. package/build/install/hosts/codex.js +171 -0
  19. package/build/install/hosts/codex.test.js +124 -0
  20. package/build/install/hosts/index.js +15 -0
  21. package/build/install/hosts/opencode.js +48 -0
  22. package/build/install/hosts/opencode.test.js +60 -0
  23. package/build/install/hosts/zed.js +37 -0
  24. package/build/install/hosts/zed.test.js +47 -0
  25. package/build/install/index.js +170 -0
  26. package/build/install/index.test.js +115 -0
  27. package/build/install/install.integration.test.js +103 -0
  28. package/build/install/types.js +1 -0
  29. package/build/multi-transport.test.js +1 -0
  30. package/build/plugin/mcp-plugin.js +406 -89
  31. package/build/real-environment/import-export.test.js +107 -0
  32. package/build/stdio-shutdown.test.js +177 -0
  33. package/build/tool-registry.test.js +57 -0
  34. package/build/tools/index.js +4 -0
  35. package/build/tools/save-document.js +5 -0
  36. package/build/tools/set-document-title.js +12 -0
  37. package/build/vendored/compat/index.js +35 -0
  38. package/package.json +15 -10
@@ -0,0 +1,115 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
2
+ import { parseArgs } from "./index.js";
3
+ import { spawnSync } from "node:child_process";
4
+ import { mkdtempSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join, dirname } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ describe("parseArgs", () => {
10
+ it("parses host + defaults", () => {
11
+ const opts = parseArgs(["codex"]);
12
+ expect(opts).toEqual({
13
+ host: "codex",
14
+ name: "drawio",
15
+ editor: true,
16
+ httpPort: 3000,
17
+ extraArgs: [],
18
+ env: {},
19
+ configPath: undefined,
20
+ configPathByHost: {},
21
+ print: false,
22
+ dryRun: false,
23
+ uninstall: false,
24
+ yes: false,
25
+ });
26
+ });
27
+ it("supports --no-editor, --name, --http-port, --extra-arg, --env, --config-path, --print, --dry-run, --uninstall, --yes", () => {
28
+ const opts = parseArgs([
29
+ "zed",
30
+ "--no-editor",
31
+ "--name",
32
+ "diagrams",
33
+ "--http-port",
34
+ "4000",
35
+ "--extra-arg",
36
+ "--transport",
37
+ "--extra-arg",
38
+ "http",
39
+ "--env",
40
+ "FOO=bar",
41
+ "--config-path",
42
+ "/tmp/z.json",
43
+ "--print",
44
+ "--dry-run",
45
+ "--uninstall",
46
+ "--yes",
47
+ ]);
48
+ expect(opts).toEqual({
49
+ host: "zed",
50
+ name: "diagrams",
51
+ editor: false,
52
+ httpPort: 4000,
53
+ extraArgs: ["--transport", "http"],
54
+ env: { FOO: "bar" },
55
+ configPath: "/tmp/z.json",
56
+ configPathByHost: {},
57
+ print: true,
58
+ dryRun: true,
59
+ uninstall: true,
60
+ yes: true,
61
+ });
62
+ });
63
+ it("throws on missing host", () => {
64
+ expect(() => parseArgs([])).toThrow(/host required/);
65
+ });
66
+ it("throws on unknown flag", () => {
67
+ expect(() => parseArgs(["codex", "--nope"])).toThrow(/unknown option: --nope/);
68
+ });
69
+ });
70
+ describe("dispatch (smoke)", () => {
71
+ it("`install` before all other work exits 0 on --print without booting transport", () => {
72
+ const bin = join(__dirname, "..", "..", "build", "index.js");
73
+ const result = spawnSync("node", [
74
+ bin,
75
+ "install",
76
+ "codex",
77
+ "--print",
78
+ "--config-path",
79
+ "/tmp/nonexistent-drawio-install-test.toml",
80
+ ], {
81
+ encoding: "utf8",
82
+ stdio: ["ignore", "pipe", "pipe"],
83
+ });
84
+ expect(result.stdout).toContain("[mcp_servers.drawio]");
85
+ expect(result.status).toBe(0);
86
+ });
87
+ });
88
+ describe("runInstall (codex + --print)", () => {
89
+ let dir;
90
+ beforeEach(() => {
91
+ dir = mkdtempSync(join(tmpdir(), "drawio-install-"));
92
+ });
93
+ afterEach(() => {
94
+ rmSync(dir, { recursive: true, force: true });
95
+ });
96
+ it("prints codex TOML to stdout without touching disk", async () => {
97
+ const target = join(dir, "config.toml");
98
+ const chunks = [];
99
+ const orig = process.stdout.write.bind(process.stdout);
100
+ process.stdout.write = (s) => {
101
+ chunks.push(String(s));
102
+ return true;
103
+ };
104
+ const { runInstall } = await import("./index.js");
105
+ const code = await runInstall([
106
+ "codex",
107
+ "--print",
108
+ "--config-path",
109
+ target,
110
+ ]);
111
+ process.stdout.write = orig;
112
+ expect(code).toBe(0);
113
+ expect(chunks.join("")).toContain("[mcp_servers.drawio]");
114
+ });
115
+ });
@@ -0,0 +1,103 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "@jest/globals";
2
+ import { mkdtempSync, rmSync, existsSync, readFileSync, writeFileSync, } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { spawnSync } from "node:child_process";
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ const BIN = join(__dirname, "..", "..", "build", "index.js");
9
+ // Sandbox every adapter's defaultPaths() resolution so a spawned `install`
10
+ // process can never resolve to the real developer's home directory.
11
+ // homedir() reads HOME on POSIX and USERPROFILE/HOMEDRIVE+HOMEPATH on
12
+ // Windows; claude-desktop's resolver reads APPDATA directly on win32.
13
+ function isolateEnv(homeDir) {
14
+ const env = { ...process.env };
15
+ env.HOME = homeDir;
16
+ env.USERPROFILE = homeDir;
17
+ env.APPDATA = join(homeDir, "AppData", "Roaming");
18
+ env.LOCALAPPDATA = join(homeDir, "AppData", "Local");
19
+ env.HOMEDRIVE = "C:"; // benign default; ignored on POSIX
20
+ env.HOMEPATH = homeDir;
21
+ delete env.XDG_CONFIG_HOME;
22
+ delete env.XDG_DATA_HOME;
23
+ delete env.XDG_STATE_HOME;
24
+ delete env.XDG_CACHE_HOME;
25
+ return env;
26
+ }
27
+ describe("install subcommand — end to end", () => {
28
+ let dir;
29
+ let home;
30
+ beforeEach(() => {
31
+ dir = mkdtempSync(join(tmpdir(), "drawio-e2e-"));
32
+ home = mkdtempSync(join(tmpdir(), "drawio-home-"));
33
+ });
34
+ afterEach(() => {
35
+ rmSync(dir, { recursive: true, force: true });
36
+ rmSync(home, { recursive: true, force: true });
37
+ });
38
+ function run(args) {
39
+ return spawnSync("node", [BIN, "install", ...args], {
40
+ encoding: "utf8",
41
+ env: isolateEnv(home),
42
+ cwd: home,
43
+ });
44
+ }
45
+ it("codex --print emits TOML on stdout, no file written", () => {
46
+ const target = join(dir, "codex.toml");
47
+ const r = run(["codex", "--print", "--config-path", target]);
48
+ expect(r.status).toBe(0);
49
+ expect(r.stdout).toContain("[mcp_servers.drawio]");
50
+ expect(existsSync(target)).toBe(false);
51
+ });
52
+ it("zed --print emits JSON", () => {
53
+ const target = join(dir, "zed.json");
54
+ const r = run(["zed", "--print", "--config-path", target]);
55
+ expect(r.status).toBe(0);
56
+ expect(r.stdout).toContain('"context_servers"');
57
+ });
58
+ it("codex writes to target when --yes passed", () => {
59
+ const target = join(dir, "codex.toml");
60
+ const r = run(["codex", "--yes", "--config-path", target]);
61
+ expect(r.status).toBe(0);
62
+ expect(existsSync(target)).toBe(true);
63
+ expect(readFileSync(target, "utf8")).toContain("[mcp_servers.drawio]");
64
+ });
65
+ it("codex refuses to overwrite non-empty target without --yes and returns exit 4", () => {
66
+ const target = join(dir, "codex.toml");
67
+ const first = run(["codex", "--yes", "--config-path", target]);
68
+ expect(first.status).toBe(0);
69
+ const modified = readFileSync(target, "utf8").replace("drawio-mcp-server", "drawio-mcp-server-old");
70
+ writeFileSync(target, modified);
71
+ const second = run(["codex", "--config-path", target]);
72
+ expect(second.status).toBe(4);
73
+ expect(second.stderr).toContain("Re-run with --yes");
74
+ });
75
+ it("all with per-host config paths writes to each, and never touches unoverridden adapters' real paths", () => {
76
+ const codexPath = join(dir, "codex.toml");
77
+ const zedPath = join(dir, "zed.json");
78
+ const r = run([
79
+ "all",
80
+ "--yes",
81
+ "--config-path",
82
+ `codex=${codexPath}`,
83
+ "--config-path",
84
+ `zed=${zedPath}`,
85
+ ]);
86
+ expect(r.status).toBe(0);
87
+ expect(existsSync(codexPath)).toBe(true);
88
+ expect(existsSync(zedPath)).toBe(true);
89
+ // opencode, claude-desktop, and claude-code got no --config-path
90
+ // override. With HOME sandboxed to a fresh temp dir, their
91
+ // defaultPaths() resolve to non-existent files, so detect() reports
92
+ // "absent" and applyAll() skips them entirely — proving isolation
93
+ // rather than just asserting the explicit overrides worked.
94
+ expect(existsSync(join(home, ".claude.json"))).toBe(false);
95
+ expect(existsSync(join(home, ".config", "Claude", "claude_desktop_config.json"))).toBe(false);
96
+ expect(existsSync(join(home, ".config", "opencode", "opencode.json"))).toBe(false);
97
+ });
98
+ it("returns exit 1 on unknown host", () => {
99
+ const r = run(["bogus"]);
100
+ expect(r.status).toBe(1);
101
+ expect(r.stderr).toContain("unknown host");
102
+ });
103
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -138,6 +138,7 @@ describe("HTTP transport (stateless per-request)", () => {
138
138
  await client.connect(transport);
139
139
  const tools = await client.listTools();
140
140
  expect(tools.tools.length).toBeGreaterThan(0);
141
+ expect(tools.tools.map((tool) => tool.name)).toEqual(expect.arrayContaining(["set-document-title", "save-document"]));
141
142
  await client.close();
142
143
  });
143
144
  it("handles multiple sequential HTTP client requests without reuse error", async () => {