@adeu/mcp-server 1.15.1 → 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 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 matches = Array.from(body.matchAll(regex));
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
- const ui_markdown2 = `> **Search Results** \u2014 No matches found for query \`${search_query}\` in \`${basename(file_path)}\`.
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
- ${ui_markdown2}`;
269
+ ${body_msg}`;
222
270
  return {
223
271
  content: [{ type: "text", text: llm_content2 }],
224
272
  structuredContent: {
225
- markdown: ui_markdown2,
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 pag_res = paginate(body, "");
232
- const page_offsets = pag_res.body_page_offsets;
233
- const total_matches = matches.length;
234
- const total_pages = Math.ceil(total_matches / 10);
235
- let start_idx = 0;
236
- let end_idx = total_matches;
237
- let page_text = "all";
238
- const pageStr = String(page).toLowerCase();
239
- if (pageStr !== "all") {
240
- const page_num = parseInt(pageStr, 10);
241
- if (isNaN(page_num) || page_num < 1 || page_num > total_pages) {
242
- throw new Error(`Page ${page} out of range (search has ${total_pages} pages).`);
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 matches) {
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 = start_idx + 1;
285
- for (const m of page_matches) {
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
- let p_num = 1;
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(`*Occurrences:* This exact phrasing appears ${count} time${count !== 1 ? "s" : ""} in the document.`);
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 = "8fc8371";
1215
- var packageVersion = "1.15.1";
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.string()]).default(1).describe("Page number (1-indexed) for mode='full', or 'all' to return unpaginated."),
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
+ ),
1319
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("The substring or regex pattern to search for. When provided, filters results to matching paragraphs."),
1322
- search_regex: z.boolean().default(false).describe("Set to true to interpret search_query as a regular expression."),
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(doc, clean_view, true, true);
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(text, search_query, search_regex, search_case_sensitive, page, file_path);
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, typeof page === "number" ? page : parseInt(page, 10) || 1, file_path);
1423
+ const res2 = build_appendix_response(text, resolvedPage, file_path);
1360
1424
  return res2;
1361
1425
  }
1362
- const res = build_paginated_response(text, typeof page === "number" ? page : parseInt(page, 10) || 1, file_path);
1426
+ const res = build_paginated_response(text, resolvedPage, file_path);
1363
1427
  return res;
1364
1428
  } catch (e) {
1365
1429
  return {
@@ -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((acc, e) => acc + (e.status === "applied" ? e.occurrences_modified || 1 : 0), 0) : 0;
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 = "8fc8371";
1764
- const buildTs = "2026-06-25T11:57:32.023Z";
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
  );