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,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 () => {
|