mcp-word-bridge 4.1.2 → 4.1.4

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/README.md CHANGED
@@ -60,7 +60,7 @@ MCP Client ←stdio→ Server ←WebSocket→ Taskpane (Office Add-in) ←→ Wo
60
60
 
61
61
  Single process. The MCP client spawns the server, which starts both the HTTPS bridge (for the add-in) and the MCP protocol handler (on stdio). Everything starts and stops together.
62
62
 
63
- ## Tools (92)
63
+ ## Tools (91)
64
64
 
65
65
  ### Document
66
66
  | Tool | Description |
@@ -300,6 +300,7 @@ Platform constraints in the Word JavaScript API that cannot be resolved in this
300
300
  | Highlight Colors | `highlightColor` only supports 17 named colors (Yellow, Green, Cyan, Magenta, Blue, Red, DarkBlue, DarkCyan, DarkGreen, DarkMagenta, DarkRed, DarkYellow, Gray25, Gray50, Black, White, NoHighlight). The Word API documentation claims `#RRGGBB` is accepted, but Desktop silently maps hex values to the nearest named color. This tool rejects hex to prevent silent mismatch — use `color` for arbitrary RGB. | [office-js#4638](https://github.com/OfficeDev/office-js/issues/4638) |
301
301
  | Paragraph Style | TOC field entries and certain table paragraphs return `style: ""` (empty string) instead of the actual style name. Paragraphs are flagged with `isTocEntry: true` when detected as TOC entries. Use this flag rather than the style field for identification. | [office-js#5934](https://github.com/OfficeDev/office-js/issues/5934) |
302
302
  | Mixed Formatting | `word_get_paragraph_by_index` returns `null` for font properties (`bold`, `italic`, `size`, etc.) when the paragraph contains mixed formatting (e.g. partially bold text). This is the Word API's way of indicating "no single value" — treat `null` as "mixed". | [Word.Font docs](https://learn.microsoft.com/en-us/javascript/api/word/word.font?view=word-js-preview) |
303
+ | TOC Search | `body.search()` matches text inside TOC field entries as well as body content. Heading text appears in both locations, so search results return TOC matches first. Use `occurrence` to skip past TOC entries and target the body instance. | |
303
304
  | Page Info | `word_get_page_info` requires WordApiDesktop 1.2+ (Word for Windows/Mac desktop). Not available on Word for the web. | [WordApiDesktop 1.2](https://learn.microsoft.com/en-us/javascript/api/requirement-sets/word/word-api-desktop-1-2-requirement-set) |
304
305
  | Undo | The Word JavaScript API does not expose a programmatic `undo()` method. Changes made by the add-in appear in Word's undo stack (Ctrl+Z works for the user), but there is no way to trigger undo from code. | [Stack Overflow](https://stackoverflow.com/questions/58440921/accessing-the-undo-stack-from-word-javascript-api) |
305
306
 
package/dist/server.js CHANGED
@@ -18646,6 +18646,7 @@ var HEAVY_OPERATIONS = /* @__PURE__ */ new Set([
18646
18646
  "batchExecute"
18647
18647
  ]);
18648
18648
  var MAX_PAYLOAD = 10 * 1024 * 1024;
18649
+ var HIGHLIGHT_COLORS = ["Yellow", "Green", "Cyan", "Magenta", "Blue", "Red", "DarkBlue", "DarkCyan", "DarkGreen", "DarkMagenta", "DarkRed", "DarkYellow", "Gray25", "Gray50", "Black", "White", "NoHighlight"];
18649
18650
 
18650
18651
  // src/server/bridge.ts
18651
18652
  var Bridge = class {
@@ -18722,7 +18723,7 @@ var Bridge = class {
18722
18723
 
18723
18724
  // src/server/mcp.ts
18724
18725
  var import_server = require("@modelcontextprotocol/sdk/server/index.js");
18725
- var import_types9 = require("@modelcontextprotocol/sdk/types.js");
18726
+ var import_types10 = require("@modelcontextprotocol/sdk/types.js");
18726
18727
 
18727
18728
  // src/server/types.ts
18728
18729
  var ToolError = class extends Error {
@@ -18742,6 +18743,7 @@ function forwardTool(name, description, schema, action, validate) {
18742
18743
  description,
18743
18744
  schema,
18744
18745
  bridgeAction: action,
18746
+ validate,
18745
18747
  async handler(args, bridge2) {
18746
18748
  if (validate) validate(args);
18747
18749
  const result = await bridge2.send(action, args);
@@ -18779,7 +18781,15 @@ var setDocumentProperties = forwardTool(
18779
18781
  format: { type: "string" }
18780
18782
  }
18781
18783
  },
18782
- "setDocumentProperties"
18784
+ "setDocumentProperties",
18785
+ (args) => {
18786
+ const hasProperty = args.title !== void 0 || args.subject !== void 0 || args.author !== void 0 || args.keywords !== void 0 || args.comments !== void 0 || args.category !== void 0 || args.company !== void 0 || args.manager !== void 0 || args.format !== void 0;
18787
+ if (!hasProperty) {
18788
+ throw new ToolError(
18789
+ "At least one document property must be provided (title, subject, author, keywords, comments, category, company, manager, format)."
18790
+ );
18791
+ }
18792
+ }
18783
18793
  );
18784
18794
  var save = forwardTool(
18785
18795
  "word_save",
@@ -18884,18 +18894,6 @@ function checkBounds(index, count, name) {
18884
18894
  );
18885
18895
  }
18886
18896
  }
18887
- function checkOccurrence(occurrence, count) {
18888
- const idx = occurrence ?? 0;
18889
- if (idx < 0) {
18890
- throw new ToolError("occurrence must be non-negative (0-indexed).");
18891
- }
18892
- if (idx >= count) {
18893
- throw new ToolError(
18894
- `Occurrence ${idx} not found (only ${count} match${count === 1 ? "" : "es"}).`
18895
- );
18896
- }
18897
- return idx;
18898
- }
18899
18897
  var MAX_SPACING_POINTS = 1584;
18900
18898
  var MAX_CUSTOM_PROPERTY_KEY_LENGTH = 255;
18901
18899
  function checkSpacingBounds(value, name) {
@@ -18935,6 +18933,14 @@ function checkHexColor(color, name) {
18935
18933
  throw new ToolError(`${name} must be a valid hex color (e.g. #FF0000).`);
18936
18934
  }
18937
18935
  }
18936
+ function checkUrl(url) {
18937
+ if (!/^https?:\/\/.+/i.test(url)) {
18938
+ throw new ToolError("URL must be a valid HTTP or HTTPS URL (e.g. https://example.com).");
18939
+ }
18940
+ if (/[<>"{}|\\^`]/.test(url)) {
18941
+ throw new ToolError(`Malformed URL: "${url}". URL contains invalid characters that must be percent-encoded.`);
18942
+ }
18943
+ }
18938
18944
 
18939
18945
  // src/server/tools/paragraphs.ts
18940
18946
  var getParagraphs = forwardTool(
@@ -19022,7 +19028,12 @@ var setParagraphStyle = forwardTool(
19022
19028
  },
19023
19029
  required: ["index"]
19024
19030
  },
19025
- "setParagraphStyle"
19031
+ "setParagraphStyle",
19032
+ (args) => {
19033
+ if (args.style === void 0 && args.alignment === void 0) {
19034
+ throw new ToolError('At least one of "style" or "alignment" must be provided.');
19035
+ }
19036
+ }
19026
19037
  );
19027
19038
  var setParagraphSpacing = {
19028
19039
  name: "word_set_paragraph_spacing",
@@ -19123,10 +19134,8 @@ var moveParagraph = {
19123
19134
  }
19124
19135
  }
19125
19136
  const adjustedTo = fromIndex < toIndex ? toIndex - count : toIndex;
19126
- if (toIndex === fromIndex + count && location === "After") {
19127
- return jsonResult({ success: true, warning: "No move performed \u2014 destination is equivalent to source position.", moved: null });
19128
- }
19129
- if (adjustedTo === fromIndex && location === "After") {
19137
+ const effectiveInsertPos = location === "Before" ? adjustedTo : adjustedTo + 1;
19138
+ if (effectiveInsertPos === fromIndex) {
19130
19139
  return jsonResult({ success: true, warning: "No move performed \u2014 destination is equivalent to source position.", moved: null });
19131
19140
  }
19132
19141
  const ooxmlResult = await bridge2.send("getParaOoxml", { index: fromIndex, count });
@@ -19221,7 +19230,10 @@ var search = forwardTool(
19221
19230
  },
19222
19231
  required: ["query"]
19223
19232
  },
19224
- "search"
19233
+ "search",
19234
+ (args) => {
19235
+ checkNonEmpty(args.query, "query");
19236
+ }
19225
19237
  );
19226
19238
  var searchAndReplace = {
19227
19239
  name: "word_search_and_replace",
@@ -19268,7 +19280,10 @@ var insertTextAtMatch = forwardTool(
19268
19280
  },
19269
19281
  required: ["text"]
19270
19282
  },
19271
- "insertText"
19283
+ "insertText",
19284
+ (args) => {
19285
+ checkNonEmpty(args.text, "text");
19286
+ }
19272
19287
  );
19273
19288
  var getSelectionInfo = forwardTool(
19274
19289
  "word_get_selection_info",
@@ -19300,7 +19315,10 @@ var insertLineBreak = forwardTool(
19300
19315
  },
19301
19316
  required: ["anchorText"]
19302
19317
  },
19303
- "insertLineBreak"
19318
+ "insertLineBreak",
19319
+ (args) => {
19320
+ checkNonEmpty(args.anchorText, "anchorText");
19321
+ }
19304
19322
  );
19305
19323
  var searchTools = [
19306
19324
  search,
@@ -19312,7 +19330,6 @@ var searchTools = [
19312
19330
  ];
19313
19331
 
19314
19332
  // src/server/tools/formatting.ts
19315
- var HIGHLIGHT_COLORS = ["Yellow", "Green", "Cyan", "Magenta", "Blue", "Red", "DarkBlue", "DarkCyan", "DarkGreen", "DarkMagenta", "DarkRed", "DarkYellow", "Gray25", "Gray50", "Black", "White", "NoHighlight"];
19316
19333
  function validateFormatText(args) {
19317
19334
  checkNonEmpty(args.text, "text");
19318
19335
  const hasFormatting = args.bold !== void 0 || args.italic !== void 0 || args.underline !== void 0 || args.strikeThrough !== void 0 || args.color !== void 0 || args.highlightColor !== void 0 || args.size !== void 0 || args.name !== void 0;
@@ -19373,7 +19390,10 @@ var clearFormatting = forwardTool(
19373
19390
  },
19374
19391
  required: ["text"]
19375
19392
  },
19376
- "clearFormatting"
19393
+ "clearFormatting",
19394
+ (args) => {
19395
+ checkNonEmpty(args.text, "text");
19396
+ }
19377
19397
  );
19378
19398
  var getFontInfo = forwardTool(
19379
19399
  "word_get_font_info",
@@ -19386,7 +19406,10 @@ var getFontInfo = forwardTool(
19386
19406
  },
19387
19407
  required: ["text"]
19388
19408
  },
19389
- "getFontInfo"
19409
+ "getFontInfo",
19410
+ (args) => {
19411
+ checkNonEmpty(args.text, "text");
19412
+ }
19390
19413
  );
19391
19414
  var formattingTools = [
19392
19415
  formatText,
@@ -19409,7 +19432,24 @@ var insertTable = forwardTool(
19409
19432
  },
19410
19433
  required: ["rows", "cols"]
19411
19434
  },
19412
- "insertTable"
19435
+ "insertTable",
19436
+ (args) => {
19437
+ const rows = args.rows;
19438
+ const cols = args.cols;
19439
+ if (typeof rows !== "number" || !Number.isInteger(rows) || rows <= 0) throw new ToolError("rows must be a positive integer (minimum 1).");
19440
+ if (typeof cols !== "number" || !Number.isInteger(cols) || cols <= 0) throw new ToolError("cols must be a positive integer (minimum 1).");
19441
+ if (cols > 63) throw new ToolError("cols must not exceed 63 (Word maximum column limit).");
19442
+ if (rows > 500) throw new ToolError("rows must not exceed 500 (practical limit for performance).");
19443
+ if (args.data !== void 0) {
19444
+ const data = args.data;
19445
+ if (!Array.isArray(data)) throw new ToolError("data must be an array of arrays.");
19446
+ if (data.length !== rows) throw new ToolError(`Data rows (${data.length}) do not match specified rows (${rows}). Provide exactly ${rows} row(s) in the data array.`);
19447
+ for (let i = 0; i < data.length; i++) {
19448
+ if (!Array.isArray(data[i])) throw new ToolError(`data[${i}] must be an array.`);
19449
+ if (data[i].length !== cols) throw new ToolError(`Data row ${i} has ${data[i].length} columns but expected ${cols}.`);
19450
+ }
19451
+ }
19452
+ }
19413
19453
  );
