mcp-word-bridge 4.0.2 → 4.0.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 +3 -0
- package/dist/server.js +240 -91
- package/dist/server.js.map +4 -4
- package/dist/taskpane-app.js +61 -31
- package/dist/taskpane-app.js.map +2 -2
- package/manifest.xml +1 -1
- package/package.json +11 -2
- package/dist/server.cjs +0 -20495
- package/dist/server.cjs.map +0 -7
package/README.md
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# MCP Word Bridge
|
|
2
2
|
|
|
3
|
+
[](https://github.com/likelion/mcp-word-bridge/actions/workflows/tests.yml)
|
|
4
|
+
[](https://codecov.io/gh/likelion/mcp-word-bridge)
|
|
5
|
+
|
|
3
6
|
MCP server for live Word document editing via Office Add-in. Enables programmatic editing of Word documents through the Word JavaScript API, with changes appearing as user edits in co-authoring sessions.
|
|
4
7
|
|
|
5
8
|
## Quick Start
|
package/dist/server.js
CHANGED
|
@@ -18722,7 +18722,7 @@ var Bridge = class {
|
|
|
18722
18722
|
|
|
18723
18723
|
// src/server/mcp.ts
|
|
18724
18724
|
var import_server = require("@modelcontextprotocol/sdk/server/index.js");
|
|
18725
|
-
var
|
|
18725
|
+
var import_types7 = require("@modelcontextprotocol/sdk/types.js");
|
|
18726
18726
|
|
|
18727
18727
|
// src/server/tools/helpers.ts
|
|
18728
18728
|
function jsonResult(data) {
|
|
@@ -18874,9 +18874,23 @@ var ToolError = class extends Error {
|
|
|
18874
18874
|
};
|
|
18875
18875
|
|
|
18876
18876
|
// src/server/validation.ts
|
|
18877
|
+
var WORD_SPECIAL_CODES = /\^(p|w|t|l|m|b|n|s|d|a|e|f|g|v|~|\^|\-|13|11|14|12|07|09|30|31|32|34|36|37|38|39|40|41|42|43|44|45|46|47|92|94|127|129|130|131|132|133|134|135|136|137|138|139|140|141|142|143|144|145|146|147|148|149|150|151|152|153|154|155|156|157|158|159|160|161|162|163|164|165|166|167|168|169|170|171|172|173|174|175|176|177|178|179|180|181|182|183|184|185|186|187|188|189|190|191|192|193|194|195|196|197|198|199|200|201|202|203|204|205|206|207|208|209|210|211|212|213|214|215|216|217|218|219|220|221|222|223|224|225|226|227|228|229|230|231|232|233|234|235|236|237|238|239|240|241|242|243|244|245|246|247|248|249|250|251|252|253|254|255)/;
|
|
18878
|
+
function checkNoSpecialCodes(text2, paramName) {
|
|
18879
|
+
const match = text2.match(WORD_SPECIAL_CODES);
|
|
18880
|
+
if (match) {
|
|
18881
|
+
throw new ToolError(
|
|
18882
|
+
`${paramName} contains Word special code "${match[0]}" which can corrupt document structure. Use literal text only. Common special codes: ^p (paragraph mark), ^t (tab), ^w (whitespace), ^13 (paragraph mark).`
|
|
18883
|
+
);
|
|
18884
|
+
}
|
|
18885
|
+
}
|
|
18886
|
+
function checkNonEmpty(value, name) {
|
|
18887
|
+
if (!value || typeof value !== "string" || value.trim() === "") {
|
|
18888
|
+
throw new ToolError(`${name} must be a non-empty string.`);
|
|
18889
|
+
}
|
|
18890
|
+
}
|
|
18877
18891
|
function checkNonNegative(value, name) {
|
|
18878
|
-
if (typeof value !== "number" || value < 0) {
|
|
18879
|
-
throw new ToolError(`${name} must be non-negative.`);
|
|
18892
|
+
if (typeof value !== "number" || value < 0 || !Number.isInteger(value)) {
|
|
18893
|
+
throw new ToolError(`${name} must be a non-negative integer.`);
|
|
18880
18894
|
}
|
|
18881
18895
|
}
|
|
18882
18896
|
function checkBounds(index, count, name) {
|
|
@@ -18898,6 +18912,22 @@ function checkOccurrence(occurrence, count) {
|
|
|
18898
18912
|
}
|
|
18899
18913
|
return idx;
|
|
18900
18914
|
}
|
|
18915
|
+
var MAX_SPACING_POINTS = 1584;
|
|
18916
|
+
var MAX_CUSTOM_PROPERTY_KEY_LENGTH = 255;
|
|
18917
|
+
function checkSpacingBounds(value, name) {
|
|
18918
|
+
if (value > MAX_SPACING_POINTS) {
|
|
18919
|
+
throw new ToolError(
|
|
18920
|
+
`${name} value ${value} exceeds maximum (${MAX_SPACING_POINTS} points = 22 inches). Use a value between 0 and ${MAX_SPACING_POINTS}.`
|
|
18921
|
+
);
|
|
18922
|
+
}
|
|
18923
|
+
}
|
|
18924
|
+
function checkPropertyKeyLength(key) {
|
|
18925
|
+
if (key.length > MAX_CUSTOM_PROPERTY_KEY_LENGTH) {
|
|
18926
|
+
throw new ToolError(
|
|
18927
|
+
`key must be ${MAX_CUSTOM_PROPERTY_KEY_LENGTH} characters or fewer (got ${key.length}).`
|
|
18928
|
+
);
|
|
18929
|
+
}
|
|
18930
|
+
}
|
|
18901
18931
|
|
|
18902
18932
|
// src/server/tools/paragraphs.ts
|
|
18903
18933
|
var getParagraphs = forwardTool(
|
|
@@ -18987,10 +19017,10 @@ var setParagraphStyle = forwardTool(
|
|
|
18987
19017
|
},
|
|
18988
19018
|
"setParagraphStyle"
|
|
18989
19019
|
);
|
|
18990
|
-
var setParagraphSpacing =
|
|
18991
|
-
"word_set_paragraph_spacing",
|
|
18992
|
-
"[Paragraphs] Set line spacing, before/after spacing, and indentation on a paragraph by index.",
|
|
18993
|
-
{
|
|
19020
|
+
var setParagraphSpacing = {
|
|
19021
|
+
name: "word_set_paragraph_spacing",
|
|
19022
|
+
description: "[Paragraphs] Set line spacing, before/after spacing, and indentation on a paragraph by index.",
|
|
19023
|
+
schema: {
|
|
18994
19024
|
properties: {
|
|
18995
19025
|
index: { type: "number", description: "Paragraph index (0-based)" },
|
|
18996
19026
|
lineSpacing: { type: "number", description: "Line spacing in points" },
|
|
@@ -19002,8 +19032,26 @@ var setParagraphSpacing = forwardTool(
|
|
|
19002
19032
|
},
|
|
19003
19033
|
required: ["index"]
|
|
19004
19034
|
},
|
|
19005
|
-
|
|
19006
|
-
|
|
19035
|
+
async handler(args, bridge2) {
|
|
19036
|
+
const index = args.index;
|
|
19037
|
+
checkNonNegative(index, "index");
|
|
19038
|
+
const spacingFields = [
|
|
19039
|
+
["lineSpacing", args.lineSpacing],
|
|
19040
|
+
["spaceBefore", args.spaceBefore],
|
|
19041
|
+
["spaceAfter", args.spaceAfter],
|
|
19042
|
+
["firstLineIndent", args.firstLineIndent],
|
|
19043
|
+
["leftIndent", args.leftIndent],
|
|
19044
|
+
["rightIndent", args.rightIndent]
|
|
19045
|
+
];
|
|
19046
|
+
for (const [name, value] of spacingFields) {
|
|
19047
|
+
if (value !== void 0 && typeof value === "number") {
|
|
19048
|
+
checkSpacingBounds(value, name);
|
|
19049
|
+
}
|
|
19050
|
+
}
|
|
19051
|
+
const result = await bridge2.send("setParagraphSpacing", args);
|
|
19052
|
+
return jsonResult(result);
|
|
19053
|
+
}
|
|
19054
|
+
};
|
|
19007
19055
|
var moveParagraph = {
|
|
19008
19056
|
name: "word_move_paragraph",
|
|
19009
19057
|
description: "[Paragraphs] Move paragraph(s) to another position. Preserves all rich content including footnotes, hyperlinks, formatting, images, and comments.",
|
|
@@ -19023,6 +19071,7 @@ var moveParagraph = {
|
|
|
19023
19071
|
const location = args.location ?? "After";
|
|
19024
19072
|
checkNonNegative(fromIndex, "fromIndex");
|
|
19025
19073
|
checkNonNegative(toIndex, "toIndex");
|
|
19074
|
+
if (!Number.isInteger(count)) throw new ToolError("count must be an integer.");
|
|
19026
19075
|
if (count < 1) throw new ToolError("count must be at least 1");
|
|
19027
19076
|
if (fromIndex === toIndex && count === 1) throw new ToolError("fromIndex and toIndex must be different");
|
|
19028
19077
|
if (toIndex >= fromIndex && toIndex < fromIndex + count) {
|
|
@@ -19071,6 +19120,7 @@ var copyParagraph = {
|
|
|
19071
19120
|
const location = args.location ?? "After";
|
|
19072
19121
|
checkNonNegative(fromIndex, "fromIndex");
|
|
19073
19122
|
checkNonNegative(toIndex, "toIndex");
|
|
19123
|
+
if (!Number.isInteger(count)) throw new ToolError("count must be an integer.");
|
|
19074
19124
|
if (count < 1) throw new ToolError("count must be at least 1");
|
|
19075
19125
|
const paraCount = await bridge2.send("getParagraphs", {});
|
|
19076
19126
|
const total = paraCount.count;
|
|
@@ -19109,10 +19159,10 @@ var search = forwardTool(
|
|
|
19109
19159
|
},
|
|
19110
19160
|
"search"
|
|
19111
19161
|
);
|
|
19112
|
-
var searchAndReplace =
|
|
19113
|
-
"word_search_and_replace",
|
|
19114
|
-
"[Search] Find and replace ALL occurrences. For single-paragraph edits, prefer word_replace_paragraph_text.",
|
|
19115
|
-
{
|
|
19162
|
+
var searchAndReplace = {
|
|
19163
|
+
name: "word_search_and_replace",
|
|
19164
|
+
description: "[Search] Find and replace ALL occurrences. For single-paragraph edits, prefer word_replace_paragraph_text.",
|
|
19165
|
+
schema: {
|
|
19116
19166
|
properties: {
|
|
19117
19167
|
find: { type: "string" },
|
|
19118
19168
|
replace: { type: "string" },
|
|
@@ -19121,8 +19171,18 @@ var searchAndReplace = forwardTool(
|
|
|
19121
19171
|
},
|
|
19122
19172
|
required: ["find", "replace"]
|
|
19123
19173
|
},
|
|
19124
|
-
|
|
19125
|
-
|
|
19174
|
+
async handler(args, bridge2) {
|
|
19175
|
+
const find = args.find;
|
|
19176
|
+
const replace = args.replace;
|
|
19177
|
+
if (!find || typeof find !== "string" || find.trim() === "") {
|
|
19178
|
+
throw new ToolError("find string cannot be empty.");
|
|
19179
|
+
}
|
|
19180
|
+
checkNoSpecialCodes(find, "find");
|
|
19181
|
+
checkNoSpecialCodes(replace, "replace");
|
|
19182
|
+
const result = await bridge2.send("searchAndReplace", args);
|
|
19183
|
+
return jsonResult(result);
|
|
19184
|
+
}
|
|
19185
|
+
};
|
|
19126
19186
|
var insertTextAtMatch = forwardTool(
|
|
19127
19187
|
"word_insert_text_at_match",
|
|
19128
19188
|
'[Search] Insert text before or after a search match. Provide "after" OR "before" as the anchor text. Use occurrence for Nth match.',
|
|
@@ -19703,10 +19763,10 @@ var insertContentControl = forwardTool(
|
|
|
19703
19763
|
},
|
|
19704
19764
|
"insertContentControl"
|
|
19705
19765
|
);
|
|
19706
|
-
var setContentControlText =
|
|
19707
|
-
"word_set_content_control_text",
|
|
19708
|
-
"[Content Controls] Set text in a content control identified by ID or tag. Does NOT work on CheckBox controls.",
|
|
19709
|
-
{
|
|
19766
|
+
var setContentControlText = {
|
|
19767
|
+
name: "word_set_content_control_text",
|
|
19768
|
+
description: "[Content Controls] Set text in a content control identified by ID or tag. Does NOT work on CheckBox controls.",
|
|
19769
|
+
schema: {
|
|
19710
19770
|
properties: {
|
|
19711
19771
|
id: { type: "number", description: "Content control ID" },
|
|
19712
19772
|
tag: { type: "string", description: "Content control tag (alternative to ID)" },
|
|
@@ -19714,8 +19774,25 @@ var setContentControlText = forwardTool(
|
|
|
19714
19774
|
},
|
|
19715
19775
|
required: ["text"]
|
|
19716
19776
|
},
|
|
19717
|
-
|
|
19718
|
-
|
|
19777
|
+
async handler(args, bridge2) {
|
|
19778
|
+
const tag = args.tag;
|
|
19779
|
+
const id = args.id;
|
|
19780
|
+
if (!tag && id === void 0) {
|
|
19781
|
+
throw new ToolError('Provide "id" or "tag" to identify the content control. Use word_get_content_controls to list available controls.');
|
|
19782
|
+
}
|
|
19783
|
+
if (tag) {
|
|
19784
|
+
const ccResult = await bridge2.send("getContentControls", {});
|
|
19785
|
+
const matches = ccResult.controls.filter((c) => c.tag === tag);
|
|
19786
|
+
if (matches.length > 1) {
|
|
19787
|
+
throw new ToolError(
|
|
19788
|
+
`Multiple content controls (${matches.length}) share tag "${tag}". Use "id" instead to target a specific control. Matching IDs: ${matches.map((m) => m.id).join(", ")}.`
|
|
19789
|
+
);
|
|
19790
|
+
}
|
|
19791
|
+
}
|
|
19792
|
+
const result = await bridge2.send("setContentControlText", args);
|
|
19793
|
+
return jsonResult(result);
|
|
19794
|
+
}
|
|
19795
|
+
};
|
|
19719
19796
|
var contentControlTools = [
|
|
19720
19797
|
getContentControls,
|
|
19721
19798
|
insertContentControl,
|
|
@@ -19930,18 +20007,24 @@ var getCustomProperties = forwardTool(
|
|
|
19930
20007
|
{ properties: {} },
|
|
19931
20008
|
"getCustomProperties"
|
|
19932
20009
|
);
|
|
19933
|
-
var setCustomProperty =
|
|
19934
|
-
"word_set_custom_property",
|
|
19935
|
-
"[Properties] Set a custom document property. Creates or updates the key-value pair.",
|
|
19936
|
-
{
|
|
20010
|
+
var setCustomProperty = {
|
|
20011
|
+
name: "word_set_custom_property",
|
|
20012
|
+
description: "[Properties] Set a custom document property. Creates or updates the key-value pair.",
|
|
20013
|
+
schema: {
|
|
19937
20014
|
properties: {
|
|
19938
20015
|
key: { type: "string" },
|
|
19939
20016
|
value: { type: "string" }
|
|
19940
20017
|
},
|
|
19941
20018
|
required: ["key", "value"]
|
|
19942
20019
|
},
|
|
19943
|
-
|
|
19944
|
-
|
|
20020
|
+
async handler(args, bridge2) {
|
|
20021
|
+
const key = args.key;
|
|
20022
|
+
checkNonEmpty(key, "key");
|
|
20023
|
+
checkPropertyKeyLength(key);
|
|
20024
|
+
const result = await bridge2.send("setCustomProperty", args);
|
|
20025
|
+
return jsonResult(result);
|
|
20026
|
+
}
|
|
20027
|
+
};
|
|
19945
20028
|
var deleteCustomProperty = forwardTool(
|
|
19946
20029
|
"word_delete_custom_property",
|
|
19947
20030
|
"[Properties] Delete a custom document property by key.",
|
|
@@ -20203,112 +20286,178 @@ function buildToolRegistry() {
|
|
|
20203
20286
|
// src/server/usage-guide.ts
|
|
20204
20287
|
var usageGuide = `# MCP Word Bridge \u2014 Usage Guide
|
|
20205
20288
|
|
|
20206
|
-
Controls a live Word document
|
|
20289
|
+
Controls a live Word document. All operations execute immediately.
|
|
20207
20290
|
|
|
20208
20291
|
## Quick Start
|
|
20209
20292
|
|
|
20210
|
-
1. **Read before writing** \u2014 call \`word_get_document_outline\` or \`word_get_paragraphs\` to understand
|
|
20211
|
-
2. **Use the right tool
|
|
20212
|
-
-
|
|
20213
|
-
-
|
|
20214
|
-
-
|
|
20293
|
+
1. **Read before writing** \u2014 call \`word_get_document_outline\` or \`word_get_paragraphs\` to understand structure
|
|
20294
|
+
2. **Use the right tool:**
|
|
20295
|
+
- Append content \u2192 \`word_insert_paragraph\` (Start/End)
|
|
20296
|
+
- Insert at position \u2192 \`word_insert_paragraph_at_index\` (Before/After by index)
|
|
20297
|
+
- Edit existing text \u2192 \`word_replace_paragraph_text\` (by index, preferred)
|
|
20215
20298
|
- Bulk find/replace \u2192 \`word_search_and_replace\` (all occurrences)
|
|
20216
|
-
-
|
|
20217
|
-
3. **Batch
|
|
20299
|
+
- Insert adjacent to text \u2192 \`word_insert_text_at_match\` (searches then inserts)
|
|
20300
|
+
3. **Batch operations** \u2014 use \`word_batch\` for multiple operations in one call (faster, fewer round-trips)
|
|
20218
20301
|
4. **Save explicitly** \u2014 call \`word_save\` after significant changes
|
|
20219
20302
|
|
|
20220
|
-
## Reading
|
|
20221
|
-
|
|
20222
|
-
- \`
|
|
20223
|
-
- \`
|
|
20303
|
+
## Reading
|
|
20304
|
+
|
|
20305
|
+
- \`word_get_document_outline\` \u2014 heading tree (fast structural overview)
|
|
20306
|
+
- \`word_get_paragraphs\` \u2014 paragraphs with text, style, alignment. Paginate with start/end.
|
|
20307
|
+
- \`word_get_paragraph_by_index\` \u2014 full details of one paragraph (font, spacing, indent)
|
|
20308
|
+
- \`word_get_text\` \u2014 plain-text dump (no structure)
|
|
20224
20309
|
- \`word_search\` \u2014 locate text before operating on it
|
|
20225
20310
|
|
|
20226
|
-
## Editing
|
|
20227
|
-
|
|
20311
|
+
## Editing
|
|
20312
|
+
|
|
20313
|
+
- \`word_replace_paragraph_text\` \u2014 replace by index (safe for collaboration, preserves style)
|
|
20228
20314
|
- \`word_search_and_replace\` \u2014 bulk find/replace across document
|
|
20229
|
-
- \`word_insert_text_at_match\` \u2014 insert before/after a search match
|
|
20230
|
-
- \`word_move_paragraph\` \u2014 reorder content (
|
|
20231
|
-
- \`word_copy_paragraph\` \u2014 duplicate content with full fidelity
|
|
20315
|
+
- \`word_insert_text_at_match\` \u2014 insert before/after a search match (use \`occurrence\` for Nth)
|
|
20316
|
+
- \`word_move_paragraph\` \u2014 reorder content (preserves footnotes, formatting, hyperlinks)
|
|
20317
|
+
- \`word_copy_paragraph\` \u2014 duplicate content with full fidelity
|
|
20318
|
+
- Verify edits with \`word_search\` or \`word_get_paragraphs\`
|
|
20319
|
+
|
|
20320
|
+
## Batch
|
|
20232
20321
|
|
|
20233
|
-
## Batch Operations
|
|
20234
|
-
\`word_batch\` executes multiple operations in a single MCP call:
|
|
20235
20322
|
\`\`\`json
|
|
20236
20323
|
{"operations": [
|
|
20237
20324
|
{"tool": "word_insert_paragraph", "args": {"text": "Hello", "style": "Heading 1"}},
|
|
20238
|
-
{"tool": "word_insert_paragraph", "args": {"text": "World"
|
|
20239
|
-
{"tool": "
|
|
20325
|
+
{"tool": "word_insert_paragraph", "args": {"text": "World"}},
|
|
20326
|
+
{"tool": "word_format_text", "args": {"text": "Hello", "bold": true}}
|
|
20240
20327
|
]}
|
|
20241
20328
|
\`\`\`
|
|
20242
|
-
|
|
20329
|
+
Runs sequentially. Stops on first error. Maximum 50 per batch. Prefer batching over individual calls.
|
|
20243
20330
|
|
|
20244
|
-
## Search
|
|
20245
|
-
- Case-insensitive by default. Pass \`matchCase: true\` for exact case match.
|
|
20246
|
-
- Query must be \u2264255 characters.
|
|
20247
|
-
- Affected tools: search, search_and_replace, insert_text_at_match, format_text, insert_footnote, add_comment, insert_hyperlink, insert_bookmark, insert_content_control, clear_formatting, get_font_info, insert_line_break, remove_hyperlink, insert_endnote, insert_equation (inline with anchor).
|
|
20331
|
+
## Search
|
|
20248
20332
|
|
|
20249
|
-
|
|
20250
|
-
|
|
20333
|
+
- Case-insensitive by default. Pass \`matchCase: true\` for exact case.
|
|
20334
|
+
- Text parameters must be \u2264255 characters.
|
|
20335
|
+
- Use \`occurrence\` (0-indexed) to target the Nth match when multiple exist.
|
|
20251
20336
|
|
|
20252
|
-
##
|
|
20253
|
-
- Call \`word_set_change_tracking({mode:"TrackAll"})\` BEFORE edits for tracked changes
|
|
20254
|
-
- \`search_and_replace\` with tracking may only expose the "Added" half
|
|
20337
|
+
## Comments \u2014 Important
|
|
20255
20338
|
|
|
20256
|
-
|
|
20257
|
-
-
|
|
20258
|
-
-
|
|
20259
|
-
- Safe pattern: get_comments \u2192 reply/resolve \u2192 then replace text
|
|
20339
|
+
- \`word_get_comments\` returns comments with their anchor text
|
|
20340
|
+
- **Replacing text that anchors a comment deletes the comment** \u2014 always check comments first
|
|
20341
|
+
- Safe pattern: \`word_get_comments\` \u2192 \`word_reply_to_comment\`/\`word_resolve_comment\` \u2192 then replace
|
|
20260
20342
|
|
|
20261
20343
|
## Tables
|
|
20344
|
+
|
|
20262
20345
|
- All indices 0-based: tableIndex, row, col
|
|
20263
|
-
- \`word_list_tables\` for metadata
|
|
20264
|
-
-
|
|
20346
|
+
- \`word_list_tables\` for metadata \u2192 \`word_get_table_data\` for cell values
|
|
20347
|
+
- Cannot insert page/section breaks inside table cells
|
|
20265
20348
|
|
|
20266
20349
|
## Footnotes & Endnotes
|
|
20267
|
-
|
|
20268
|
-
- \`
|
|
20350
|
+
|
|
20351
|
+
- \`word_insert_footnote\` \u2014 anchor to a text match
|
|
20352
|
+
- \`word_insert_footnote_at_index\` \u2014 anchor to a paragraph by index (no search needed)
|
|
20269
20353
|
|
|
20270
20354
|
## Page Layout
|
|
20355
|
+
|
|
20271
20356
|
- Margins in points (72 pt = 1 inch)
|
|
20272
|
-
- \`lineSpacing\` in points, not a multiplier (12pt font: 12=single, 18=1.5x, 24=double)
|
|
20357
|
+
- \`lineSpacing\` is in points, not a multiplier (12pt font: 12=single, 18=1.5x, 24=double)
|
|
20273
20358
|
|
|
20274
20359
|
## Content Controls
|
|
20275
|
-
- RichText/PlainText: wraps the anchor text (non-destructive)
|
|
20276
|
-
- CheckBox: REPLACES anchor text with a checkbox (cannot use set_content_control_text)
|
|
20277
20360
|
|
|
20278
|
-
|
|
20361
|
+
- RichText/PlainText: wraps anchor text (non-destructive)
|
|
20362
|
+
- CheckBox: REPLACES anchor text with a checkbox (cannot set text after)
|
|
20363
|
+
|
|
20364
|
+
## TOC
|
|
20365
|
+
|
|
20279
20366
|
After inserting a Table of Contents, heading text appears twice (in TOC and body). Search matches TOC entries first \u2014 use \`occurrence\` to target the body instance.
|
|
20280
20367
|
|
|
20281
20368
|
## Equations
|
|
20282
|
-
|
|
20283
|
-
- \`
|
|
20284
|
-
-
|
|
20369
|
+
|
|
20370
|
+
- \`word_insert_equation\` takes LaTeX, inserts a native editable Word equation
|
|
20371
|
+
- \`displayMode: true\` (default) = centered block equation
|
|
20372
|
+
- \`displayMode: false\` = inline; provide \`anchorText\` to position after a match
|
|
20285
20373
|
- Supports: fractions, roots, integrals, sums, matrices, Greek letters, AMS math
|
|
20374
|
+
- Examples: \`\\\\frac{a}{b}\`, \`\\\\int_0^\\\\infty e^{-x} dx\`, \`\\\\sum_{i=1}^n x_i\`
|
|
20375
|
+
|
|
20376
|
+
## Change Tracking
|
|
20286
20377
|
|
|
20287
|
-
|
|
20288
|
-
-
|
|
20289
|
-
- "Anchor not found" \u2014 search text not found in document
|
|
20290
|
-
- "Occurrence N not found" \u2014 match index out of range
|
|
20378
|
+
- Call \`word_set_change_tracking({mode:"TrackAll"})\` BEFORE edits to track them
|
|
20379
|
+
- Tracked edits appear as revisions in Word (visible to collaborators)
|
|
20291
20380
|
|
|
20292
20381
|
## Best Practices
|
|
20293
|
-
|
|
20294
|
-
|
|
20295
|
-
|
|
20296
|
-
|
|
20297
|
-
|
|
20298
|
-
|
|
20299
|
-
|
|
20300
|
-
|
|
20301
|
-
|
|
20382
|
+
|
|
20383
|
+
1. Read structure first (\`word_get_document_outline\`)
|
|
20384
|
+
2. Check comments before bulk replacements (\`word_get_comments\`)
|
|
20385
|
+
3. Batch multiple operations (\`word_batch\`)
|
|
20386
|
+
4. Prefer index-based tools over search-based in collaborative editing
|
|
20387
|
+
5. Use \`word_move_paragraph\` to reorder (not delete+insert)
|
|
20388
|
+
6. Use \`word_copy_paragraph\` to duplicate (preserves everything)
|
|
20389
|
+
7. Save explicitly after significant changes
|
|
20390
|
+
8. Resolve comments rather than deleting (preserves audit trail)
|
|
20302
20391
|
`;
|
|
20303
20392
|
|
|
20393
|
+
// package.json
|
|
20394
|
+
var package_default = {
|
|
20395
|
+
name: "mcp-word-bridge",
|
|
20396
|
+
version: "4.0.4",
|
|
20397
|
+
description: "MCP server for live Word document editing via Office Add-in",
|
|
20398
|
+
main: "dist/server.js",
|
|
20399
|
+
bin: {
|
|
20400
|
+
"mcp-word-bridge": "dist/server.js",
|
|
20401
|
+
"mcp-word-bridge-install": "scripts/install-manifest.js"
|
|
20402
|
+
},
|
|
20403
|
+
scripts: {
|
|
20404
|
+
build: "node scripts/build.mjs",
|
|
20405
|
+
start: "node dist/server.cjs",
|
|
20406
|
+
dev: "node scripts/build.mjs --watch",
|
|
20407
|
+
test: "vitest run",
|
|
20408
|
+
"test:ci": "vitest run --reporter=default",
|
|
20409
|
+
"test:live": "vitest run --config vitest.live.config.ts",
|
|
20410
|
+
lint: "eslint src/",
|
|
20411
|
+
typecheck: "tsc --noEmit",
|
|
20412
|
+
prepublishOnly: "npm run build"
|
|
20413
|
+
},
|
|
20414
|
+
keywords: [
|
|
20415
|
+
"mcp",
|
|
20416
|
+
"word",
|
|
20417
|
+
"office",
|
|
20418
|
+
"add-in",
|
|
20419
|
+
"document",
|
|
20420
|
+
"editing"
|
|
20421
|
+
],
|
|
20422
|
+
repository: {
|
|
20423
|
+
type: "git",
|
|
20424
|
+
url: "https://github.com/likelion/mcp-word-bridge.git"
|
|
20425
|
+
},
|
|
20426
|
+
author: "Leonid Mokrushin <likelion@gmail.com>",
|
|
20427
|
+
license: "MIT",
|
|
20428
|
+
dependencies: {
|
|
20429
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
20430
|
+
mathml2omml: "^0.5.0",
|
|
20431
|
+
temml: "^0.13.3",
|
|
20432
|
+
ws: "^8.16.0"
|
|
20433
|
+
},
|
|
20434
|
+
devDependencies: {
|
|
20435
|
+
"@types/node": "^22.0.0",
|
|
20436
|
+
"@types/ws": "^8.5.10",
|
|
20437
|
+
"@vitest/coverage-v8": "^3.2.6",
|
|
20438
|
+
esbuild: "^0.25.0",
|
|
20439
|
+
eslint: "^10.4.1",
|
|
20440
|
+
typescript: "^5.7.0",
|
|
20441
|
+
"typescript-eslint": "^8.0.0",
|
|
20442
|
+
vitest: "^3.0.0"
|
|
20443
|
+
},
|
|
20444
|
+
files: [
|
|
20445
|
+
"dist/",
|
|
20446
|
+
"certs/cert.conf",
|
|
20447
|
+
"taskpane.html",
|
|
20448
|
+
"manifest.xml",
|
|
20449
|
+
"scripts/install-manifest.js"
|
|
20450
|
+
]
|
|
20451
|
+
};
|
|
20452
|
+
|
|
20304
20453
|
// src/server/mcp.ts
|
|
20305
20454
|
function createMcpServer(bridge2) {
|
|
20306
20455
|
const server = new import_server.Server(
|
|
20307
|
-
{ name: "mcp-word-bridge", version:
|
|
20456
|
+
{ name: "mcp-word-bridge", version: package_default.version },
|
|
20308
20457
|
{ capabilities: { tools: {}, resources: {} } }
|
|
20309
20458
|
);
|
|
20310
20459
|
const { tools, handlers } = buildToolRegistry();
|
|
20311
|
-
server.setRequestHandler(
|
|
20460
|
+
server.setRequestHandler(import_types7.ListToolsRequestSchema, async () => ({
|
|
20312
20461
|
tools: tools.map((t) => ({
|
|
20313
20462
|
name: t.name,
|
|
20314
20463
|
description: t.description,
|
|
@@ -20319,7 +20468,7 @@ function createMcpServer(bridge2) {
|
|
|
20319
20468
|
}
|
|
20320
20469
|
}))
|
|
20321
20470
|
}));
|
|
20322
|
-
server.setRequestHandler(
|
|
20471
|
+
server.setRequestHandler(import_types7.CallToolRequestSchema, async (request) => {
|
|
20323
20472
|
const { name, arguments: args } = request.params;
|
|
20324
20473
|
const handler = handlers.get(name);
|
|
20325
20474
|
if (!handler) {
|
|
@@ -20333,7 +20482,7 @@ function createMcpServer(bridge2) {
|
|
|
20333
20482
|
return { content: [{ type: "text", text: msg }], isError: true };
|
|
20334
20483
|
}
|
|
20335
20484
|
});
|
|
20336
|
-
server.setRequestHandler(
|
|
20485
|
+
server.setRequestHandler(import_types7.ListResourcesRequestSchema, async () => ({
|
|
20337
20486
|
resources: [{
|
|
20338
20487
|
uri: "word-bridge://usage-guide",
|
|
20339
20488
|
name: "Word Bridge Usage Guide",
|
|
@@ -20341,7 +20490,7 @@ function createMcpServer(bridge2) {
|
|
|
20341
20490
|
mimeType: "text/markdown"
|
|
20342
20491
|
}]
|
|
20343
20492
|
}));
|
|
20344
|
-
server.setRequestHandler(
|
|
20493
|
+
server.setRequestHandler(import_types7.ReadResourceRequestSchema, async (request) => {
|
|
20345
20494
|
if (request.params.uri === "word-bridge://usage-guide") {
|
|
20346
20495
|
return { contents: [{ uri: request.params.uri, mimeType: "text/markdown", text: usageGuide }] };
|
|
20347
20496
|
}
|