@adeu/mcp-server 1.15.1 → 1.16.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/dist/index.js +156 -57
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +293 -180
- package/src/mcp.bugs.test.ts +23 -0
- package/src/mcp.schema-gaps.test.ts +384 -0
- 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");
|
|
@@ -1209,10 +1254,10 @@ function getAssetContent(folder, filename, fallbackMessage) {
|
|
|
1209
1254
|
var READ_DOCX_COMMON_DESC = "Reads a DOCX file. Returns text with inline CriticMarkup for Tracked Changes and Comments: {++inserted++}, {--deleted--}, {==highlighted==}{>>comment<<}. Set clean_view=True for the finalized 'Accepted' text without markup.\n\n";
|
|
1210
1255
|
var READ_DOCX_TAIL = "Modes:\n- 'full' (default): paginated body content. Use page=N to navigate.\n- 'outline': heading map only \u2014 start here for large docs to plan targeted reads. Defaults to L1-L2 headings; pass outline_max_level=3-6 to see deeper structure.\n- 'appendix': defined terms, anchors, and cross-reference targets. Consult before editing legal/technical docs to avoid breaking references.";
|
|
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
|
-
var PROCESS_BATCH_OPERATIONS_DESC = "Each item in `changes` must specify a `type`:\n1. 'modify': Search-and-replace. `target_text` must uniquely
|
|
1213
|
-
var DIFF_DOCX_DESC = "Compares two DOCX files and returns a
|
|
1214
|
-
var gitSha = "
|
|
1215
|
-
var packageVersion = "1.
|
|
1257
|
+
var PROCESS_BATCH_OPERATIONS_DESC = "Each item in `changes` must specify a `type`:\n1. 'modify': Search-and-replace. By default `target_text` must match uniquely (`match_mode`:'strict') \u2014 add surrounding context to disambiguate, or set `match_mode`:'first'/'all' to edit the first or every occurrence. Set `regex`:true to treat `target_text` as a regular expression (capture groups available in `new_text` as $1, $2\u2026). `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.";
|
|
1258
|
+
var DIFF_DOCX_DESC = "Compares two DOCX files and returns a compact `@@ Word Patch @@` diff \u2014 Adeu's token-level, sub-word patch format \u2014 of their text content. Useful for analyzing differences between versions before editing.";
|
|
1259
|
+
var gitSha = "2179c76";
|
|
1260
|
+
var packageVersion = "1.16.0";
|
|
1216
1261
|
var buildTag = ` [Adeu v${packageVersion}+${gitSha}]`;
|
|
1217
1262
|
var args = process.argv.slice(2);
|
|
1218
1263
|
var scopeIdx = args.indexOf("--scope");
|
|
@@ -1225,7 +1270,7 @@ var server = new McpServer({
|
|
|
1225
1270
|
var originalRegisterTool = server.registerTool.bind(server);
|
|
1226
1271
|
server.registerTool = (name, schema, handler) => {
|
|
1227
1272
|
if (schema && typeof schema === "object") {
|
|
1228
|
-
if (schema.description) {
|
|
1273
|
+
if (schema.description && !schema.description.includes(buildTag.trim())) {
|
|
1229
1274
|
schema.description = schema.description.trim() + buildTag;
|
|
1230
1275
|
}
|
|
1231
1276
|
}
|
|
@@ -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()]).
|
|
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(
|
|
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 {
|
|
@@ -1374,6 +1438,34 @@ registerAppTool(
|
|
|
1374
1438
|
}
|
|
1375
1439
|
}
|
|
1376
1440
|
);
|
|
1441
|
+
var CHANGE_ITEM_SCHEMA = z.object({
|
|
1442
|
+
type: z.enum(["modify", "accept", "reject", "reply", "insert_row", "delete_row"]).describe(
|
|
1443
|
+
"Change kind: 'modify' (search-and-replace), 'accept'/'reject' (resolve a tracked change by id), 'reply' (reply to a comment by id), 'insert_row'/'delete_row' (table edits; disk mode only)."
|
|
1444
|
+
),
|
|
1445
|
+
target_text: z.string().optional().describe(
|
|
1446
|
+
"modify / insert_row / delete_row: the existing text to locate (interpreted as a regex when regex=true)."
|
|
1447
|
+
),
|
|
1448
|
+
new_text: z.string().optional().describe(
|
|
1449
|
+
"modify: replacement text. Supports Markdown (headings, **bold**, _italic_, '\\n\\n' paragraph splits); empty string deletes. Regex capture groups are available as $1, $2\u2026"
|
|
1450
|
+
),
|
|
1451
|
+
target_id: z.string().optional().describe(
|
|
1452
|
+
"accept / reject / reply: the 'Chg:N' or 'Com:N' id taken from a fresh read_docx."
|
|
1453
|
+
),
|
|
1454
|
+
text: z.string().optional().describe("reply: the reply body."),
|
|
1455
|
+
comment: z.string().optional().describe(
|
|
1456
|
+
"modify / accept / reject: attach a margin comment to the change (no manual CriticMarkup)."
|
|
1457
|
+
),
|
|
1458
|
+
match_mode: z.enum(["strict", "first", "all"]).optional().describe(
|
|
1459
|
+
"modify only: 'strict' (default \u2014 target must match uniquely), 'first' (first occurrence), or 'all' (every occurrence)."
|
|
1460
|
+
),
|
|
1461
|
+
regex: z.boolean().optional().describe(
|
|
1462
|
+
"modify only: treat target_text as a regular expression (default false)."
|
|
1463
|
+
),
|
|
1464
|
+
position: z.enum(["above", "below"]).optional().describe(
|
|
1465
|
+
"insert_row: place the new row above or below the matched row."
|
|
1466
|
+
),
|
|
1467
|
+
cells: z.array(z.string()).optional().describe("insert_row: the cell values for the new row, left to right.")
|
|
1468
|
+
}).passthrough();
|
|
1377
1469
|
server.registerTool(
|
|
1378
1470
|
"process_document_batch",
|
|
1379
1471
|
{
|
|
@@ -1381,7 +1473,9 @@ server.registerTool(
|
|
|
1381
1473
|
inputSchema: {
|
|
1382
1474
|
original_docx_path: z.string().describe("Absolute path to the source file."),
|
|
1383
1475
|
author_name: z.string().describe("Name to appear in Track Changes (e.g., 'Reviewer AI')."),
|
|
1384
|
-
changes: z.array(z.
|
|
1476
|
+
changes: z.array(z.union([z.string(), CHANGE_ITEM_SCHEMA])).describe(
|
|
1477
|
+
"Ordered list of changes to apply. Each item is an object carrying a `type` discriminator plus that type's fields (see the per-field docs and the tool description). All items evaluate against the ORIGINAL document state."
|
|
1478
|
+
),
|
|
1385
1479
|
output_path: z.string().optional().describe("Optional output path."),
|
|
1386
1480
|
dry_run: z.boolean().optional().default(false).describe(
|
|
1387
1481
|
"If True, simulates the changes and returns a detailed preview report without modifying any files."
|
|
@@ -1538,7 +1632,7 @@ server.registerTool(
|
|
|
1538
1632
|
server.registerTool(
|
|
1539
1633
|
"finalize_document",
|
|
1540
1634
|
{
|
|
1541
|
-
description: "Prepares a document for external distribution or e-signature.",
|
|
1635
|
+
description: "Prepares a document for external distribution or e-signature. Note: in this zero-dependency environment, protection_mode='encrypt' is unsupported and falls back to a native read-only lock; export_pdf and password are ignored.",
|
|
1542
1636
|
inputSchema: {
|
|
1543
1637
|
file_path: z.string().describe("Absolute path to the DOCX file."),
|
|
1544
1638
|
output_path: z.string().optional().describe("Optional output path."),
|
|
@@ -1546,7 +1640,9 @@ server.registerTool(
|
|
|
1546
1640
|
accept_all: z.boolean().optional().describe(
|
|
1547
1641
|
"If true, auto-accepts all unresolved track changes before finalizing."
|
|
1548
1642
|
),
|
|
1549
|
-
protection_mode: z.enum(["read_only", "encrypt"]).optional().describe(
|
|
1643
|
+
protection_mode: z.enum(["read_only", "encrypt"]).optional().describe(
|
|
1644
|
+
"Native OOXML document locking. Note: 'encrypt' is unsupported in this zero-dependency build and falls back to 'read_only'."
|
|
1645
|
+
),
|
|
1550
1646
|
password: z.string().optional().describe("Ignored in this environment."),
|
|
1551
1647
|
author: z.string().optional().describe("Replace all remaining markup authorship with this name."),
|
|
1552
1648
|
export_pdf: z.boolean().optional().describe("Ignored in this environment.")
|
|
@@ -1707,7 +1803,10 @@ function formatBatchResult(stats, outPath, dry_run) {
|
|
|
1707
1803
|
res = `Batch complete. Saved to: ${outPath}
|
|
1708
1804
|
`;
|
|
1709
1805
|
}
|
|
1710
|
-
const total_occurrences = stats.edits ? stats.edits.reduce(
|
|
1806
|
+
const total_occurrences = stats.edits ? stats.edits.reduce(
|
|
1807
|
+
(acc, e) => acc + (e.status === "applied" ? e.occurrences_modified || 1 : 0),
|
|
1808
|
+
0
|
|
1809
|
+
) : 0;
|
|
1711
1810
|
const occ_text = total_occurrences > stats.edits_applied ? ` (${total_occurrences} occurrences)` : "";
|
|
1712
1811
|
res += `Actions: ${stats.actions_applied} applied, ${stats.actions_skipped} skipped.
|
|
1713
1812
|
`;
|
|
@@ -1760,8 +1859,8 @@ ${stats.skipped_details.join("\n")}`;
|
|
|
1760
1859
|
async function main() {
|
|
1761
1860
|
const transport = new StdioServerTransport();
|
|
1762
1861
|
await server.connect(transport);
|
|
1763
|
-
const gitSha2 = "
|
|
1764
|
-
const buildTs = "2026-06-
|
|
1862
|
+
const gitSha2 = "2179c76";
|
|
1863
|
+
const buildTs = "2026-06-26T07:48:21.832Z";
|
|
1765
1864
|
console.error(
|
|
1766
1865
|
`Adeu MCP Server (Node.js Engine: ${identifyEngine()}) running on stdio build=${gitSha2}@${buildTs}`
|
|
1767
1866
|
);
|