19414
19454
  var listTables = forwardTool(
19415
19455
  "word_list_tables",
@@ -19440,7 +19480,12 @@ var setTableCell = forwardTool(
19440
19480
  },
19441
19481
  required: ["tableIndex", "row", "col", "text"]
19442
19482
  },
19443
- "setTableCell"
19483
+ "setTableCell",
19484
+ (args) => {
19485
+ checkNonNegative(args.tableIndex, "tableIndex");
19486
+ checkNonNegative(args.row, "row");
19487
+ checkNonNegative(args.col, "col");
19488
+ }
19444
19489
  );
19445
19490
  var addTableRow = forwardTool(
19446
19491
  "word_add_table_row",
@@ -19465,7 +19510,11 @@ var deleteTableRow = forwardTool(
19465
19510
  },
19466
19511
  required: ["tableIndex", "rowIndex"]
19467
19512
  },
19468
- "deleteTableRow"
19513
+ "deleteTableRow",
19514
+ (args) => {
19515
+ checkNonNegative(args.tableIndex, "tableIndex");
19516
+ checkNonNegative(args.rowIndex, "rowIndex");
19517
+ }
19469
19518
  );
19470
19519
  var mergeTableCells = forwardTool(
19471
19520
  "word_merge_table_cells",
@@ -19480,7 +19529,17 @@ var mergeTableCells = forwardTool(
19480
19529
  },
19481
19530
  required: ["tableIndex", "topRow", "firstCell", "bottomRow", "lastCell"]
19482
19531
  },
