mcp-word-bridge 4.1.2 → 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 +1 -0
- package/dist/server.js +83 -21
- package/dist/server.js.map +2 -2
- package/dist/taskpane-app.js +7 -6
- package/dist/taskpane-app.js.map +2 -2
- package/manifest.xml +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -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 {
|
|
@@ -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);
|
|
@@ -18935,6 +18937,14 @@ function checkHexColor(color, name) {
|
|
|
18935
18937
|
throw new ToolError(`${name} must be a valid hex color (e.g. #FF0000).`);
|
|
18936
18938
|
}
|
|
18937
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
|
+
}
|
|
18938
18948
|
|
|
18939
18949
|
// src/server/tools/paragraphs.ts
|
|
18940
18950
|
var getParagraphs = forwardTool(
|
|
@@ -19123,10 +19133,8 @@ var moveParagraph = {
|
|
|
19123
19133
|
}
|
|
19124
19134
|
}
|
|
19125
19135
|
const adjustedTo = fromIndex < toIndex ? toIndex - count : toIndex;
|
|
19126
|
-
|
|
19127
|
-
|
|
19128
|
-
}
|
|
19129
|
-
if (adjustedTo === fromIndex && location === "After") {
|
|
19136
|
+
const effectiveInsertPos = location === "Before" ? adjustedTo : adjustedTo + 1;
|
|
19137
|
+
if (effectiveInsertPos === fromIndex) {
|
|
19130
19138
|
return jsonResult({ success: true, warning: "No move performed \u2014 destination is equivalent to source position.", moved: null });
|
|
19131
19139
|
}
|
|
19132
19140
|
const ooxmlResult = await bridge2.send("getParaOoxml", { index: fromIndex, count });
|
|
@@ -19221,7 +19229,10 @@ var search = forwardTool(
|
|
|
19221
19229
|
},
|
|
19222
19230
|
required: ["query"]
|
|
19223
19231
|
},
|
|
19224
|
-
"search"
|
|
19232
|
+
"search",
|
|
19233
|
+
(args) => {
|
|
19234
|
+
checkNonEmpty(args.query, "query");
|
|
19235
|
+
}
|
|
19225
19236
|
);
|
|
19226
19237
|
var searchAndReplace = {
|
|
19227
19238
|
name: "word_search_and_replace",
|
|
@@ -19300,7 +19311,10 @@ var insertLineBreak = forwardTool(
|
|
|
19300
19311
|
},
|
|
19301
19312
|
required: ["anchorText"]
|
|
19302
19313
|
},
|
|
19303
|
-
"insertLineBreak"
|
|
19314
|
+
"insertLineBreak",
|
|
19315
|
+
(args) => {
|
|
19316
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19317
|
+
}
|
|
19304
19318
|
);
|
|
19305
19319
|
var searchTools = [
|
|
19306
19320
|
search,
|
|
@@ -19312,7 +19326,6 @@ var searchTools = [
|
|
|
19312
19326
|
];
|
|
19313
19327
|
|
|
19314
19328
|
// 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
19329
|
function validateFormatText(args) {
|
|
19317
19330
|
checkNonEmpty(args.text, "text");
|
|
19318
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;
|
|
@@ -19674,7 +19687,11 @@ var insertFootnote = forwardTool(
|
|
|
19674
19687
|
},
|
|
19675
19688
|
required: ["anchorText", "text"]
|
|
19676
19689
|
},
|
|
19677
|
-
"insertFootnote"
|
|
19690
|
+
"insertFootnote",
|
|
19691
|
+
(args) => {
|
|
19692
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19693
|
+
checkNonEmpty(args.text, "text");
|
|
19694
|
+
}
|
|
19678
19695
|
);
|
|
19679
19696
|
var insertFootnoteAtIndex = forwardTool(
|
|
19680
19697
|
"word_insert_footnote_at_index",
|
|
@@ -19686,7 +19703,10 @@ var insertFootnoteAtIndex = forwardTool(
|
|
|
19686
19703
|
},
|
|
19687
19704
|
required: ["paragraphIndex", "text"]
|
|
19688
19705
|
},
|
|
19689
|
-
"insertFootnoteAtIndex"
|
|
19706
|
+
"insertFootnoteAtIndex",
|
|
19707
|
+
(args) => {
|
|
19708
|
+
checkNonEmpty(args.text, "text");
|
|
19709
|
+
}
|
|
19690
19710
|
);
|
|
19691
19711
|
var insertEndnote = forwardTool(
|
|
19692
19712
|
"word_insert_endnote",
|
|
@@ -19700,7 +19720,11 @@ var insertEndnote = forwardTool(
|
|
|
19700
19720
|
},
|
|
19701
19721
|
required: ["anchorText", "text"]
|
|
19702
19722
|
},
|
|
19703
|
-
"insertEndnote"
|
|
19723
|
+
"insertEndnote",
|
|
19724
|
+
(args) => {
|
|
19725
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19726
|
+
checkNonEmpty(args.text, "text");
|
|
19727
|
+
}
|
|
19704
19728
|
);
|
|
19705
19729
|
var getFootnotes = forwardTool(
|
|
19706
19730
|
"word_get_footnotes",
|
|
@@ -19765,7 +19789,11 @@ var insertBookmark = forwardTool(
|
|
|
19765
19789
|
},
|
|
19766
19790
|
required: ["name", "anchorText"]
|
|
19767
19791
|
},
|
|
19768
|
-
"insertBookmark"
|
|
19792
|
+
"insertBookmark",
|
|
19793
|
+
(args) => {
|
|
19794
|
+
checkNonEmpty(args.name, "name");
|
|
19795
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19796
|
+
}
|
|
19769
19797
|
);
|
|
19770
19798
|
var deleteBookmark = forwardTool(
|
|
19771
19799
|
"word_delete_bookmark",
|
|
@@ -19776,7 +19804,10 @@ var deleteBookmark = forwardTool(
|
|
|
19776
19804
|
},
|
|
19777
19805
|
required: ["name"]
|
|
19778
19806
|
},
|
|
19779
|
-
"deleteBookmark"
|
|
19807
|
+
"deleteBookmark",
|
|
19808
|
+
(args) => {
|
|
19809
|
+
checkNonEmpty(args.name, "name");
|
|
19810
|
+
}
|
|
19780
19811
|
);
|
|
19781
19812
|
var goToBookmark = forwardTool(
|
|
19782
19813
|
"word_go_to_bookmark",
|
|
@@ -19787,7 +19818,10 @@ var goToBookmark = forwardTool(
|
|
|
19787
19818
|
},
|
|
19788
19819
|
required: ["name"]
|
|
19789
19820
|
},
|
|
19790
|
-
"goToBookmark"
|
|
19821
|
+
"goToBookmark",
|
|
19822
|
+
(args) => {
|
|
19823
|
+
checkNonEmpty(args.name, "name");
|
|
19824
|
+
}
|
|
19791
19825
|
);
|
|
19792
19826
|
var getBookmarkText = forwardTool(
|
|
19793
19827
|
"word_get_bookmark_text",
|
|
@@ -19798,7 +19832,10 @@ var getBookmarkText = forwardTool(
|
|
|
19798
19832
|
},
|
|
19799
19833
|
required: ["name"]
|
|
19800
19834
|
},
|
|
19801
|
-
"getBookmarkText"
|
|
19835
|
+
"getBookmarkText",
|
|
19836
|
+
(args) => {
|
|
19837
|
+
checkNonEmpty(args.name, "name");
|
|
19838
|
+
}
|
|
19802
19839
|
);
|
|
19803
19840
|
var bookmarkTools = [
|
|
19804
19841
|
getBookmarks,
|
|
@@ -19821,7 +19858,12 @@ var insertHyperlink = forwardTool(
|
|
|
19821
19858
|
},
|
|
19822
19859
|
required: ["anchorText", "url"]
|
|
19823
19860
|
},
|
|
19824
|
-
"insertHyperlink"
|
|
19861
|
+
"insertHyperlink",
|
|
19862
|
+
(args) => {
|
|
19863
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19864
|
+
checkNonEmpty(args.url, "url");
|
|
19865
|
+
checkUrl(args.url);
|
|
19866
|
+
}
|
|
19825
19867
|
);
|
|
19826
19868
|
var getHyperlinks = forwardTool(
|
|
19827
19869
|
"word_get_hyperlinks",
|
|
@@ -19840,7 +19882,10 @@ var removeHyperlink = forwardTool(
|
|
|
19840
19882
|
},
|
|
19841
19883
|
required: ["anchorText"]
|
|
19842
19884
|
},
|
|
19843
|
-
"removeHyperlink"
|
|
19885
|
+
"removeHyperlink",
|
|
19886
|
+
(args) => {
|
|
19887
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19888
|
+
}
|
|
19844
19889
|
);
|
|
19845
19890
|
var hyperlinkTools = [
|
|
19846
19891
|
insertHyperlink,
|
|
@@ -19869,7 +19914,10 @@ var insertContentControl = forwardTool(
|
|
|
19869
19914
|
matchCase: { type: "boolean", description: "Default: false" }
|
|
19870
19915
|
}
|
|
19871
19916
|
},
|
|
19872
|
-
"insertContentControl"
|
|
19917
|
+
"insertContentControl",
|
|
19918
|
+
(args) => {
|
|
19919
|
+
checkNonEmpty(args.anchorText, "anchorText");
|
|
19920
|
+
}
|
|
19873
19921
|
);
|
|
19874
19922
|
var setContentControlText = {
|
|
19875
19923
|
name: "word_set_content_control_text",
|
|
@@ -20259,7 +20307,7 @@ var equationTools = [
|
|
|
20259
20307
|
];
|
|
20260
20308
|
|
|
20261
20309
|
// src/server/tools/batch.ts
|
|
20262
|
-
function createBatchTool(registry, actionMap) {
|
|
20310
|
+
function createBatchTool(registry, actionMap, validators) {
|
|
20263
20311
|
return {
|
|
20264
20312
|
name: "word_batch",
|
|
20265
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).",
|
|
@@ -20328,6 +20376,16 @@ function createBatchTool(registry, actionMap) {
|
|
|
20328
20376
|
break;
|
|
20329
20377
|
}
|
|
20330
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
|
+
}
|
|
20331
20389
|
nativeBuf.push({ tool: op.tool, args: op.args || {}, originalIndex: i });
|
|
20332
20390
|
} else {
|
|
20333
20391
|
const ok = await flushNative();
|
|
@@ -20387,13 +20445,17 @@ function buildToolRegistry() {
|
|
|
20387
20445
|
];
|
|
20388
20446
|
const handlers = /* @__PURE__ */ new Map();
|
|
20389
20447
|
const actionMap = /* @__PURE__ */ new Map();
|
|
20448
|
+
const validators = /* @__PURE__ */ new Map();
|
|
20390
20449
|
for (const tool of allTools) {
|
|
20391
20450
|
handlers.set(tool.name, tool.handler);
|
|
20392
20451
|
if ("bridgeAction" in tool && typeof tool.bridgeAction === "string") {
|
|
20393
20452
|
actionMap.set(tool.name, tool.bridgeAction);
|
|
20394
20453
|
}
|
|
20454
|
+
if ("validate" in tool && typeof tool.validate === "function") {
|
|
20455
|
+
validators.set(tool.name, tool.validate);
|
|
20456
|
+
}
|
|
20395
20457
|
}
|
|
20396
|
-
const batchTool = createBatchTool(handlers, actionMap);
|
|
20458
|
+
const batchTool = createBatchTool(handlers, actionMap, validators);
|
|
20397
20459
|
allTools.push(batchTool);
|
|
20398
20460
|
handlers.set(batchTool.name, batchTool.handler);
|
|
20399
20461
|
return { tools: allTools, handlers };
|
|
@@ -20534,7 +20596,7 @@ Output always uses canonical form: "Left", "Center", "Right", "Justified".
|
|
|
20534
20596
|
// package.json
|
|
20535
20597
|
var package_default = {
|
|
20536
20598
|
name: "mcp-word-bridge",
|
|
20537
|
-
version: "4.1.
|
|
20599
|
+
version: "4.1.3",
|
|
20538
20600
|
description: "MCP server for live Word document editing via Office Add-in",
|
|
20539
20601
|
main: "dist/server.js",
|
|
20540
20602
|
bin: {
|
|
@@ -20705,7 +20767,7 @@ function createHttpsServer() {
|
|
|
20705
20767
|
};
|
|
20706
20768
|
const rootDir = import_path.default.join(__dirname, "..");
|
|
20707
20769
|
return import_https.default.createServer(options, (req, res) => {
|
|
20708
|
-
|
|
20770
|
+
const urlPath = (req.url || "/").split("?")[0];
|
|
20709
20771
|
let filePath = urlPath === "/" ? "/taskpane.html" : urlPath;
|
|
20710
20772
|
filePath = import_path.default.resolve(rootDir, "." + filePath);
|
|
20711
20773
|
if (!filePath.startsWith(rootDir + import_path.default.sep) && filePath !== rootDir) {
|