mcp-word-bridge 4.1.1 → 4.1.3
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 +5 -3
- package/dist/server.js +167 -34
- package/dist/server.js.map +2 -2
- package/dist/taskpane-app.js +27 -10
- package/dist/taskpane-app.js.map +2 -2
- package/manifest.xml +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -266,7 +266,7 @@ npm install
|
|
|
266
266
|
npm run build # build server + taskpane
|
|
267
267
|
npm run dev # watch mode
|
|
268
268
|
npm run typecheck # TypeScript type checking
|
|
269
|
-
npm test # unit tests (
|
|
269
|
+
npm test # unit tests (272 tests, <600ms)
|
|
270
270
|
npm run test:live # integration tests (requires Word)
|
|
271
271
|
```
|
|
272
272
|
|
|
@@ -299,8 +299,10 @@ Platform constraints in the Word JavaScript API that cannot be resolved in this
|
|
|
299
299
|
| Tracked Changes | `getTrackedChanges()` throws if the document contains "moved" tracked changes (drag-and-drop reorders). These produce paired "moved from"/"moved to" entries that the API cannot handle. | [office-js#5535](https://github.com/OfficeDev/office-js/issues/5535) |
|
|
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
|
-
|
|
|
303
|
-
|
|
|
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. | |
|
|
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) |
|
|
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) |
|
|
304
306
|
|
|
305
307
|
## License
|
|
306
308
|
|
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
|
|
18726
|
+
var import_types9 = require("@modelcontextprotocol/sdk/types.js");
|
|
18726
18727
|
|
|
18727
18728
|
// src/server/types.ts
|
|
18728
18729
|
var ToolError = class extends Error {
|
|
@@ -18736,13 +18737,15 @@ var ToolError = class extends Error {
|
|
|
18736
18737
|
function jsonResult(data) {
|
|
18737
18738
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
18738
18739
|
}
|
|
18739
|
-
function forwardTool(name, description, schema, action) {
|
|
18740
|
+
function forwardTool(name, description, schema, action, validate) {
|
|
18740
18741
|
return {
|
|
18741
18742
|
name,
|
|
18742
18743
|
description,
|
|
18743
18744
|
schema,
|
|
18744
18745
|
bridgeAction: action,
|
|
18746
|
+
validate,
|
|
18745
18747
|
async handler(args, bridge2) {
|
|
18748
|
+
if (validate) validate(args);
|
|
18746
18749
|
const result = await bridge2.send(action, args);
|
|
18747
18750
|
return jsonResult(result);
|
|
18748
18751
|
}
|
|
@@ -18788,7 +18791,7 @@ var save = forwardTool(
|
|
|
18788
18791
|
);
|
|
18789
18792
|
var clear = forwardTool(
|
|
18790
18793
|
"word_clear",
|
|
18791
|
-
"[Document] Clear all document body content. Does not clear headers/footers or custom properties.",
|
|
18794
|
+
"[Document] Clear all document body content. Does not clear headers/footers or custom properties. In multi-section documents, section breaks are removed and the last section's layout (margins, orientation) is preserved.",
|
|
18792
18795
|
{ properties: {} },
|
|
18793
18796
|
"clearDocument"
|
|
18794
18797
|
);
|
|
@@ -18929,6 +18932,19 @@ function checkPropertyKeyLength(key) {
|
|
|
18929
18932
|
);
|
|
18930
18933
|
}
|
|
18931
18934
|
}
|
|
18935
|
+
function checkHexColor(color, name) {
|
|
18936
|
+
if (!/^#[0-9A-Fa-f]{6}$/.test(color)) {
|
|
18937
|
+
throw new ToolError(`${name} must be a valid hex color (e.g. #FF0000).`);
|
|
18938
|
+
}
|
|
18939
|
+
}
|
|
18940
|
+
function checkUrl(url) {
|
|
18941
|
+
if (!/^https?:\/\/.+/i.test(url)) {
|
|
18942
|
+
throw new ToolError("URL must be a valid HTTP or HTTPS URL (e.g. https://example.com).");
|
|
18943
|
+
}
|
|
18944
|
+
if (/[<>"{}|\\^`]/.test(url)) {
|
|
18945
|
+
throw new ToolError(`Malformed URL: "${url}". URL contains invalid characters that must be percent-encoded.`);
|
|
18946
|
+
}
|
|
18947
|
+
}
|
|
18932
18948
|
|
|
18933
18949
|
// src/server/tools/paragraphs.ts
|
|
18934
18950
|
var getParagraphs = forwardTool(
|
|
@@ -18944,7 +18960,7 @@ var getParagraphs = forwardTool(
|
|
|
18944
18960
|
);
|
|
18945
18961
|
var getParagraphByIndex = forwardTool(
|
|
18946
18962
|
"word_get_paragraph_by_index",
|
|
18947
|
-
"[Paragraphs] Get full details of a single paragraph including font, spacing, indentation, and outline level.",
|
|
18963
|
+
"[Paragraphs] Get full details of a single paragraph including font, spacing, indentation, and outline level. Font properties return null when the paragraph has mixed formatting (e.g. partially bold).",
|
|
18948
18964
|
{
|
|
18949
18965
|
properties: {
|
|
18950
18966
|
index: { type: "number", description: "Paragraph index (0-based)" }
|
|
@@ -19036,6 +19052,12 @@ var setParagraphSpacing = {
|
|
|
19036
19052
|
async handler(args, bridge2) {
|
|
19037
19053
|
const index = args.index;
|
|
19038
19054
|
checkNonNegative(index, "index");
|
|
19055
|
+
const hasProperty = args.lineSpacing !== void 0 || args.spaceBefore !== void 0 || args.spaceAfter !== void 0 || args.firstLineIndent !== void 0 || args.leftIndent !== void 0 || args.rightIndent !== void 0;
|
|
19056
|
+
if (!hasProperty) {
|
|
19057
|
+
throw new ToolError(
|
|
19058
|
+
"At least one spacing or indent property must be provided (lineSpacing, spaceBefore, spaceAfter, firstLineIndent, leftIndent, rightIndent)."
|
|
19059
|
+
);
|
|
19060
|
+
}
|
|
19039
19061
|
const spacingFields = [
|
|
19040
19062
|
["lineSpacing", args.lineSpacing],
|
|
19041
19063
|
["spaceBefore", args.spaceBefore],
|
|
@@ -19095,6 +19117,12 @@ var moveParagraph = {
|
|
|
19095
19117
|
throw new ToolError(`Paragraph ${para.index} is inside a table cell. Use table-specific tools to modify table content.`);
|
|
19096
19118
|
}
|
|
19097
19119
|
}
|
|
19120
|
+
const destPara = paraCount.paragraphs.find((p) => p.index === toIndex);
|
|
19121
|
+
if (destPara?.inTable) {
|
|
19122
|
+
throw new ToolError(
|
|
19123
|
+
`Destination paragraph ${toIndex} is inside a table cell. Moving content here would corrupt table structure. Use table-specific tools or target a paragraph outside the table.`
|
|
19124
|
+
);
|
|
19125
|
+
}
|
|
19098
19126
|
const lastIdx = total - 1;
|
|
19099
19127
|
if (fromIndex + count - 1 === lastIdx) {
|
|
19100
19128
|
const lastPara = paraCount.paragraphs.find((p) => p.index === lastIdx);
|
|
@@ -19105,10 +19133,8 @@ var moveParagraph = {
|
|
|
19105
19133
|
}
|
|
19106
19134
|
}
|
|
19107
19135
|
const adjustedTo = fromIndex < toIndex ? toIndex - count : toIndex;
|
|
19108
|
-
|
|
19109
|
-
|
|
19110
|
-
}
|
|
19111
|
-
if (adjustedTo === fromIndex && location === "After") {
|
|
19136
|
+
const effectiveInsertPos = location === "Before" ? adjustedTo : adjustedTo + 1;
|
|
19137
|
+
if (effectiveInsertPos === fromIndex) {
|
|
19112
19138
|
return jsonResult({ success: true, warning: "No move performed \u2014 destination is equivalent to source position.", moved: null });
|
|
19113
19139
|
}
|
|
19114
19140
|
const ooxmlResult = await bridge2.send("getParaOoxml", { index: fromIndex, count });
|
|
@@ -19160,6 +19186,12 @@ var copyParagraph = {
|
|
|
19160
19186
|
throw new ToolError(`Paragraph ${para.index} is inside a table cell. Use table-specific tools to modify table content.`);
|
|
19161
19187
|
}
|
|
19162
19188
|
}
|
|
19189
|
+
const destPara = paraCount.paragraphs.find((p) => p.index === toIndex);
|
|
19190
|
+
if (destPara?.inTable) {
|
|
19191
|
+
throw new ToolError(
|
|
19192
|
+
`Destination paragraph ${toIndex} is inside a table cell. Copying content here would corrupt table structure. Use table-specific tools or target a paragraph outside the table.`
|
|
19193
|
+
);
|
|
19194
|
+
}
|
|
19163
19195
|
const ooxmlResult = await bridge2.send("getParaOoxml", { index: fromIndex, count });
|
|
19164
19196
|
const effectiveToIndex = toIndex >= fromIndex && toIndex < fromIndex + count ? fromIndex + count - 1 : toIndex;
|
|
19165
19197
|
const effectiveLocation = toIndex >= fromIndex && toIndex < fromIndex + count ? "After" : location;
|
|
@@ -19197,7 +19229,10 @@ var search = forwardTool(
|
|
|
19197
19229
|
},
|
|
19198
19230
|
required: ["query"]
|
|
19199
19231
|
},
|
|
19200
|
-
"search"
|
|
19232
|
+
"search",
|
|
19233
|
+
(args) => {
|
|
19234
|
+
checkNonEmpty(args.query, "query");
|
|
19235
|
+
}
|
|
19201
19236
|
);
|
|
19202
19237
|
var searchAndReplace = {
|
|
19203
19238
|
name: "word_search_and_replace",
|
|
@@ -19228,7 +19263,7 @@ var searchAndReplace = {
|
|
|
19228
19263
|
};
|
|
19229
19264
|
var insertTextAtMatch = forwardTool(
|
|
19230
19265
|
"word_insert_text_at_match",
|
|
19231
|
-
'[Search] Insert text before or after a search match. Supports Word search codes in inserted text: ^p (paragraph mark), ^l (line break), ^t (tab), ^s (non-breaking space), ^m (page break), ^n (column break), ^~ (non-breaking hyphen), ^- (optional hyphen), ^+ (em dash), ^= (en dash), ^^ (literal caret). Provide "after" OR "before" as the anchor text. Use occurrence for Nth match.',
|
|
19266
|
+
'[Search] Insert text before or after a search match. Supports Word search codes in inserted text: ^p (paragraph mark), ^l (line break), ^t (tab), ^s (non-breaking space), ^m (page break), ^n (column break), ^~ (non-breaking hyphen), ^- (optional hyphen), ^+ (em dash), ^= (en dash), ^^ (literal caret). Provide "after" OR "before" as the anchor text. Use occurrence for Nth match. Note: inserting ^p after hyperlinked text may create a paragraph that inherits the hyperlink character style.',
|
|
19232
19267
|
{
|
|
19233
19268
|
properties: {
|
|
19234
19269
|
text: { type: "string", description: "Text to insert" },
|
|
@@ -19276,7 +19311,10 @@ var insertLineBreak = forwardTool(
|
|
|
19276
19311
|
},
|
|
19277
19312
|
required: ["anchorText"]
|
|
19278
19313
|
},
|
|
19279
|
-
"insertLineBreak"
|
|
19314
|
+
"insertLineBreak",
|
|
19315
|
+
(args) => {
|
|
19316
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19317
|
+
}
|
|
19280
19318
|
);
|
|
19281
19319
|
var searchTools = [
|
|
19282
19320
|
search,
|
|
@@ -19288,6 +19326,33 @@ var searchTools = [
|
|
|
19288
19326
|
];
|
|
19289
19327
|
|
|
19290
19328
|
// src/server/tools/formatting.ts
|
|
19329
|
+
function validateFormatText(args) {
|
|
19330
|
+
checkNonEmpty(args.text, "text");
|
|
19331
|
+
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;
|
|
19332
|
+
if (!hasFormatting) {
|
|
19333
|
+
throw new ToolError(
|
|
19334
|
+
"At least one formatting property must be specified (bold, italic, underline, strikeThrough, color, highlightColor, size, or name)."
|
|
19335
|
+
);
|
|
19336
|
+
}
|
|
19337
|
+
if (args.size !== void 0) {
|
|
19338
|
+
const size = args.size;
|
|
19339
|
+
if (size <= 0) throw new ToolError("size must be positive (minimum 1 point).");
|
|
19340
|
+
if (size > 1638) throw new ToolError("size must not exceed 1638 points (Word maximum).");
|
|
19341
|
+
if (!Number.isFinite(size)) throw new ToolError("size must be a finite number.");
|
|
19342
|
+
}
|
|
19343
|
+
if (args.color !== void 0) {
|
|
19344
|
+
checkHexColor(args.color, "color");
|
|
19345
|
+
}
|
|
19346
|
+
if (args.highlightColor !== void 0) {
|
|
19347
|
+
const hc = args.highlightColor;
|
|
19348
|
+
const isNamed = HIGHLIGHT_COLORS.some((c) => c.toLowerCase() === hc.toLowerCase());
|
|
19349
|
+
if (!isNamed) {
|
|
19350
|
+
throw new ToolError(
|
|
19351
|
+
`Invalid highlightColor: "${hc}". Valid values: ${HIGHLIGHT_COLORS.join(", ")}.`
|
|
19352
|
+
);
|
|
19353
|
+
}
|
|
19354
|
+
}
|
|
19355
|
+
}
|
|
19291
19356
|
var formatText = forwardTool(
|
|
19292
19357
|
"word_format_text",
|
|
19293
19358
|
"[Formatting] Apply formatting (bold, italic, color, size, font) to a text match. Color must be hex (#FF0000). Size: 1-1638pt.",
|
|
@@ -19307,7 +19372,8 @@ var formatText = forwardTool(
|
|
|
19307
19372
|
},
|
|
19308
19373
|
required: ["text"]
|
|
19309
19374
|
},
|
|
19310
|
-
"formatRange"
|
|
19375
|
+
"formatRange",
|
|
19376
|
+
validateFormatText
|
|
19311
19377
|
);
|
|
19312
19378
|
var clearFormatting = forwardTool(
|
|
19313
19379
|
"word_clear_formatting",
|
|
@@ -19486,7 +19552,7 @@ var tableTools = [
|
|
|
19486
19552
|
// src/server/tools/lists.ts
|
|
19487
19553
|
var insertList = forwardTool(
|
|
19488
19554
|
"word_insert_list",
|
|
19489
|
-
"[Lists] Insert a bulleted or numbered list from an array of item strings.",
|
|
19555
|
+
"[Lists] Insert a bulleted or numbered list from an array of item strings. Text is inserted literally (Word search codes like ^p or ^t are NOT interpreted).",
|
|
19490
19556
|
{
|
|
19491
19557
|
properties: {
|
|
19492
19558
|
items: { type: "array", items: { type: "string" }, description: "List item strings" },
|
|
@@ -19539,7 +19605,11 @@ var addComment = forwardTool(
|
|
|
19539
19605
|
},
|
|
19540
19606
|
required: ["anchorText", "comment"]
|
|
19541
19607
|
},
|
|
19542
|
-
"addComment"
|
|
19608
|
+
"addComment",
|
|
19609
|
+
(args) => {
|
|
19610
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19611
|
+
checkNonEmpty(args.comment, "comment");
|
|
19612
|
+
}
|
|
19543
19613
|
);
|
|
19544
19614
|
var getComments = forwardTool(
|
|
19545
19615
|
"word_get_comments",
|
|
@@ -19568,7 +19638,10 @@ var replyToComment = forwardTool(
|
|
|
19568
19638
|
},
|
|
19569
19639
|
required: ["commentId", "text"]
|
|
19570
19640
|
},
|
|
19571
|
-
"replyToComment"
|
|
19641
|
+
"replyToComment",
|
|
19642
|
+
(args) => {
|
|
19643
|
+
checkNonEmpty(args.text, "text");
|
|
19644
|
+
}
|
|
19572
19645
|
);
|
|
19573
19646
|
var resolveComment = forwardTool(
|
|
19574
19647
|
"word_resolve_comment",
|
|
@@ -19614,7 +19687,11 @@ var insertFootnote = forwardTool(
|
|
|
19614
19687
|
},
|
|
19615
19688
|
required: ["anchorText", "text"]
|
|
19616
19689
|
},
|
|
19617
|
-
"insertFootnote"
|
|
19690
|
+
"insertFootnote",
|
|
19691
|
+
(args) => {
|
|
19692
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19693
|
+
checkNonEmpty(args.text, "text");
|
|
19694
|
+
}
|
|
19618
19695
|
);
|
|
19619
19696
|
var insertFootnoteAtIndex = forwardTool(
|
|
19620
19697
|
"word_insert_footnote_at_index",
|
|
@@ -19626,7 +19703,10 @@ var insertFootnoteAtIndex = forwardTool(
|
|
|
19626
19703
|
},
|
|
19627
19704
|
required: ["paragraphIndex", "text"]
|
|
19628
19705
|
},
|
|
19629
|
-
"insertFootnoteAtIndex"
|
|
19706
|
+
"insertFootnoteAtIndex",
|
|
19707
|
+
(args) => {
|
|
19708
|
+
checkNonEmpty(args.text, "text");
|
|
19709
|
+
}
|
|
19630
19710
|
);
|
|
19631
19711
|
var insertEndnote = forwardTool(
|
|
19632
19712
|
"word_insert_endnote",
|
|
@@ -19640,7 +19720,11 @@ var insertEndnote = forwardTool(
|
|
|
19640
19720
|
},
|
|
19641
19721
|
required: ["anchorText", "text"]
|
|
19642
19722
|
},
|
|
19643
|
-
"insertEndnote"
|
|
19723
|
+
"insertEndnote",
|
|
19724
|
+
(args) => {
|
|
19725
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19726
|
+
checkNonEmpty(args.text, "text");
|
|
19727
|
+
}
|
|
19644
19728
|
);
|
|
19645
19729
|
var getFootnotes = forwardTool(
|
|
19646
19730
|
"word_get_footnotes",
|
|
@@ -19705,7 +19789,11 @@ var insertBookmark = forwardTool(
|
|
|
19705
19789
|
},
|
|
19706
19790
|
required: ["name", "anchorText"]
|
|
19707
19791
|
},
|
|
19708
|
-
"insertBookmark"
|
|
19792
|
+
"insertBookmark",
|
|
19793
|
+
(args) => {
|
|
19794
|
+
checkNonEmpty(args.name, "name");
|
|
19795
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19796
|
+
}
|
|
19709
19797
|
);
|
|
19710
19798
|
var deleteBookmark = forwardTool(
|
|
19711
19799
|
"word_delete_bookmark",
|
|
@@ -19716,7 +19804,10 @@ var deleteBookmark = forwardTool(
|
|
|
19716
19804
|
},
|
|
19717
19805
|
required: ["name"]
|
|
19718
19806
|
},
|
|
19719
|
-
"deleteBookmark"
|
|
19807
|
+
"deleteBookmark",
|
|
19808
|
+
(args) => {
|
|
19809
|
+
checkNonEmpty(args.name, "name");
|
|
19810
|
+
}
|
|
19720
19811
|
);
|
|
19721
19812
|
var goToBookmark = forwardTool(
|
|
19722
19813
|
"word_go_to_bookmark",
|
|
@@ -19727,7 +19818,10 @@ var goToBookmark = forwardTool(
|
|
|
19727
19818
|
},
|
|
19728
19819
|
required: ["name"]
|
|
19729
19820
|
},
|
|
19730
|
-
"goToBookmark"
|
|
19821
|
+
"goToBookmark",
|
|
19822
|
+
(args) => {
|
|
19823
|
+
checkNonEmpty(args.name, "name");
|
|
19824
|
+
}
|
|
19731
19825
|
);
|
|
19732
19826
|
var getBookmarkText = forwardTool(
|
|
19733
19827
|
"word_get_bookmark_text",
|
|
@@ -19738,7 +19832,10 @@ var getBookmarkText = forwardTool(
|
|
|
19738
19832
|
},
|
|
19739
19833
|
required: ["name"]
|
|
19740
19834
|
},
|
|
19741
|
-
"getBookmarkText"
|
|
19835
|
+
"getBookmarkText",
|
|
19836
|
+
(args) => {
|
|
19837
|
+
checkNonEmpty(args.name, "name");
|
|
19838
|
+
}
|
|
19742
19839
|
);
|
|
19743
19840
|
var bookmarkTools = [
|
|
19744
19841
|
getBookmarks,
|
|
@@ -19761,7 +19858,12 @@ var insertHyperlink = forwardTool(
|
|
|
19761
19858
|
},
|
|
19762
19859
|
required: ["anchorText", "url"]
|
|
19763
19860
|
},
|
|
19764
|
-
"insertHyperlink"
|
|
19861
|
+
"insertHyperlink",
|
|
19862
|
+
(args) => {
|
|
19863
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19864
|
+
checkNonEmpty(args.url, "url");
|
|
19865
|
+
checkUrl(args.url);
|
|
19866
|
+
}
|
|
19765
19867
|
);
|
|
19766
19868
|
var getHyperlinks = forwardTool(
|
|
19767
19869
|
"word_get_hyperlinks",
|
|
@@ -19780,7 +19882,10 @@ var removeHyperlink = forwardTool(
|
|
|
19780
19882
|
},
|
|
19781
19883
|
required: ["anchorText"]
|
|
19782
19884
|
},
|
|
19783
|
-
"removeHyperlink"
|
|
19885
|
+
"removeHyperlink",
|
|
19886
|
+
(args) => {
|
|
19887
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19888
|
+
}
|
|
19784
19889
|
);
|
|
19785
19890
|
var hyperlinkTools = [
|
|
19786
19891
|
insertHyperlink,
|
|
@@ -19809,7 +19914,10 @@ var insertContentControl = forwardTool(
|
|
|
19809
19914
|
matchCase: { type: "boolean", description: "Default: false" }
|
|
19810
19915
|
}
|
|
19811
19916
|
},
|
|
19812
|
-
"insertContentControl"
|
|
19917
|
+
"insertContentControl",
|
|
19918
|
+
(args) => {
|
|
19919
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19920
|
+
}
|
|
19813
19921
|
);
|
|
19814
19922
|
var setContentControlText = {
|
|
19815
19923
|
name: "word_set_content_control_text",
|
|
@@ -20199,10 +20307,10 @@ var equationTools = [
|
|
|
20199
20307
|
];
|
|
20200
20308
|
|
|
20201
20309
|
// src/server/tools/batch.ts
|
|
20202
|
-
function createBatchTool(registry, actionMap) {
|
|
20310
|
+
function createBatchTool(registry, actionMap, validators) {
|
|
20203
20311
|
return {
|
|
20204
20312
|
name: "word_batch",
|
|
20205
|
-
description: "[Batch] Execute multiple operations in a single call. Operations execute sequentially \u2014 if one fails, subsequent are skipped.",
|
|
20313
|
+
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).",
|
|
20206
20314
|
schema: {
|
|
20207
20315
|
properties: {
|
|
20208
20316
|
operations: {
|
|
@@ -20268,6 +20376,16 @@ function createBatchTool(registry, actionMap) {
|
|
|
20268
20376
|
break;
|
|
20269
20377
|
}
|
|
20270
20378
|
if (actionMap.has(op.tool)) {
|
|
20379
|
+
const validate = validators.get(op.tool);
|
|
20380
|
+
if (validate) {
|
|
20381
|
+
try {
|
|
20382
|
+
validate(op.args || {});
|
|
20383
|
+
} catch (e) {
|
|
20384
|
+
results.push({ index: i, tool: op.tool, success: false, error: e.message });
|
|
20385
|
+
stopped = true;
|
|
20386
|
+
break;
|
|
20387
|
+
}
|
|
20388
|
+
}
|
|
20271
20389
|
nativeBuf.push({ tool: op.tool, args: op.args || {}, originalIndex: i });
|
|
20272
20390
|
} else {
|
|
20273
20391
|
const ok = await flushNative();
|
|
@@ -20327,13 +20445,17 @@ function buildToolRegistry() {
|
|
|
20327
20445
|
];
|
|
20328
20446
|
const handlers = /* @__PURE__ */ new Map();
|
|
20329
20447
|
const actionMap = /* @__PURE__ */ new Map();
|
|
20448
|
+
const validators = /* @__PURE__ */ new Map();
|
|
20330
20449
|
for (const tool of allTools) {
|
|
20331
20450
|
handlers.set(tool.name, tool.handler);
|
|
20332
20451
|
if ("bridgeAction" in tool && typeof tool.bridgeAction === "string") {
|
|
20333
20452
|
actionMap.set(tool.name, tool.bridgeAction);
|
|
20334
20453
|
}
|
|
20454
|
+
if ("validate" in tool && typeof tool.validate === "function") {
|
|
20455
|
+
validators.set(tool.name, tool.validate);
|
|
20456
|
+
}
|
|
20335
20457
|
}
|
|
20336
|
-
const batchTool = createBatchTool(handlers, actionMap);
|
|
20458
|
+
const batchTool = createBatchTool(handlers, actionMap, validators);
|
|
20337
20459
|
allTools.push(batchTool);
|
|
20338
20460
|
handlers.set(batchTool.name, batchTool.handler);
|
|
20339
20461
|
return { tools: allTools, handlers };
|
|
@@ -20398,6 +20520,8 @@ Controls a live Word document. All operations execute immediately.
|
|
|
20398
20520
|
\`\`\`
|
|
20399
20521
|
Runs sequentially. Stops on first error. Maximum 50 per batch. Prefer batching over individual calls.
|
|
20400
20522
|
|
|
20523
|
+
**Index caveat:** Paragraph indices are NOT auto-adjusted between operations. Multiple inserts at the same index produce reversed order (last inserted appears first). To insert A, B, C in order at position 5, either increment the index or insert in reverse.
|
|
20524
|
+
|
|
20401
20525
|
## Search
|
|
20402
20526
|
|
|
20403
20527
|
- Case-insensitive by default. Pass \`matchCase: true\` for exact case.
|
|
@@ -20458,12 +20582,21 @@ After inserting a Table of Contents, heading text appears twice (in TOC and body
|
|
|
20458
20582
|
6. Use \`word_copy_paragraph\` to duplicate (preserves everything)
|
|
20459
20583
|
7. Save explicitly after significant changes
|
|
20460
20584
|
8. Resolve comments rather than deleting (preserves audit trail)
|
|
20585
|
+
|
|
20586
|
+
## Alignment Values
|
|
20587
|
+
|
|
20588
|
+
Input accepts case-insensitive: "left", "center", "right", "justified".
|
|
20589
|
+
Output always uses canonical form: "Left", "Center", "Right", "Justified".
|
|
20590
|
+
|
|
20591
|
+
## Mixed Formatting
|
|
20592
|
+
|
|
20593
|
+
\`word_get_paragraph_by_index\` returns \`null\` for font properties when the paragraph has mixed formatting (e.g. partially bold). Treat \`null\` as "mixed" \u2014 use \`word_get_font_info\` with a specific text match for precise values.
|
|
20461
20594
|
`;
|
|
20462
20595
|
|
|
20463
20596
|
// package.json
|
|
20464
20597
|
var package_default = {
|
|
20465
20598
|
name: "mcp-word-bridge",
|
|
20466
|
-
version: "4.1.
|
|
20599
|
+
version: "4.1.3",
|
|
20467
20600
|
description: "MCP server for live Word document editing via Office Add-in",
|
|
20468
20601
|
main: "dist/server.js",
|
|
20469
20602
|
bin: {
|
|
@@ -20528,7 +20661,7 @@ function createMcpServer(bridge2) {
|
|
|
20528
20661
|
);
|
|
20529
20662
|
const { tools, handlers } = buildToolRegistry();
|
|
20530
20663
|
const toolMutex = createMutex();
|
|
20531
|
-
server.setRequestHandler(
|
|
20664
|
+
server.setRequestHandler(import_types9.ListToolsRequestSchema, async () => ({
|
|
20532
20665
|
tools: tools.map((t) => ({
|
|
20533
20666
|
name: t.name,
|
|
20534
20667
|
description: t.description,
|
|
@@ -20539,7 +20672,7 @@ function createMcpServer(bridge2) {
|
|
|
20539
20672
|
}
|
|
20540
20673
|
}))
|
|
20541
20674
|
}));
|
|
20542
|
-
server.setRequestHandler(
|
|
20675
|
+
server.setRequestHandler(import_types9.CallToolRequestSchema, async (request) => {
|
|
20543
20676
|
return toolMutex.run(async () => {
|
|
20544
20677
|
const { name, arguments: args } = request.params;
|
|
20545
20678
|
const handler = handlers.get(name);
|
|
@@ -20555,7 +20688,7 @@ function createMcpServer(bridge2) {
|
|
|
20555
20688
|
}
|
|
20556
20689
|
});
|
|
20557
20690
|
});
|
|
20558
|
-
server.setRequestHandler(
|
|
20691
|
+
server.setRequestHandler(import_types9.ListResourcesRequestSchema, async () => ({
|
|
20559
20692
|
resources: [{
|
|
20560
20693
|
uri: "word-bridge://usage-guide",
|
|
20561
20694
|
name: "Word Bridge Usage Guide",
|
|
@@ -20563,7 +20696,7 @@ function createMcpServer(bridge2) {
|
|
|
20563
20696
|
mimeType: "text/markdown"
|
|
20564
20697
|
}]
|
|
20565
20698
|
}));
|
|
20566
|
-
server.setRequestHandler(
|
|
20699
|
+
server.setRequestHandler(import_types9.ReadResourceRequestSchema, async (request) => {
|
|
20567
20700
|
if (request.params.uri === "word-bridge://usage-guide") {
|
|
20568
20701
|
return { contents: [{ uri: request.params.uri, mimeType: "text/markdown", text: usageGuide }] };
|
|
20569
20702
|
}
|
|
@@ -20634,7 +20767,7 @@ function createHttpsServer() {
|
|
|
20634
20767
|
};
|
|
20635
20768
|
const rootDir = import_path.default.join(__dirname, "..");
|
|
20636
20769
|
return import_https.default.createServer(options, (req, res) => {
|
|
20637
|
-
|
|
20770
|
+
const urlPath = (req.url || "/").split("?")[0];
|
|
20638
20771
|
let filePath = urlPath === "/" ? "/taskpane.html" : urlPath;
|
|
20639
20772
|
filePath = import_path.default.resolve(rootDir, "." + filePath);
|
|
20640
20773
|
if (!filePath.startsWith(rootDir + import_path.default.sep) && filePath !== rootDir) {
|