19483
- "mergeTableCells"
19532
+ "mergeTableCells",
19533
+ (args) => {
19534
+ checkNonNegative(args.tableIndex, "tableIndex");
19535
+ checkNonNegative(args.topRow, "topRow");
19536
+ checkNonNegative(args.firstCell, "firstCell");
19537
+ checkNonNegative(args.bottomRow, "bottomRow");
19538
+ checkNonNegative(args.lastCell, "lastCell");
19539
+ if (args.topRow > args.bottomRow) throw new ToolError(`topRow (${args.topRow}) must be less than or equal to bottomRow (${args.bottomRow}).`);
19540
+ if (args.firstCell > args.lastCell) throw new ToolError(`firstCell (${args.firstCell}) must be less than or equal to lastCell (${args.lastCell}).`);
19541
+ if (args.topRow === args.bottomRow && args.firstCell === args.lastCell) throw new ToolError("Cannot merge a single cell with itself. Provide a range spanning at least 2 cells.");
19542
+ }
19484
19543
  );
19485
19544
  var splitTableCell = forwardTool(
19486
19545
  "word_split_table_cell",
@@ -19495,7 +19554,14 @@ var splitTableCell = forwardTool(
19495
19554
  },
19496
19555
  required: ["tableIndex", "row", "col"]
19497
19556
  },
