@adeu/mcp-server 1.15.0 → 1.15.2
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/dist/index.js +123 -56
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +217 -173
- package/src/mcp.bugs.test.ts +22 -1
- package/src/response-builders.ts +118 -50
- package/src/response_builders.test.ts +266 -0
package/dist/index.js
CHANGED
|
@@ -211,50 +211,100 @@ function build_search_response(text, search_query, search_regex, search_case_sen
|
|
|
211
211
|
} catch (e) {
|
|
212
212
|
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
213
213
|
}
|
|
214
|
-
const
|
|
214
|
+
const allMatches = Array.from(body.matchAll(regex));
|
|
215
|
+
const pag_res = paginate(body, "");
|
|
216
|
+
const page_offsets = pag_res.body_page_offsets;
|
|
217
|
+
const total_doc_pages = pag_res.total_pages;
|
|
218
|
+
let filter_doc_page = null;
|
|
219
|
+
if (page !== void 0 && page !== null) {
|
|
220
|
+
const pageStr = String(page).toLowerCase();
|
|
221
|
+
if (pageStr !== "all") {
|
|
222
|
+
const parsed = parseInt(pageStr, 10);
|
|
223
|
+
if (isNaN(parsed) || parsed < 1) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`Invalid page value: \`${page}\`. Pass a positive integer to restrict the search to that document page, omit \`page\` to search all pages, or pass \`page='all'\` explicitly.`
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (parsed > total_doc_pages) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Document page ${parsed} is out of range \u2014 the document has ${total_doc_pages} page(s). In search mode, \`page\` filters matches to a specific document page; omit it or pass \`page='all'\` to search the whole document.`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
filter_doc_page = parsed;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const pageOfOffset = (offset) => {
|
|
237
|
+
let p = 1;
|
|
238
|
+
for (let j = 0; j < page_offsets.length; j++) {
|
|
239
|
+
if (offset >= page_offsets[j]) p = j + 1;
|
|
240
|
+
else break;
|
|
241
|
+
}
|
|
242
|
+
return p;
|
|
243
|
+
};
|
|
244
|
+
const pagesWithHits = /* @__PURE__ */ new Set();
|
|
245
|
+
for (const m of allMatches) {
|
|
246
|
+
pagesWithHits.add(pageOfOffset(m.index));
|
|
247
|
+
}
|
|
248
|
+
const matches = filter_doc_page === null ? allMatches : allMatches.filter((m) => pageOfOffset(m.index) === filter_doc_page);
|
|
215
249
|
if (matches.length === 0) {
|
|
216
|
-
|
|
250
|
+
let body_msg;
|
|
251
|
+
if (filter_doc_page !== null) {
|
|
252
|
+
if (allMatches.length === 0) {
|
|
253
|
+
body_msg = `> **Search Results** \u2014 No matches found for query \`${search_query}\` in \`${basename(file_path)}\`.
|
|
254
|
+
|
|
255
|
+
Verify your search spelling, or try setting \`search_case_sensitive\` to false or enabling \`search_regex\` if you used pattern wildcards.`;
|
|
256
|
+
} else {
|
|
257
|
+
const hitPages = Array.from(pagesWithHits).sort((a, b) => a - b);
|
|
258
|
+
body_msg = `> **Search Results** \u2014 No matches for \`${search_query}\` on document page ${filter_doc_page}.
|
|
259
|
+
|
|
260
|
+
The query DOES appear elsewhere in the document (${allMatches.length} match${allMatches.length !== 1 ? "es" : ""} on page${hitPages.length !== 1 ? "s" : ""} ${hitPages.join(", ")}). Omit \`page\` or pass \`page='all'\` to see them.`;
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
body_msg = `> **Search Results** \u2014 No matches found for query \`${search_query}\` in \`${basename(file_path)}\`.
|
|
217
264
|
|
|
218
265
|
Verify your search spelling, or try setting \`search_case_sensitive\` to false or enabling \`search_regex\` if you used pattern wildcards.`;
|
|
266
|
+
}
|
|
219
267
|
const llm_content2 = `> **File Path:** \`${resolve(file_path)}\`
|
|
220
268
|
|
|
221
|
-
${
|
|
269
|
+
${body_msg}`;
|
|
222
270
|
return {
|
|
223
271
|
content: [{ type: "text", text: llm_content2 }],
|
|
224
272
|
structuredContent: {
|
|
225
|
-
markdown:
|
|
273
|
+
markdown: body_msg,
|
|
226
274
|
title: `Search: ${basename(file_path)}`,
|
|
227
275
|
file_path: resolve(file_path)
|
|
228
276
|
}
|
|
229
277
|
};
|
|
230
278
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
279
|
+
const ui_parts = [];
|
|
280
|
+
if (filter_doc_page !== null) {
|
|
281
|
+
ui_parts.push(
|
|
282
|
+
`> **Search Results** \u2014 Found ${matches.length} match${matches.length !== 1 ? "es" : ""} for \`${search_query}\` on document page ${filter_doc_page} of ${total_doc_pages} in \`${basename(file_path)}\`.`
|
|
283
|
+
);
|
|
284
|
+
const otherPages = Array.from(pagesWithHits).filter((p) => p !== filter_doc_page).sort((a, b) => a - b);
|
|
285
|
+
if (otherPages.length > 0) {
|
|
286
|
+
ui_parts.push(
|
|
287
|
+
`> Additional matches exist on page${otherPages.length !== 1 ? "s" : ""} ${otherPages.join(", ")} \u2014 omit \`page\` or pass \`page='all'\` to see them.`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
ui_parts.push(
|
|
292
|
+
`> **Search Results** \u2014 Found ${matches.length} match${matches.length !== 1 ? "es" : ""} for \`${search_query}\` in \`${basename(file_path)}\`.`
|
|
293
|
+
);
|
|
294
|
+
if (total_doc_pages > 1) {
|
|
295
|
+
const counts = /* @__PURE__ */ new Map();
|
|
296
|
+
for (const m of allMatches) {
|
|
297
|
+
const p = pageOfOffset(m.index);
|
|
298
|
+
counts.set(p, (counts.get(p) || 0) + 1);
|
|
299
|
+
}
|
|
300
|
+
const distribution = Array.from(counts.entries()).sort((a, b) => a[0] - b[0]).map(([p, n]) => `p${p}: ${n}`).join(", ");
|
|
301
|
+
ui_parts.push(
|
|
302
|
+
`> Distribution across ${total_doc_pages} document pages \u2014 ${distribution}. Pass \`page=N\` to filter to a specific document page.`
|
|
303
|
+
);
|
|
243
304
|
}
|
|
244
|
-
start_idx = (page_num - 1) * 10;
|
|
245
|
-
end_idx = Math.min(start_idx + 10, total_matches);
|
|
246
|
-
page_text = `${page_num} of ${total_pages}`;
|
|
247
|
-
}
|
|
248
|
-
const page_matches = matches.slice(start_idx, end_idx);
|
|
249
|
-
const ui_parts = [
|
|
250
|
-
`> **Search Results** \u2014 Found ${total_matches} matches for query \`${search_query}\` in \`${basename(file_path)}\`.`
|
|
251
|
-
];
|
|
252
|
-
if (total_pages > 1 && pageStr !== "all") {
|
|
253
|
-
const nextPage = parseInt(pageStr, 10) + 1;
|
|
254
|
-
ui_parts.push(`> Showing page ${page_text} (matches ${start_idx + 1}-${end_idx}). To see more matches, call \`read_docx\` with \`search_query='${search_query}'\`, \`search_regex=${search_regex ? "true" : "false"}\`, and \`page=${nextPage}\`.`);
|
|
255
305
|
}
|
|
256
306
|
const occurrences_map = {};
|
|
257
|
-
for (const m of
|
|
307
|
+
for (const m of allMatches) {
|
|
258
308
|
const matched_str = m[0];
|
|
259
309
|
occurrences_map[matched_str] = (occurrences_map[matched_str] || 0) + 1;
|
|
260
310
|
}
|
|
@@ -281,19 +331,12 @@ ${ui_markdown2}`;
|
|
|
281
331
|
}
|
|
282
332
|
return path.join(" > ");
|
|
283
333
|
}
|
|
284
|
-
let i =
|
|
285
|
-
for (const m of
|
|
334
|
+
let i = 1;
|
|
335
|
+
for (const m of matches) {
|
|
286
336
|
const matched_str = m[0];
|
|
287
337
|
const m_start = m.index;
|
|
288
338
|
const m_end = m_start + matched_str.length;
|
|
289
|
-
|
|
290
|
-
for (let j = 0; j < page_offsets.length; j++) {
|
|
291
|
-
if (m_start >= page_offsets[j]) {
|
|
292
|
-
p_num = j + 1;
|
|
293
|
-
} else {
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
339
|
+
const p_num = pageOfOffset(m_start);
|
|
297
340
|
const snippet_start = Math.max(0, m_start - 100);
|
|
298
341
|
const snippet_end = Math.min(body.length, m_end + 100);
|
|
299
342
|
const snippet = body.substring(snippet_start, m_start) + `**${matched_str}**` + body.substring(m_end, snippet_end);
|
|
@@ -306,7 +349,9 @@ ${ui_markdown2}`;
|
|
|
306
349
|
}
|
|
307
350
|
const count = occurrences_map[matched_str];
|
|
308
351
|
ui_parts.push(snippet_lines);
|
|
309
|
-
ui_parts.push(
|
|
352
|
+
ui_parts.push(
|
|
353
|
+
`*Occurrences:* This exact phrasing appears ${count} time${count !== 1 ? "s" : ""} in the document.`
|
|
354
|
+
);
|
|
310
355
|
i++;
|
|
311
356
|
}
|
|
312
357
|
const ui_markdown = ui_parts.join("\n\n");
|
|
@@ -1211,8 +1256,8 @@ var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use pa
|
|
|
1211
1256
|
var PROCESS_BATCH_COMMON_DESC = "Applies a batch of edits and review actions to a DOCX.\n\nAll changes evaluate against the ORIGINAL document state \u2014 do not chain dependent edits within one batch (e.g. rename X to Y, then modify Y). Apply the rename first, then send a second batch.\n\n";
|
|
1212
1257
|
var PROCESS_BATCH_OPERATIONS_DESC = "Each item in `changes` must specify a `type`:\n1. 'modify': Search-and-replace. `target_text` must uniquely match \u2014 include surrounding context if the phrase is ambiguous. `new_text` supports Markdown: '# Heading 1' through '###### Heading 6', '**bold**', '_italic_', and '\\n\\n' to split into multiple paragraphs. Empty `new_text` deletes. Do NOT write CriticMarkup tags ({++, {--, {>>) manually \u2014 use the `comment` parameter for comments.\n2. 'accept' / 'reject': Finalize or revert a tracked change by `target_id` (e.g. 'Chg:12').\n3. 'reply': Reply to a comment by `target_id` (e.g. 'Com:5') with `text`.\n4. 'insert_row' / 'delete_row': Table edits. Disk mode only \u2014 not supported on Live Word canvas.\n\nID VOLATILITY: 'Chg:N' and 'Com:N' shift between document states. Always call `read_docx` immediately before any accept/reject/reply \u2014 do not reuse IDs from earlier in the conversation.\n\n`author_name` is used for attribution on all tracked changes and comments, in both disk and Live Word modes.";
|
|
1213
1258
|
var DIFF_DOCX_DESC = "Compares two DOCX files and returns a unified diff of their text content. Useful for analyzing differences between versions before editing.";
|
|
1214
|
-
var gitSha = "
|
|
1215
|
-
var packageVersion = "1.15.
|
|
1259
|
+
var gitSha = "23b2879";
|
|
1260
|
+
var packageVersion = "1.15.2";
|
|
1216
1261
|
var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
|
|
1217
1262
|
var args = process.argv.slice(2);
|
|
1218
1263
|
var scopeIdx = args.indexOf("--scope");
|
|
@@ -1315,11 +1360,17 @@ registerAppTool(
|
|
|
1315
1360
|
mode: z.enum(["full", "outline", "appendix"]).default("full").describe(
|
|
1316
1361
|
"'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms."
|
|
1317
1362
|
),
|
|
1318
|
-
page: z.union([z.number(), z.
|
|
1319
|
-
|
|
1363
|
+
page: z.union([z.number(), z.string()]).optional().describe(
|
|
1364
|
+
"Without `search_query`: 1-indexed document page to display (defaults to 1). With `search_query`: restricts matches to that document page (defaults to searching all pages; pass `page='all'` to be explicit)."
|
|
1365
|
+
),
|
|
1366
|
+
outline_max_level: z.coerce.number().default(2).describe("For mode='outline' only: cap on heading depth."),
|
|
1320
1367
|
outline_verbose: z.boolean().default(false).describe("For mode='outline' only: includes metadata."),
|
|
1321
|
-
search_query: z.string().optional().describe(
|
|
1322
|
-
|
|
1368
|
+
search_query: z.string().optional().describe(
|
|
1369
|
+
"The substring or regex pattern to search for. When provided, filters results to matching paragraphs."
|
|
1370
|
+
),
|
|
1371
|
+
search_regex: z.boolean().default(false).describe(
|
|
1372
|
+
"Set to true to interpret search_query as a regular expression."
|
|
1373
|
+
),
|
|
1323
1374
|
search_case_sensitive: z.boolean().default(true).describe("Set to false to perform case-insensitive matching.")
|
|
1324
1375
|
}),
|
|
1325
1376
|
_meta: { ui: { resourceUri: MARKDOWN_UI_URI } }
|
|
@@ -1339,7 +1390,12 @@ registerAppTool(
|
|
|
1339
1390
|
const buf = readFileBytesOrThrow(file_path);
|
|
1340
1391
|
if (mode === "outline") {
|
|
1341
1392
|
const doc = await DocumentObject2.load(buf);
|
|
1342
|
-
const extract_res = _extractTextFromDoc(
|
|
1393
|
+
const extract_res = _extractTextFromDoc(
|
|
1394
|
+
doc,
|
|
1395
|
+
clean_view,
|
|
1396
|
+
true,
|
|
1397
|
+
true
|
|
1398
|
+
);
|
|
1343
1399
|
const res2 = build_outline_response(
|
|
1344
1400
|
doc,
|
|
1345
1401
|
extract_res.text,
|
|
@@ -1352,14 +1408,22 @@ registerAppTool(
|
|
|
1352
1408
|
}
|
|
1353
1409
|
const text = await extractTextFromBuffer(buf, clean_view);
|
|
1354
1410
|
if (search_query !== void 0 && search_query !== null) {
|
|
1355
|
-
const res2 = build_search_response(
|
|
1411
|
+
const res2 = build_search_response(
|
|
1412
|
+
text,
|
|
1413
|
+
search_query,
|
|
1414
|
+
search_regex,
|
|
1415
|
+
search_case_sensitive,
|
|
1416
|
+
page,
|
|
1417
|
+
file_path
|
|
1418
|
+
);
|
|
1356
1419
|
return res2;
|
|
1357
1420
|
}
|
|
1421
|
+
const resolvedPage = page === void 0 || page === null ? 1 : typeof page === "number" ? page : parseInt(String(page), 10) || 1;
|
|
1358
1422
|
if (mode === "appendix") {
|
|
1359
|
-
const res2 = build_appendix_response(text,
|
|
1423
|
+
const res2 = build_appendix_response(text, resolvedPage, file_path);
|
|
1360
1424
|
return res2;
|
|
1361
1425
|
}
|
|
1362
|
-
const res = build_paginated_response(text,
|
|
1426
|
+
const res = build_paginated_response(text, resolvedPage, file_path);
|
|
1363
1427
|
return res;
|
|
1364
1428
|
} catch (e) {
|
|
1365
1429
|
return {
|
|
@@ -1611,15 +1675,15 @@ if (!isDocxOnly) {
|
|
|
1611
1675
|
has_attachments: z.boolean().optional(),
|
|
1612
1676
|
attachment_name: z.string().optional(),
|
|
1613
1677
|
is_unread: z.boolean().optional(),
|
|
1614
|
-
days_ago: z.number().optional(),
|
|
1678
|
+
days_ago: z.coerce.number().optional(),
|
|
1615
1679
|
folder: z.enum(["inbox", "sent", "all"]).optional(),
|
|
1616
|
-
limit: z.number().default(10),
|
|
1617
|
-
offset: z.number().default(0),
|
|
1680
|
+
limit: z.coerce.number().default(10),
|
|
1681
|
+
offset: z.coerce.number().default(0),
|
|
1618
1682
|
email_id: z.string().optional(),
|
|
1619
1683
|
working_directory: z.string().optional(),
|
|
1620
1684
|
mailbox_address: z.string().optional().describe("Optional target mailbox email address to search within."),
|
|
1621
1685
|
task_id: z.string().optional().describe("If resuming a pending check, provide the task ID here."),
|
|
1622
|
-
max_attachment_size_mb: z.number().optional().describe(
|
|
1686
|
+
max_attachment_size_mb: z.coerce.number().optional().describe(
|
|
1623
1687
|
"Maximum attachment size in MB to download (default 10). Attachments larger than this are listed in the response but not downloaded. Raise this to fetch large files."
|
|
1624
1688
|
)
|
|
1625
1689
|
}),
|
|
@@ -1707,7 +1771,10 @@ function formatBatchResult(stats, outPath, dry_run) {
|
|
|
1707
1771
|
res = `Batch complete. Saved to: ${outPath}
|
|
1708
1772
|
`;
|
|
1709
1773
|
}
|
|
1710
|
-
const total_occurrences = stats.edits ? stats.edits.reduce(
|
|
1774
|
+
const total_occurrences = stats.edits ? stats.edits.reduce(
|
|
1775
|
+
(acc, e) => acc + (e.status === "applied" ? e.occurrences_modified || 1 : 0),
|
|
1776
|
+
0
|
|
1777
|
+
) : 0;
|
|
1711
1778
|
const occ_text = total_occurrences > stats.edits_applied ? ` (${total_occurrences} occurrences)` : "";
|
|
1712
1779
|
res += `Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.
|
|
1713
1780
|
`;
|
|
@@ -1760,8 +1827,8 @@ ${stats.skipped_details.join("\n")}`;
|
|
|
1760
1827
|
async function main() {
|
|
1761
1828
|
const transport = new StdioServerTransport();
|
|
1762
1829
|
await server.connect(transport);
|
|
1763
|
-
const gitSha2 = "
|
|
1764
|
-
const buildTs = "2026-06-
|
|
1830
|
+
const gitSha2 = "23b2879";
|
|
1831
|
+
const buildTs = "2026-06-25T13:51:23.036Z";
|
|
1765
1832
|
console.error(
|
|
1766
1833
|
`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
|
|
1767
1834
|
);
|