@adeu/mcp-server 1.6.8 → 1.6.9

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.
@@ -0,0 +1,162 @@
1
+ // FILE: node/packages/mcp-server/src/mcp.bugs.test.ts
2
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
3
+ import { spawn, ChildProcess } from "node:child_process";
4
+ import { resolve, join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
7
+ import { DocumentObject, RedlineEngine } from "@adeu/core";
8
+
9
+ describe("Resolved Bugs MCP Server Verification", () => {
10
+ let serverProc: ChildProcess;
11
+ let cleanDocPath: string;
12
+ let dirtyDocPath: string;
13
+
14
+ beforeAll(async () => {
15
+ // 1. Grab the shared golden fixture from the monorepo root
16
+ const fixturePath = resolve(
17
+ __dirname,
18
+ "../../../../shared/fixtures/golden.docx",
19
+ );
20
+
21
+ cleanDocPath = join(tmpdir(), `adeu_clean_${Date.now()}.docx`);
22
+ dirtyDocPath = join(tmpdir(), `adeu_dirty_${Date.now()}.docx`);
23
+
24
+ // Save a clean copy
25
+ const fixtureBuf = readFileSync(fixturePath);
26
+ writeFileSync(cleanDocPath, fixtureBuf);
27
+
28
+ // Load it via the public API, dirty it, and save a dirty copy
29
+ const doc = await DocumentObject.load(fixtureBuf);
30
+ const engine = new RedlineEngine(doc, "Reviewer");
31
+
32
+ // "document" is original base text, so we won't trigger the cross-author nested redline constraint
33
+ engine.process_batch([
34
+ {
35
+ type: "modify",
36
+ target_text: "document",
37
+ new_text: "dirty modified document",
38
+ },
39
+ ]);
40
+ writeFileSync(dirtyDocPath, await doc.save());
41
+
42
+ // 2. Boot the compiled MCP server
43
+ const serverPath = resolve(__dirname, "../dist/index.js");
44
+ if (!existsSync(serverPath)) {
45
+ throw new Error(
46
+ "MCP server not built. Run 'npm run build' before tests.",
47
+ );
48
+ }
49
+
50
+ serverProc = spawn("node", [serverPath]);
51
+ });
52
+
53
+ afterAll(() => {
54
+ if (serverProc && !serverProc.killed) serverProc.kill();
55
+ if (existsSync(cleanDocPath)) unlinkSync(cleanDocPath);
56
+ if (existsSync(dirtyDocPath)) unlinkSync(dirtyDocPath);
57
+ });
58
+
59
+ // Helper to interact with the stdio JSON-RPC server
60
+ function sendRpc(method: string, params: any, id: number = 1): Promise<any> {
61
+ return new Promise((resolve, reject) => {
62
+ const timeout = setTimeout(() => reject(new Error("RPC Timeout")), 5000);
63
+
64
+ const listener = (data: Buffer) => {
65
+ const lines = data.toString().trim().split("\n");
66
+ for (const line of lines) {
67
+ if (!line.startsWith("{")) continue;
68
+ try {
69
+ const res = JSON.parse(line);
70
+ if (res.id === id) {
71
+ clearTimeout(timeout);
72
+ serverProc.stdout?.removeListener("data", listener);
73
+ resolve(res);
74
+ }
75
+ } catch (e) {
76
+ // Ignore incomplete chunks
77
+ }
78
+ }
79
+ };
80
+
81
+ serverProc.stdout?.on("data", listener);
82
+ serverProc.stdin?.write(
83
+ JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n",
84
+ );
85
+ });
86
+ }
87
+
88
+ it("BUG-5: Rejects empty changes array early without writing files", async () => {
89
+ const outPath = join(tmpdir(), `adeu_out_${Date.now()}.docx`);
90
+ if (existsSync(outPath)) unlinkSync(outPath);
91
+
92
+ const res = await sendRpc(
93
+ "tools/call",
94
+ {
95
+ name: "process_document_batch",
96
+ arguments: {
97
+ original_docx_path: cleanDocPath,
98
+ author_name: "Agent",
99
+ changes: [],
100
+ output_path: outPath,
101
+ },
102
+ },
103
+ 101,
104
+ );
105
+
106
+ expect(res.result.content[0].text).toBe("Error: No changes provided.");
107
+ expect(existsSync(outPath)).toBe(false); // Proves no-op
108
+ });
109
+
110
+ it("BUG-9: diff_docx_files tool respects compare_clean parameter", async () => {
111
+ // 1. compare_clean = true (Default) -> Should output clean text comparison
112
+ const resClean = await sendRpc(
113
+ "tools/call",
114
+ {
115
+ name: "diff_docx_files",
116
+ arguments: {
117
+ original_path: cleanDocPath,
118
+ modified_path: dirtyDocPath,
119
+ compare_clean: true,
120
+ },
121
+ },
122
+ 102,
123
+ );
124
+
125
+ const cleanText = resClean.result.content[0].text;
126
+ expect(cleanText).toContain("dirty modified");
127
+ expect(cleanText).not.toContain("{++"); // No CriticMarkup
128
+
129
+ // 2. compare_clean = false -> Should output raw CriticMarkup comparison
130
+ const resRaw = await sendRpc(
131
+ "tools/call",
132
+ {
133
+ name: "diff_docx_files",
134
+ arguments: {
135
+ original_path: cleanDocPath,
136
+ modified_path: dirtyDocPath,
137
+ compare_clean: false,
138
+ },
139
+ },
140
+ 103,
141
+ );
142
+
143
+ const rawText = resRaw.result.content[0].text;
144
+ expect(rawText).toContain("{++dirty modified ++}");
145
+ });
146
+ it("BUG-10: Traps ENOENT and returns clean File Not Found errors", async () => {
147
+ const res = await sendRpc(
148
+ "tools/call",
149
+ {
150
+ name: "read_docx",
151
+ arguments: { file_path: join(tmpdir(), "DEF_DOES_NOT_EXIST.docx") },
152
+ },
153
+ 104,
154
+ );
155
+
156
+ expect(res.result.isError).toBe(true);
157
+ expect(res.result.content[0].text).toContain(
158
+ "Error executing tool read_docx: File not found:",
159
+ );
160
+ expect(res.result.content[0].text).not.toContain("ENOENT"); // Raw node error must not leak
161
+ });
162
+ });