19498
- "splitTableCell"
19557
+ "splitTableCell",
19558
+ (args) => {
19559
+ checkNonNegative(args.tableIndex, "tableIndex");
19560
+ checkNonNegative(args.row, "row");
19561
+ checkNonNegative(args.col, "col");
19562
+ if (args.rowCount !== void 0 && (typeof args.rowCount !== "number" || args.rowCount <= 0)) throw new ToolError("rowCount must be a positive integer.");
19563
+ if (args.colCount !== void 0 && (typeof args.colCount !== "number" || args.colCount <= 0)) throw new ToolError("colCount must be a positive integer.");
19564
+ }
19499
19565
  );
19500
19566
  var setTableStyle = forwardTool(
19501
19567
  "word_set_table_style",
@@ -19521,7 +19587,13 @@ var setTableCellShading = forwardTool(
19521
19587
  },
19522
19588
  required: ["tableIndex", "row", "col", "color"]
19523
19589
  },
19524
- "setTableCellShading"
19590
+ "setTableCellShading",
19591
+ (args) => {
19592
+ checkNonNegative(args.tableIndex, "tableIndex");
19593
+ checkNonNegative(args.row, "row");
19594
+ checkNonNegative(args.col, "col");
19595
+ checkHexColor(args.color, "color");
19596
+ }
19525
19597
  );
