mcp-word-bridge 3.4.2 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -9
- package/index.js +189 -25
- package/lib/tools.js +99 -94
- package/lib/usage-guide.js +62 -34
- package/package.json +1 -1
- package/taskpane-app.js +66 -2
- package/taskpane.html +1 -0
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ That's it. The MCP server starts automatically when your MCP client loads the co
|
|
|
58
58
|
mcp-word-bridge/
|
|
59
59
|
├── index.js # Entry point: HTTPS server + MCP handlers + WebSocket relay
|
|
60
60
|
├── lib/
|
|
61
|
-
│ ├── tools.js # Tool definitions
|
|
61
|
+
│ ├── tools.js # Tool definitions and action mapping
|
|
62
62
|
│ ├── equations.js # LaTeX→OMML pipeline (fixDelimiters, fixNaryOperands, latexToOmml)
|
|
63
63
|
│ └── usage-guide.js # MCP resource content (usage patterns for LLMs)
|
|
64
64
|
├── taskpane-app.js # Client-side Word JS API logic (runs in add-in)
|
|
@@ -80,7 +80,7 @@ mcp-word-bridge/
|
|
|
80
80
|
└── README.md
|
|
81
81
|
```
|
|
82
82
|
|
|
83
|
-
## Tools (
|
|
83
|
+
## Tools (90)
|
|
84
84
|
|
|
85
85
|
### Document
|
|
86
86
|
| Tool | Description |
|
|
@@ -95,14 +95,18 @@ mcp-word-bridge/
|
|
|
95
95
|
| `word_get_styles` | List available styles |
|
|
96
96
|
| `word_get_coauthors` | Get co-authoring status and active authors |
|
|
97
97
|
| `word_set_change_tracking` | Enable/disable track changes |
|
|
98
|
+
| `word_get_document_outline` | Get heading hierarchy as a structured outline tree |
|
|
98
99
|
|
|
99
100
|
### Paragraphs
|
|
100
101
|
| Tool | Description |
|
|
101
102
|
|------|-------------|
|
|
102
103
|
| `word_get_paragraphs` | Get paragraphs with style, alignment, TOC flag (paginated) |
|
|
103
104
|
| `word_get_paragraph_by_index` | Get full details of one paragraph (font, spacing, indent) |
|
|
104
|
-
| `word_insert_paragraph` |
|
|
105
|
+
| `word_insert_paragraph` | Append/prepend a styled paragraph at document Start or End |
|
|
106
|
+
| `word_insert_paragraph_at_index` | Insert a paragraph before or after a specific paragraph index |
|
|
105
107
|
| `word_delete_paragraph` | Delete a paragraph by index |
|
|
108
|
+
| `word_replace_paragraph_text` | Replace paragraph text by index (preserves style) |
|
|
109
|
+
| `word_move_paragraph` | Move one or more consecutive paragraphs to another position |
|
|
106
110
|
| `word_set_paragraph_style` | Change style or alignment of a paragraph |
|
|
107
111
|
| `word_set_paragraph_spacing` | Set line spacing, before/after, and indentation |
|
|
108
112
|
|
|
@@ -111,7 +115,7 @@ mcp-word-bridge/
|
|
|
111
115
|
|------|-------------|
|
|
112
116
|
| `word_search` | Find text matches (case-insensitive by default) |
|
|
113
117
|
| `word_search_and_replace` | Find and replace all occurrences |
|
|
114
|
-
| `
|
|
118
|
+
| `word_insert_text_at_match` | Insert text before or after a search match |
|
|
115
119
|
| `word_get_selection_info` | Get current selection with font details |
|
|
116
120
|
| `word_insert_text_at_selection` | Insert or replace text at cursor |
|
|
117
121
|
| `word_insert_line_break` | Insert a soft line break (Shift+Enter) |
|
|
@@ -127,7 +131,7 @@ mcp-word-bridge/
|
|
|
127
131
|
| Tool | Description |
|
|
128
132
|
|------|-------------|
|
|
129
133
|
| `word_insert_table` | Insert a table with data and optional style |
|
|
130
|
-
| `
|
|
134
|
+
| `word_list_tables` | List table metadata (count, dimensions, style) — no cell values |
|
|
131
135
|
| `word_get_table_data` | Get cell values from a specific table |
|
|
132
136
|
| `word_set_table_cell` | Set text in a cell |
|
|
133
137
|
| `word_add_table_row` | Add a row with optional values |
|
|
@@ -148,13 +152,11 @@ mcp-word-bridge/
|
|
|
148
152
|
| Tool | Description |
|
|
149
153
|
|------|-------------|
|
|
150
154
|
| `word_add_comment` | Add a comment anchored to text |
|
|
151
|
-
| `word_get_comments` | Get all comments with author, date, status |
|
|
155
|
+
| `word_get_comments` | Get all comments with author, date, status, and anchor text |
|
|
152
156
|
| `word_get_comment_replies` | Get replies for a comment |
|
|
153
157
|
| `word_reply_to_comment` | Reply to a comment |
|
|
154
158
|
| `word_resolve_comment` | Mark a comment as resolved |
|
|
155
159
|
| `word_delete_comment` | Delete a comment and its replies |
|
|
156
|
-
| `word_get_comment_anchor` | Get the document text a comment is anchored to |
|
|
157
|
-
| `word_get_comments_with_anchor` | Get all comments with their anchor text included |
|
|
158
160
|
|
|
159
161
|
### Footnotes & Endnotes
|
|
160
162
|
| Tool | Description |
|
|
@@ -239,7 +241,12 @@ mcp-word-bridge/
|
|
|
239
241
|
### Equations
|
|
240
242
|
| Tool | Description |
|
|
241
243
|
|------|-------------|
|
|
242
|
-
| `word_insert_equation` | Insert a LaTeX equation as a native editable Word equation.
|
|
244
|
+
| `word_insert_equation` | Insert a LaTeX equation as a native editable Word equation. Display (block) or inline mode with optional anchor text positioning. |
|
|
245
|
+
|
|
246
|
+
### Batch
|
|
247
|
+
| Tool | Description |
|
|
248
|
+
|------|-------------|
|
|
249
|
+
| `word_batch` | Execute up to 50 operations in a single call. Native ops are bundled into one message to Word for maximum speed. |
|
|
243
250
|
|
|
244
251
|
## How it works
|
|
245
252
|
|
|
@@ -264,6 +271,8 @@ LaTeX → temml → MathML → mathml2omml → OMML → OOXML → Word
|
|
|
264
271
|
|
|
265
272
|
- **Display mode** (`displayMode: true`, default): centered block equation on its own line
|
|
266
273
|
- **Inline mode** (`displayMode: false`): inserted at the current cursor position within a paragraph
|
|
274
|
+
- With `anchorText`: searches for the text and inserts the equation after the match (recommended — no manual cursor positioning needed)
|
|
275
|
+
- Without `anchorText`: inserts at wherever the cursor currently is
|
|
267
276
|
- Supports: fractions, roots, integrals, sums, products, matrices, Greek letters, piecewise functions, aligned systems, and all standard LaTeX math
|
|
268
277
|
- Equations are fully editable in Word's built-in equation editor
|
|
269
278
|
- Invalid LaTeX returns a descriptive error message
|
package/index.js
CHANGED
|
@@ -119,7 +119,7 @@ function sendToTaskpane(action, params) {
|
|
|
119
119
|
return;
|
|
120
120
|
}
|
|
121
121
|
const id = String(++mcpRequestId);
|
|
122
|
-
const heavyOps = ['insertHtml', 'insertOoxml', 'getStyles', 'insertTableOfContents'];
|
|
122
|
+
const heavyOps = ['insertHtml', 'insertOoxml', 'getStyles', 'insertTableOfContents', 'batchExecute'];
|
|
123
123
|
const timeoutMs = heavyOps.includes(action) ? 60000 : 30000;
|
|
124
124
|
const timeout = setTimeout(() => {
|
|
125
125
|
bridgePending.delete(id);
|
|
@@ -134,47 +134,211 @@ function sendToTaskpane(action, params) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
const mcpServer = new Server(
|
|
137
|
-
{ name: 'mcp-word-bridge', version: '3.
|
|
137
|
+
{ name: 'mcp-word-bridge', version: '3.5.0' },
|
|
138
138
|
{ capabilities: { tools: {}, resources: {} } }
|
|
139
139
|
);
|
|
140
140
|
|
|
141
141
|
mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
142
142
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
// Special handling for word_insert_equation (server-side LaTeX→OMML conversion)
|
|
143
|
+
// Helper: execute a single tool call (used by both direct calls and batch)
|
|
144
|
+
async function executeTool(name, args) {
|
|
145
|
+
// word_insert_equation: server-side LaTeX→OMML conversion
|
|
147
146
|
if (name === 'word_insert_equation') {
|
|
147
|
+
const latex = args.latex;
|
|
148
|
+
const displayMode = args.displayMode !== false;
|
|
149
|
+
|
|
150
|
+
let result;
|
|
148
151
|
try {
|
|
149
|
-
const
|
|
150
|
-
|
|
152
|
+
const { mml2omml } = require('mathml2omml');
|
|
153
|
+
result = latexToOmml(latex, displayMode, mml2omml);
|
|
154
|
+
} catch (e) {
|
|
155
|
+
const msg = e.message.startsWith('LaTeX parse error') || e.message.startsWith('"latex"') ? e.message : 'Error: ' + e.message;
|
|
156
|
+
return { content: [{ type: 'text', text: msg }], isError: true };
|
|
157
|
+
}
|
|
151
158
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
const cleanOmml = result.omml;
|
|
160
|
+
const ooxml = buildEquationOoxml(cleanOmml, displayMode);
|
|
161
|
+
|
|
162
|
+
if (displayMode) {
|
|
163
|
+
await sendToTaskpane('insertOoxml', { ooxml, location: args.location || 'End' });
|
|
164
|
+
} else if (args.anchorText) {
|
|
165
|
+
// Inline mode with anchor: search for anchor text, insert space+marker after it, insert equation at cursor, clean up marker
|
|
166
|
+
const marker = '\u200B\uFEFF'; // unique two-char sequence unlikely to exist in normal text
|
|
167
|
+
const searchResult = await sendToTaskpane('search', { query: args.anchorText, matchCase: args.matchCase || false });
|
|
168
|
+
if (!searchResult || searchResult.count === 0) throw new Error('Anchor not found: ' + args.anchorText);
|
|
169
|
+
const occurrence = args.occurrence || 0;
|
|
170
|
+
if (occurrence >= searchResult.count) throw new Error('Occurrence ' + occurrence + ' not found (only ' + searchResult.count + ' match' + (searchResult.count === 1 ? '' : 'es') + ')');
|
|
171
|
+
// Insert space + marker to position cursor (space ensures equation doesn't glue to preceding text)
|
|
172
|
+
await sendToTaskpane('insertText', { text: ' ' + marker, after: args.anchorText, occurrence: occurrence, matchCase: args.matchCase || false });
|
|
173
|
+
await sendToTaskpane('insertOoxmlAtSelection', { ooxml });
|
|
174
|
+
// Clean up the marker (space remains, giving proper separation)
|
|
175
|
+
await sendToTaskpane('searchAndReplace', { find: marker, replace: '' });
|
|
176
|
+
} else {
|
|
177
|
+
await sendToTaskpane('insertOoxmlAtSelection', { ooxml });
|
|
178
|
+
}
|
|
179
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, displayMode, latex }) }] };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// word_get_document_outline: server-side filtering of paragraphs by heading style
|
|
183
|
+
if (name === 'word_get_document_outline') {
|
|
184
|
+
const maxLevel = args.maxLevel !== undefined ? args.maxLevel : 3;
|
|
185
|
+
const result = await sendToTaskpane('getParagraphs', {});
|
|
186
|
+
const headings = [];
|
|
187
|
+
for (const para of result.paragraphs) {
|
|
188
|
+
const match = para.style && para.style.match(/^Heading (\d)$/i);
|
|
189
|
+
if (match) {
|
|
190
|
+
const level = parseInt(match[1], 10);
|
|
191
|
+
if (level <= maxLevel) {
|
|
192
|
+
headings.push({ level, text: para.text, index: para.index });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: headings.length, outline: headings }, null, 2) }] };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// word_move_paragraph: atomic move (read text+style, delete, insert at new position)
|
|
200
|
+
// Supports moving multiple consecutive paragraphs via optional 'count' parameter
|
|
201
|
+
if (name === 'word_move_paragraph') {
|
|
202
|
+
const fromIndex = args.fromIndex;
|
|
203
|
+
const toIndex = args.toIndex;
|
|
204
|
+
const count = args.count || 1;
|
|
205
|
+
const location = args.location || 'After';
|
|
206
|
+
if (fromIndex < 0) throw new Error('fromIndex must be non-negative');
|
|
207
|
+
if (toIndex < 0) throw new Error('toIndex must be non-negative');
|
|
208
|
+
if (count < 1) throw new Error('count must be at least 1');
|
|
209
|
+
if (fromIndex === toIndex && count === 1) throw new Error('fromIndex and toIndex must be different');
|
|
210
|
+
// Validate all indices are in bounds before mutating
|
|
211
|
+
const paraCount = await sendToTaskpane('getParagraphs', {});
|
|
212
|
+
const total = paraCount.count;
|
|
213
|
+
if (fromIndex >= total) throw new Error('fromIndex ' + fromIndex + ' out of range. Document has ' + total + ' paragraphs (0-indexed).');
|
|
214
|
+
if (fromIndex + count - 1 >= total) throw new Error('fromIndex + count (' + (fromIndex + count) + ') exceeds paragraph count (' + total + ').');
|
|
215
|
+
if (toIndex >= total) throw new Error('toIndex ' + toIndex + ' out of range. Document has ' + total + ' paragraphs (0-indexed).');
|
|
216
|
+
// Collect all paragraphs to move (text + style)
|
|
217
|
+
const collected = [];
|
|
218
|
+
for (let i = 0; i < count; i++) {
|
|
219
|
+
const details = await sendToTaskpane('getParagraphByIndex', { index: fromIndex + i });
|
|
220
|
+
collected.push({ text: details.text, style: details.style });
|
|
221
|
+
}
|
|
222
|
+
// Delete from last to first to preserve indices during deletion
|
|
223
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
224
|
+
await sendToTaskpane('deleteParagraph', { index: fromIndex + i });
|
|
225
|
+
}
|
|
226
|
+
// Adjust destination index after deletions
|
|
227
|
+
let adjustedTo;
|
|
228
|
+
if (fromIndex < toIndex) {
|
|
229
|
+
adjustedTo = toIndex - count;
|
|
230
|
+
} else {
|
|
231
|
+
adjustedTo = toIndex;
|
|
232
|
+
}
|
|
233
|
+
// Insert in order at destination
|
|
234
|
+
for (let i = 0; i < collected.length; i++) {
|
|
235
|
+
if (i === 0) {
|
|
236
|
+
await sendToTaskpane('insertParagraphAtIndex', { index: adjustedTo, text: collected[i].text, style: collected[i].style, location: location });
|
|
237
|
+
} else {
|
|
238
|
+
// After the first insert, the paragraph we just inserted is at a known position.
|
|
239
|
+
// For 'Before': first para went to adjustedTo, so second goes After adjustedTo.
|
|
240
|
+
// For 'After': first para went to adjustedTo+1, so second goes After adjustedTo+1.
|
|
241
|
+
const prevInsertedAt = location === 'Before' ? adjustedTo + (i - 1) : adjustedTo + i;
|
|
242
|
+
await sendToTaskpane('insertParagraphAtIndex', { index: prevInsertedAt, text: collected[i].text, style: collected[i].style, location: 'After' });
|
|
159
243
|
}
|
|
244
|
+
}
|
|
245
|
+
return { content: [{ type: 'text', text: JSON.stringify({ success: true, moved: { from: fromIndex, count, to: adjustedTo, location } }) }] };
|
|
246
|
+
}
|
|
160
247
|
|
|
161
|
-
|
|
162
|
-
|
|
248
|
+
// word_batch: execute multiple operations in a single call
|
|
249
|
+
if (name === 'word_batch') {
|
|
250
|
+
const operations = args.operations;
|
|
251
|
+
if (!operations || !Array.isArray(operations) || operations.length === 0) {
|
|
252
|
+
return { content: [{ type: 'text', text: 'Error: operations must be a non-empty array' }], isError: true };
|
|
253
|
+
}
|
|
254
|
+
if (operations.length > 50) {
|
|
255
|
+
return { content: [{ type: 'text', text: 'Error: maximum 50 operations per batch' }], isError: true };
|
|
256
|
+
}
|
|
163
257
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
258
|
+
// Partition into taskpane-native ops (have action mapping) vs server-composed ops
|
|
259
|
+
const SERVER_HANDLED = new Set(['word_insert_equation', 'word_batch', 'word_get_document_outline', 'word_move_paragraph']);
|
|
260
|
+
const results = [];
|
|
261
|
+
|
|
262
|
+
// Collect consecutive taskpane-native ops into batches, flush when hitting a server op
|
|
263
|
+
let nativeBuf = [];
|
|
264
|
+
|
|
265
|
+
const flushNative = async () => {
|
|
266
|
+
if (nativeBuf.length === 0) return;
|
|
267
|
+
const batchOps = nativeBuf.map(item => ({
|
|
268
|
+
action: toolActionMap[item.tool],
|
|
269
|
+
params: item.args || {}
|
|
270
|
+
}));
|
|
271
|
+
const batchResult = await sendToTaskpane('batchExecute', { operations: batchOps });
|
|
272
|
+
for (const r of batchResult.results) {
|
|
273
|
+
const item = nativeBuf[r.index];
|
|
274
|
+
if (r.success) {
|
|
275
|
+
results.push({ index: item.originalIndex, tool: item.tool, success: true, result: r.result });
|
|
276
|
+
} else {
|
|
277
|
+
results.push({ index: item.originalIndex, tool: item.tool, success: false, error: r.error });
|
|
278
|
+
// Mark that we should stop
|
|
279
|
+
nativeBuf = [];
|
|
280
|
+
return false;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
nativeBuf = [];
|
|
284
|
+
return true;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
let stopped = false;
|
|
288
|
+
for (let i = 0; i < operations.length && !stopped; i++) {
|
|
289
|
+
const op = operations[i];
|
|
290
|
+
if (!op.tool) {
|
|
291
|
+
results.push({ index: i, tool: op.tool, success: false, error: 'Missing tool name' });
|
|
292
|
+
stopped = true;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (SERVER_HANDLED.has(op.tool)) {
|
|
297
|
+
// Flush any pending native ops first
|
|
298
|
+
const ok = await flushNative();
|
|
299
|
+
if (ok === false) { stopped = true; break; }
|
|
300
|
+
// Execute server-side
|
|
301
|
+
try {
|
|
302
|
+
const opResult = await executeTool(op.tool, op.args || {});
|
|
303
|
+
if (opResult.isError) {
|
|
304
|
+
results.push({ index: i, tool: op.tool, success: false, result: opResult.content[0].text });
|
|
305
|
+
stopped = true;
|
|
306
|
+
} else {
|
|
307
|
+
results.push({ index: i, tool: op.tool, success: true, result: JSON.parse(opResult.content[0].text) });
|
|
308
|
+
}
|
|
309
|
+
} catch (e) {
|
|
310
|
+
results.push({ index: i, tool: op.tool, success: false, error: e.message });
|
|
311
|
+
stopped = true;
|
|
312
|
+
}
|
|
313
|
+
} else if (toolActionMap[op.tool]) {
|
|
314
|
+
// Buffer taskpane-native op
|
|
315
|
+
nativeBuf.push({ tool: op.tool, args: op.args || {}, originalIndex: i });
|
|
316
|
+
} else {
|
|
317
|
+
// Flush pending, then report unknown tool
|
|
318
|
+
const ok = await flushNative();
|
|
319
|
+
if (ok === false) { stopped = true; break; }
|
|
320
|
+
results.push({ index: i, tool: op.tool, success: false, error: 'Unknown tool: ' + op.tool });
|
|
321
|
+
stopped = true;
|
|
322
|
+
}
|
|
170
323
|
}
|
|
324
|
+
// Flush any remaining native ops
|
|
325
|
+
if (!stopped) await flushNative();
|
|
326
|
+
|
|
327
|
+
const succeeded = results.filter(r => r.success).length;
|
|
328
|
+
return { content: [{ type: 'text', text: JSON.stringify({ completed: succeeded, failed: results.length - succeeded, total: operations.length, results }, null, 2) }] };
|
|
171
329
|
}
|
|
172
330
|
|
|
331
|
+
// Default: forward to taskpane via action map
|
|
173
332
|
const action = toolActionMap[name];
|
|
174
333
|
if (!action) return { content: [{ type: 'text', text: 'Unknown tool: ' + name }], isError: true };
|
|
334
|
+
const result = await sendToTaskpane(action, args || {});
|
|
335
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
339
|
+
const { name, arguments: args } = request.params;
|
|
175
340
|
try {
|
|
176
|
-
|
|
177
|
-
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
341
|
+
return await executeTool(name, args || {});
|
|
178
342
|
} catch (e) {
|
|
179
343
|
return { content: [{ type: 'text', text: 'Error: ' + e.message }], isError: true };
|
|
180
344
|
}
|
package/lib/tools.js
CHANGED
|
@@ -5,137 +5,140 @@
|
|
|
5
5
|
|
|
6
6
|
const tools = [
|
|
7
7
|
// 1. DOCUMENT
|
|
8
|
-
{ name: 'word_get_text', description: 'Get full plain text of the active document. Use for overview; for structured content use
|
|
9
|
-
{ name: 'word_get_document_properties', description: 'Get all document metadata including title, author, path, changeTrackingMode, template, security, and timestamps.', inputSchema: { type: 'object', properties: {} } },
|
|
10
|
-
{ name: 'word_set_document_properties', description: 'Set document metadata (title, subject, author, keywords, comments, category, company, manager, format).', inputSchema: { type: 'object', properties: { title: { type: 'string' }, subject: { type: 'string' }, author: { type: 'string' }, keywords: { type: 'string' }, comments: { type: 'string' }, category: { type: 'string' }, company: { type: 'string' }, manager: { type: 'string' }, format: { type: 'string' } } } },
|
|
11
|
-
{ name: 'word_save', description: 'Save the document to disk.', inputSchema: { type: 'object', properties: {} } },
|
|
12
|
-
{ name: 'word_clear', description: 'Clear all document
|
|
13
|
-
{ name: 'word_create_document', description: 'Create and open a new blank document in a new Word window. Optionally provide base64-encoded .docx as template. NOTE: the add-in stays in the original window — use word_clear to reset the current document instead.', inputSchema: { type: 'object', properties: { base64: { type: 'string', description: 'Optional base64-encoded .docx file to use as template' } } } },
|
|
14
|
-
{ name: 'word_get_word_count', description: 'Get word, character, and paragraph counts.', inputSchema: { type: 'object', properties: {} } },
|
|
15
|
-
{ name: 'word_get_styles', description: 'Get available document styles.', inputSchema: { type: 'object', properties: {} } },
|
|
16
|
-
{ name: 'word_get_coauthors', description: 'Get current co-authors and coauthoring status.', inputSchema: { type: 'object', properties: {} } },
|
|
17
|
-
{ name: 'word_set_change_tracking', description: 'Set change tracking mode.
|
|
8
|
+
{ name: 'word_get_text', description: '[Document] Get full plain text of the active document. Use for a quick overview; for structured content with styles use word_get_paragraphs instead.', inputSchema: { type: 'object', properties: {} } },
|
|
9
|
+
{ name: 'word_get_document_properties', description: '[Document] Get all document metadata including title, author, path, changeTrackingMode, template, security, and timestamps.', inputSchema: { type: 'object', properties: {} } },
|
|
10
|
+
{ name: 'word_set_document_properties', description: '[Document] Set document metadata (title, subject, author, keywords, comments, category, company, manager, format).', inputSchema: { type: 'object', properties: { title: { type: 'string' }, subject: { type: 'string' }, author: { type: 'string' }, keywords: { type: 'string' }, comments: { type: 'string' }, category: { type: 'string' }, company: { type: 'string' }, manager: { type: 'string' }, format: { type: 'string' } } } },
|
|
11
|
+
{ name: 'word_save', description: '[Document] Save the document to disk.', inputSchema: { type: 'object', properties: {} } },
|
|
12
|
+
{ name: 'word_clear', description: '[Document] Clear all document body content. Leaves one empty Normal paragraph. Fast reset — does not clear headers/footers or custom properties.', inputSchema: { type: 'object', properties: {} } },
|
|
13
|
+
{ name: 'word_create_document', description: '[Document] Create and open a new blank document in a new Word window. Optionally provide base64-encoded .docx as template. NOTE: the add-in stays in the original window — use word_clear to reset the current document instead.', inputSchema: { type: 'object', properties: { base64: { type: 'string', description: 'Optional base64-encoded .docx file to use as template' } } } },
|
|
14
|
+
{ name: 'word_get_word_count', description: '[Document] Get word, character, and paragraph counts.', inputSchema: { type: 'object', properties: {} } },
|
|
15
|
+
{ name: 'word_get_styles', description: '[Document] Get available document styles (returns up to 80 styles with name, type, and builtIn flag).', inputSchema: { type: 'object', properties: {} } },
|
|
16
|
+
{ name: 'word_get_coauthors', description: '[Document] Get current co-authors and coauthoring status (Desktop only).', inputSchema: { type: 'object', properties: {} } },
|
|
17
|
+
{ name: 'word_set_change_tracking', description: '[Document] Set change tracking mode. Call with "TrackAll" BEFORE making edits to show them as tracked changes.', inputSchema: { type: 'object', properties: { mode: { type: 'string', enum: ['TrackAll', 'TrackMineOnly', 'Off'] } }, required: ['mode'] } },
|
|
18
|
+
{ name: 'word_get_document_outline', description: '[Document] Get the document heading hierarchy as a structured outline tree. Returns headings with their level, text, and paragraph index — useful for understanding document structure before editing.', inputSchema: { type: 'object', properties: { maxLevel: { type: 'number', description: 'Maximum heading level to include (1-9). Default: 3 (includes Heading 1, 2, 3)' } } } },
|
|
18
19
|
// 2. PARAGRAPHS
|
|
19
|
-
{ name: 'word_get_paragraphs', description: 'Get paragraphs with text, style, alignment, isTocEntry.
|
|
20
|
-
{ name: 'word_get_paragraph_by_index', description: 'Get full details of a single paragraph including font, spacing, indentation, and outline level.', inputSchema: { type: 'object', properties: { index: { type: 'number' } }, required: ['index'] } },
|
|
21
|
-
{ name: 'word_insert_paragraph', description: '
|
|
22
|
-
{ name: '
|
|
23
|
-
{ name: '
|
|
24
|
-
{ name: '
|
|
20
|
+
{ name: 'word_get_paragraphs', description: '[Paragraphs] Get paragraphs with text, style, alignment, and isTocEntry flag. Supports pagination via optional start/end index range (0-based). Returns total paragraph count.', inputSchema: { type: 'object', properties: { start: { type: 'number', description: 'First paragraph index to return (0-based, inclusive)' }, end: { type: 'number', description: 'Last paragraph index (exclusive). Omit to get all from start.' } } } },
|
|
21
|
+
{ name: 'word_get_paragraph_by_index', description: '[Paragraphs] Get full details of a single paragraph including font, spacing, indentation, and outline level.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' } }, required: ['index'] } },
|
|
22
|
+
{ name: 'word_insert_paragraph', description: '[Paragraphs] Append or prepend a styled paragraph to the document (Start or End only). For inserting at a specific position, use word_insert_paragraph_at_index instead.', inputSchema: { type: 'object', properties: { text: { type: 'string' }, location: { type: 'string', enum: ['Start', 'End'], description: 'Where in the document. Default: End' }, style: { type: 'string', description: 'Paragraph style (e.g. "Heading 1", "Normal"). Default: Normal' }, alignment: { type: 'string', description: 'Paragraph alignment: Left, Center, Right, or Justified' } }, required: ['text'] } },
|
|
23
|
+
{ name: 'word_insert_paragraph_at_index', description: '[Paragraphs] Insert a new paragraph Before or After a specific paragraph index. Use this for precise positioning within the document (preferred over word_insert_paragraph for mid-document insertions).', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Reference paragraph index (0-based)' }, text: { type: 'string', description: 'Text content for the new paragraph' }, location: { type: 'string', enum: ['Before', 'After'], description: 'Insert before or after the reference paragraph. Default: After' }, style: { type: 'string', description: 'Paragraph style (e.g. "Heading 1", "Normal")' } }, required: ['index', 'text'] } },
|
|
24
|
+
{ name: 'word_delete_paragraph', description: '[Paragraphs] Delete a paragraph by its 0-based index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' } }, required: ['index'] } },
|
|
25
|
+
{ name: 'word_replace_paragraph_text', description: '[Paragraphs] Replace the entire text of a paragraph by index. Preserves style/formatting. Preferred over word_search_and_replace in collaborative editing (targets by position, immune to text-drift).', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' }, text: { type: 'string', description: 'New text content for the paragraph' } }, required: ['index', 'text'] } },
|
|
26
|
+
{ name: 'word_move_paragraph', description: '[Paragraphs] Move a paragraph from one position to another. Handles the delete+insert atomically with correct index adjustment.', inputSchema: { type: 'object', properties: { fromIndex: { type: 'number', description: 'Source paragraph index (0-based)' }, toIndex: { type: 'number', description: 'Destination paragraph index (0-based, position after removal)' }, location: { type: 'string', enum: ['Before', 'After'], description: 'Insert before or after the destination index. Default: After' }, count: { type: 'number', description: 'Number of consecutive paragraphs to move (default: 1). Use to move a heading + its body together.' } }, required: ['fromIndex', 'toIndex'] } },
|
|
27
|
+
{ name: 'word_set_paragraph_style', description: '[Paragraphs] Change the style or alignment of a paragraph by index. Alignment accepts: Left, Center, Right, Justified.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' }, style: { type: 'string' }, alignment: { type: 'string', description: 'Paragraph alignment: Left, Center, Right, or Justified' } }, required: ['index'] } },
|
|
28
|
+
{ name: 'word_set_paragraph_spacing', description: '[Paragraphs] Set line spacing (in points: 12=single for 12pt font, 18=1.5x, 24=double), before/after spacing, and indentation on a paragraph by index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' }, lineSpacing: { type: 'number', description: 'Line spacing in points (e.g. 12=single for 12pt, 18=1.5x, 24=double)' }, spaceBefore: { type: 'number', description: 'Space before paragraph in points' }, spaceAfter: { type: 'number', description: 'Space after paragraph in points' }, firstLineIndent: { type: 'number', description: 'First line indent in points' }, leftIndent: { type: 'number', description: 'Left indent in points' }, rightIndent: { type: 'number', description: 'Right indent in points' } }, required: ['index'] } },
|
|
25
29
|
// 3. SEARCH & TEXT
|
|
26
|
-
{ name: 'word_search', description: 'Search
|
|
27
|
-
{ name: 'word_search_and_replace', description: 'Find and replace
|
|
28
|
-
{ name: '
|
|
29
|
-
{ name: 'word_get_selection_info', description: 'Get the current selection text with full font and style details.', inputSchema: { type: 'object', properties: {} } },
|
|
30
|
-
{ name: 'word_insert_text_at_selection', description: 'Insert text at the current cursor position, or replace the current selection (set replace=true).', inputSchema: { type: 'object', properties: { text: { type: 'string' }, replace: { type: 'boolean', description: 'Replace current selection instead of appending. Default: false' } }, required: ['text'] } },
|
|
31
|
-
{ name: 'word_insert_line_break', description: 'Insert a soft line break (Shift+Enter) before or after a text match.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string' }, before: { type: 'boolean' }, occurrence: { type: 'number', description: '
|
|
30
|
+
{ name: 'word_search', description: '[Search] Find text in the document (case-insensitive by default). Returns match count and up to 30 matches. Query must be ≤255 chars. Supports Word wildcards (^p for paragraph mark).', inputSchema: { type: 'object', properties: { query: { type: 'string' }, matchCase: { type: 'boolean', description: 'Case-sensitive search. Default: false' }, matchWholeWord: { type: 'boolean' } }, required: ['query'] } },
|
|
31
|
+
{ name: 'word_search_and_replace', description: '[Search] Find and replace ALL occurrences (case-insensitive by default). Returns replacement count. For single-paragraph edits, prefer word_replace_paragraph_text (safer in collaborative docs).', inputSchema: { type: 'object', properties: { find: { type: 'string' }, replace: { type: 'string' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' }, matchWholeWord: { type: 'boolean' } }, required: ['find', 'replace'] } },
|
|
32
|
+
{ name: 'word_insert_text_at_match', description: '[Search] Insert text before or after a SEARCH MATCH. First searches for the anchor string, then inserts adjacent to it. Provide "after" OR "before" (not both) as the anchor text to locate. Use occurrence (0-indexed) to target the Nth match.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Text to insert' }, after: { type: 'string', description: 'Search for this text and insert AFTER it' }, before: { type: 'string', description: 'Search for this text and insert BEFORE it' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['text'] } },
|
|
33
|
+
{ name: 'word_get_selection_info', description: '[Search] Get the current cursor selection text with full font and style details.', inputSchema: { type: 'object', properties: {} } },
|
|
34
|
+
{ name: 'word_insert_text_at_selection', description: '[Search] Insert text at the current cursor position, or replace the current selection (set replace=true). Cursor moves to end of inserted text.', inputSchema: { type: 'object', properties: { text: { type: 'string' }, replace: { type: 'boolean', description: 'Replace current selection instead of appending. Default: false' } }, required: ['text'] } },
|
|
35
|
+
{ name: 'word_insert_line_break', description: '[Search] Insert a soft line break (Shift+Enter) before or after a text match.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text to search for as anchor point' }, before: { type: 'boolean', description: 'Insert before the match (default: after)' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['anchorText'] } },
|
|
32
36
|
// 4. FORMATTING
|
|
33
|
-
{ name: 'word_format_text', description: 'Apply formatting (bold, italic, color, size, font) to a text match found by search. Color must be hex (
|
|
34
|
-
{ name: 'word_clear_formatting', description: 'Clear direct formatting from a text match, reverting it to the paragraph style defaults.', inputSchema: { type: 'object', properties: { text: { type: 'string' }, occurrence: { type: 'number', description: '
|
|
35
|
-
{ name: 'word_get_font_info', description: 'Inspect font properties (name, size, bold, italic, color) of a text match.', inputSchema: { type: 'object', properties: { text: { type: 'string' }, occurrence: { type: 'number', description: '
|
|
37
|
+
{ name: 'word_format_text', description: '[Formatting] Apply formatting (bold, italic, color, size, font) to a text match found by search. Color must be hex (#FF0000). Size: 1-1638pt.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Text to search for and format' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, bold: { type: 'boolean' }, italic: { type: 'boolean' }, underline: { type: 'boolean' }, strikeThrough: { type: 'boolean' }, color: { type: 'string', description: 'Hex color e.g. #FF0000' }, highlightColor: { type: 'string' }, size: { type: 'number', description: 'Font size in points (1-1638)' }, name: { type: 'string', description: 'Font name' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['text'] } },
|
|
38
|
+
{ name: 'word_clear_formatting', description: '[Formatting] Clear direct formatting from a text match, reverting it to the paragraph style defaults.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Text to search for' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['text'] } },
|
|
39
|
+
{ name: 'word_get_font_info', description: '[Formatting] Inspect font properties (name, size, bold, italic, color) of a text match.', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'Text to search for' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['text'] } },
|
|
36
40
|
// 5. TABLES
|
|
37
|
-
{ name: 'word_insert_table', description: 'Insert a table with data. Provide rows (1-500), cols (1-63), and data as array of arrays (e.g. [["A","B"],["C","D"]]).', inputSchema: { type: 'object', properties: { rows: { type: 'number' }, cols: { type: 'number' }, data: { type: 'array', items: { type: 'array', items: { type: 'string' } } }, location: { type: 'string', enum: ['Start', 'End'] }, style: { type: 'string' }, headerRowCount: { type: 'number' } }, required: ['rows', 'cols'] } },
|
|
38
|
-
{ name: '
|
|
39
|
-
{ name: 'word_get_table_data', description: 'Get all cell values from a specific table
|
|
40
|
-
{ name: 'word_set_table_cell', description: 'Set text in a specific table cell
|
|
41
|
-
{ name: 'word_add_table_row', description: 'Add a row to a table with optional cell values.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, values: { type: 'array', items: { type: 'string' } }, location: { type: 'string', enum: ['Start', 'End'] } }, required: ['tableIndex'] } },
|
|
42
|
-
{ name: 'word_delete_table_row', description: 'Delete a row from a table
|
|
43
|
-
{ name: 'word_merge_table_cells', description: 'Merge a rectangular range of cells (topRow/firstCell to bottomRow/lastCell).', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, topRow: { type: 'number' }, firstCell: { type: 'number' }, bottomRow: { type: 'number' }, lastCell: { type: 'number' } }, required: ['tableIndex', 'topRow', 'firstCell', 'bottomRow', 'lastCell'] } },
|
|
44
|
-
{ name: 'word_split_table_cell', description: 'Split a table cell into multiple rows/columns.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, row: { type: 'number' }, col: { type: 'number' }, rowCount: { type: 'number' }, colCount: { type: 'number' } }, required: ['tableIndex', 'row', 'col'] } },
|
|
45
|
-
{ name: 'word_set_table_style', description: 'Apply a built-in table style (e.g. "Grid Table 4 - Accent 1").', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, style: { type: 'string' } }, required: ['tableIndex', 'style'] } },
|
|
46
|
-
{ name: 'word_set_table_cell_shading', description: 'Set background color on a table cell.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, row: { type: 'number' }, col: { type: 'number' }, color: { type: 'string', description: 'Hex color e.g. #FFD700' } }, required: ['tableIndex', 'row', 'col', 'color'] } },
|
|
41
|
+
{ name: 'word_insert_table', description: '[Tables] Insert a table with data. Provide rows (1-500), cols (1-63), and data as array of arrays (e.g. [["A","B"],["C","D"]]). Data dimensions must match rows×cols.', inputSchema: { type: 'object', properties: { rows: { type: 'number' }, cols: { type: 'number' }, data: { type: 'array', items: { type: 'array', items: { type: 'string' } }, description: 'Cell values as array of row arrays. Must have exactly rows×cols dimensions.' }, location: { type: 'string', enum: ['Start', 'End'] }, style: { type: 'string', description: 'Table style name (e.g. "Grid Table 4 - Accent 1")' }, headerRowCount: { type: 'number' } }, required: ['rows', 'cols'] } },
|
|
42
|
+
{ name: 'word_list_tables', description: '[Tables] List table metadata: count, rowCount, columnCount, style, headerRowCount for each table. Does NOT return cell values — use word_get_table_data for cell content.', inputSchema: { type: 'object', properties: {} } },
|
|
43
|
+
{ name: 'word_get_table_data', description: '[Tables] Get all cell values from a specific table as a 2D array. Use word_list_tables first to find the table index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Table index (0-based)' } }, required: ['index'] } },
|
|
44
|
+
{ name: 'word_set_table_cell', description: '[Tables] Set text in a specific table cell.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number', description: 'Table index (0-based)' }, row: { type: 'number', description: 'Row index (0-based)' }, col: { type: 'number', description: 'Column index (0-based)' }, text: { type: 'string' } }, required: ['tableIndex', 'row', 'col', 'text'] } },
|
|
45
|
+
{ name: 'word_add_table_row', description: '[Tables] Add a row to a table with optional cell values.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number', description: 'Table index (0-based)' }, values: { type: 'array', items: { type: 'string' }, description: 'Cell values for the new row' }, location: { type: 'string', enum: ['Start', 'End'], description: 'Add at start or end of table. Default: End' } }, required: ['tableIndex'] } },
|
|
46
|
+
{ name: 'word_delete_table_row', description: '[Tables] Delete a row from a table.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number', description: 'Table index (0-based)' }, rowIndex: { type: 'number', description: 'Row index (0-based)' } }, required: ['tableIndex', 'rowIndex'] } },
|
|
47
|
+
{ name: 'word_merge_table_cells', description: '[Tables] Merge a rectangular range of cells (topRow/firstCell to bottomRow/lastCell). All indices 0-based.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, topRow: { type: 'number' }, firstCell: { type: 'number' }, bottomRow: { type: 'number' }, lastCell: { type: 'number' } }, required: ['tableIndex', 'topRow', 'firstCell', 'bottomRow', 'lastCell'] } },
|
|
48
|
+
{ name: 'word_split_table_cell', description: '[Tables] Split a table cell into multiple rows/columns.', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, row: { type: 'number' }, col: { type: 'number' }, rowCount: { type: 'number', description: 'Split into this many rows. Default: 1' }, colCount: { type: 'number', description: 'Split into this many columns. Default: 2' } }, required: ['tableIndex', 'row', 'col'] } },
|
|
49
|
+
{ name: 'word_set_table_style', description: '[Tables] Apply a built-in table style (e.g. "Grid Table 4 - Accent 1").', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, style: { type: 'string' } }, required: ['tableIndex', 'style'] } },
|
|
50
|
+
{ name: 'word_set_table_cell_shading', description: '[Tables] Set background color on a table cell. Color must be hex (e.g. #FFD700).', inputSchema: { type: 'object', properties: { tableIndex: { type: 'number' }, row: { type: 'number' }, col: { type: 'number' }, color: { type: 'string', description: 'Hex color e.g. #FFD700' } }, required: ['tableIndex', 'row', 'col', 'color'] } },
|
|
47
51
|
// 6. LISTS
|
|
48
|
-
{ name: 'word_insert_list', description: 'Insert a bulleted or numbered list from an array of item strings.', inputSchema: { type: 'object', properties: { items: { type: 'array', items: { type: 'string' } }, numbered: { type: 'boolean' }, location: { type: 'string', enum: ['Start', 'End'] } }, required: ['items'] } },
|
|
49
|
-
{ name: 'word_get_list_info', description: 'Get list formatting details (level, numbering) for a paragraph by index.', inputSchema: { type: 'object', properties: { index: { type: 'number' } }, required: ['index'] } },
|
|
50
|
-
{ name: 'word_set_list_level', description: 'Set indent level of a list item (0=top, 1=sub-item,
|
|
52
|
+
{ name: 'word_insert_list', description: '[Lists] Insert a bulleted or numbered list from an array of item strings.', inputSchema: { type: 'object', properties: { items: { type: 'array', items: { type: 'string' }, description: 'List item strings' }, numbered: { type: 'boolean', description: 'true = numbered list, false/omit = bulleted' }, location: { type: 'string', enum: ['Start', 'End'] } }, required: ['items'] } },
|
|
53
|
+
{ name: 'word_get_list_info', description: '[Lists] Get list formatting details (level, numbering) for a paragraph by index. Returns isListItem:false if not in a list.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' } }, required: ['index'] } },
|
|
54
|
+
{ name: 'word_set_list_level', description: '[Lists] Set indent level of a list item (0=top level, 1=sub-item, up to 8).', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Paragraph index (0-based)' }, level: { type: 'number', description: 'Indent level: 0-8' } }, required: ['index', 'level'] } },
|
|
51
55
|
// 7. COMMENTS
|
|
52
|
-
{ name: 'word_add_comment', description: 'Add a review comment anchored to a text match
|
|
53
|
-
{ name: 'word_get_comments', description: 'Get all comments with ID, author, content, date, and
|
|
54
|
-
{ name: 'word_get_comment_replies', description: 'Get all replies for a specific comment by ID.', inputSchema: { type: 'object', properties: { commentId: { type: 'string' } }, required: ['commentId'] } },
|
|
55
|
-
{ name: 'word_reply_to_comment', description: 'Reply to a comment by its ID
|
|
56
|
-
{ name: 'word_resolve_comment', description: 'Mark a comment as resolved
|
|
57
|
-
{ name: 'word_delete_comment', description: '
|
|
58
|
-
{ name: 'word_get_comment_anchor', description: 'Get the document text that a comment is anchored to (the highlighted/marked text the comment refers to).', inputSchema: { type: 'object', properties: { commentId: { type: 'string' } }, required: ['commentId'] } },
|
|
59
|
-
{ name: 'word_get_comments_with_anchor', description: 'Get all comments with ID, author, content, date, resolved status, AND the anchor text each comment is attached to.', inputSchema: { type: 'object', properties: {} } },
|
|
56
|
+
{ name: 'word_add_comment', description: '[Comments] Add a review comment anchored to a text match. IMPORTANT: Read word_get_comments before bulk text replacements to avoid damaging comment anchors.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text to search for as comment anchor' }, comment: { type: 'string', description: 'Comment text' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['anchorText', 'comment'] } },
|
|
57
|
+
{ name: 'word_get_comments', description: '[Comments] Get all comments with ID, author, content, date, resolved status, and the anchor text each comment is attached to. Call this BEFORE any bulk text replacements.', inputSchema: { type: 'object', properties: {} } },
|
|
58
|
+
{ name: 'word_get_comment_replies', description: '[Comments] Get all replies for a specific comment by its ID (from word_get_comments).', inputSchema: { type: 'object', properties: { commentId: { type: 'string' } }, required: ['commentId'] } },
|
|
59
|
+
{ name: 'word_reply_to_comment', description: '[Comments] Reply to a comment thread by its ID.', inputSchema: { type: 'object', properties: { commentId: { type: 'string' }, text: { type: 'string' } }, required: ['commentId', 'text'] } },
|
|
60
|
+
{ name: 'word_resolve_comment', description: '[Comments] Mark a comment as resolved (hides in Word UI, preserves audit trail). Preferred over delete.', inputSchema: { type: 'object', properties: { commentId: { type: 'string' } }, required: ['commentId'] } },
|
|
61
|
+
{ name: 'word_delete_comment', description: '[Comments] Permanently delete a comment and all its replies. Prefer word_resolve_comment to preserve history.', inputSchema: { type: 'object', properties: { commentId: { type: 'string' } }, required: ['commentId'] } },
|
|
60
62
|
// 8. FOOTNOTES & ENDNOTES
|
|
61
|
-
{ name: 'word_insert_footnote', description: 'Insert a footnote anchored to a text match.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string' }, text: { type: 'string' }, occurrence: { type: 'number', description: '
|
|
62
|
-
{ name: 'word_insert_footnote_at_index', description: 'Insert a footnote at the end of a paragraph by index (no search needed).', inputSchema: { type: 'object', properties: { paragraphIndex: { type: 'number' }, text: { type: 'string' } }, required: ['paragraphIndex', 'text'] } },
|
|
63
|
-
{ name: 'word_insert_endnote', description: 'Insert an endnote anchored to a text match.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string' }, text: { type: 'string' }, occurrence: { type: 'number', description: '
|
|
64
|
-
{ name: 'word_get_footnotes', description: 'Get all footnotes with index and text content.', inputSchema: { type: 'object', properties: {} } },
|
|
65
|
-
{ name: 'word_get_endnotes', description: 'Get all endnotes with index and text content.', inputSchema: { type: 'object', properties: {} } },
|
|
66
|
-
{ name: 'word_delete_footnote', description: 'Delete a footnote by its
|
|
67
|
-
{ name: 'word_delete_endnote', description: 'Delete an endnote by its
|
|
63
|
+
{ name: 'word_insert_footnote', description: '[Footnotes] Insert a footnote anchored to a text match (searches for anchorText, attaches footnote there).', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text to search for as anchor point' }, text: { type: 'string', description: 'Footnote content' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['anchorText', 'text'] } },
|
|
64
|
+
{ name: 'word_insert_footnote_at_index', description: '[Footnotes] Insert a footnote at the end of a paragraph by index (no search needed — use when you know the paragraph position).', inputSchema: { type: 'object', properties: { paragraphIndex: { type: 'number', description: 'Paragraph index (0-based)' }, text: { type: 'string', description: 'Footnote content' } }, required: ['paragraphIndex', 'text'] } },
|
|
65
|
+
{ name: 'word_insert_endnote', description: '[Footnotes] Insert an endnote anchored to a text match.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text to search for as anchor point' }, text: { type: 'string', description: 'Endnote content' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['anchorText', 'text'] } },
|
|
66
|
+
{ name: 'word_get_footnotes', description: '[Footnotes] Get all footnotes with index and text content.', inputSchema: { type: 'object', properties: {} } },
|
|
67
|
+
{ name: 'word_get_endnotes', description: '[Footnotes] Get all endnotes with index and text content.', inputSchema: { type: 'object', properties: {} } },
|
|
68
|
+
{ name: 'word_delete_footnote', description: '[Footnotes] Delete a footnote by its 0-based index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Footnote index (0-based)' } }, required: ['index'] } },
|
|
69
|
+
{ name: 'word_delete_endnote', description: '[Footnotes] Delete an endnote by its 0-based index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Endnote index (0-based)' } }, required: ['index'] } },
|
|
68
70
|
// 9. TRACK CHANGES
|
|
69
|
-
{ name: 'word_get_tracked_changes', description: 'Get all tracked changes with index, type, author, date, and text.', inputSchema: { type: 'object', properties: {} } },
|
|
70
|
-
{ name: 'word_accept_tracked_change', description: 'Accept a tracked change by index.', inputSchema: { type: 'object', properties: { index: { type: 'number' } }, required: ['index'] } },
|
|
71
|
-
{ name: 'word_reject_tracked_change', description: 'Reject a tracked change by index.', inputSchema: { type: 'object', properties: { index: { type: 'number' } }, required: ['index'] } },
|
|
72
|
-
{ name: 'word_accept_all_tracked_changes', description: 'Accept all tracked changes.', inputSchema: { type: 'object', properties: {} } },
|
|
73
|
-
{ name: 'word_reject_all_tracked_changes', description: 'Reject all tracked changes.', inputSchema: { type: 'object', properties: {} } },
|
|
71
|
+
{ name: 'word_get_tracked_changes', description: '[Track Changes] Get all tracked changes with index, type (Added/Deleted), author, date, and text.', inputSchema: { type: 'object', properties: {} } },
|
|
72
|
+
{ name: 'word_accept_tracked_change', description: '[Track Changes] Accept a single tracked change by its index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Change index (0-based, from word_get_tracked_changes)' } }, required: ['index'] } },
|
|
73
|
+
{ name: 'word_reject_tracked_change', description: '[Track Changes] Reject a single tracked change by its index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Change index (0-based, from word_get_tracked_changes)' } }, required: ['index'] } },
|
|
74
|
+
{ name: 'word_accept_all_tracked_changes', description: '[Track Changes] Accept all tracked changes at once.', inputSchema: { type: 'object', properties: {} } },
|
|
75
|
+
{ name: 'word_reject_all_tracked_changes', description: '[Track Changes] Reject all tracked changes at once.', inputSchema: { type: 'object', properties: {} } },
|
|
74
76
|
// 10. CONTENT CONTROLS
|
|
75
|
-
{ name: 'word_get_content_controls', description: 'Get all content controls with id, tag, title, type, and text.', inputSchema: { type: 'object', properties: {} } },
|
|
76
|
-
{ name: 'word_insert_content_control', description: 'Wrap a text match in a content control
|
|
77
|
-
{ name: 'word_set_content_control_text', description: 'Set text in a content control by ID or tag.', inputSchema: { type: 'object', properties: { id: { type: 'number' }, tag: { type: 'string' }, text: { type: 'string' } }, required: ['text'] } },
|
|
77
|
+
{ name: 'word_get_content_controls', description: '[Content Controls] Get all content controls with id, tag, title, type (RichText/PlainText/CheckBox), and text.', inputSchema: { type: 'object', properties: {} } },
|
|
78
|
+
{ name: 'word_insert_content_control', description: '[Content Controls] Wrap a text match in a content control. RichText/PlainText preserve anchor text; CheckBox REPLACES it with a checkbox widget.', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text to search for and wrap' }, type: { type: 'string', enum: ['RichText', 'PlainText', 'CheckBox'], description: 'Control type. Default: RichText' }, title: { type: 'string' }, tag: { type: 'string', description: 'Tag for programmatic identification' }, color: { type: 'string', description: 'Border color' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } } } },
|
|
79
|
+
{ name: 'word_set_content_control_text', description: '[Content Controls] Set text in a content control identified by ID or tag. Does NOT work on CheckBox controls.', inputSchema: { type: 'object', properties: { id: { type: 'number', description: 'Content control ID (from word_get_content_controls)' }, tag: { type: 'string', description: 'Content control tag (alternative to ID)' }, text: { type: 'string' } }, required: ['text'] } },
|
|
78
80
|
// 11. BOOKMARKS
|
|
79
|
-
{ name: 'word_get_bookmarks', description: 'Get all bookmark names.', inputSchema: { type: 'object', properties: {} } },
|
|
80
|
-
{ name: 'word_insert_bookmark', description: '
|
|
81
|
-
{ name: 'word_delete_bookmark', description: 'Delete a bookmark by name.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
82
|
-
{ name: 'word_go_to_bookmark', description: 'Navigate to a bookmark and select its text range.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
83
|
-
{ name: 'word_get_bookmark_text', description: 'Get the text content within a named bookmark.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
81
|
+
{ name: 'word_get_bookmarks', description: '[Bookmarks] Get all bookmark names in the document.', inputSchema: { type: 'object', properties: {} } },
|
|
82
|
+
{ name: 'word_insert_bookmark', description: '[Bookmarks] Create a named bookmark at a text match. If name already exists, the bookmark is moved (returns warning).', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Bookmark name (must be unique)' }, anchorText: { type: 'string', description: 'Text to search for as bookmark location' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['name', 'anchorText'] } },
|
|
83
|
+
{ name: 'word_delete_bookmark', description: '[Bookmarks] Delete a bookmark by name (text remains, only the bookmark reference is removed).', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
84
|
+
{ name: 'word_go_to_bookmark', description: '[Bookmarks] Navigate to a bookmark and select its text range. Returns the bookmark text.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
85
|
+
{ name: 'word_get_bookmark_text', description: '[Bookmarks] Get the text content within a named bookmark.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
|
|
84
86
|
// 12. HYPERLINKS
|
|
85
|
-
{ name: 'word_insert_hyperlink', description: '
|
|
86
|
-
{ name: 'word_get_hyperlinks', description: 'List all hyperlinks with URL, display text, and
|
|
87
|
-
{ name: 'word_remove_hyperlink', description: 'Remove a hyperlink from text (keeps the text, removes the link).', inputSchema: { type: 'object', properties: { anchorText: { type: 'string' }, occurrence: { type: 'number', description: '
|
|
87
|
+
{ name: 'word_insert_hyperlink', description: '[Hyperlinks] Add a hyperlink URL to existing text (searches for anchorText, applies the link).', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text to search for and link' }, url: { type: 'string', description: 'URL (must start with http:// or https://)' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['anchorText', 'url'] } },
|
|
88
|
+
{ name: 'word_get_hyperlinks', description: '[Hyperlinks] List all hyperlinks with URL, display text, and internal flag (TOC links).', inputSchema: { type: 'object', properties: {} } },
|
|
89
|
+
{ name: 'word_remove_hyperlink', description: '[Hyperlinks] Remove a hyperlink from text (keeps the text, removes only the link).', inputSchema: { type: 'object', properties: { anchorText: { type: 'string', description: 'Text of the hyperlink to remove' }, occurrence: { type: 'number', description: 'Which match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive matching. Default: false' } }, required: ['anchorText'] } },
|
|
88
90
|
// 13. HEADERS & FOOTERS
|
|
89
|
-
{ name: 'word_get_header_footer', description: 'Get header or footer text.', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['header', 'footer'] }, sectionIndex: { type: 'number' }, headerType: { type: 'string', enum: ['Primary', 'FirstPage', 'EvenPages'] } }, required: ['type'] } },
|
|
90
|
-
{ name: 'word_set_header_footer', description: 'Set header or footer text.', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['header', 'footer'] }, text: { type: 'string' }, sectionIndex: { type: 'number' }, headerType: { type: 'string', enum: ['Primary', 'FirstPage', 'EvenPages'] } }, required: ['type', 'text'] } },
|
|
91
|
+
{ name: 'word_get_header_footer', description: '[Headers/Footers] Get header or footer text content.', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['header', 'footer'] }, sectionIndex: { type: 'number', description: 'Section index (0-based). Default: 0' }, headerType: { type: 'string', enum: ['Primary', 'FirstPage', 'EvenPages'], description: 'Default: Primary' } }, required: ['type'] } },
|
|
92
|
+
{ name: 'word_set_header_footer', description: '[Headers/Footers] Set header or footer text (replaces existing content).', inputSchema: { type: 'object', properties: { type: { type: 'string', enum: ['header', 'footer'] }, text: { type: 'string' }, sectionIndex: { type: 'number', description: 'Section index (0-based). Default: 0' }, headerType: { type: 'string', enum: ['Primary', 'FirstPage', 'EvenPages'], description: 'Default: Primary' } }, required: ['type', 'text'] } },
|
|
91
93
|
// 14. IMAGES
|
|
92
|
-
{ name: 'word_insert_image', description: 'Insert an image from base64 data.', inputSchema: { type: 'object', properties: { base64: { type: 'string', description: 'Base64-encoded image data' }, location: { type: 'string', enum: ['Start', 'End'] }, width: { type: 'number' }, height: { type: 'number' }, altText: { type: 'string' } }, required: ['base64'] } },
|
|
93
|
-
{ name: 'word_get_images', description: 'List all inline images with dimensions, alt text, and hyperlinks.', inputSchema: { type: 'object', properties: {} } },
|
|
94
|
-
{ name: 'word_delete_image', description: 'Delete an inline image by its
|
|
94
|
+
{ name: 'word_insert_image', description: '[Images] Insert an inline image from base64-encoded data (PNG, JPEG, or GIF).', inputSchema: { type: 'object', properties: { base64: { type: 'string', description: 'Base64-encoded image data' }, location: { type: 'string', enum: ['Start', 'End'] }, width: { type: 'number', description: 'Width in points' }, height: { type: 'number', description: 'Height in points' }, altText: { type: 'string', description: 'Alt text for accessibility' } }, required: ['base64'] } },
|
|
95
|
+
{ name: 'word_get_images', description: '[Images] List all inline images with index, dimensions, alt text, and hyperlinks.', inputSchema: { type: 'object', properties: {} } },
|
|
96
|
+
{ name: 'word_delete_image', description: '[Images] Delete an inline image by its 0-based index.', inputSchema: { type: 'object', properties: { index: { type: 'number', description: 'Image index (0-based)' } }, required: ['index'] } },
|
|
95
97
|
// 15. PAGE LAYOUT & SECTIONS
|
|
96
|
-
{ name: 'word_get_page_layout', description: 'Get page layout (margins, orientation, paper size) for a section.', inputSchema: { type: 'object', properties: { sectionIndex: { type: 'number' } } } },
|
|
97
|
-
{ name: 'word_set_page_layout', description: 'Set page margins (in points), orientation, or paper size for a section.', inputSchema: { type: 'object', properties: { orientation: { type: 'string', enum: ['Portrait', 'Landscape'] }, topMargin: { type: 'number' }, bottomMargin: { type: 'number' }, leftMargin: { type: 'number' }, rightMargin: { type: 'number' }, paperSize: { type: 'string', enum: ['Letter', 'A4', 'A3', 'Legal', 'Custom'] }, sectionIndex: { type: 'number' } } } },
|
|
98
|
-
{ name: 'word_get_sections', description: 'List all sections with their page setup (margins, orientation, paper size).', inputSchema: { type: 'object', properties: {} } },
|
|
99
|
-
{ name: 'word_insert_page_break', description: 'Insert a page break after a paragraph. Omit paragraphIndex
|
|
100
|
-
{ name: 'word_insert_section_break', description: 'Insert a section break after a paragraph.', inputSchema: { type: 'object', properties: { paragraphIndex: { type: 'number' }, breakType: { type: 'string', enum: ['SectionNext', 'SectionContinuous', 'SectionEven', 'SectionOdd'], description: 'Default: SectionNext' } } } },
|
|
98
|
+
{ name: 'word_get_page_layout', description: '[Layout] Get page layout (margins, orientation, paper size) for a section.', inputSchema: { type: 'object', properties: { sectionIndex: { type: 'number', description: 'Section index (0-based). Default: 0' } } } },
|
|
99
|
+
{ name: 'word_set_page_layout', description: '[Layout] Set page margins (in points, 72pt = 1 inch), orientation, or paper size for a section.', inputSchema: { type: 'object', properties: { orientation: { type: 'string', enum: ['Portrait', 'Landscape'] }, topMargin: { type: 'number', description: 'Points (72 = 1 inch)' }, bottomMargin: { type: 'number', description: 'Points (72 = 1 inch)' }, leftMargin: { type: 'number', description: 'Points (72 = 1 inch)' }, rightMargin: { type: 'number', description: 'Points (72 = 1 inch)' }, paperSize: { type: 'string', enum: ['Letter', 'A4', 'A3', 'Legal', 'Custom'] }, sectionIndex: { type: 'number', description: 'Section index (0-based). Default: 0' } } } },
|
|
100
|
+
{ name: 'word_get_sections', description: '[Layout] List all sections with their page setup (margins, orientation, paper size).', inputSchema: { type: 'object', properties: {} } },
|
|
101
|
+
{ name: 'word_insert_page_break', description: '[Layout] Insert a page break after a paragraph. Omit paragraphIndex to insert at end of document.', inputSchema: { type: 'object', properties: { paragraphIndex: { type: 'number', description: 'Paragraph index (0-based). Omit for end of document.' } } } },
|
|
102
|
+
{ name: 'word_insert_section_break', description: '[Layout] Insert a section break after a paragraph. Cannot be used inside table cells.', inputSchema: { type: 'object', properties: { paragraphIndex: { type: 'number', description: 'Paragraph index (0-based). Omit for end of document.' }, breakType: { type: 'string', enum: ['SectionNext', 'SectionContinuous', 'SectionEven', 'SectionOdd'], description: 'Default: SectionNext' } } } },
|
|
101
103
|
// 16. CUSTOM PROPERTIES
|
|
102
|
-
{ name: 'word_get_custom_properties', description: 'Get all custom document properties (key-value pairs with types).', inputSchema: { type: 'object', properties: {} } },
|
|
103
|
-
{ name: 'word_set_custom_property', description: 'Set a custom document property. Creates or updates the key-value pair.', inputSchema: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } }, required: ['key', 'value'] } },
|
|
104
|
-
{ name: 'word_delete_custom_property', description: 'Delete a custom document property by key.', inputSchema: { type: 'object', properties: { key: { type: 'string' } }, required: ['key'] } },
|
|
104
|
+
{ name: 'word_get_custom_properties', description: '[Properties] Get all custom document properties (key-value pairs with types).', inputSchema: { type: 'object', properties: {} } },
|
|
105
|
+
{ name: 'word_set_custom_property', description: '[Properties] Set a custom document property. Creates or updates the key-value pair.', inputSchema: { type: 'object', properties: { key: { type: 'string' }, value: { type: 'string' } }, required: ['key', 'value'] } },
|
|
106
|
+
{ name: 'word_delete_custom_property', description: '[Properties] Delete a custom document property by key.', inputSchema: { type: 'object', properties: { key: { type: 'string' } }, required: ['key'] } },
|
|
105
107
|
// 17. ADVANCED INSERTION & FIELDS
|
|
106
|
-
{ name: 'word_insert_html', description: 'Insert HTML content that Word converts to native formatting. Supports headings, bold, italic, links, tables.', inputSchema: { type: 'object', properties: { html: { type: 'string' }, location: { type: 'string', enum: ['Start', 'End'] } }, required: ['html'] } },
|
|
107
|
-
{ name: 'word_insert_ooxml', description: 'Insert raw Office Open XML for precise formatting control when HTML is insufficient.', inputSchema: { type: 'object', properties: { ooxml: { type: 'string' }, location: { type: 'string', enum: ['Start', 'End'] } }, required: ['ooxml'] } },
|
|
108
|
-
{ name: 'word_insert_table_of_contents', description: 'Insert a table of contents based on heading styles.', inputSchema: { type: 'object', properties: { location: { type: 'string', enum: ['Start', 'End'] }, switches: { type: 'string' } } } },
|
|
109
|
-
{ name: 'word_get_fields', description: 'Get all fields in the document (hyperlinks, TOC entries, page numbers, etc).', inputSchema: { type: 'object', properties: {} } },
|
|
108
|
+
{ name: 'word_insert_html', description: '[Advanced] Insert HTML content that Word converts to native formatting. Supports headings, bold, italic, links, tables, and lists.', inputSchema: { type: 'object', properties: { html: { type: 'string' }, location: { type: 'string', enum: ['Start', 'End'], description: 'Default: End' } }, required: ['html'] } },
|
|
109
|
+
{ name: 'word_insert_ooxml', description: '[Advanced] Insert raw Office Open XML for precise formatting control when HTML is insufficient. Must follow pkg:package structure.', inputSchema: { type: 'object', properties: { ooxml: { type: 'string' }, location: { type: 'string', enum: ['Start', 'End'], description: 'Default: End' } }, required: ['ooxml'] } },
|
|
110
|
+
{ name: 'word_insert_table_of_contents', description: '[Advanced] Insert a table of contents based on heading styles. Note: after insertion, heading text appears twice (in TOC and body) — search matches TOC entries first.', inputSchema: { type: 'object', properties: { location: { type: 'string', enum: ['Start', 'End'], description: 'Default: Start' }, switches: { type: 'string', description: 'TOC field switches. Default: \\o "1-3" \\h \\z \\u' } } } },
|
|
111
|
+
{ name: 'word_get_fields', description: '[Advanced] Get all fields in the document (hyperlinks, TOC entries, page numbers, etc).', inputSchema: { type: 'object', properties: {} } },
|
|
110
112
|
// 18. EQUATIONS
|
|
111
|
-
{ name: 'word_insert_equation', description: 'Insert a LaTeX math equation as a native
|
|
113
|
+
{ name: 'word_insert_equation', description: '[Equations] Insert a LaTeX math equation as a native editable Word equation. Supports fractions, roots, integrals, matrices, Greek letters, and all standard LaTeX math. Display mode (default) inserts a centered block equation. Inline mode inserts after a search match (provide anchorText) or at cursor position.', inputSchema: { type: 'object', properties: { latex: { type: 'string', description: 'LaTeX math expression (e.g. "\\\\frac{a}{b}", "\\\\int_0^\\\\infty e^{-x} dx")' }, displayMode: { type: 'boolean', description: 'true (default) = centered block equation, false = inline equation' }, location: { type: 'string', enum: ['Start', 'End'], description: 'Where to insert display-mode equations. Default: End' }, anchorText: { type: 'string', description: 'For inline mode: search for this text and insert equation after it (avoids manual cursor positioning)' }, occurrence: { type: 'number', description: 'Which anchorText match to target: 0=first, 1=second, etc. Default: 0' }, matchCase: { type: 'boolean', description: 'Case-sensitive anchor matching. Default: false' } }, required: ['latex'] } },
|
|
114
|
+
// 19. BATCH OPERATIONS
|
|
115
|
+
{ name: 'word_batch', description: '[Batch] Execute multiple operations in a single call to reduce round-trips. Each operation is a tool call object with "tool" and "args" fields. Operations execute sequentially — if one fails, subsequent operations are skipped. Returns results array with success/error for each operation. Consecutive native operations are sent to Word in one message for maximum speed.', inputSchema: { type: 'object', properties: { operations: { type: 'array', items: { type: 'object', properties: { tool: { type: 'string', description: 'Tool name (e.g. "word_insert_paragraph")' }, args: { type: 'object', description: 'Arguments for the tool' } }, required: ['tool'] }, description: 'Array of {tool, args} objects to execute sequentially' } }, required: ['operations'] } },
|
|
112
116
|
];
|
|
113
117
|
|
|
114
118
|
const toolActionMap = {
|
|
115
119
|
word_get_text: 'getDocumentText', word_get_document_properties: 'getDocumentProperties',
|
|
116
120
|
word_set_document_properties: 'setDocumentProperties', word_save: 'saveDocument',
|
|
117
|
-
word_clear: 'clearDocument',
|
|
118
|
-
word_create_document: 'createNewDocument',
|
|
121
|
+
word_clear: 'clearDocument', word_create_document: 'createNewDocument',
|
|
119
122
|
word_get_word_count: 'getWordCount', word_get_styles: 'getStyles',
|
|
120
123
|
word_get_coauthors: 'getCoauthors', word_set_change_tracking: 'setChangeTracking',
|
|
121
124
|
word_get_paragraphs: 'getParagraphs', word_get_paragraph_by_index: 'getParagraphByIndex',
|
|
122
|
-
word_insert_paragraph: 'insertParagraph',
|
|
125
|
+
word_insert_paragraph: 'insertParagraph', word_insert_paragraph_at_index: 'insertParagraphAtIndex',
|
|
126
|
+
word_delete_paragraph: 'deleteParagraph', word_replace_paragraph_text: 'replaceParagraphText',
|
|
123
127
|
word_set_paragraph_style: 'setParagraphStyle', word_set_paragraph_spacing: 'setParagraphSpacing',
|
|
124
128
|
word_search: 'search', word_search_and_replace: 'searchAndReplace',
|
|
125
|
-
|
|
129
|
+
word_insert_text_at_match: 'insertText', word_get_selection_info: 'getSelectionInfo',
|
|
126
130
|
word_insert_text_at_selection: 'insertTextAtSelection', word_insert_line_break: 'insertLineBreak',
|
|
127
131
|
word_format_text: 'formatRange', word_clear_formatting: 'clearFormatting',
|
|
128
132
|
word_get_font_info: 'getFontInfo',
|
|
129
|
-
word_insert_table: 'insertTable',
|
|
133
|
+
word_insert_table: 'insertTable', word_list_tables: 'getTables',
|
|
130
134
|
word_get_table_data: 'getTableData', word_set_table_cell: 'setTableCell',
|
|
131
135
|
word_add_table_row: 'addTableRow', word_delete_table_row: 'deleteTableRow',
|
|
132
136
|
word_merge_table_cells: 'mergeTableCells', word_split_table_cell: 'splitTableCell',
|
|
133
137
|
word_set_table_style: 'setTableStyle', word_set_table_cell_shading: 'setTableCellShading',
|
|
134
138
|
word_insert_list: 'insertList', word_get_list_info: 'getListInfo', word_set_list_level: 'setListLevel',
|
|
135
|
-
word_add_comment: 'addComment', word_get_comments: '
|
|
139
|
+
word_add_comment: 'addComment', word_get_comments: 'getCommentsWithAnchor',
|
|
136
140
|
word_get_comment_replies: 'getCommentReplies', word_reply_to_comment: 'replyToComment',
|
|
137
141
|
word_resolve_comment: 'resolveComment', word_delete_comment: 'deleteComment',
|
|
138
|
-
word_get_comment_anchor: 'getCommentAnchor', word_get_comments_with_anchor: 'getCommentsWithAnchor',
|
|
139
142
|
word_insert_footnote: 'insertFootnote', word_insert_footnote_at_index: 'insertFootnoteAtIndex',
|
|
140
143
|
word_insert_endnote: 'insertEndnote', word_get_footnotes: 'getFootnotes',
|
|
141
144
|
word_get_endnotes: 'getEndnotes', word_delete_footnote: 'deleteFootnote', word_delete_endnote: 'deleteEndnote',
|
|
@@ -158,6 +161,8 @@ const toolActionMap = {
|
|
|
158
161
|
word_delete_custom_property: 'deleteCustomProperty',
|
|
159
162
|
word_insert_html: 'insertHtml', word_insert_ooxml: 'insertOoxml',
|
|
160
163
|
word_insert_table_of_contents: 'insertTableOfContents', word_get_fields: 'getFields',
|
|
164
|
+
// word_insert_equation, word_batch, word_get_document_outline, word_move_paragraph
|
|
165
|
+
// are handled directly in index.js (not via taskpane action map)
|
|
161
166
|
};
|
|
162
167
|
|
|
163
168
|
module.exports = { tools, toolActionMap };
|
package/lib/usage-guide.js
CHANGED
|
@@ -6,25 +6,52 @@ const USAGE_GUIDE = `# MCP Word Bridge — Usage Guide
|
|
|
6
6
|
|
|
7
7
|
Controls a live Word document through an Office Add-in. All operations execute immediately in Word.
|
|
8
8
|
|
|
9
|
+
## Quick Start — Read This First
|
|
10
|
+
|
|
11
|
+
1. **Read before writing** — call \`word_get_document_outline\` or \`word_get_paragraphs\` to understand the document structure
|
|
12
|
+
2. **Use the right tool for the job:**
|
|
13
|
+
- Appending content → \`word_insert_paragraph\` (Start/End)
|
|
14
|
+
- Inserting at a position → \`word_insert_paragraph_at_index\` (Before/After by index)
|
|
15
|
+
- Editing existing text → \`word_replace_paragraph_text\` (by index, safe for collaboration)
|
|
16
|
+
- Bulk find/replace → \`word_search_and_replace\` (all occurrences)
|
|
17
|
+
- Inserting adjacent to text → \`word_insert_text_at_match\` (searches then inserts)
|
|
18
|
+
3. **Batch multiple operations** — use \`word_batch\` to send up to 50 operations in one call
|
|
19
|
+
4. **Save explicitly** — call \`word_save\` after significant changes
|
|
20
|
+
|
|
9
21
|
## Reading Content
|
|
10
|
-
- \`
|
|
22
|
+
- \`word_get_document_outline\` — heading hierarchy tree (fast structural overview)
|
|
23
|
+
- \`word_get_paragraphs\` — all paragraphs with text, style, alignment. Paginate with start/end.
|
|
11
24
|
- \`word_get_text\` — quick plain-text dump (no structure)
|
|
12
25
|
- \`word_search\` — locate text before operating on it
|
|
13
26
|
|
|
14
27
|
## Document Lifecycle
|
|
15
|
-
- \`word_clear\` — clear all body content
|
|
16
|
-
- \`word_create_document\` — create
|
|
28
|
+
- \`word_clear\` — clear all body content (fast reset). Does not clear headers/footers.
|
|
29
|
+
- \`word_create_document\` — create a new document in a separate Word window
|
|
17
30
|
- \`word_save\` — persist changes to disk
|
|
18
31
|
|
|
19
32
|
## Editing Text
|
|
20
|
-
- \`
|
|
21
|
-
- \`
|
|
33
|
+
- \`word_replace_paragraph_text\` — replace text by paragraph index (PREFERRED for collaborative docs)
|
|
34
|
+
- \`word_search_and_replace\` — bulk find/replace across document
|
|
35
|
+
- \`word_insert_text_at_match\` — insert before/after a search match (use \`occurrence\` for Nth match)
|
|
22
36
|
- \`word_insert_text_at_selection\` — insert at cursor or replace selection
|
|
23
37
|
- Verify edits with \`word_search\` or \`word_get_paragraphs\`
|
|
24
38
|
|
|
39
|
+
## Batch Operations
|
|
40
|
+
\`word_batch\` executes multiple operations in a single MCP call:
|
|
41
|
+
\`\`\`json
|
|
42
|
+
{"operations": [
|
|
43
|
+
{"tool": "word_insert_paragraph", "args": {"text": "Hello", "style": "Heading 1"}},
|
|
44
|
+
{"tool": "word_insert_paragraph", "args": {"text": "World", "style": "Normal"}},
|
|
45
|
+
{"tool": "word_save", "args": {}}
|
|
46
|
+
]}
|
|
47
|
+
\`\`\`
|
|
48
|
+
Operations run sequentially. If one fails, the rest are skipped. Maximum 50 per batch.
|
|
49
|
+
|
|
50
|
+
**Performance:** Consecutive standard operations (paragraphs, search, formatting, tables, etc.) are bundled into a single WebSocket message to Word — executing in one round-trip instead of one per operation. Only server-composed tools (equations, outline, move_paragraph) break the batch into segments.
|
|
51
|
+
|
|
25
52
|
## Search Behavior (applies to ALL search-based tools)
|
|
26
|
-
- Case-insensitive by default. Pass \`matchCase: true\` for exact match.
|
|
27
|
-
- Affected tools: search, search_and_replace,
|
|
53
|
+
- Case-insensitive by default. Pass \`matchCase: true\` for exact case match.
|
|
54
|
+
- 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).
|
|
28
55
|
|
|
29
56
|
## Alignment Values
|
|
30
57
|
The bridge normalizes aliases: Left, Center/Centered, Right, Justify/Justified.
|
|
@@ -32,19 +59,18 @@ The bridge normalizes aliases: Left, Center/Centered, Right, Justify/Justified.
|
|
|
32
59
|
## Change Tracking
|
|
33
60
|
- Call \`word_set_change_tracking({mode:"TrackAll"})\` BEFORE edits for tracked changes
|
|
34
61
|
- Adjacent insertions by the same author may coalesce into a single tracked change
|
|
35
|
-
- \`search_and_replace\` with tracking may only expose the "Added" half
|
|
62
|
+
- \`search_and_replace\` with tracking may only expose the "Added" half
|
|
36
63
|
|
|
37
64
|
## Comments — CRITICAL PATTERNS
|
|
38
65
|
|
|
39
66
|
### Reading
|
|
40
|
-
- \`
|
|
41
|
-
- \`word_get_comment_replies\` — reply thread for a specific comment
|
|
67
|
+
- \`word_get_comments\` — returns all comments with their anchored document text, author, dates, resolved status
|
|
42
68
|
|
|
43
69
|
### Comment + text editing interaction
|
|
44
70
|
**Problem:** \`word_search_and_replace\` on text anchoring a comment collapses the anchor (shrinks to empty). The comment survives but loses its positional context.
|
|
45
71
|
|
|
46
72
|
**Safe pattern:**
|
|
47
|
-
1. \`
|
|
73
|
+
1. \`word_get_comments\` — identify commented text via anchorText field
|
|
48
74
|
2. If replacement overlaps a comment anchor:
|
|
49
75
|
a. \`word_reply_to_comment\` explaining resolution (if appropriate)
|
|
50
76
|
b. \`word_resolve_comment\`
|
|
@@ -53,19 +79,15 @@ The bridge normalizes aliases: Left, Center/Centered, Right, Justify/Justified.
|
|
|
53
79
|
|
|
54
80
|
**Avoid:** replacing text first (anchor collapses), or deleting+recreating comments (loses author/date/thread).
|
|
55
81
|
|
|
56
|
-
### Adding
|
|
57
|
-
- \`word_add_comment\` — anchor to a text match
|
|
58
|
-
- \`word_reply_to_comment\` — reply to existing thread
|
|
59
|
-
- \`word_resolve_comment\` — hides in Word UI, preserves history
|
|
60
|
-
|
|
61
82
|
## Tables
|
|
62
|
-
- 0-based
|
|
63
|
-
- \`
|
|
83
|
+
- All indices 0-based: tableIndex, row, col
|
|
84
|
+
- \`word_list_tables\` for metadata overview (count, dimensions, style — no cell values)
|
|
85
|
+
- \`word_get_table_data\` for a specific table's cell values
|
|
64
86
|
- Table cell paragraphs can't be structurally deleted (only cleared)
|
|
65
87
|
- Can't insert page/section breaks inside table cells
|
|
66
88
|
|
|
67
89
|
## Footnotes & Endnotes
|
|
68
|
-
- \`word_insert_footnote\` — anchor to text match
|
|
90
|
+
- \`word_insert_footnote\` — anchor to text match (searches for anchorText)
|
|
69
91
|
- \`word_insert_footnote_at_index\` — anchor to paragraph by index (no search needed)
|
|
70
92
|
|
|
71
93
|
## Page Layout
|
|
@@ -79,28 +101,34 @@ The bridge normalizes aliases: Left, Center/Centered, Right, Justify/Justified.
|
|
|
79
101
|
## TOC Behavior
|
|
80
102
|
After inserting a Table of Contents, heading text appears twice in the document (once in TOC, once in body). Search matches TOC entries first — use \`occurrence\` parameter to target the body instance.
|
|
81
103
|
|
|
82
|
-
## Error Messages
|
|
83
|
-
- "Word taskpane not connected" — user must open the MCP Word Bridge add-in in Word
|
|
84
|
-
- "Anchor not found" — search text not found in document
|
|
85
|
-
- "Occurrence N not found" — match index out of range
|
|
86
|
-
- Timeout: 30s default, 60s for HTML/OOXML/styles/TOC insertion
|
|
87
|
-
|
|
88
104
|
## Equations
|
|
89
105
|
- \`word_insert_equation\` takes a LaTeX string and inserts a native Word equation
|
|
90
|
-
- \`displayMode: true\` (default) = centered block equation
|
|
91
|
-
-
|
|
92
|
-
-
|
|
106
|
+
- \`displayMode: true\` (default) = centered block equation
|
|
107
|
+
- \`displayMode: false\` = inline equation
|
|
108
|
+
- For inline equations, provide \`anchorText\` to position the equation after a search match (avoids manual cursor management)
|
|
109
|
+
- Without \`anchorText\`, inline equations insert at the current cursor position
|
|
93
110
|
- Supports: fractions, roots, integrals, sums, matrices, Greek letters, AMS math
|
|
94
|
-
- Invalid LaTeX returns a descriptive parse error
|
|
111
|
+
- Invalid LaTeX returns a descriptive parse error
|
|
95
112
|
- The equation is fully editable in Word's built-in equation editor
|
|
96
113
|
- Examples: \`\\\\frac{a}{b}\`, \`\\\\int_0^\\\\infty e^{-x} dx\`, \`\\\\sum_{i=1}^n x_i\`
|
|
97
114
|
|
|
115
|
+
## Error Messages
|
|
116
|
+
- "Word taskpane not connected" — user must open the MCP Word Bridge add-in in Word
|
|
117
|
+
- "Anchor not found" — search text not found in document
|
|
118
|
+
- "Occurrence N not found" — match index out of range
|
|
119
|
+
- Timeout: 30s default, 60s for HTML/OOXML/styles/TOC insertion
|
|
120
|
+
|
|
98
121
|
## Best Practices
|
|
99
|
-
1.
|
|
100
|
-
2. \`
|
|
101
|
-
3.
|
|
102
|
-
4.
|
|
103
|
-
|
|
122
|
+
1. Start with \`word_get_document_outline\` to understand structure
|
|
123
|
+
2. \`word_get_comments\` before bulk replacements to avoid anchor damage
|
|
124
|
+
3. Use \`word_batch\` for multiple sequential operations (reduces latency)
|
|
125
|
+
4. In collaborative editing, prefer index-based tools over search-based:
|
|
126
|
+
- \`word_replace_paragraph_text\` over \`word_search_and_replace\`
|
|
127
|
+
- \`word_insert_paragraph_at_index\` over \`word_insert_paragraph\`
|
|
128
|
+
5. Enable change tracking for collaborative documents
|
|
129
|
+
6. \`word_save\` explicitly after significant changes
|
|
130
|
+
7. Resolve comments rather than deleting them (preserves audit trail)
|
|
131
|
+
8. Use \`word_move_paragraph\` to reorder content (avoids index-shifting bugs with manual delete+insert)
|
|
104
132
|
`;
|
|
105
133
|
|
|
106
134
|
module.exports = USAGE_GUIDE;
|
package/package.json
CHANGED
package/taskpane-app.js
CHANGED
|
@@ -70,6 +70,30 @@ async function handleCommand(cmd) {
|
|
|
70
70
|
// --- Command registry ---
|
|
71
71
|
const commands = {};
|
|
72
72
|
|
|
73
|
+
// == BATCH EXECUTION (runs multiple actions in one WebSocket message) ==
|
|
74
|
+
commands.batchExecute = async (p) => {
|
|
75
|
+
if (!p.operations || !Array.isArray(p.operations) || p.operations.length === 0) {
|
|
76
|
+
throw new Error('operations must be a non-empty array');
|
|
77
|
+
}
|
|
78
|
+
log(' batch: ' + p.operations.length + ' ops', 'log-batch');
|
|
79
|
+
const results = [];
|
|
80
|
+
for (let i = 0; i < p.operations.length; i++) {
|
|
81
|
+
const op = p.operations[i];
|
|
82
|
+
try {
|
|
83
|
+
const handler = commands[op.action];
|
|
84
|
+
if (!handler) throw new Error('Unknown action: ' + op.action);
|
|
85
|
+
log(' [' + (i + 1) + '/' + p.operations.length + '] ' + op.action, 'log-batch');
|
|
86
|
+
const result = await handler(op.params || {});
|
|
87
|
+
results.push({ index: i, success: true, result: result });
|
|
88
|
+
} catch (e) {
|
|
89
|
+
log(' [' + (i + 1) + '/' + p.operations.length + '] ' + (op.action || '?') + ' ✗ ' + e.message, 'log-err');
|
|
90
|
+
results.push({ index: i, success: false, error: e.message });
|
|
91
|
+
break; // stop on first error
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { results: results };
|
|
95
|
+
};
|
|
96
|
+
|
|
73
97
|
// == BASIC ==
|
|
74
98
|
commands.ping = async () => ({ status: 'ok', wordReady });
|
|
75
99
|
|
|
@@ -231,6 +255,15 @@ commands.deleteParagraph = (p) => Word.run(async (ctx) => {
|
|
|
231
255
|
if (p.index >= paragraphs.items.length) throw new Error('Paragraph index out of range. Valid indices: 0-' + (paragraphs.items.length - 1) + ' (document has ' + paragraphs.items.length + ' paragraphs).');
|
|
232
256
|
paragraphs.items[p.index].delete();
|
|
233
257
|
await ctx.sync();
|
|
258
|
+
// Move cursor to the paragraph that now occupies this position (or previous if at end)
|
|
259
|
+
const parasAfter = ctx.document.body.paragraphs;
|
|
260
|
+
parasAfter.load('text');
|
|
261
|
+
await ctx.sync();
|
|
262
|
+
if (parasAfter.items.length > 0) {
|
|
263
|
+
const cursorIdx = Math.min(p.index, parasAfter.items.length - 1);
|
|
264
|
+
parasAfter.items[cursorIdx].getRange('Start').select();
|
|
265
|
+
await ctx.sync();
|
|
266
|
+
}
|
|
234
267
|
return { success: true };
|
|
235
268
|
});
|
|
236
269
|
|
|
@@ -270,6 +303,9 @@ commands.setParagraphStyle = (p) => Word.run(async (ctx) => {
|
|
|
270
303
|
para.alignment = alignment;
|
|
271
304
|
await ctx.sync();
|
|
272
305
|
}
|
|
306
|
+
// Move cursor to the styled paragraph
|
|
307
|
+
para.getRange('End').select();
|
|
308
|
+
await ctx.sync();
|
|
273
309
|
return { success: true };
|
|
274
310
|
});
|
|
275
311
|
|
|
@@ -400,8 +436,7 @@ commands.getTables = () => Word.run(async (ctx) => {
|
|
|
400
436
|
tables.load('rowCount,values,style,headerRowCount');
|
|
401
437
|
await ctx.sync();
|
|
402
438
|
const items = tables.items.map((t, i) => ({
|
|
403
|
-
index: i, rowCount: t.rowCount, style: t.style, headerRowCount: t.headerRowCount
|
|
404
|
-
values: t.values
|
|
439
|
+
index: i, rowCount: t.rowCount, columnCount: (t.values && t.values[0]) ? t.values[0].length : 0, style: t.style, headerRowCount: t.headerRowCount
|
|
405
440
|
}));
|
|
406
441
|
return { count: items.length, tables: items };
|
|
407
442
|
});
|
|
@@ -1180,6 +1215,35 @@ commands.setParagraphSpacing = (p) => Word.run(async (ctx) => {
|
|
|
1180
1215
|
return { success: true };
|
|
1181
1216
|
});
|
|
1182
1217
|
|
|
1218
|
+
// == SURGICAL PARAGRAPH EDITING ==
|
|
1219
|
+
commands.replaceParagraphText = (p) => Word.run(async (ctx) => {
|
|
1220
|
+
if (p.index < 0) throw new Error('Index must be non-negative');
|
|
1221
|
+
const paragraphs = ctx.document.body.paragraphs;
|
|
1222
|
+
paragraphs.load('text');
|
|
1223
|
+
await ctx.sync();
|
|
1224
|
+
if (p.index >= paragraphs.items.length) throw new Error('Paragraph index out of range. Valid indices: 0-' + (paragraphs.items.length - 1) + ' (document has ' + paragraphs.items.length + ' paragraphs).');
|
|
1225
|
+
const para = paragraphs.items[p.index];
|
|
1226
|
+
const inserted = para.insertText(p.text, Word.InsertLocation.replace);
|
|
1227
|
+
inserted.getRange('End').select();
|
|
1228
|
+
await ctx.sync();
|
|
1229
|
+
return { success: true };
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
commands.insertParagraphAtIndex = (p) => Word.run(async (ctx) => {
|
|
1233
|
+
if (p.index < 0) throw new Error('Index must be non-negative');
|
|
1234
|
+
const paragraphs = ctx.document.body.paragraphs;
|
|
1235
|
+
paragraphs.load('text');
|
|
1236
|
+
await ctx.sync();
|
|
1237
|
+
if (p.index >= paragraphs.items.length) throw new Error('Paragraph index out of range. Valid indices: 0-' + (paragraphs.items.length - 1) + ' (document has ' + paragraphs.items.length + ' paragraphs).');
|
|
1238
|
+
const ref = paragraphs.items[p.index];
|
|
1239
|
+
const location = p.location === 'Before' ? Word.InsertLocation.before : Word.InsertLocation.after;
|
|
1240
|
+
const newPara = ref.insertParagraph(p.text, location);
|
|
1241
|
+
newPara.style = p.style || 'Normal';
|
|
1242
|
+
newPara.getRange('End').select();
|
|
1243
|
+
await ctx.sync();
|
|
1244
|
+
return { success: true };
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1183
1247
|
// == BOOKMARK NAVIGATION ==
|
|
1184
1248
|
commands.goToBookmark = (p) => Word.run(async (ctx) => {
|
|
1185
1249
|
let range;
|