@adeu/mcp-server 1.14.0 → 1.15.1
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 +169 -28
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/formatter.qa.test.ts +71 -0
- package/src/formatter.test.ts +12 -6
- package/src/index.ts +58 -20
- package/src/mcp.bugs.test.ts +22 -1
- package/src/response-builders.ts +151 -0
package/dist/index.js
CHANGED
|
@@ -200,6 +200,128 @@ ${ui_markdown}`;
|
|
|
200
200
|
}
|
|
201
201
|
};
|
|
202
202
|
}
|
|
203
|
+
function build_search_response(text, search_query, search_regex, search_case_sensitive, page, file_path) {
|
|
204
|
+
const [body] = split_structural_appendix(text);
|
|
205
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
206
|
+
const flags = search_case_sensitive ? "g" : "gi";
|
|
207
|
+
const patternStr = search_regex ? search_query : escapeRegExp(search_query);
|
|
208
|
+
let regex;
|
|
209
|
+
try {
|
|
210
|
+
regex = new RegExp(patternStr, flags);
|
|
211
|
+
} catch (e) {
|
|
212
|
+
throw new Error(`Invalid regex pattern: ${e.message}`);
|
|
213
|
+
}
|
|
214
|
+
const matches = Array.from(body.matchAll(regex));
|
|
215
|
+
if (matches.length === 0) {
|
|
216
|
+
const ui_markdown2 = `> **Search Results** \u2014 No matches found for query \`${search_query}\` in \`${basename(file_path)}\`.
|
|
217
|
+
|
|
218
|
+
Verify your search spelling, or try setting \`search_case_sensitive\` to false or enabling \`search_regex\` if you used pattern wildcards.`;
|
|
219
|
+
const llm_content2 = `> **File Path:** \`${resolve(file_path)}\`
|
|
220
|
+
|
|
221
|
+
${ui_markdown2}`;
|
|
222
|
+
return {
|
|
223
|
+
content: [{ type: "text", text: llm_content2 }],
|
|
224
|
+
structuredContent: {
|
|
225
|
+
markdown: ui_markdown2,
|
|
226
|
+
title: `Search: ${basename(file_path)}`,
|
|
227
|
+
file_path: resolve(file_path)
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
}
|
|
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).`);
|
|
243
|
+
}
|
|
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
|
+
}
|
|
256
|
+
const occurrences_map = {};
|
|
257
|
+
for (const m of matches) {
|
|
258
|
+
const matched_str = m[0];
|
|
259
|
+
occurrences_map[matched_str] = (occurrences_map[matched_str] || 0) + 1;
|
|
260
|
+
}
|
|
261
|
+
function get_heading(idx, txt) {
|
|
262
|
+
const txtBefore = txt.substring(0, idx);
|
|
263
|
+
const lines = txtBefore.split("\n");
|
|
264
|
+
const path = [];
|
|
265
|
+
let current_level = 999;
|
|
266
|
+
for (let i2 = lines.length - 1; i2 >= 0; i2--) {
|
|
267
|
+
const line = lines[i2];
|
|
268
|
+
const m = line.match(/^(#{1,6})\s+(.*)/);
|
|
269
|
+
if (m) {
|
|
270
|
+
const level = m[1].length;
|
|
271
|
+
if (level < current_level) {
|
|
272
|
+
let cleanHeading = m[2].replace(/\*\*|__|[*_]/g, "").replace(/\{#[^}]+\}/g, "").trim();
|
|
273
|
+
if (cleanHeading.length > 80) {
|
|
274
|
+
cleanHeading = cleanHeading.substring(0, 80) + "...";
|
|
275
|
+
}
|
|
276
|
+
path.unshift(cleanHeading);
|
|
277
|
+
current_level = level;
|
|
278
|
+
if (level === 1) break;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return path.join(" > ");
|
|
283
|
+
}
|
|
284
|
+
let i = start_idx + 1;
|
|
285
|
+
for (const m of page_matches) {
|
|
286
|
+
const matched_str = m[0];
|
|
287
|
+
const m_start = m.index;
|
|
288
|
+
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
|
+
}
|
|
297
|
+
const snippet_start = Math.max(0, m_start - 100);
|
|
298
|
+
const snippet_end = Math.min(body.length, m_end + 100);
|
|
299
|
+
const snippet = body.substring(snippet_start, m_start) + `**${matched_str}**` + body.substring(m_end, snippet_end);
|
|
300
|
+
const snippet_lines = snippet.split("\n").filter((line) => line.trim().length > 0).map((line) => `> ${line}`).join("\n");
|
|
301
|
+
ui_parts.push("---");
|
|
302
|
+
ui_parts.push(`### Match ${i} (p${p_num})`);
|
|
303
|
+
const h_path = get_heading(m_start, body);
|
|
304
|
+
if (h_path) {
|
|
305
|
+
ui_parts.push(`**Path:** \`${h_path}\``);
|
|
306
|
+
}
|
|
307
|
+
const count = occurrences_map[matched_str];
|
|
308
|
+
ui_parts.push(snippet_lines);
|
|
309
|
+
ui_parts.push(`*Occurrences:* This exact phrasing appears ${count} time${count !== 1 ? "s" : ""} in the document.`);
|
|
310
|
+
i++;
|
|
311
|
+
}
|
|
312
|
+
const ui_markdown = ui_parts.join("\n\n");
|
|
313
|
+
const llm_content = `> **File Path:** \`${resolve(file_path)}\`
|
|
314
|
+
|
|
315
|
+
${ui_markdown}`;
|
|
316
|
+
return {
|
|
317
|
+
content: [{ type: "text", text: llm_content }],
|
|
318
|
+
structuredContent: {
|
|
319
|
+
markdown: ui_markdown,
|
|
320
|
+
title: `Search: ${basename(file_path)}`,
|
|
321
|
+
file_path: resolve(file_path)
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
}
|
|
203
325
|
|
|
204
326
|
// src/desktop-auth.ts
|
|
205
327
|
import { createServer } from "http";
|
|
@@ -1089,8 +1211,8 @@ var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use pa
|
|
|
1089
1211
|
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";
|
|
1090
1212
|
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.";
|
|
1091
1213
|
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.";
|
|
1092
|
-
var gitSha = "
|
|
1093
|
-
var packageVersion = "1.
|
|
1214
|
+
var gitSha = "8fc8371";
|
|
1215
|
+
var packageVersion = "1.15.1";
|
|
1094
1216
|
var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
|
|
1095
1217
|
var args = process.argv.slice(2);
|
|
1096
1218
|
var scopeIdx = args.indexOf("--scope");
|
|
@@ -1193,9 +1315,12 @@ registerAppTool(
|
|
|
1193
1315
|
mode: z.enum(["full", "outline", "appendix"]).default("full").describe(
|
|
1194
1316
|
"'full' returns body content. 'outline' returns a structural heading map. 'appendix' returns defined terms."
|
|
1195
1317
|
),
|
|
1196
|
-
page: z.number().default(1).describe("Page number (1-indexed) for mode='full'
|
|
1197
|
-
outline_max_level: z.number().default(2).describe("For mode='outline' only: cap on heading depth."),
|
|
1198
|
-
outline_verbose: z.boolean().default(false).describe("For mode='outline' only: includes metadata.")
|
|
1318
|
+
page: z.union([z.number(), z.string()]).default(1).describe("Page number (1-indexed) for mode='full', or 'all' to return unpaginated."),
|
|
1319
|
+
outline_max_level: z.coerce.number().default(2).describe("For mode='outline' only: cap on heading depth."),
|
|
1320
|
+
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."),
|
|
1323
|
+
search_case_sensitive: z.boolean().default(true).describe("Set to false to perform case-insensitive matching.")
|
|
1199
1324
|
}),
|
|
1200
1325
|
_meta: { ui: { resourceUri: MARKDOWN_UI_URI } }
|
|
1201
1326
|
},
|
|
@@ -1205,7 +1330,10 @@ registerAppTool(
|
|
|
1205
1330
|
mode,
|
|
1206
1331
|
page,
|
|
1207
1332
|
outline_max_level,
|
|
1208
|
-
outline_verbose
|
|
1333
|
+
outline_verbose,
|
|
1334
|
+
search_query,
|
|
1335
|
+
search_regex,
|
|
1336
|
+
search_case_sensitive
|
|
1209
1337
|
}) => {
|
|
1210
1338
|
try {
|
|
1211
1339
|
const buf = readFileBytesOrThrow(file_path);
|
|
@@ -1223,11 +1351,15 @@ registerAppTool(
|
|
|
1223
1351
|
return res2;
|
|
1224
1352
|
}
|
|
1225
1353
|
const text = await extractTextFromBuffer(buf, clean_view);
|
|
1354
|
+
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);
|
|
1356
|
+
return res2;
|
|
1357
|
+
}
|
|
1226
1358
|
if (mode === "appendix") {
|
|
1227
|
-
const res2 = build_appendix_response(text, page, file_path);
|
|
1359
|
+
const res2 = build_appendix_response(text, typeof page === "number" ? page : parseInt(page, 10) || 1, file_path);
|
|
1228
1360
|
return res2;
|
|
1229
1361
|
}
|
|
1230
|
-
const res = build_paginated_response(text, page, file_path);
|
|
1362
|
+
const res = build_paginated_response(text, typeof page === "number" ? page : parseInt(page, 10) || 1, file_path);
|
|
1231
1363
|
return res;
|
|
1232
1364
|
} catch (e) {
|
|
1233
1365
|
return {
|
|
@@ -1479,15 +1611,15 @@ if (!isDocxOnly) {
|
|
|
1479
1611
|
has_attachments: z.boolean().optional(),
|
|
1480
1612
|
attachment_name: z.string().optional(),
|
|
1481
1613
|
is_unread: z.boolean().optional(),
|
|
1482
|
-
days_ago: z.number().optional(),
|
|
1614
|
+
days_ago: z.coerce.number().optional(),
|
|
1483
1615
|
folder: z.enum(["inbox", "sent", "all"]).optional(),
|
|
1484
|
-
limit: z.number().default(10),
|
|
1485
|
-
offset: z.number().default(0),
|
|
1616
|
+
limit: z.coerce.number().default(10),
|
|
1617
|
+
offset: z.coerce.number().default(0),
|
|
1486
1618
|
email_id: z.string().optional(),
|
|
1487
1619
|
working_directory: z.string().optional(),
|
|
1488
1620
|
mailbox_address: z.string().optional().describe("Optional target mailbox email address to search within."),
|
|
1489
1621
|
task_id: z.string().optional().describe("If resuming a pending check, provide the task ID here."),
|
|
1490
|
-
max_attachment_size_mb: z.number().optional().describe(
|
|
1622
|
+
max_attachment_size_mb: z.coerce.number().optional().describe(
|
|
1491
1623
|
"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."
|
|
1492
1624
|
)
|
|
1493
1625
|
}),
|
|
@@ -1575,52 +1707,61 @@ function formatBatchResult(stats, outPath, dry_run) {
|
|
|
1575
1707
|
res = `Batch complete. Saved to: ${outPath}
|
|
1576
1708
|
`;
|
|
1577
1709
|
}
|
|
1710
|
+
const total_occurrences = stats.edits ? stats.edits.reduce((acc, e) => acc + (e.status === "applied" ? e.occurrences_modified || 1 : 0), 0) : 0;
|
|
1711
|
+
const occ_text = total_occurrences > stats.edits_applied ? ` (${total_occurrences} occurrences)` : "";
|
|
1578
1712
|
res += `Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.
|
|
1579
1713
|
`;
|
|
1580
|
-
res += `Edits: ${stats.edits_applied} applied, ${stats.edits_skipped} skipped.
|
|
1714
|
+
res += `Edits: ${stats.edits_applied} applied${occ_text}, ${stats.edits_skipped} skipped.
|
|
1581
1715
|
`;
|
|
1582
1716
|
if (stats.edits && stats.edits.length > 0) {
|
|
1583
1717
|
res += "\nDetailed Edit Reports:\n";
|
|
1584
1718
|
for (let i = 0; i < stats.edits.length; i++) {
|
|
1585
1719
|
const report = stats.edits[i];
|
|
1586
1720
|
const status_indicator = report.status === "applied" ? "\u2705 [applied]" : "\u274C [failed]";
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
res += ` Target: '${report.target_text}'
|
|
1721
|
+
const pagesStr = report.pages && report.pages.length > 0 ? ` (p${report.pages.join(", p")})` : "";
|
|
1722
|
+
res += `### Edit ${i + 1} ${status_indicator}${pagesStr}
|
|
1590
1723
|
`;
|
|
1591
|
-
|
|
1724
|
+
if (report.heading_path) {
|
|
1725
|
+
res += `**Path:** \`${report.heading_path}\`
|
|
1592
1726
|
`;
|
|
1593
|
-
|
|
1594
|
-
|
|
1727
|
+
}
|
|
1728
|
+
if (report.match_mode) {
|
|
1729
|
+
const occ = report.occurrences_modified || (report.status === "applied" ? 1 : 0);
|
|
1730
|
+
res += `**Mode:** \`${report.match_mode}\` (${occ} occurrence${occ !== 1 ? "s" : ""} modified)
|
|
1595
1731
|
`;
|
|
1596
1732
|
}
|
|
1597
1733
|
if (report.error) {
|
|
1598
|
-
res +=
|
|
1734
|
+
res += `*Error:* ${report.error}
|
|
1735
|
+
`;
|
|
1736
|
+
}
|
|
1737
|
+
if (report.warning) {
|
|
1738
|
+
res += `*Warning:* ${report.warning}
|
|
1599
1739
|
`;
|
|
1600
1740
|
}
|
|
1601
1741
|
if (report.critic_markup) {
|
|
1602
|
-
res +=
|
|
1742
|
+
res += `*Preview (CriticMarkup):*
|
|
1743
|
+
> ${report.critic_markup.split("\\n").join("\\n> ")}
|
|
1603
1744
|
`;
|
|
1604
1745
|
}
|
|
1605
1746
|
if (report.clean_text) {
|
|
1606
|
-
res +=
|
|
1747
|
+
res += `*Preview (Clean):*
|
|
1748
|
+
> ${report.clean_text.split("\\n").join("\\n> ")}
|
|
1607
1749
|
`;
|
|
1608
1750
|
}
|
|
1751
|
+
res += "\n";
|
|
1609
1752
|
}
|
|
1610
1753
|
}
|
|
1611
1754
|
if (stats.skipped_details && stats.skipped_details.length > 0) {
|
|
1612
|
-
res += `
|
|
1613
|
-
|
|
1614
|
-
Skipped Details:
|
|
1755
|
+
res += `Skipped Details:
|
|
1615
1756
|
${stats.skipped_details.join("\n")}`;
|
|
1616
1757
|
}
|
|
1617
|
-
return res;
|
|
1758
|
+
return res.trim();
|
|
1618
1759
|
}
|
|
1619
1760
|
async function main() {
|
|
1620
1761
|
const transport = new StdioServerTransport();
|
|
1621
1762
|
await server.connect(transport);
|
|
1622
|
-
const gitSha2 = "
|
|
1623
|
-
const buildTs = "2026-06-
|
|
1763
|
+
const gitSha2 = "8fc8371";
|
|
1764
|
+
const buildTs = "2026-06-25T11:57:32.023Z";
|
|
1624
1765
|
console.error(
|
|
1625
1766
|
`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
|
|
1626
1767
|
);
|