19526
19598
  var tableTools = [
19527
19599
  insertTable,
@@ -19674,7 +19746,11 @@ var insertFootnote = forwardTool(
19674
19746
  },
19675
19747
  required: ["anchorText", "text"]
19676
19748
  },
19677
- "insertFootnote"
19749
+ "insertFootnote",
19750
+ (args) => {
19751
+ checkNonEmpty(args.anchorText, "anchorText");
19752
+ checkNonEmpty(args.text, "text");
19753
+ }
19678
19754
  );
19679
19755
  var insertFootnoteAtIndex = forwardTool(
19680
19756
  "word_insert_footnote_at_index",
@@ -19686,7 +19762,10 @@ var insertFootnoteAtIndex = forwardTool(
19686
19762
  },
19687
19763
  required: ["paragraphIndex", "text"]
19688
19764
  },
19689
- "insertFootnoteAtIndex"
19765
+ "insertFootnoteAtIndex",
19766
+ (args) => {
19767
+ checkNonEmpty(args.text, "text");
19768
+ }
19690
19769
  );
19691
19770
  var insertEndnote = forwardTool(
19692
19771
  "word_insert_endnote",
@@ -19700,7 +19779,11 @@ var insertEndnote = forwardTool(
19700
19779
  },
19701
19780
  required: ["anchorText", "text"]
19702
19781
  },
19703
- "insertEndnote"
19782
+ "insertEndnote",
19783
+ (args) => {
19784
+ checkNonEmpty(args.anchorText, "anchorText");
19785
+ checkNonEmpty(args.text, "text");
19786
+ }
19704
19787
  );
19705
19788
  var getFootnotes = forwardTool(
19706
19789
  "word_get_footnotes",
@@ -19765,7 +19848,11 @@ var insertBookmark = forwardTool(
19765
19848
  },
19766
19849
  required: ["name", "anchorText"]
19767
19850
  },
19768
- "insertBookmark"
19851
+ "insertBookmark",
19852
+ (args) => {
19853
+ checkNonEmpty(args.name, "name");
19854
+ checkNonEmpty(args.anchorText, "anchorText");
19855
+ }
19769
19856
  );
19770
19857
  var deleteBookmark = forwardTool(
19771
19858
  "word_delete_bookmark",
@@ -19776,7 +19863,10 @@ var deleteBookmark = forwardTool(
19776
19863
  },
19777
19864
  required: ["name"]
19778
19865
  },
19779
- "deleteBookmark"
19866
+ "deleteBookmark",
19867
+ (args) => {
19868
+ checkNonEmpty(args.name, "name");
19869
+ }
19780
19870
  );
19781
19871
  var goToBookmark = forwardTool(
19782
19872
  "word_go_to_bookmark",
@@ -19787,7 +19877,10 @@ var goToBookmark = forwardTool(
19787
19877
  },
19788
19878
  required: ["name"]
19789
19879
  },
19790
- "goToBookmark"
19880
+ "goToBookmark",
19881
+ (args) => {
19882
+ checkNonEmpty(args.name, "name");
19883
+ }
19791
19884
  );
19792
19885
  var getBookmarkText = forwardTool(
19793
19886
  "word_get_bookmark_text",
@@ -19798,7 +19891,10 @@ var getBookmarkText = forwardTool(
19798
19891
  },
19799
19892
  required: ["name"]
19800
19893
  },
19801
- "getBookmarkText"
19894
+ "getBookmarkText",
19895
+ (args) => {
19896
+ checkNonEmpty(args.name, "name");
19897
+ }
19802
19898
  );
19803
19899
  var bookmarkTools = [
19804
19900
  getBookmarks,
@@ -19821,7 +19917,12 @@ var insertHyperlink = forwardTool(
19821
19917
  },
19822
19918
  required: ["anchorText", "url"]
19823
19919
  },
19824
- "insertHyperlink"
19920
+ "insertHyperlink",
19921
+ (args) => {
19922
+ checkNonEmpty(args.anchorText, "anchorText");
19923
+ checkNonEmpty(args.url, "url");
19924
+ checkUrl(args.url);
19925
+ }
19825
19926
  );
19826
19927
  var getHyperlinks = forwardTool(
19827
19928
  "word_get_hyperlinks",
@@ -19840,7 +19941,10 @@ var removeHyperlink = forwardTool(
19840
19941
  },
19841
19942
  required: ["anchorText"]
19842
19943
  },
19843
- "removeHyperlink"
19944
+ "removeHyperlink",
19945
+ (args) => {
19946
+ checkNonEmpty(args.anchorText, "anchorText");
19947
+ }
19844
19948
  );
19845
19949
  var hyperlinkTools = [
19846
19950
  insertHyperlink,
@@ -19869,7 +19973,10 @@ var insertContentControl = forwardTool(
19869
19973
  matchCase: { type: "boolean", description: "Default: false" }
19870
19974
  }
19871
19975
  },
19872
- "insertContentControl"
19976
+ "insertContentControl",
19977
+ (args) => {
19978
+ checkNonEmpty(args.anchorText, "anchorText");
19979
+ }
19873
19980
  );
19874
19981
  var setContentControlText = {
19875
19982
  name: "word_set_content_control_text",
@@ -20007,7 +20114,15 @@ var setPageLayout = forwardTool(
20007
20114
  paperSize: { type: "string", description: "Letter, A4, etc." }
20008
20115
  }
20009
20116
  },
20010
- "setPageLayout"
20117
+ "setPageLayout",
20118
+ (args) => {
20119
+ const hasProperty = args.orientation !== void 0 || args.topMargin !== void 0 || args.bottomMargin !== void 0 || args.leftMargin !== void 0 || args.rightMargin !== void 0 || args.paperSize !== void 0;
20120
+ if (!hasProperty) {
20121
+ throw new ToolError(
20122
+ "At least one layout property must be provided (orientation, topMargin, bottomMargin, leftMargin, rightMargin, paperSize)."
20123
+ );
20124
+ }
20125
+ }
20011
20126
  );
20012
20127
  var getSections = forwardTool(
20013
20128
  "word_get_sections",
@@ -20145,7 +20260,10 @@ var deleteCustomProperty = forwardTool(
20145
20260
  },
20146
20261
  required: ["key"]
20147
20262
  },
20148
- "deleteCustomProperty"
20263
+ "deleteCustomProperty",
20264
+ (args) => {
20265
+ checkNonEmpty(args.key, "key");
20266
+ }
20149
20267
  );
20150
20268
  var propertyTools = [
20151
20269
  getCustomProperties,
@@ -20205,7 +20323,6 @@ async function loadEquationLib() {
20205
20323
  buildEquationOoxml2 = lib.buildEquationOoxml;
20206
20324
  }
20207
20325
  }
20208
- var MARKER = "\u200B\uFEFF\u200B";
20209
20326
  var insertEquation = {
20210
20327
  name: "word_insert_equation",
20211
20328
  description: "[Equations] Insert a LaTeX math equation as a native editable Word equation. Display mode (default) inserts a centered block. Inline mode inserts after a search match (provide anchorText) or at cursor.",
@@ -20224,6 +20341,7 @@ var insertEquation = {
20224
20341
  await loadEquationLib();
20225
20342
  const { mml2omml: mml2omml2 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
20226
20343
  const latex = args.latex;
20344
+ checkNonEmpty(latex, "latex");
20227
20345
  const displayMode = args.displayMode !== false;
20228
20346
  let result;
20229
20347
  try {
@@ -20239,15 +20357,8 @@ var insertEquation = {
20239
20357
  } else if (args.anchorText) {
20240
20358
  const anchorText = args.anchorText;
20241
20359
  const matchCase = args.matchCase || false;
20242
- const searchResult = await bridge2.send("search", { query: anchorText, matchCase });
20243
- if (!searchResult || searchResult.count === 0) throw new ToolError("Anchor not found: " + anchorText);
20244
- const occurrence = checkOccurrence(args.occurrence, searchResult.count);
20245
- await bridge2.send("insertText", { text: " " + MARKER, after: anchorText, occurrence, matchCase });
20246
- try {
20247
- await bridge2.send("insertOoxmlAtSelection", { ooxml });
20248
- } finally {
20249
- await bridge2.send("searchAndReplace", { find: MARKER, replace: "" });
20250
- }
20360
+ const occurrence = args.occurrence ?? 0;
20361
+ await bridge2.send("insertOoxmlAfterMatch", { ooxml, anchorText, occurrence, matchCase });
20251
20362
  } else {
20252
20363
  await bridge2.send("insertOoxmlAtSelection", { ooxml });
20253
20364
  }
@@ -20259,7 +20370,7 @@ var equationTools = [
20259
20370
  ];
20260
20371
 
20261
20372
  // src/server/tools/batch.ts
20262
- function createBatchTool(registry, actionMap) {
20373
+ function createBatchTool(registry, actionMap, validators) {
20263
20374
  return {
20264
20375
  name: "word_batch",
20265
20376
  description: "[Batch] Execute multiple operations in a single call. Operations execute sequentially \u2014 if one fails, subsequent are skipped. Note: paragraph indices are NOT auto-adjusted between operations. Multiple inserts at the same index will produce reversed order (last inserted appears first).",
@@ -20328,6 +20439,16 @@ function createBatchTool(registry, actionMap) {
20328
20439
  break;
20329
20440
  }
20330
20441
  if (actionMap.has(op.tool)) {
20442
+ const validate = validators.get(op.tool);
20443
+ if (validate) {
20444
+ try {
20445
+ validate(op.args || {});
20446
+ } catch (e) {
20447
+ results.push({ index: i, tool: op.tool, success: false, error: e.message });
20448
+ stopped = true;
20449
+ break;
20450
+ }
20451
+ }
20331
20452
  nativeBuf.push({ tool: op.tool, args: op.args || {}, originalIndex: i });
20332
20453
  } else {
20333
20454
  const ok = await flushNative();
@@ -20387,13 +20508,17 @@ function buildToolRegistry() {
20387
20508
  ];
20388
20509
  const handlers = /* @__PURE__ */ new Map();
20389
20510
  const actionMap = /* @__PURE__ */ new Map();
20511
+ const validators = /* @__PURE__ */ new Map();
20390
20512
  for (const tool of allTools) {
20391
20513
  handlers.set(tool.name, tool.handler);
20392
20514
  if ("bridgeAction" in tool && typeof tool.bridgeAction === "string") {
20393
20515
  actionMap.set(tool.name, tool.bridgeAction);
20394
20516
  }
20517
+ if ("validate" in tool && typeof tool.validate === "function") {
20518
+ validators.set(tool.name, tool.validate);
20519
+ }
20395
20520
  }
20396
- const batchTool = createBatchTool(handlers, actionMap);
20521
+ const batchTool = createBatchTool(handlers, actionMap, validators);
20397
20522
  allTools.push(batchTool);
20398
20523
  handlers.set(batchTool.name, batchTool.handler);
20399
20524
  return { tools: allTools, handlers };
@@ -20534,7 +20659,7 @@ Output always uses canonical form: "Left", "Center", "Right", "Justified".
20534
20659
  // package.json
20535
20660
  var package_default = {
20536
20661
  name: "mcp-word-bridge",
20537
- version: "4.1.2",
20662
+ version: "4.1.4",
20538
20663
  description: "MCP server for live Word document editing via Office Add-in",
20539
20664
  main: "dist/server.js",
20540
20665
  bin: {
@@ -20599,7 +20724,7 @@ function createMcpServer(bridge2) {
20599
20724
  );
20600
20725
  const { tools, handlers } = buildToolRegistry();
20601
20726
  const toolMutex = createMutex();
20602
- server.setRequestHandler(import_types9.ListToolsRequestSchema, async () => ({
20727
+ server.setRequestHandler(import_types10.ListToolsRequestSchema, async () => ({
20603
20728
  tools: tools.map((t) => ({
20604
20729
  name: t.name,
20605
20730
  description: t.description,
@@ -20610,7 +20735,7 @@ function createMcpServer(bridge2) {
20610
20735
  }
20611
20736
  }))
20612
20737
  }));
20613
- server.setRequestHandler(import_types9.CallToolRequestSchema, async (request) => {
20738
+ server.setRequestHandler(import_types10.CallToolRequestSchema, async (request) => {
20614
20739
  return toolMutex.run(async () => {
20615
20740
  const { name, arguments: args } = request.params;
20616
20741
  const handler = handlers.get(name);
@@ -20626,7 +20751,7 @@ function createMcpServer(bridge2) {
20626
20751
  }
20627
20752
  });
20628
20753
  });
20629
- server.setRequestHandler(import_types9.ListResourcesRequestSchema, async () => ({
20754
+ server.setRequestHandler(import_types10.ListResourcesRequestSchema, async () => ({
20630
20755
  resources: [{
20631
20756
  uri: "word-bridge://usage-guide",
20632
20757
  name: "Word Bridge Usage Guide",
@@ -20634,7 +20759,7 @@ function createMcpServer(bridge2) {
20634
20759
  mimeType: "text/markdown"
20635
20760
  }]
20636
20761
  }));
20637
- server.setRequestHandler(import_types9.ReadResourceRequestSchema, async (request) => {
20762
+ server.setRequestHandler(import_types10.ReadResourceRequestSchema, async (request) => {
20638
20763
  if (request.params.uri === "word-bridge://usage-guide") {
20639
20764
  return { contents: [{ uri: request.params.uri, mimeType: "text/markdown", text: usageGuide }] };
20640
20765
  }
@@ -20705,7 +20830,7 @@ function createHttpsServer() {
20705
20830
  };
20706
20831
  const rootDir = import_path.default.join(__dirname, "..");
20707
20832
  return import_https.default.createServer(options, (req, res) => {
20708
- let urlPath = (req.url || "/").split("?")[0];
20833
+ const urlPath = (req.url || "/").split("?")[0];
20709
20834
  let filePath = urlPath === "/" ? "/taskpane.html" : urlPath;
20710
20835
  filePath = import_path.default.resolve(rootDir, "." + filePath);
20711
20836
  if (!filePath.startsWith(rootDir + import_path.default.sep) && filePath !== rootDir) {