@components-kit/open-workbook-mcp-server 0.1.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/dist/index.js ADDED
@@ -0,0 +1,3779 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import * as z from "zod/v4";
5
+ import { RuntimeService } from "@components-kit/open-workbook-backend/runtime";
6
+ import { startBackendServer } from "@components-kit/open-workbook-backend/server";
7
+ import { isToolExposed, makeId } from "@components-kit/open-workbook-protocol";
8
+ const host = process.env.OPEN_WORKBOOK_HOST ?? "127.0.0.1";
9
+ const port = Number(process.env.OPEN_WORKBOOK_PORT ?? 37845);
10
+ const addinPath = process.env.OPEN_WORKBOOK_ADDIN_PATH ?? "/addin";
11
+ const daemonUrl = trimTrailingSlash(readArg("--daemon-url") ?? process.env.OPEN_WORKBOOK_DAEMON_URL ?? `http://${host}:${port}`);
12
+ const agentName = readArg("--agent-name") ?? process.env.OPEN_WORKBOOK_AGENT_NAME;
13
+ const standalone = hasArg("--standalone") || process.env.OPEN_WORKBOOK_MCP_STANDALONE === "1";
14
+ const catalogOptions = {
15
+ includePreview: process.env.OPEN_WORKBOOK_PREVIEW_TOOLS === "1"
16
+ };
17
+ const STYLE_DIMENSIONS = [
18
+ "columnWidths",
19
+ "rowHeights",
20
+ "borders",
21
+ "fills",
22
+ "fonts",
23
+ "alignment",
24
+ "numberFormats",
25
+ "conditionalFormatting",
26
+ "dataValidation",
27
+ "freezePanes",
28
+ "printSettings",
29
+ "pageLayout",
30
+ "hiddenRowsColumns"
31
+ ];
32
+ const STYLE_COPY_TOOL_DIMENSIONS = {
33
+ "excel.style.copy_column_widths": "columnWidths",
34
+ "excel.style.copy_row_heights": "rowHeights",
35
+ "excel.style.copy_borders": "borders",
36
+ "excel.style.copy_fills": "fills",
37
+ "excel.style.copy_fonts": "fonts",
38
+ "excel.style.copy_alignment": "alignment",
39
+ "excel.style.copy_number_formats": "numberFormats",
40
+ "excel.style.copy_conditional_formatting": "conditionalFormatting",
41
+ "excel.style.copy_data_validation": "dataValidation",
42
+ "excel.style.copy_freeze_panes": "freezePanes",
43
+ "excel.style.copy_print_settings": "printSettings",
44
+ "excel.style.copy_page_layout": "pageLayout",
45
+ "excel.style.copy_hidden_rows_columns": "hiddenRowsColumns"
46
+ };
47
+ const runtime = await createRuntimeFacade();
48
+ const server = new McpServer({
49
+ name: "open-workbook",
50
+ version: "0.1.0"
51
+ });
52
+ registerRuntimeTools(server);
53
+ registerWorkbookTools(server);
54
+ registerBackupTools(server);
55
+ registerSheetTools(server);
56
+ registerRangeTools(server);
57
+ registerBatchTools(server);
58
+ registerPlanTools(server);
59
+ registerTemplateTools(server);
60
+ registerStyleTools(server);
61
+ registerFormulaTools(server);
62
+ registerTableTools(server);
63
+ registerFilterTools(server);
64
+ registerSortTools(server);
65
+ registerPivotTools(server);
66
+ registerChartTools(server);
67
+ registerNamesTools(server);
68
+ registerRegionTools(server);
69
+ registerTaskTools(server);
70
+ registerCollaborationTools(server);
71
+ registerLockTools(server);
72
+ registerConflictTools(server);
73
+ registerTransactionTools(server);
74
+ registerPermissionsTools(server);
75
+ registerCleanTools(server);
76
+ registerValidateTools(server);
77
+ registerRepairTools(server);
78
+ registerSnapshotTools(server);
79
+ registerDiffTools(server);
80
+ registerEventTools(server);
81
+ registerResources(server);
82
+ registerPrompts(server);
83
+ await server.connect(new StdioServerTransport());
84
+ async function createRuntimeFacade() {
85
+ if (!standalone && await daemonAvailable(daemonUrl)) {
86
+ const proxy = createDaemonRuntimeProxy(daemonUrl);
87
+ const registration = await proxy.registerAgent({ agentName, clientType: "mcp", pid: process.pid });
88
+ const registeredAgent = registration.agent;
89
+ console.error(`open-workbook MCP adapter connected to ${daemonUrl}${registeredAgent?.agentId ? ` as ${registeredAgent.agentId}` : ""}`);
90
+ return proxy;
91
+ }
92
+ const localRuntime = new RuntimeService();
93
+ if (agentName !== undefined) {
94
+ localRuntime.registerAgent({ agentName, clientType: "mcp", pid: process.pid });
95
+ }
96
+ await startBackendServer(localRuntime, { host, port, addinPath });
97
+ console.error(`open-workbook MCP standalone backend listening on ws://${host}:${port}${addinPath}`);
98
+ return localRuntime;
99
+ }
100
+ async function daemonAvailable(baseUrl) {
101
+ try {
102
+ const response = await fetch(`${baseUrl}/status`);
103
+ return response.ok;
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ function createDaemonRuntimeProxy(baseUrl) {
110
+ const call = async (method, args) => {
111
+ const response = await fetch(`${baseUrl}/rpc`, {
112
+ method: "POST",
113
+ headers: { "content-type": "application/json" },
114
+ body: JSON.stringify({ method, args })
115
+ });
116
+ const payload = await response.json();
117
+ if (!response.ok || !payload.ok) {
118
+ throw new Error(JSON.stringify(payload.error ?? { code: "OPERATION_FAILED", message: `Daemon RPC failed: ${method}` }));
119
+ }
120
+ return payload.result;
121
+ };
122
+ return new Proxy({}, {
123
+ get(_target, property) {
124
+ if (typeof property !== "string") {
125
+ return undefined;
126
+ }
127
+ if (property === "then") {
128
+ return undefined;
129
+ }
130
+ return (...args) => call(property, args);
131
+ }
132
+ });
133
+ }
134
+ function registerResources(mcp) {
135
+ registerJsonResource(mcp, "runtime status", "excel://runtime/status", "Runtime connection, collaboration, and capability status.", async (uri) => ({
136
+ status: runtime.getStatus(),
137
+ capabilities: runtime.getCapabilities(),
138
+ collaboration: runtime.getCollaborationStatus()
139
+ }));
140
+ registerJsonResource(mcp, "workbooks", "excel://workbooks", "Open workbook references visible to connected add-ins.", async () => {
141
+ const sessions = runtime.getStatus().sessions;
142
+ return {
143
+ workbooks: sessions.flatMap((session) => (session.activeWorkbook ? [session.activeWorkbook] : [])),
144
+ sessions
145
+ };
146
+ });
147
+ registerJsonTemplateResource(mcp, "workbook map", "excel://workbooks/{workbook_id}/map", "Workbook map with sheets, used ranges, and table names.", async (_uri, variables) => runtime.getWorkbookMap());
148
+ registerJsonTemplateResource(mcp, "workbook sheets", "excel://workbooks/{workbook_id}/sheets", "Worksheet list from the workbook map.", async (_uri, variables) => {
149
+ const workbookId = resourceVariable(variables, "workbook_id");
150
+ const map = await runtime.getWorkbookMap();
151
+ return {
152
+ ok: map.ok,
153
+ workbookId,
154
+ sheets: map.map?.sheets ?? [],
155
+ source: map
156
+ };
157
+ });
158
+ registerJsonTemplateResource(mcp, "sheet used range", "excel://workbooks/{workbook_id}/sheets/{sheet_name}/used-range", "Used range metadata for one worksheet.", async (_uri, variables) => {
159
+ const workbookId = resourceVariable(variables, "workbook_id");
160
+ const sheetName = resourceVariable(variables, "sheet_name");
161
+ const map = await runtime.getWorkbookMap();
162
+ const sheet = map.map?.sheets?.find((item) => item.name === sheetName);
163
+ return {
164
+ ok: Boolean(sheet),
165
+ workbookId,
166
+ sheetName,
167
+ usedRange: sheet?.usedRange,
168
+ source: map
169
+ };
170
+ });
171
+ registerJsonTemplateResource(mcp, "sheet style fingerprint", "excel://workbooks/{workbook_id}/sheets/{sheet_name}/style-fingerprint", "Style fingerprint for one worksheet used range.", async (_uri, variables) => {
172
+ const workbookId = resourceVariable(variables, "workbook_id");
173
+ const sheetName = resourceVariable(variables, "sheet_name");
174
+ return runtime.getStyleFingerprint({ workbookId, sheetName });
175
+ });
176
+ registerJsonTemplateResource(mcp, "sheet formula patterns", "excel://workbooks/{workbook_id}/sheets/{sheet_name}/formula-patterns", "Formula pattern summary for one worksheet used range.", async (_uri, variables) => {
177
+ const workbookId = resourceVariable(variables, "workbook_id");
178
+ const sheetName = resourceVariable(variables, "sheet_name");
179
+ const map = await runtime.getWorkbookMap();
180
+ const sheet = map.map?.sheets?.find((item) => item.name === sheetName);
181
+ const address = sheet?.usedRange?.address;
182
+ if (!address) {
183
+ return {
184
+ ok: false,
185
+ workbookId,
186
+ sheetName,
187
+ error: { code: "RANGE_INVALID", message: "Sheet used range is unavailable." },
188
+ source: map
189
+ };
190
+ }
191
+ return runtime.readFormulaPatterns({ workbookId, sheetName, address: stripResourceSheetName(address) });
192
+ });
193
+ registerJsonTemplateResource(mcp, "workbook tables", "excel://workbooks/{workbook_id}/tables", "Structured table list for a workbook.", async (_uri, variables) => runtime.listTables(resourceVariable(variables, "workbook_id")));
194
+ registerJsonTemplateResource(mcp, "workbook templates", "excel://workbooks/{workbook_id}/templates", "Registered Open Workbook templates for a workbook.", async (_uri, variables) => ({
195
+ ok: true,
196
+ workbookId: resourceVariable(variables, "workbook_id"),
197
+ templates: runtime.listTemplates(resourceVariable(variables, "workbook_id"))
198
+ }));
199
+ registerJsonTemplateResource(mcp, "workbook snapshot", "excel://workbooks/{workbook_id}/snapshots/{snapshot_id}", "Stored snapshot metadata and payload reference.", async (_uri, variables) => {
200
+ const snapshot = runtime.getSnapshot(resourceVariable(variables, "snapshot_id"));
201
+ return {
202
+ workbookId: resourceVariable(variables, "workbook_id"),
203
+ ...snapshot
204
+ };
205
+ });
206
+ registerJsonTemplateResource(mcp, "plan diff", "excel://workbooks/{workbook_id}/plans/{plan_id}/diff", "Stored plan preview diff summary.", async (_uri, variables) => {
207
+ const workbookId = resourceVariable(variables, "workbook_id");
208
+ const planId = resourceVariable(variables, "plan_id");
209
+ return runtime.getPlanDiffResource(workbookId, planId);
210
+ });
211
+ }
212
+ function registerJsonResource(mcp, name, uri, description, read) {
213
+ mcp.registerResource(name, uri, {
214
+ title: name,
215
+ description,
216
+ mimeType: "application/json"
217
+ }, async (resourceUri) => jsonResource(resourceUri.toString(), await read(resourceUri)));
218
+ }
219
+ function registerJsonTemplateResource(mcp, name, uriTemplate, description, read) {
220
+ mcp.registerResource(name, new ResourceTemplate(uriTemplate, { list: undefined }), {
221
+ title: name,
222
+ description,
223
+ mimeType: "application/json"
224
+ }, async (resourceUri, variables) => jsonResource(resourceUri.toString(), await read(resourceUri, variables)));
225
+ }
226
+ function registerPrompts(mcp) {
227
+ const promptArgs = {
228
+ workbookId: z.string().optional(),
229
+ sheetName: z.string().optional(),
230
+ templateId: z.string().optional(),
231
+ targetSheetName: z.string().optional(),
232
+ goal: z.string().optional()
233
+ };
234
+ registerWorkflowPrompt(mcp, "excel.prompts.create_next_month_sheet", "Create next month sheet", "Plan and safely create a next-period worksheet from an existing template or previous-period sheet.", promptArgs, (args) => [
235
+ "Create a next-period worksheet without damaging formulas, formatting, filters, tables, print layout, or named regions.",
236
+ promptContext(args),
237
+ "Workflow:",
238
+ "1. Read `excel.runtime.get_active_context`, then inspect `excel.workbook.get_workbook_map` and `excel.template.list`.",
239
+ "2. Prefer a registered template. If no template is registered, call `excel.template.detect_templates` and ask the user to confirm the source sheet.",
240
+ "3. Use `excel.plan.create` and `excel.plan.preview` before mutation.",
241
+ "4. Use `excel.template.create_sheet_from_template` with the confirmed template or previous-period sheet as the source.",
242
+ "5. Clear only declared data regions with `excel.template.clear_data_regions` or `excel.range.clear_values_keep_format`.",
243
+ "6. Validate with `excel.template.validate_sheet_against_template`, `excel.formula.validate_against_template`, `excel.style.validate_consistency`, and `excel.validate.no_formula_errors`.",
244
+ "7. Commit only after validation is clean or after discussing warnings with the user."
245
+ ]);
246
+ registerWorkflowPrompt(mcp, "excel.prompts.clean_current_sheet", "Clean current sheet", "Clean worksheet data while preserving workbook structure, styling, formulas, filters, and templates.", promptArgs, (args) => [
247
+ "Clean the current worksheet conservatively. Do not overwrite formulas, templates, filters, styling, or hidden layout areas.",
248
+ promptContext(args),
249
+ "Workflow:",
250
+ "1. Read active context, selection, used range, tables, filters, formulas, and style fingerprint.",
251
+ "2. Identify data-entry regions using registered regions, table data bodies, or template data regions.",
252
+ "3. Preview transformations with read-only tools first: header detection, trim/normalize, parse dates/numbers, duplicate/outlier checks.",
253
+ "4. Create a plan and preview it. Apply only scoped range/table operations.",
254
+ "5. Prefer `excel.table.update_rows`, `excel.region.write_values`, or `excel.range.write_values` with format preservation.",
255
+ "6. Re-run table/filter/style/formula validation and summarize exactly what changed."
256
+ ]);
257
+ registerWorkflowPrompt(mcp, "excel.prompts.fix_formula_errors", "Fix formula errors", "Diagnose formula errors, compare against template patterns, and repair only after preview and validation.", promptArgs, (args) => [
258
+ "Fix formula errors without converting formulas to values unless the user explicitly asks.",
259
+ promptContext(args),
260
+ "Workflow:",
261
+ "1. Locate errors with `excel.formula.find_errors` and `excel.validate.no_formula_errors`.",
262
+ "2. Read formula patterns and dependency graph with `excel.formula.read_patterns`, `excel.formula.get_dependency_graph`, `trace_precedents`, and `trace_dependents`.",
263
+ "3. If a template exists, compare with `excel.formula.validate_against_template`.",
264
+ "4. Create a repair plan using `excel.formula.repair_patterns`, `fill_down`, `fill_right`, or explicit `range.write_formulas`.",
265
+ "5. Preview, apply, recalculate, and re-run formula validation before reporting success."
266
+ ]);
267
+ registerWorkflowPrompt(mcp, "excel.prompts.format_like_template", "Format like template", "Repair styling and layout consistency using registered template fingerprints.", promptArgs, (args) => [
268
+ "Make the target sheet look like the template while preserving current data values.",
269
+ promptContext(args),
270
+ "Workflow:",
271
+ "1. Read template registry and current style fingerprints.",
272
+ "2. Compare with `excel.style.compare_fingerprint` and `excel.style.validate_consistency`.",
273
+ "3. Ask before changing structure-level layout such as hidden rows/columns, freeze panes, print settings, or page layout.",
274
+ "4. Use granular style copy tools or `excel.style.repair_consistency` for confirmed dimensions.",
275
+ "5. Validate styles, formulas, tables, filters, and print layout after applying."
276
+ ]);
277
+ registerWorkflowPrompt(mcp, "excel.prompts.validate_report_before_saving", "Validate report before saving", "Run workbook/report validation before saving or handing a file back to the user.", promptArgs, (args) => [
278
+ "Validate the report before saving. Do not save if validation finds material formula, reference, template, or unintended-change issues.",
279
+ promptContext(args),
280
+ "Workflow:",
281
+ "1. Create or refresh a snapshot if one is available for unintended-change checks.",
282
+ "2. Run workbook, sheet, template, formula, style, table, filter, print-layout, broken-reference, formula-error, and unintended-change validators.",
283
+ "3. Summarize issues by severity and affected range/table/sheet.",
284
+ "4. Repair only with explicit scoped tools and backups.",
285
+ "5. Save with `excel.workbook.save` only when errors are clean or the user confirms known warnings."
286
+ ]);
287
+ registerWorkflowPrompt(mcp, "excel.prompts.create_summary_report", "Create summary report", "Create a summary/report sheet from existing workbook data with safe planning and validation.", promptArgs, (args) => [
288
+ "Create a summary report from existing workbook data without disturbing source sheets.",
289
+ promptContext(args),
290
+ "Workflow:",
291
+ "1. Map workbook sheets, tables, names, regions, filters, PivotTables, and charts.",
292
+ "2. Ask the user which metrics/groupings/date ranges matter if not obvious.",
293
+ "3. Prefer creating a new sheet from a template or copying a previous report sheet.",
294
+ "4. Use table reads, formulas, PivotTables, and charts through planned operations.",
295
+ "5. Preview and apply via plan/batch; never write directly outside the target report regions.",
296
+ "6. Validate formulas, style consistency, tables, charts, and no unintended source changes."
297
+ ]);
298
+ }
299
+ function registerWorkflowPrompt(mcp, name, title, description, argsSchema, body) {
300
+ mcp.registerPrompt(name, {
301
+ title,
302
+ description,
303
+ argsSchema
304
+ }, (args) => ({
305
+ description,
306
+ messages: [
307
+ {
308
+ role: "user",
309
+ content: {
310
+ type: "text",
311
+ text: body(args).filter(Boolean).join("\n")
312
+ }
313
+ }
314
+ ]
315
+ }));
316
+ }
317
+ function promptContext(args) {
318
+ const entries = Object.entries(args).filter(([, value]) => value !== undefined && value !== "");
319
+ if (entries.length === 0) {
320
+ return "";
321
+ }
322
+ return `Context: ${entries.map(([key, value]) => `${key}=${String(value)}`).join(", ")}`;
323
+ }
324
+ function registerRuntimeTools(mcp) {
325
+ registerMcpTool(mcp, "excel.runtime.get_status", {
326
+ title: "Get Excel runtime status",
327
+ description: "Return backend, Excel add-in, and optional native file bridge health status.",
328
+ inputSchema: {
329
+ probeFileBridge: z.boolean().optional()
330
+ },
331
+ annotations: {
332
+ readOnlyHint: true,
333
+ destructiveHint: false,
334
+ openWorldHint: false
335
+ }
336
+ }, async ({ probeFileBridge }) => jsonResult(probeFileBridge ? await runtime.getStatusWithFileBridgeProbe() : runtime.getStatus()));
337
+ registerMcpTool(mcp, "excel.runtime.connect_addin", {
338
+ title: "Get add-in connection instructions",
339
+ description: "Return the local backend WebSocket URL that the Excel add-in should connect to.",
340
+ inputSchema: {},
341
+ annotations: {
342
+ readOnlyHint: true,
343
+ destructiveHint: false,
344
+ openWorldHint: false
345
+ }
346
+ }, async () => jsonResult(runtime.connectAddinInfo()));
347
+ registerMcpTool(mcp, "excel.runtime.disconnect_addin", {
348
+ title: "Disconnect active add-in",
349
+ description: "Close the active Excel add-in session.",
350
+ inputSchema: {},
351
+ annotations: {
352
+ readOnlyHint: false,
353
+ destructiveHint: false,
354
+ openWorldHint: false
355
+ }
356
+ }, async () => jsonResult(runtime.disconnectActiveAddin()));
357
+ registerMcpTool(mcp, "excel.runtime.ping_addin", {
358
+ title: "Ping active add-in",
359
+ description: "Ping the active Excel add-in session.",
360
+ inputSchema: {},
361
+ annotations: {
362
+ readOnlyHint: true,
363
+ destructiveHint: false,
364
+ openWorldHint: false
365
+ }
366
+ }, async () => jsonResult(await runtime.pingAddin()));
367
+ registerMcpTool(mcp, "excel.runtime.get_capabilities", {
368
+ title: "Get Open Workbook capabilities",
369
+ description: "Return complete tool/resource/prompt catalog status and runtime capability metadata.",
370
+ inputSchema: {
371
+ includePreview: z.boolean().optional()
372
+ },
373
+ annotations: {
374
+ readOnlyHint: true,
375
+ destructiveHint: false,
376
+ openWorldHint: false
377
+ }
378
+ }, async ({ includePreview }) => jsonResult(runtime.getCapabilities(includePreview === undefined ? {} : { includePreview })));
379
+ registerMcpTool(mcp, "excel.runtime.get_active_context", {
380
+ title: "Get active Excel context",
381
+ description: "Return active workbook context from the connected Excel add-in.",
382
+ inputSchema: {},
383
+ annotations: {
384
+ readOnlyHint: true,
385
+ destructiveHint: false,
386
+ openWorldHint: false
387
+ }
388
+ }, async () => jsonResult(await runtime.getActiveContext()));
389
+ registerMcpTool(mcp, "excel.runtime.get_selection", {
390
+ title: "Get active Excel selection",
391
+ description: "Return the current selected range from the active Excel add-in, including A1 address, top-left/start cell, bottom-right/end cell, one-based row/column numbers, zero-based row/column indexes, and range dimensions.",
392
+ inputSchema: {},
393
+ annotations: {
394
+ readOnlyHint: true,
395
+ destructiveHint: false,
396
+ openWorldHint: false
397
+ }
398
+ }, async () => jsonResult(await runtime.getSelection()));
399
+ registerMcpTool(mcp, "excel.runtime.set_active_workbook", {
400
+ title: "Set active workbook session",
401
+ description: "Select the active connected workbook session by workbook ID or workbook name.",
402
+ inputSchema: {
403
+ workbookIdOrName: z.string()
404
+ },
405
+ annotations: {
406
+ readOnlyHint: false,
407
+ destructiveHint: false,
408
+ openWorldHint: false
409
+ }
410
+ }, async ({ workbookIdOrName }) => jsonResult(runtime.setActiveWorkbook(workbookIdOrName)));
411
+ registerMcpTool(mcp, "excel.runtime.set_active_sheet", {
412
+ title: "Set active worksheet",
413
+ description: "Activate a worksheet in the active connected workbook.",
414
+ inputSchema: {
415
+ sheetName: z.string()
416
+ },
417
+ annotations: {
418
+ readOnlyHint: false,
419
+ destructiveHint: false,
420
+ openWorldHint: false
421
+ }
422
+ }, async ({ sheetName }) => jsonResult(await runtime.setActiveSheet(sheetName)));
423
+ }
424
+ function registerWorkbookTools(mcp) {
425
+ registerMcpTool(mcp, "excel.workbook.list_open_workbooks", {
426
+ title: "List open Excel workbooks",
427
+ description: "List workbooks currently visible to connected add-ins.",
428
+ inputSchema: {},
429
+ annotations: {
430
+ readOnlyHint: true,
431
+ destructiveHint: false,
432
+ openWorldHint: false
433
+ }
434
+ }, async () => {
435
+ const sessions = runtime.getStatus().sessions;
436
+ return jsonResult({
437
+ workbooks: sessions.flatMap((session) => (session.activeWorkbook ? [session.activeWorkbook] : []))
438
+ });
439
+ });
440
+ registerMcpTool(mcp, "excel.workbook.get_workbook_info", {
441
+ title: "Get workbook info",
442
+ description: "Return active workbook metadata from Excel.",
443
+ inputSchema: {},
444
+ annotations: {
445
+ readOnlyHint: true,
446
+ destructiveHint: false,
447
+ openWorldHint: false
448
+ }
449
+ }, async () => jsonResult(await runtime.getWorkbookInfo()));
450
+ registerMcpTool(mcp, "excel.workbook.get_workbook_map", {
451
+ title: "Get workbook map",
452
+ description: "Return worksheets, used ranges, and table names for the active workbook.",
453
+ inputSchema: {},
454
+ annotations: {
455
+ readOnlyHint: true,
456
+ destructiveHint: false,
457
+ openWorldHint: false
458
+ }
459
+ }, async () => jsonResult(await runtime.getWorkbookMap()));
460
+ registerMcpTool(mcp, "excel.workbook.snapshot", {
461
+ title: "Create workbook snapshot",
462
+ description: "Capture a restorable snapshot of used ranges or specific ranges.",
463
+ inputSchema: snapshotInputSchema(),
464
+ annotations: {
465
+ readOnlyHint: true,
466
+ destructiveHint: false,
467
+ openWorldHint: false
468
+ }
469
+ }, async ({ workbookId, reason, ranges }) => jsonResult(await runtime.createWorkbookSnapshot(snapshotRequest(workbookId, reason, ranges))));
470
+ registerMcpTool(mcp, "excel.workbook.refresh_snapshot", {
471
+ title: "Refresh workbook snapshot",
472
+ description: "Capture a fresh snapshot over the same ranges as an existing snapshot.",
473
+ inputSchema: {
474
+ snapshotId: z.string(),
475
+ reason: z.string().optional()
476
+ },
477
+ annotations: {
478
+ readOnlyHint: true,
479
+ destructiveHint: false,
480
+ openWorldHint: false
481
+ }
482
+ }, async ({ snapshotId, reason }) => {
483
+ const existing = await runtime.getSnapshot(snapshotId);
484
+ if (!existing.ok || !("snapshot" in existing)) {
485
+ return jsonResult(existing);
486
+ }
487
+ return jsonResult(await runtime.createWorkbookSnapshot({
488
+ workbookId: existing.snapshot.workbookId,
489
+ reason: reason ?? `Refresh snapshot ${snapshotId}`,
490
+ ranges: existing.snapshot.affectedRanges
491
+ }));
492
+ });
493
+ registerMcpTool(mcp, "excel.workbook.get_snapshot", {
494
+ title: "Get workbook snapshot",
495
+ description: "Return a captured workbook snapshot by ID.",
496
+ inputSchema: {
497
+ snapshotId: z.string()
498
+ },
499
+ annotations: {
500
+ readOnlyHint: true,
501
+ destructiveHint: false,
502
+ openWorldHint: false
503
+ }
504
+ }, async ({ snapshotId }) => jsonResult(runtime.getSnapshot(snapshotId)));
505
+ registerMcpTool(mcp, "excel.workbook.detect_external_changes", {
506
+ title: "Detect external workbook changes",
507
+ description: "Compare a stored snapshot with the current workbook state over the same ranges.",
508
+ inputSchema: {
509
+ workbookId: z.string(),
510
+ snapshotId: z.string()
511
+ },
512
+ annotations: {
513
+ readOnlyHint: true,
514
+ destructiveHint: false,
515
+ openWorldHint: false
516
+ }
517
+ }, async ({ workbookId, snapshotId }) => jsonResult(await runtime.detectExternalChanges({ workbookId: workbookId, snapshotId: snapshotId })));
518
+ registerMcpTool(mcp, "excel.workbook.calculate", {
519
+ title: "Calculate workbook",
520
+ description: "Recalculate the active workbook.",
521
+ inputSchema: {
522
+ workbookId: z.string(),
523
+ calculationType: z.enum(["full", "recalculate"]).optional()
524
+ },
525
+ annotations: {
526
+ readOnlyHint: false,
527
+ destructiveHint: false,
528
+ openWorldHint: false
529
+ }
530
+ }, async ({ workbookId, calculationType }) => jsonResult(await runtime.calculateWorkbook(workbookId, calculationType)));
531
+ registerMcpTool(mcp, "excel.workbook.save", {
532
+ title: "Save workbook",
533
+ description: "Save the active workbook.",
534
+ inputSchema: {
535
+ workbookId: z.string()
536
+ },
537
+ annotations: {
538
+ readOnlyHint: false,
539
+ destructiveHint: false,
540
+ openWorldHint: false
541
+ }
542
+ }, async ({ workbookId }) => jsonResult(await runtime.saveWorkbook(workbookId)));
543
+ registerMcpTool(mcp, "excel.workbook.save_as", {
544
+ title: "Save workbook as",
545
+ description: "Save the workbook through the native file bridge when configured, otherwise report Save As capability status.",
546
+ inputSchema: {
547
+ workbookId: z.string(),
548
+ targetPath: z.string().optional()
549
+ },
550
+ annotations: {
551
+ readOnlyHint: false,
552
+ destructiveHint: true,
553
+ openWorldHint: false
554
+ }
555
+ }, async ({ workbookId, targetPath }) => jsonResult(await runtime.saveWorkbookAs(workbookId, targetPath)));
556
+ registerMcpTool(mcp, "excel.workbook.create_backup", {
557
+ title: "Create workbook backup",
558
+ description: "Create a session-restorable workbook backup snapshot.",
559
+ inputSchema: snapshotInputSchema(),
560
+ annotations: {
561
+ readOnlyHint: true,
562
+ destructiveHint: false,
563
+ openWorldHint: false
564
+ }
565
+ }, async ({ workbookId, reason, ranges }) => jsonResult(await runtime.createWorkbookBackup(snapshotRequest(workbookId, reason, ranges))));
566
+ registerMcpTool(mcp, "excel.workbook.restore_backup", {
567
+ title: "Restore workbook backup",
568
+ description: "Restore a backup captured by Open Workbook.",
569
+ inputSchema: {
570
+ backupId: z.string(),
571
+ confirmationToken: z.string().optional()
572
+ },
573
+ annotations: {
574
+ readOnlyHint: false,
575
+ destructiveHint: true,
576
+ openWorldHint: false
577
+ }
578
+ }, async ({ backupId, confirmationToken }) => jsonResult(await runtime.restoreBackup(backupId, confirmationToken)));
579
+ registerMcpTool(mcp, "excel.workbook.export_copy", {
580
+ title: "Export workbook copy",
581
+ description: "Create a persistent snapshot backup and report .xlsx export capability status.",
582
+ inputSchema: {
583
+ workbookId: z.string(),
584
+ reason: z.string().optional(),
585
+ targetPath: z.string().optional(),
586
+ ranges: z
587
+ .array(z.object({
588
+ workbookId: z.string(),
589
+ sheetName: z.string(),
590
+ address: z.string()
591
+ }))
592
+ .optional()
593
+ },
594
+ annotations: {
595
+ readOnlyHint: false,
596
+ destructiveHint: false,
597
+ openWorldHint: false
598
+ }
599
+ }, async ({ workbookId, reason, targetPath, ranges }) => {
600
+ const request = {
601
+ workbookId: workbookId
602
+ };
603
+ if (reason !== undefined) {
604
+ request.reason = reason;
605
+ }
606
+ if (targetPath !== undefined) {
607
+ request.targetPath = targetPath;
608
+ }
609
+ if (ranges !== undefined) {
610
+ request.ranges = ranges;
611
+ }
612
+ return jsonResult(await runtime.exportWorkbookCopy(request));
613
+ });
614
+ registerMcpTool(mcp, "excel.workbook.export_local_config", {
615
+ title: "Export workbook local config",
616
+ description: "Export Open Workbook templates, registered regions, and workbook permission metadata as portable JSON.",
617
+ inputSchema: {
618
+ workbookId: z.string(),
619
+ includePermissions: z.boolean().optional()
620
+ },
621
+ annotations: {
622
+ readOnlyHint: true,
623
+ destructiveHint: false,
624
+ openWorldHint: false
625
+ }
626
+ }, async ({ workbookId, includePermissions }) => {
627
+ const options = {};
628
+ if (includePermissions !== undefined) {
629
+ options.includePermissions = includePermissions;
630
+ }
631
+ return jsonResult(await runtime.exportWorkbookLocalConfig(workbookId, options));
632
+ });
633
+ registerMcpTool(mcp, "excel.workbook.import_local_config", {
634
+ title: "Import workbook local config",
635
+ description: "Import portable Open Workbook templates, registered regions, and workbook permission metadata into the local daemon registry.",
636
+ inputSchema: {
637
+ workbookId: z.string(),
638
+ config: z.object({
639
+ version: z.literal(1),
640
+ workbookId: z.string(),
641
+ exportedAt: z.string(),
642
+ source: z.literal("open-workbook-local-config"),
643
+ templates: z.array(z.record(z.string(), z.unknown())),
644
+ regions: z.array(z.any()),
645
+ permissions: z.any().optional()
646
+ }),
647
+ includeTemplates: z.boolean().optional(),
648
+ includeRegions: z.boolean().optional(),
649
+ includePermissions: z.boolean().optional(),
650
+ overwrite: z.boolean().optional()
651
+ },
652
+ annotations: {
653
+ readOnlyHint: false,
654
+ destructiveHint: false,
655
+ openWorldHint: false
656
+ }
657
+ }, async ({ workbookId, config, includeTemplates, includeRegions, includePermissions, overwrite }) => {
658
+ const request = {
659
+ workbookId: workbookId,
660
+ config
661
+ };
662
+ if (includeTemplates !== undefined) {
663
+ request.includeTemplates = includeTemplates;
664
+ }
665
+ if (includeRegions !== undefined) {
666
+ request.includeRegions = includeRegions;
667
+ }
668
+ if (includePermissions !== undefined) {
669
+ request.includePermissions = includePermissions;
670
+ }
671
+ if (overwrite !== undefined) {
672
+ request.overwrite = overwrite;
673
+ }
674
+ return jsonResult(await runtime.importWorkbookLocalConfig(request));
675
+ });
676
+ registerMcpTool(mcp, "excel.workbook.embed_local_config", {
677
+ title: "Embed workbook local config",
678
+ description: "Write Open Workbook template, region, and permission metadata into the workbook custom XML part when the Excel host supports it.",
679
+ inputSchema: {
680
+ workbookId: z.string(),
681
+ includePermissions: z.boolean().optional()
682
+ },
683
+ annotations: {
684
+ readOnlyHint: false,
685
+ destructiveHint: false,
686
+ openWorldHint: false
687
+ }
688
+ }, async ({ workbookId, includePermissions }) => {
689
+ const options = {};
690
+ if (includePermissions !== undefined) {
691
+ options.includePermissions = includePermissions;
692
+ }
693
+ return jsonResult(await runtime.embedWorkbookLocalConfig(workbookId, options));
694
+ });
695
+ registerMcpTool(mcp, "excel.workbook.read_embedded_local_config", {
696
+ title: "Read embedded workbook local config",
697
+ description: "Read Open Workbook local config metadata from the workbook custom XML part when present.",
698
+ inputSchema: {
699
+ workbookId: z.string()
700
+ },
701
+ annotations: {
702
+ readOnlyHint: true,
703
+ destructiveHint: false,
704
+ openWorldHint: false
705
+ }
706
+ }, async ({ workbookId }) => jsonResult(await runtime.readWorkbookEmbeddedLocalConfig(workbookId)));
707
+ registerMcpTool(mcp, "excel.workbook.import_embedded_local_config", {
708
+ title: "Import embedded workbook local config",
709
+ description: "Read workbook custom XML metadata and import it into the local daemon registry.",
710
+ inputSchema: {
711
+ workbookId: z.string(),
712
+ includeTemplates: z.boolean().optional(),
713
+ includeRegions: z.boolean().optional(),
714
+ includePermissions: z.boolean().optional(),
715
+ overwrite: z.boolean().optional()
716
+ },
717
+ annotations: {
718
+ readOnlyHint: false,
719
+ destructiveHint: false,
720
+ openWorldHint: false
721
+ }
722
+ }, async ({ workbookId, includeTemplates, includeRegions, includePermissions, overwrite }) => {
723
+ const request = { workbookId: workbookId };
724
+ if (includeTemplates !== undefined) {
725
+ request.includeTemplates = includeTemplates;
726
+ }
727
+ if (includeRegions !== undefined) {
728
+ request.includeRegions = includeRegions;
729
+ }
730
+ if (includePermissions !== undefined) {
731
+ request.includePermissions = includePermissions;
732
+ }
733
+ if (overwrite !== undefined) {
734
+ request.overwrite = overwrite;
735
+ }
736
+ return jsonResult(await runtime.importWorkbookEmbeddedLocalConfig(request));
737
+ });
738
+ registerMcpTool(mcp, "excel.workbook.close", {
739
+ title: "Close workbook",
740
+ description: "Close the active workbook through Office.js.",
741
+ inputSchema: {
742
+ workbookId: z.string(),
743
+ closeBehavior: z.enum(["Save", "SkipSave"]).optional()
744
+ },
745
+ annotations: {
746
+ readOnlyHint: false,
747
+ destructiveHint: true,
748
+ openWorldHint: false
749
+ }
750
+ }, async ({ workbookId, closeBehavior }) => jsonResult(await runtime.closeWorkbook(workbookId, closeBehavior)));
751
+ }
752
+ function registerBackupTools(mcp) {
753
+ registerMcpTool(mcp, "excel.backup.create_file", {
754
+ title: "Create file backup",
755
+ description: "Create a verified full .xlsx file backup using native SaveCopyAs or the supported Office.js file export fallback.",
756
+ inputSchema: {
757
+ workbookId: z.string(),
758
+ reason: z.string().optional(),
759
+ targetPath: z.string().optional(),
760
+ mode: z.enum(["export-copy", "save-copy-as"]).optional(),
761
+ pin: z.boolean().optional()
762
+ },
763
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
764
+ }, async (args) => {
765
+ const request = { workbookId: args.workbookId };
766
+ if (args.reason !== undefined)
767
+ request.reason = args.reason;
768
+ if (args.targetPath !== undefined)
769
+ request.targetPath = args.targetPath;
770
+ if (args.mode !== undefined)
771
+ request.mode = args.mode;
772
+ if (args.pin !== undefined)
773
+ request.pin = args.pin;
774
+ return jsonResult(await runtime.createFileBackup(request));
775
+ });
776
+ registerMcpTool(mcp, "excel.backup.list", {
777
+ title: "List file backups",
778
+ description: "List durable full-file workbook backups.",
779
+ inputSchema: { workbookId: z.string().optional() },
780
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
781
+ }, async ({ workbookId }) => jsonResult(runtime.listFileBackups(workbookId)));
782
+ registerMcpTool(mcp, "excel.backup.get", {
783
+ title: "Get file backup",
784
+ description: "Return one durable full-file workbook backup manifest.",
785
+ inputSchema: { backupId: z.string() },
786
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
787
+ }, async ({ backupId }) => jsonResult(runtime.getFileBackup(backupId)));
788
+ registerMcpTool(mcp, "excel.backup.verify", {
789
+ title: "Verify file backup",
790
+ description: "Verify that a full-file backup exists and still matches its checksum.",
791
+ inputSchema: { backupId: z.string() },
792
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
793
+ }, async ({ backupId }) => jsonResult(await runtime.verifyFileBackup(backupId)));
794
+ registerMcpTool(mcp, "excel.backup.restore_file", {
795
+ title: "Restore file backup",
796
+ description: "Restore a full-file backup. open-as-new is safe; destructive modes require confirmation and native bridge support.",
797
+ inputSchema: {
798
+ workbookId: z.string(),
799
+ backupId: z.string(),
800
+ mode: z.enum(["open-as-new", "replace-open-workbook", "restore-into-open-workbook"]).optional(),
801
+ restoreTargetPath: z.string().optional(),
802
+ confirmationToken: z.string().optional(),
803
+ force: z.boolean().optional()
804
+ },
805
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
806
+ }, async (args) => {
807
+ const request = {
808
+ workbookId: args.workbookId,
809
+ backupId: args.backupId
810
+ };
811
+ if (args.mode !== undefined)
812
+ request.mode = args.mode;
813
+ if (args.restoreTargetPath !== undefined)
814
+ request.restoreTargetPath = args.restoreTargetPath;
815
+ if (args.confirmationToken !== undefined)
816
+ request.confirmationToken = args.confirmationToken;
817
+ if (args.force !== undefined)
818
+ request.force = args.force;
819
+ return jsonResult(await runtime.restoreFileBackup(request));
820
+ });
821
+ registerMcpTool(mcp, "excel.backup.delete", {
822
+ title: "Delete file backup",
823
+ description: "Delete an unpinned durable full-file backup and its file payload when possible.",
824
+ inputSchema: { backupId: z.string() },
825
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
826
+ }, async ({ backupId }) => jsonResult(runtime.deleteFileBackup(backupId)));
827
+ registerMcpTool(mcp, "excel.backup.prune", {
828
+ title: "Prune file backups",
829
+ description: "Prune unpinned durable file backups by age or per-workbook retention count.",
830
+ inputSchema: {
831
+ workbookId: z.string().optional(),
832
+ maxAgeDays: z.number().optional(),
833
+ maxBackupsPerWorkbook: z.number().optional(),
834
+ dryRun: z.boolean().optional()
835
+ },
836
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
837
+ }, async (args) => {
838
+ const request = {};
839
+ if (args.workbookId !== undefined)
840
+ request.workbookId = args.workbookId;
841
+ if (args.maxAgeDays !== undefined)
842
+ request.maxAgeDays = args.maxAgeDays;
843
+ if (args.maxBackupsPerWorkbook !== undefined)
844
+ request.maxBackupsPerWorkbook = args.maxBackupsPerWorkbook;
845
+ if (args.dryRun !== undefined)
846
+ request.dryRun = args.dryRun;
847
+ return jsonResult(runtime.pruneFileBackups(request));
848
+ });
849
+ registerMcpTool(mcp, "excel.backup.pin", {
850
+ title: "Pin file backup",
851
+ description: "Prevent a durable file backup from being pruned or deleted.",
852
+ inputSchema: { backupId: z.string() },
853
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
854
+ }, async ({ backupId }) => jsonResult(runtime.pinFileBackup(backupId, true)));
855
+ registerMcpTool(mcp, "excel.backup.unpin", {
856
+ title: "Unpin file backup",
857
+ description: "Allow a durable file backup to be pruned or deleted.",
858
+ inputSchema: { backupId: z.string() },
859
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
860
+ }, async ({ backupId }) => jsonResult(runtime.pinFileBackup(backupId, false)));
861
+ }
862
+ function registerSheetTools(mcp) {
863
+ registerMcpTool(mcp, "excel.sheet.list", {
864
+ title: "List worksheets",
865
+ description: "List worksheets in the active workbook.",
866
+ inputSchema: {},
867
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
868
+ }, async () => {
869
+ const result = await runtime.getWorkbookMap();
870
+ const map = "map" in result ? result.map : undefined;
871
+ return jsonResult({ ok: result.ok, sheets: map?.sheets ?? [], result });
872
+ });
873
+ registerMcpTool(mcp, "excel.sheet.get_info", {
874
+ title: "Get worksheet info",
875
+ description: "Return worksheet metadata by sheet name.",
876
+ inputSchema: { sheetName: z.string() },
877
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
878
+ }, async ({ sheetName }) => jsonResult(await selectSheetInfo(sheetName)));
879
+ registerMcpTool(mcp, "excel.sheet.get_used_range", {
880
+ title: "Get worksheet used range",
881
+ description: "Return the used range for a worksheet.",
882
+ inputSchema: { sheetName: z.string() },
883
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
884
+ }, async ({ sheetName }) => {
885
+ const info = await selectSheetInfo(sheetName);
886
+ return jsonResult({ ok: info.ok, usedRange: info.sheet?.usedRange, sheet: info.sheet });
887
+ });
888
+ registerSheetOperation(mcp, "excel.sheet.create", {
889
+ workbookId: z.string(),
890
+ sheetName: z.string(),
891
+ activate: z.boolean().optional()
892
+ }, (args) => ({
893
+ kind: "sheet.create",
894
+ workbookId: args.workbookId,
895
+ sheetName: args.sheetName,
896
+ activate: args.activate,
897
+ destructiveLevel: "structure",
898
+ reason: "MCP sheet create"
899
+ }));
900
+ registerSheetOperation(mcp, "excel.sheet.copy", {
901
+ workbookId: z.string(),
902
+ sourceSheetName: z.string(),
903
+ newSheetName: z.string(),
904
+ activate: z.boolean().optional()
905
+ }, (args) => ({
906
+ kind: "sheet.copy",
907
+ workbookId: args.workbookId,
908
+ sourceSheetName: args.sourceSheetName,
909
+ newSheetName: args.newSheetName,
910
+ activate: args.activate,
911
+ destructiveLevel: "structure",
912
+ reason: "MCP sheet copy"
913
+ }));
914
+ registerSheetOperation(mcp, "excel.sheet.rename", {
915
+ workbookId: z.string(),
916
+ sheetName: z.string(),
917
+ newSheetName: z.string()
918
+ }, (args) => ({
919
+ kind: "sheet.rename",
920
+ workbookId: args.workbookId,
921
+ sheetName: args.sheetName,
922
+ newSheetName: args.newSheetName,
923
+ destructiveLevel: "structure",
924
+ reason: "MCP sheet rename"
925
+ }));
926
+ for (const [name, destructiveLevel] of [
927
+ ["excel.sheet.delete", "structure"],
928
+ ["excel.sheet.hide", "structure"],
929
+ ["excel.sheet.unhide", "structure"],
930
+ ["excel.sheet.protect", "structure"],
931
+ ["excel.sheet.unprotect", "structure"],
932
+ ["excel.sheet.clear", "structure"]
933
+ ]) {
934
+ registerSheetOperation(mcp, name, {
935
+ workbookId: z.string(),
936
+ sheetName: z.string(),
937
+ password: z.string().optional(),
938
+ applyTo: z.enum(["all", "contents", "formats"]).optional()
939
+ }, (args) => ({
940
+ kind: name.replace("excel.", ""),
941
+ workbookId: args.workbookId,
942
+ sheetName: args.sheetName,
943
+ password: args.password,
944
+ applyTo: args.applyTo,
945
+ destructiveLevel,
946
+ reason: `MCP ${name}`
947
+ }));
948
+ }
949
+ registerSheetOperation(mcp, "excel.sheet.set_tab_color", {
950
+ workbookId: z.string(),
951
+ sheetName: z.string(),
952
+ color: z.string()
953
+ }, (args) => ({
954
+ kind: "sheet.set_tab_color",
955
+ workbookId: args.workbookId,
956
+ sheetName: args.sheetName,
957
+ color: args.color,
958
+ destructiveLevel: "format",
959
+ reason: "MCP sheet tab color"
960
+ }));
961
+ }
962
+ function registerRangeTools(mcp) {
963
+ const readSchema = {
964
+ workbookId: z.string(),
965
+ sheetName: z.string(),
966
+ address: z.string()
967
+ };
968
+ for (const name of [
969
+ "excel.range.read_values",
970
+ "excel.range.read_formulas",
971
+ "excel.range.read_number_formats",
972
+ "excel.range.read_display_text",
973
+ "excel.range.read_styles"
974
+ ]) {
975
+ registerMcpTool(mcp, name, {
976
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
977
+ description: "Read a range facet using the full range snapshot path.",
978
+ inputSchema: readSchema,
979
+ annotations: {
980
+ readOnlyHint: true,
981
+ destructiveHint: false,
982
+ openWorldHint: false
983
+ }
984
+ }, async ({ workbookId, sheetName, address }) => jsonResult(await readRangeSnapshot(workbookId, sheetName, address)));
985
+ }
986
+ for (const [name, method] of [
987
+ ["excel.range.read_hyperlinks", "range.read_hyperlinks"],
988
+ ["excel.range.read_comments", "range.read_comments"],
989
+ ["excel.range.read_notes", "range.read_notes"],
990
+ ["excel.range.read_merged_cells", "range.read_merged_cells"],
991
+ ["excel.range.read_data_validation", "range.read_data_validation"],
992
+ ["excel.range.read_conditional_formatting", "range.read_conditional_formatting"],
993
+ ["excel.range.find_blank_cells", "range.find_blank_cells"],
994
+ ["excel.range.find_errors", "range.find_errors"]
995
+ ]) {
996
+ registerMcpTool(mcp, name, {
997
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
998
+ description: "Read advanced range metadata from the connected Excel add-in.",
999
+ inputSchema: readSchema,
1000
+ annotations: {
1001
+ readOnlyHint: true,
1002
+ destructiveHint: false,
1003
+ openWorldHint: false
1004
+ }
1005
+ }, async (args) => jsonResult(await runtime.readRangeMetadata(method, rangeMetadataRequest(args))));
1006
+ }
1007
+ registerMcpTool(mcp, "excel.range.search", {
1008
+ title: "Search Excel range",
1009
+ description: "Search a worksheet for text and return matching range areas.",
1010
+ inputSchema: {
1011
+ ...readSchema,
1012
+ text: z.string(),
1013
+ completeMatch: z.boolean().optional(),
1014
+ matchCase: z.boolean().optional(),
1015
+ searchDirection: z.enum(["Forward", "Backwards"]).optional()
1016
+ },
1017
+ annotations: {
1018
+ readOnlyHint: true,
1019
+ destructiveHint: false,
1020
+ openWorldHint: false
1021
+ }
1022
+ }, async (args) => jsonResult(await runtime.readRangeMetadata("range.search", rangeSearchRequest(args))));
1023
+ registerMcpTool(mcp, "excel.range.read_full", {
1024
+ title: "Read full Excel range state",
1025
+ description: "Read values, formulas, text, number formats, and basic style fingerprint data for a range.",
1026
+ inputSchema: {
1027
+ workbookId: z.string(),
1028
+ sheetName: z.string(),
1029
+ address: z.string(),
1030
+ includeStyles: z.boolean().optional(),
1031
+ includeFormulas: z.boolean().optional()
1032
+ },
1033
+ annotations: {
1034
+ readOnlyHint: true,
1035
+ destructiveHint: false,
1036
+ openWorldHint: false
1037
+ }
1038
+ }, async ({ workbookId, sheetName, address, includeStyles, includeFormulas }) => {
1039
+ const operation = {
1040
+ kind: "range.read_full",
1041
+ operationId: makeId("op"),
1042
+ workbookId: workbookId,
1043
+ destructiveLevel: "none",
1044
+ reason: "MCP range read",
1045
+ target: {
1046
+ workbookId: workbookId,
1047
+ sheetName,
1048
+ address
1049
+ }
1050
+ };
1051
+ if (includeStyles !== undefined) {
1052
+ operation.includeStyles = includeStyles;
1053
+ }
1054
+ if (includeFormulas !== undefined) {
1055
+ operation.includeFormulas = includeFormulas;
1056
+ }
1057
+ return jsonResult(await runtime.applyBatch({
1058
+ workbookId: workbookId,
1059
+ mode: "apply",
1060
+ operations: [operation]
1061
+ }));
1062
+ });
1063
+ registerRangeOperation(mcp, "excel.range.write_values", {
1064
+ workbookId: z.string(),
1065
+ sheetName: z.string(),
1066
+ address: z.string(),
1067
+ values: z.array(z.array(z.any()))
1068
+ }, (args) => ({
1069
+ kind: "range.write_values",
1070
+ workbookId: args.workbookId,
1071
+ target: targetFromArgs(args),
1072
+ values: args.values,
1073
+ preserveFormats: true,
1074
+ destructiveLevel: "values",
1075
+ reason: "MCP range write values"
1076
+ }));
1077
+ registerRangeOperation(mcp, "excel.range.write_formulas", {
1078
+ workbookId: z.string(),
1079
+ sheetName: z.string(),
1080
+ address: z.string(),
1081
+ formulas: z.array(z.array(z.string().nullable()))
1082
+ }, (args) => ({
1083
+ kind: "range.write_formulas",
1084
+ workbookId: args.workbookId,
1085
+ target: targetFromArgs(args),
1086
+ formulas: args.formulas,
1087
+ preserveFormats: true,
1088
+ destructiveLevel: "values",
1089
+ reason: "MCP range write formulas"
1090
+ }));
1091
+ registerRangeOperation(mcp, "excel.range.write_number_formats", {
1092
+ workbookId: z.string(),
1093
+ sheetName: z.string(),
1094
+ address: z.string(),
1095
+ numberFormat: z.array(z.array(z.string()))
1096
+ }, (args) => ({
1097
+ kind: "range.write_number_formats",
1098
+ workbookId: args.workbookId,
1099
+ target: targetFromArgs(args),
1100
+ numberFormat: args.numberFormat,
1101
+ preserveValues: true,
1102
+ destructiveLevel: "format",
1103
+ reason: "MCP range write number formats"
1104
+ }));
1105
+ registerRangeOperation(mcp, "excel.range.write_styles", {
1106
+ workbookId: z.string(),
1107
+ sheetName: z.string(),
1108
+ address: z.string(),
1109
+ style: z.record(z.string(), z.any())
1110
+ }, (args) => ({
1111
+ kind: "range.write_styles",
1112
+ workbookId: args.workbookId,
1113
+ target: targetFromArgs(args),
1114
+ style: args.style,
1115
+ preserveValues: true,
1116
+ destructiveLevel: "format",
1117
+ reason: "MCP range write styles"
1118
+ }));
1119
+ for (const name of ["excel.range.clear", "excel.range.clear_values", "excel.range.clear_formats", "excel.range.clear_values_keep_format"]) {
1120
+ registerRangeOperation(mcp, name, {
1121
+ workbookId: z.string(),
1122
+ sheetName: z.string(),
1123
+ address: z.string(),
1124
+ applyTo: z.enum(["all", "contents", "formats", "hyperlinks"]).optional()
1125
+ }, (args) => ({
1126
+ kind: name.replace("excel.", ""),
1127
+ workbookId: args.workbookId,
1128
+ target: targetFromArgs(args),
1129
+ applyTo: args.applyTo,
1130
+ destructiveLevel: name.includes("format") && !name.includes("keep_format") ? "format" : "values",
1131
+ reason: `MCP ${name}`
1132
+ }));
1133
+ }
1134
+ for (const name of [
1135
+ "excel.range.insert_rows",
1136
+ "excel.range.delete_rows",
1137
+ "excel.range.insert_columns",
1138
+ "excel.range.delete_columns",
1139
+ "excel.range.autofit_columns",
1140
+ "excel.range.autofit_rows",
1141
+ "excel.range.merge",
1142
+ "excel.range.unmerge"
1143
+ ]) {
1144
+ registerRangeOperation(mcp, name, {
1145
+ workbookId: z.string(),
1146
+ sheetName: z.string(),
1147
+ address: z.string(),
1148
+ across: z.boolean().optional()
1149
+ }, (args) => ({
1150
+ kind: name.replace("excel.", ""),
1151
+ workbookId: args.workbookId,
1152
+ target: targetFromArgs(args),
1153
+ across: args.across,
1154
+ destructiveLevel: name.includes("autofit") ? "format" : "structure",
1155
+ reason: `MCP ${name}`
1156
+ }));
1157
+ }
1158
+ for (const name of ["excel.range.copy", "excel.range.move"]) {
1159
+ registerRangeOperation(mcp, name, {
1160
+ workbookId: z.string(),
1161
+ sourceSheetName: z.string(),
1162
+ sourceAddress: z.string(),
1163
+ targetSheetName: z.string(),
1164
+ targetAddress: z.string(),
1165
+ copyType: z.enum(["all", "values", "formats", "formulas"]).optional()
1166
+ }, (args) => ({
1167
+ kind: name.replace("excel.", ""),
1168
+ workbookId: args.workbookId,
1169
+ source: {
1170
+ workbookId: args.workbookId,
1171
+ sheetName: args.sourceSheetName,
1172
+ address: args.sourceAddress
1173
+ },
1174
+ target: {
1175
+ workbookId: args.workbookId,
1176
+ sheetName: args.targetSheetName,
1177
+ address: args.targetAddress
1178
+ },
1179
+ copyType: args.copyType,
1180
+ destructiveLevel: name.endsWith(".move") ? "values" : "none",
1181
+ reason: `MCP ${name}`
1182
+ }));
1183
+ }
1184
+ }
1185
+ function registerBatchTools(mcp) {
1186
+ registerMcpTool(mcp, "excel.batch.validate", {
1187
+ title: "Validate Excel batch",
1188
+ description: "Compile and validate a batch without sending it to Excel.",
1189
+ inputSchema: {
1190
+ workbookId: z.string(),
1191
+ operations: z.array(z.any())
1192
+ },
1193
+ annotations: {
1194
+ readOnlyHint: true,
1195
+ destructiveHint: false,
1196
+ openWorldHint: false
1197
+ }
1198
+ }, async ({ workbookId, operations }) => {
1199
+ const request = {
1200
+ workbookId: workbookId,
1201
+ mode: "validate",
1202
+ operations: operations
1203
+ };
1204
+ return jsonResult(await runtime.compileBatch(request));
1205
+ });
1206
+ registerMcpTool(mcp, "excel.batch.dry_run", {
1207
+ title: "Dry-run Excel batch",
1208
+ description: "Compile a batch and report backups, touched ranges, and estimated changes.",
1209
+ inputSchema: {
1210
+ workbookId: z.string(),
1211
+ operations: z.array(z.any())
1212
+ },
1213
+ annotations: {
1214
+ readOnlyHint: true,
1215
+ destructiveHint: false,
1216
+ openWorldHint: false
1217
+ }
1218
+ }, async ({ workbookId, operations }) => {
1219
+ const request = {
1220
+ workbookId: workbookId,
1221
+ mode: "dry_run",
1222
+ operations: operations
1223
+ };
1224
+ return jsonResult(await runtime.compileBatch(request));
1225
+ });
1226
+ registerMcpTool(mcp, "excel.batch.apply", {
1227
+ title: "Apply Excel batch",
1228
+ description: "Apply a batch through snapshots, backups, target conflict checks, and Office.js execution.",
1229
+ inputSchema: {
1230
+ workbookId: z.string(),
1231
+ operations: z.array(z.any()),
1232
+ confirmationToken: z.string().optional(),
1233
+ expectedTargetFingerprints: z.array(z.any()).optional(),
1234
+ agentId: z.string().optional(),
1235
+ agentName: z.string().optional(),
1236
+ taskId: z.string().optional(),
1237
+ role: z.string().optional()
1238
+ },
1239
+ annotations: {
1240
+ readOnlyHint: false,
1241
+ destructiveHint: true,
1242
+ openWorldHint: false
1243
+ }
1244
+ }, async ({ workbookId, operations, confirmationToken, expectedTargetFingerprints, agentId, agentName, taskId, role }) => {
1245
+ const request = {
1246
+ workbookId: workbookId,
1247
+ mode: "apply",
1248
+ operations: operations
1249
+ };
1250
+ if (confirmationToken !== undefined) {
1251
+ request.confirmationToken = confirmationToken;
1252
+ }
1253
+ if (expectedTargetFingerprints !== undefined) {
1254
+ request.expectedTargetFingerprints = expectedTargetFingerprints;
1255
+ }
1256
+ if (agentId !== undefined) {
1257
+ request.agentId = agentId;
1258
+ }
1259
+ if (agentName !== undefined) {
1260
+ request.agentName = agentName;
1261
+ }
1262
+ if (taskId !== undefined) {
1263
+ request.taskId = taskId;
1264
+ }
1265
+ if (role !== undefined) {
1266
+ request.role = role;
1267
+ }
1268
+ return jsonResult(await runtime.applyBatch(request));
1269
+ });
1270
+ }
1271
+ function registerPlanTools(mcp) {
1272
+ registerMcpTool(mcp, "excel.plan.create", {
1273
+ title: "Create Excel plan",
1274
+ description: "Create a reversible plan from proposed Excel operations.",
1275
+ inputSchema: {
1276
+ workbookId: z.string(),
1277
+ goal: z.string(),
1278
+ operations: z.array(z.any()),
1279
+ agentId: z.string().optional(),
1280
+ agentName: z.string().optional(),
1281
+ taskId: z.string().optional(),
1282
+ role: z.string().optional()
1283
+ },
1284
+ annotations: {
1285
+ readOnlyHint: true,
1286
+ destructiveHint: false,
1287
+ openWorldHint: false
1288
+ }
1289
+ }, async ({ workbookId, goal, operations, agentId, agentName, taskId, role }) => jsonResult(runtime.createPlan({
1290
+ workbookId: workbookId,
1291
+ goal,
1292
+ operations: operations,
1293
+ agentId: agentId,
1294
+ agentName,
1295
+ taskId: taskId,
1296
+ role
1297
+ })));
1298
+ registerMcpTool(mcp, "excel.plan.preview", {
1299
+ title: "Preview Excel plan",
1300
+ description: "Preview a plan and capture target-region fingerprints when Excel is connected.",
1301
+ inputSchema: {
1302
+ planId: z.string()
1303
+ },
1304
+ annotations: {
1305
+ readOnlyHint: true,
1306
+ destructiveHint: false,
1307
+ openWorldHint: false
1308
+ }
1309
+ }, async ({ planId }) => jsonResult(await runtime.previewPlan(planId)));
1310
+ registerMcpTool(mcp, "excel.plan.refresh_preview", {
1311
+ title: "Refresh Excel plan preview",
1312
+ description: "Refresh plan target fingerprints only when target ranges have not changed since preview.",
1313
+ inputSchema: {
1314
+ planId: z.string()
1315
+ },
1316
+ annotations: {
1317
+ readOnlyHint: true,
1318
+ destructiveHint: false,
1319
+ openWorldHint: false
1320
+ }
1321
+ }, async ({ planId }) => jsonResult(await runtime.refreshPlanPreview(planId)));
1322
+ registerMcpTool(mcp, "excel.plan.rebase", {
1323
+ title: "Rebase Excel plan",
1324
+ description: "Safely rebase a plan by refreshing fingerprints when target ranges are unchanged.",
1325
+ inputSchema: {
1326
+ planId: z.string()
1327
+ },
1328
+ annotations: {
1329
+ readOnlyHint: true,
1330
+ destructiveHint: false,
1331
+ openWorldHint: false
1332
+ }
1333
+ }, async ({ planId }) => jsonResult(await runtime.rebasePlan(planId)));
1334
+ registerMcpTool(mcp, "excel.plan.apply", {
1335
+ title: "Apply Excel plan",
1336
+ description: "Apply a previewed plan if target-region fingerprints still match.",
1337
+ inputSchema: {
1338
+ planId: z.string(),
1339
+ confirmationToken: z.string().optional()
1340
+ },
1341
+ annotations: {
1342
+ readOnlyHint: false,
1343
+ destructiveHint: true,
1344
+ openWorldHint: false
1345
+ }
1346
+ }, async ({ planId, confirmationToken }) => jsonResult(await runtime.applyPlan(planId, confirmationToken)));
1347
+ registerMcpTool(mcp, "excel.plan.rollback", {
1348
+ title: "Rollback Excel plan",
1349
+ description: "Rollback an applied plan using captured region snapshots and created-sheet cleanup.",
1350
+ inputSchema: {
1351
+ planId: z.string(),
1352
+ confirmationToken: z.string().optional()
1353
+ },
1354
+ annotations: {
1355
+ readOnlyHint: false,
1356
+ destructiveHint: true,
1357
+ openWorldHint: false
1358
+ }
1359
+ }, async ({ planId, confirmationToken }) => jsonResult(await runtime.rollbackPlan(planId, confirmationToken)));
1360
+ }
1361
+ function registerTemplateTools(mcp) {
1362
+ registerMcpTool(mcp, "excel.template.register", {
1363
+ title: "Register Excel template",
1364
+ description: "Capture and register a sheet template fingerprint for style, formula, and layout preservation.",
1365
+ inputSchema: {
1366
+ workbookId: z.string(),
1367
+ name: z.string(),
1368
+ scope: z.enum(["workbook", "local"]).default("workbook"),
1369
+ sourceSheetName: z.string(),
1370
+ dataRegions: z.array(z.string()).default([])
1371
+ },
1372
+ annotations: {
1373
+ readOnlyHint: true,
1374
+ destructiveHint: false,
1375
+ openWorldHint: false
1376
+ }
1377
+ }, async ({ workbookId, name, scope, sourceSheetName, dataRegions }) => jsonResult(await runtime.registerTemplate({
1378
+ workbookId: workbookId,
1379
+ name,
1380
+ scope,
1381
+ sourceSheetName,
1382
+ dataRegions
1383
+ })));
1384
+ registerMcpTool(mcp, "excel.template.get", {
1385
+ title: "Get Excel template",
1386
+ description: "Return a registered template including fingerprint payload.",
1387
+ inputSchema: {
1388
+ templateId: z.string()
1389
+ },
1390
+ annotations: {
1391
+ readOnlyHint: true,
1392
+ destructiveHint: false,
1393
+ openWorldHint: false
1394
+ }
1395
+ }, async ({ templateId }) => jsonResult(runtime.getTemplate(templateId)));
1396
+ registerMcpTool(mcp, "excel.template.unregister", {
1397
+ title: "Unregister Excel template",
1398
+ description: "Remove a registered template from the local runtime registry.",
1399
+ inputSchema: {
1400
+ templateId: z.string()
1401
+ },
1402
+ annotations: {
1403
+ readOnlyHint: false,
1404
+ destructiveHint: false,
1405
+ openWorldHint: false
1406
+ }
1407
+ }, async ({ templateId }) => jsonResult(runtime.unregisterTemplate(templateId)));
1408
+ registerMcpTool(mcp, "excel.template.detect_templates", {
1409
+ title: "Detect Excel templates",
1410
+ description: "Return candidate template sheets from the active workbook.",
1411
+ inputSchema: {
1412
+ workbookId: z.string()
1413
+ },
1414
+ annotations: {
1415
+ readOnlyHint: true,
1416
+ destructiveHint: false,
1417
+ openWorldHint: false
1418
+ }
1419
+ }, async ({ workbookId }) => jsonResult(await runtime.detectTemplates(workbookId)));
1420
+ registerMcpTool(mcp, "excel.template.infer_regions", {
1421
+ title: "Infer Excel template regions",
1422
+ description: "Return declared and inferred data regions for a registered template.",
1423
+ inputSchema: {
1424
+ templateId: z.string()
1425
+ },
1426
+ annotations: {
1427
+ readOnlyHint: true,
1428
+ destructiveHint: false,
1429
+ openWorldHint: false
1430
+ }
1431
+ }, async ({ templateId }) => jsonResult(runtime.inferTemplateRegions(templateId)));
1432
+ registerMcpTool(mcp, "excel.template.create_sheet_from_template", {
1433
+ title: "Create sheet from template",
1434
+ description: "Copy a registered template sheet and clear declared data regions.",
1435
+ inputSchema: {
1436
+ workbookId: z.string(),
1437
+ templateId: z.string(),
1438
+ newSheetName: z.string(),
1439
+ clearDataRegions: z.boolean().default(true)
1440
+ },
1441
+ annotations: {
1442
+ readOnlyHint: false,
1443
+ destructiveHint: true,
1444
+ openWorldHint: false
1445
+ }
1446
+ }, async ({ workbookId, templateId, newSheetName, clearDataRegions }) => jsonResult(await applySingleOperation(workbookId, {
1447
+ kind: "template.create_sheet_from_template",
1448
+ workbookId: workbookId,
1449
+ templateId: templateId,
1450
+ newSheetName,
1451
+ clearDataRegions,
1452
+ destructiveLevel: "structure",
1453
+ reason: "MCP create sheet from template"
1454
+ })));
1455
+ registerMcpTool(mcp, "excel.template.validate_sheet_against_template", {
1456
+ title: "Validate sheet against template",
1457
+ description: "Compare a target sheet against a registered template fingerprint.",
1458
+ inputSchema: {
1459
+ workbookId: z.string(),
1460
+ templateId: z.string(),
1461
+ targetSheetName: z.string()
1462
+ },
1463
+ annotations: {
1464
+ readOnlyHint: true,
1465
+ destructiveHint: false,
1466
+ openWorldHint: false
1467
+ }
1468
+ }, async ({ workbookId, templateId, targetSheetName }) => jsonResult(await runtime.validateSheetAgainstTemplate({
1469
+ workbookId: workbookId,
1470
+ templateId: templateId,
1471
+ targetSheetName
1472
+ })));
1473
+ registerMcpTool(mcp, "excel.template.clear_data_regions", {
1474
+ title: "Clear template data regions",
1475
+ description: "Clear declared data regions on a target sheet while preserving formats.",
1476
+ inputSchema: {
1477
+ workbookId: z.string(),
1478
+ templateId: z.string(),
1479
+ targetSheetName: z.string()
1480
+ },
1481
+ annotations: {
1482
+ readOnlyHint: false,
1483
+ destructiveHint: true,
1484
+ openWorldHint: false
1485
+ }
1486
+ }, async ({ workbookId, templateId, targetSheetName }) => {
1487
+ const templateResult = runtime.getTemplate(templateId);
1488
+ if (!templateResult.ok || !("template" in templateResult)) {
1489
+ return jsonResult(templateResult);
1490
+ }
1491
+ const operations = templateResult.template.dataRegions.map((address) => ({
1492
+ kind: "range.clear_values_keep_format",
1493
+ operationId: makeId("op"),
1494
+ workbookId: workbookId,
1495
+ destructiveLevel: "values",
1496
+ reason: `Clear data region from template ${templateId}`,
1497
+ target: {
1498
+ workbookId: workbookId,
1499
+ sheetName: targetSheetName,
1500
+ address
1501
+ }
1502
+ }));
1503
+ return jsonResult(await runtime.applyBatch({ workbookId: workbookId, mode: "apply", operations }));
1504
+ });
1505
+ registerMcpTool(mcp, "excel.template.fill_regions", {
1506
+ title: "Fill template regions",
1507
+ description: "Write values to target sheet regions while preserving existing formats.",
1508
+ inputSchema: {
1509
+ workbookId: z.string(),
1510
+ targetSheetName: z.string(),
1511
+ regions: z.array(z.object({
1512
+ address: z.string(),
1513
+ values: z.array(z.array(z.any()))
1514
+ }))
1515
+ },
1516
+ annotations: {
1517
+ readOnlyHint: false,
1518
+ destructiveHint: true,
1519
+ openWorldHint: false
1520
+ }
1521
+ }, async ({ workbookId, targetSheetName, regions }) => {
1522
+ const operations = regions.map((region) => ({
1523
+ kind: "range.write_values",
1524
+ operationId: makeId("op"),
1525
+ workbookId: workbookId,
1526
+ destructiveLevel: "values",
1527
+ reason: "Fill template region",
1528
+ target: {
1529
+ workbookId: workbookId,
1530
+ sheetName: targetSheetName,
1531
+ address: region.address
1532
+ },
1533
+ values: region.values,
1534
+ preserveFormats: true
1535
+ }));
1536
+ return jsonResult(await runtime.applyBatch({ workbookId: workbookId, mode: "apply", operations }));
1537
+ });
1538
+ registerMcpTool(mcp, "excel.template.repair_sheet_from_template", {
1539
+ title: "Repair sheet from template",
1540
+ description: "Repair target sheet styles, formulas, layout, or data regions from a registered template.",
1541
+ inputSchema: templateRepairSchema(),
1542
+ annotations: {
1543
+ readOnlyHint: false,
1544
+ destructiveHint: true,
1545
+ openWorldHint: false
1546
+ }
1547
+ }, async (args) => jsonResult(await repairTemplateFromArgs(args)));
1548
+ registerMcpTool(mcp, "excel.template.list", {
1549
+ title: "List Excel templates",
1550
+ description: "List local and workbook-scoped registered templates.",
1551
+ inputSchema: {
1552
+ workbookId: z.string().optional()
1553
+ },
1554
+ annotations: {
1555
+ readOnlyHint: true,
1556
+ destructiveHint: false,
1557
+ openWorldHint: false
1558
+ }
1559
+ }, async ({ workbookId }) => jsonResult(runtime.listTemplates(workbookId)));
1560
+ }
1561
+ function registerStyleTools(mcp) {
1562
+ registerMcpTool(mcp, "excel.style.get_fingerprint", {
1563
+ title: "Get style fingerprint",
1564
+ description: "Capture a granular style fingerprint for a sheet or address.",
1565
+ inputSchema: {
1566
+ workbookId: z.string(),
1567
+ sheetName: z.string(),
1568
+ address: z.string().optional(),
1569
+ maxCellSamples: z.number().int().positive().optional()
1570
+ },
1571
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1572
+ }, async (args) => jsonResult(await runtime.getStyleFingerprint({
1573
+ workbookId: args.workbookId,
1574
+ sheetName: args.sheetName,
1575
+ ...(args.address !== undefined ? { address: args.address } : {}),
1576
+ ...(args.maxCellSamples !== undefined ? { maxCellSamples: args.maxCellSamples } : {})
1577
+ })));
1578
+ registerMcpTool(mcp, "excel.style.compare_fingerprint", {
1579
+ title: "Compare style fingerprints",
1580
+ description: "Compare source and target style fingerprints by dimension.",
1581
+ inputSchema: styleCompareSchema(),
1582
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1583
+ }, async (args) => jsonResult(await runtime.compareStyleFingerprints(styleCompareRequest(args))));
1584
+ registerMcpTool(mcp, "excel.style.copy_from_template", {
1585
+ title: "Copy style from template",
1586
+ description: "Repair only styles from a registered template.",
1587
+ inputSchema: templateRepairSchema(),
1588
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1589
+ }, async (args) => jsonResult(await repairTemplateFromArgs({ ...args, repair: ["styles"] })));
1590
+ registerMcpTool(mcp, "excel.style.apply_style", {
1591
+ title: "Apply range style",
1592
+ description: "Apply direct fill, font, alignment, row height, and column width properties to a range.",
1593
+ inputSchema: {
1594
+ workbookId: z.string(),
1595
+ sheetName: z.string(),
1596
+ address: z.string(),
1597
+ style: z.record(z.string(), z.unknown())
1598
+ },
1599
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1600
+ }, async (args) => {
1601
+ const operation = {
1602
+ kind: "range.write_styles",
1603
+ operationId: makeId("op"),
1604
+ workbookId: args.workbookId,
1605
+ destructiveLevel: "format",
1606
+ reason: "MCP style apply",
1607
+ target: {
1608
+ workbookId: args.workbookId,
1609
+ sheetName: args.sheetName,
1610
+ address: args.address
1611
+ },
1612
+ style: args.style,
1613
+ preserveValues: true
1614
+ };
1615
+ return jsonResult(await runtime.applyBatch({ workbookId: args.workbookId, mode: "apply", operations: [operation] }));
1616
+ });
1617
+ for (const name of ["excel.style.validate_consistency", "excel.style.repair_consistency"]) {
1618
+ registerMcpTool(mcp, name, {
1619
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
1620
+ description: "Validate or repair style consistency against a registered template.",
1621
+ inputSchema: templateRepairSchema(),
1622
+ annotations: { readOnlyHint: name.includes("validate"), destructiveHint: name.includes("repair"), openWorldHint: false }
1623
+ }, async (args) => jsonResult(name.includes("validate")
1624
+ ? await runtime.validateSheetAgainstTemplate({
1625
+ workbookId: args.workbookId,
1626
+ templateId: args.templateId,
1627
+ targetSheetName: args.targetSheetName
1628
+ })
1629
+ : await repairTemplateFromArgs({ ...args, repair: ["styles"] })));
1630
+ }
1631
+ for (const [name, dimension] of Object.entries(STYLE_COPY_TOOL_DIMENSIONS)) {
1632
+ registerMcpTool(mcp, name, {
1633
+ title: name.replace(/^excel\.style\./, "").replace(/_/g, " "),
1634
+ description: `Copy ${dimension} styling from one sheet/range to another with backup and post-copy validation.`,
1635
+ inputSchema: styleCopySchema(),
1636
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1637
+ }, async (args) => jsonResult(await runtime.copyStyleDimensions({ ...styleCopyRequest(args), dimensions: [dimension] })));
1638
+ }
1639
+ registerMcpTool(mcp, "excel.style.get_theme", {
1640
+ title: "Get workbook theme",
1641
+ description: "Report workbook theme capability status.",
1642
+ inputSchema: {
1643
+ workbookId: z.string()
1644
+ },
1645
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1646
+ }, async ({ workbookId }) => jsonResult({
1647
+ ok: false,
1648
+ workbookId,
1649
+ error: {
1650
+ code: "CAPABILITY_UNAVAILABLE",
1651
+ message: "Office.js does not expose a safe cross-platform workbook theme snapshot in the current implementation.",
1652
+ retryable: false
1653
+ }
1654
+ }));
1655
+ registerMcpTool(mcp, "excel.style.apply_theme", {
1656
+ title: "Apply workbook theme",
1657
+ description: "Report workbook theme apply capability status.",
1658
+ inputSchema: {
1659
+ workbookId: z.string(),
1660
+ theme: z.record(z.string(), z.unknown())
1661
+ },
1662
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1663
+ }, async ({ workbookId }) => jsonResult({
1664
+ ok: false,
1665
+ workbookId,
1666
+ error: {
1667
+ code: "CAPABILITY_UNAVAILABLE",
1668
+ message: "Theme apply is not enabled until workbook theme snapshots can be captured and replayed deterministically.",
1669
+ retryable: false
1670
+ }
1671
+ }));
1672
+ }
1673
+ function styleCompareSchema() {
1674
+ return {
1675
+ workbookId: z.string(),
1676
+ sourceSheetName: z.string(),
1677
+ targetSheetName: z.string(),
1678
+ sourceAddress: z.string().optional(),
1679
+ targetAddress: z.string().optional(),
1680
+ dimensions: z.array(z.enum(STYLE_DIMENSIONS)).optional(),
1681
+ maxCellSamples: z.number().int().positive().optional()
1682
+ };
1683
+ }
1684
+ function styleCopySchema() {
1685
+ return {
1686
+ workbookId: z.string(),
1687
+ sourceSheetName: z.string(),
1688
+ targetSheetName: z.string(),
1689
+ sourceAddress: z.string().optional(),
1690
+ targetAddress: z.string().optional()
1691
+ };
1692
+ }
1693
+ function styleCompareRequest(args) {
1694
+ return {
1695
+ workbookId: args.workbookId,
1696
+ sourceSheetName: args.sourceSheetName,
1697
+ targetSheetName: args.targetSheetName,
1698
+ ...(args.sourceAddress !== undefined ? { sourceAddress: args.sourceAddress } : {}),
1699
+ ...(args.targetAddress !== undefined ? { targetAddress: args.targetAddress } : {}),
1700
+ ...(args.dimensions !== undefined ? { dimensions: args.dimensions } : {}),
1701
+ ...(args.maxCellSamples !== undefined ? { maxCellSamples: args.maxCellSamples } : {})
1702
+ };
1703
+ }
1704
+ function styleCopyRequest(args) {
1705
+ return {
1706
+ workbookId: args.workbookId,
1707
+ sourceSheetName: args.sourceSheetName,
1708
+ targetSheetName: args.targetSheetName,
1709
+ ...(args.sourceAddress !== undefined ? { sourceAddress: args.sourceAddress } : {}),
1710
+ ...(args.targetAddress !== undefined ? { targetAddress: args.targetAddress } : {}),
1711
+ dimensions: []
1712
+ };
1713
+ }
1714
+ function registerFormulaTools(mcp) {
1715
+ registerMcpTool(mcp, "excel.formula.read_patterns", {
1716
+ title: "Read formula patterns",
1717
+ description: "Capture formulas, R1C1 pattern hashes, and pattern groups for a sheet or range.",
1718
+ inputSchema: formulaPatternSchema(),
1719
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1720
+ }, async (args) => jsonResult(await runtime.readFormulaPatterns(formulaPatternRequest(args))));
1721
+ registerMcpTool(mcp, "excel.formula.copy_patterns", {
1722
+ title: "Copy formula patterns",
1723
+ description: "Copy formulas from a source sheet/range to a target sheet/range with backup and validation.",
1724
+ inputSchema: formulaCopySchema(),
1725
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1726
+ }, async (args) => jsonResult(await runtime.copyFormulaPatterns(formulaCopyRequest(args))));
1727
+ for (const [name, direction] of [
1728
+ ["excel.formula.fill_down", "down"],
1729
+ ["excel.formula.fill_right", "right"]
1730
+ ]) {
1731
+ registerMcpTool(mcp, name, {
1732
+ title: name.replace(/^excel\.formula\./, "").replace(/_/g, " "),
1733
+ description: `Fill formulas ${direction} using R1C1 pattern semantics.`,
1734
+ inputSchema: formulaFillSchema(),
1735
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1736
+ }, async (args) => jsonResult(await runtime.fillFormulaPattern({ ...formulaFillRequest(args), direction })));
1737
+ }
1738
+ registerMcpTool(mcp, "excel.formula.validate", {
1739
+ title: "Validate formulas",
1740
+ description: "Validate formula errors in a workbook, sheet, or range.",
1741
+ inputSchema: validationRangeSchema(),
1742
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1743
+ }, async (args) => jsonResult(await runtime.validateFormulas({
1744
+ workbookId: args.workbookId,
1745
+ ...(args.sheetName !== undefined ? { sheetName: args.sheetName } : {}),
1746
+ ...(args.address !== undefined ? { address: args.address } : {})
1747
+ })));
1748
+ registerMcpTool(mcp, "excel.formula.validate_against_template", {
1749
+ title: "Validate formulas against template",
1750
+ description: "Validate formula consistency against a registered template.",
1751
+ inputSchema: templateRepairSchema(),
1752
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1753
+ }, async (args) => jsonResult(await runtime.validateSheetAgainstTemplate({
1754
+ workbookId: args.workbookId,
1755
+ templateId: args.templateId,
1756
+ targetSheetName: args.targetSheetName
1757
+ })));
1758
+ registerMcpTool(mcp, "excel.formula.repair_patterns", {
1759
+ title: "Repair formula patterns",
1760
+ description: "Repair formulas from a registered template.",
1761
+ inputSchema: templateRepairSchema(),
1762
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1763
+ }, async (args) => jsonResult(await repairTemplateFromArgs({ ...args, repair: ["formulas"] })));
1764
+ registerMcpTool(mcp, "excel.formula.find_errors", {
1765
+ title: "Find formula errors",
1766
+ description: "Find cells with formula errors in a sheet/range.",
1767
+ inputSchema: validationRangeSchema(),
1768
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1769
+ }, async (args) => jsonResult(await runtime.validateFormulas({
1770
+ workbookId: args.workbookId,
1771
+ ...(args.sheetName !== undefined ? { sheetName: args.sheetName } : {}),
1772
+ ...(args.address !== undefined ? { address: args.address } : {})
1773
+ })));
1774
+ registerMcpTool(mcp, "excel.formula.find_circular_references", {
1775
+ title: "Find circular references",
1776
+ description: "Report circular-reference detection capability status.",
1777
+ inputSchema: validationRangeSchema(),
1778
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1779
+ }, async (args) => jsonResult(capabilityUnavailable(args.workbookId, "FORMULA_CIRCULAR_REFERENCES_UNAVAILABLE", "Office.js does not expose deterministic circular-reference enumeration in this runtime yet.")));
1780
+ registerMcpTool(mcp, "excel.formula.get_dependency_graph", {
1781
+ title: "Get formula dependency graph",
1782
+ description: "Parse formulas in a sheet or range and return precedent dependency edges.",
1783
+ inputSchema: formulaPatternSchema(),
1784
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1785
+ }, async (args) => jsonResult(await runtime.getFormulaDependencyGraph(formulaPatternRequest(args))));
1786
+ registerMcpTool(mcp, "excel.formula.trace_precedents", {
1787
+ title: "Trace formula precedents",
1788
+ description: "Parse a formula cell and return referenced precedent ranges.",
1789
+ inputSchema: {
1790
+ workbookId: z.string(),
1791
+ sheetName: z.string(),
1792
+ address: z.string()
1793
+ },
1794
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1795
+ }, async (args) => jsonResult(await runtime.traceFormulaPrecedents(formulaPatternRequest(args))));
1796
+ registerMcpTool(mcp, "excel.formula.trace_dependents", {
1797
+ title: "Trace formula dependents",
1798
+ description: "Parse formulas on a sheet and return formula cells that depend on the target range.",
1799
+ inputSchema: {
1800
+ workbookId: z.string(),
1801
+ sheetName: z.string(),
1802
+ address: z.string()
1803
+ },
1804
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1805
+ }, async (args) => jsonResult(await runtime.traceFormulaDependents(formulaPatternRequest(args))));
1806
+ registerMcpTool(mcp, "excel.formula.convert_to_values", {
1807
+ title: "Convert formulas to values",
1808
+ description: "Replace formulas in a sheet/range with their current calculated values, with backup.",
1809
+ inputSchema: formulaPatternSchema(),
1810
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1811
+ }, async (args) => jsonResult(await runtime.convertFormulasToValues(formulaPatternRequest(args))));
1812
+ registerMcpTool(mcp, "excel.formula.recalculate", {
1813
+ title: "Recalculate formulas",
1814
+ description: "Run workbook recalculation.",
1815
+ inputSchema: {
1816
+ workbookId: z.string(),
1817
+ calculationType: z.enum(["full", "recalculate"]).optional()
1818
+ },
1819
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
1820
+ }, async ({ workbookId, calculationType }) => jsonResult(await runtime.calculateWorkbook(workbookId, calculationType)));
1821
+ registerMcpTool(mcp, "excel.formula.explain", {
1822
+ title: "Explain formula",
1823
+ description: "Return a lightweight parse summary for a formula string or the first formula in a range.",
1824
+ inputSchema: {
1825
+ workbookId: z.string(),
1826
+ sheetName: z.string().optional(),
1827
+ address: z.string().optional(),
1828
+ formula: z.string().optional()
1829
+ },
1830
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1831
+ }, async (args) => jsonResult(await explainFormula(args)));
1832
+ }
1833
+ function formulaPatternSchema() {
1834
+ return {
1835
+ workbookId: z.string(),
1836
+ sheetName: z.string(),
1837
+ address: z.string().optional()
1838
+ };
1839
+ }
1840
+ function formulaCopySchema() {
1841
+ return {
1842
+ workbookId: z.string(),
1843
+ sourceSheetName: z.string(),
1844
+ targetSheetName: z.string(),
1845
+ sourceAddress: z.string().optional(),
1846
+ targetAddress: z.string().optional()
1847
+ };
1848
+ }
1849
+ function formulaFillSchema() {
1850
+ return {
1851
+ workbookId: z.string(),
1852
+ sheetName: z.string(),
1853
+ sourceAddress: z.string(),
1854
+ targetAddress: z.string()
1855
+ };
1856
+ }
1857
+ function validationRangeSchema() {
1858
+ return {
1859
+ workbookId: z.string(),
1860
+ sheetName: z.string().optional(),
1861
+ address: z.string().optional()
1862
+ };
1863
+ }
1864
+ function formulaPatternRequest(args) {
1865
+ return {
1866
+ workbookId: args.workbookId,
1867
+ sheetName: args.sheetName,
1868
+ ...(args.address !== undefined ? { address: args.address } : {})
1869
+ };
1870
+ }
1871
+ function formulaCopyRequest(args) {
1872
+ return {
1873
+ workbookId: args.workbookId,
1874
+ sourceSheetName: args.sourceSheetName,
1875
+ targetSheetName: args.targetSheetName,
1876
+ ...(args.sourceAddress !== undefined ? { sourceAddress: args.sourceAddress } : {}),
1877
+ ...(args.targetAddress !== undefined ? { targetAddress: args.targetAddress } : {})
1878
+ };
1879
+ }
1880
+ function formulaFillRequest(args) {
1881
+ return {
1882
+ workbookId: args.workbookId,
1883
+ sheetName: args.sheetName,
1884
+ sourceAddress: args.sourceAddress,
1885
+ targetAddress: args.targetAddress,
1886
+ direction: "down"
1887
+ };
1888
+ }
1889
+ function capabilityUnavailable(workbookId, code, message) {
1890
+ return {
1891
+ ok: false,
1892
+ workbookId,
1893
+ error: {
1894
+ code: "CAPABILITY_UNAVAILABLE",
1895
+ message,
1896
+ retryable: false,
1897
+ details: { reasonCode: code }
1898
+ }
1899
+ };
1900
+ }
1901
+ async function explainFormula(args) {
1902
+ let formula = args.formula;
1903
+ if (!formula && args.sheetName && args.address) {
1904
+ const result = await runtime.readFormulaPatterns({
1905
+ workbookId: args.workbookId,
1906
+ sheetName: args.sheetName,
1907
+ address: args.address
1908
+ });
1909
+ if (!result.ok) {
1910
+ return result;
1911
+ }
1912
+ formula = result.patterns.cells[0]?.formula;
1913
+ }
1914
+ if (!formula) {
1915
+ return {
1916
+ ok: false,
1917
+ error: {
1918
+ code: "FORMULA_REQUIRED",
1919
+ message: "Provide a formula string or a sheetName/address containing at least one formula.",
1920
+ retryable: false
1921
+ }
1922
+ };
1923
+ }
1924
+ const normalized = formula.startsWith("=") ? formula : `=${formula}`;
1925
+ const functions = [...normalized.matchAll(/\b([A-Z][A-Z0-9_.]*)\s*\(/gi)].map((match) => match[1].toUpperCase());
1926
+ const references = [...normalized.matchAll(/(?:'[^']+'|[A-Z_][A-Z0-9_ ]*!|\b)?\$?[A-Z]{1,3}\$?\d+(?::\$?[A-Z]{1,3}\$?\d+)?/gi)].map((match) => match[0]);
1927
+ return {
1928
+ ok: true,
1929
+ formula: normalized,
1930
+ summary: {
1931
+ functions: [...new Set(functions)],
1932
+ references: [...new Set(references)],
1933
+ hasExternalReference: /\[[^\]]+\]/.test(normalized),
1934
+ hasStructuredReference: /\[[#@\w ,:[\]]+\]/.test(normalized),
1935
+ hasVolatileFunction: functions.some((fn) => ["NOW", "TODAY", "RAND", "RANDBETWEEN", "OFFSET", "INDIRECT"].includes(fn))
1936
+ }
1937
+ };
1938
+ }
1939
+ function registerTableTools(mcp) {
1940
+ registerMcpTool(mcp, "excel.table.list", {
1941
+ title: "List Excel tables",
1942
+ description: "List structured tables in the active workbook.",
1943
+ inputSchema: { workbookId: z.string() },
1944
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1945
+ }, async ({ workbookId }) => jsonResult(await runtime.listTables(workbookId)));
1946
+ registerMcpTool(mcp, "excel.table.get_info", {
1947
+ title: "Get Excel table info",
1948
+ description: "Return table range, columns, style, filter, and sort metadata.",
1949
+ inputSchema: tableSelectorSchema(),
1950
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1951
+ }, async (args) => jsonResult(await runtime.getTableInfo(tableSelector(args))));
1952
+ registerMcpTool(mcp, "excel.table.read", {
1953
+ title: "Read Excel table",
1954
+ description: "Read table headers, values, formulas, text, number formats, and metadata.",
1955
+ inputSchema: tableSelectorSchema(),
1956
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1957
+ }, async (args) => jsonResult(await runtime.readTable(tableSelector(args))));
1958
+ registerMcpTool(mcp, "excel.table.create", {
1959
+ title: "Create Excel table",
1960
+ description: "Create a structured table from a range, optionally writing values first.",
1961
+ inputSchema: {
1962
+ workbookId: z.string(),
1963
+ sheetName: z.string(),
1964
+ address: z.string(),
1965
+ tableName: z.string().optional(),
1966
+ hasHeaders: z.boolean().default(true),
1967
+ values: z.array(z.array(z.any())).optional(),
1968
+ style: z.string().optional(),
1969
+ showTotals: z.boolean().optional()
1970
+ },
1971
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1972
+ }, async (args) => jsonResult(await runtime.createTable(tableCreateRequest(args))));
1973
+ registerMcpTool(mcp, "excel.table.resize", {
1974
+ title: "Resize Excel table",
1975
+ description: "Resize a structured table to a new range.",
1976
+ inputSchema: { ...tableSelectorSchema(), address: z.string() },
1977
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1978
+ }, async (args) => jsonResult(await runtime.resizeTable({ ...tableSelector(args), address: args.address })));
1979
+ registerMcpTool(mcp, "excel.table.append_rows", {
1980
+ title: "Append Excel table rows",
1981
+ description: "Append one or more rows to a structured table.",
1982
+ inputSchema: {
1983
+ ...tableSelectorSchema(),
1984
+ values: z.array(z.array(z.any())),
1985
+ index: z.number().int().optional(),
1986
+ alwaysInsert: z.boolean().optional()
1987
+ },
1988
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1989
+ }, async (args) => jsonResult(await runtime.appendTableRows({
1990
+ ...tableSelector(args),
1991
+ values: args.values,
1992
+ index: args.index,
1993
+ alwaysInsert: args.alwaysInsert
1994
+ })));
1995
+ registerMcpTool(mcp, "excel.table.update_rows", {
1996
+ title: "Update Excel table rows",
1997
+ description: "Update table rows by zero-based table-row index.",
1998
+ inputSchema: {
1999
+ ...tableSelectorSchema(),
2000
+ rows: z.array(z.object({ index: z.number().int().min(0), values: z.array(z.any()) }))
2001
+ },
2002
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2003
+ }, async (args) => jsonResult(await runtime.updateTableRows({ ...tableSelector(args), rows: args.rows })));
2004
+ registerMcpTool(mcp, "excel.table.clear_data_keep_formulas", {
2005
+ title: "Clear table data keep formulas",
2006
+ description: "Clear constants in the table data body while preserving formulas.",
2007
+ inputSchema: tableSelectorSchema(),
2008
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2009
+ }, async (args) => jsonResult(await runtime.clearTableDataKeepFormulas(tableSelector(args))));
2010
+ registerMcpTool(mcp, "excel.table.clear_filters", {
2011
+ title: "Clear table filters",
2012
+ description: "Clear all filters on a structured table.",
2013
+ inputSchema: tableSelectorSchema(),
2014
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2015
+ }, async (args) => jsonResult(await runtime.clearTableFilters(tableSelector(args))));
2016
+ registerMcpTool(mcp, "excel.table.apply_filters", {
2017
+ title: "Apply table filters",
2018
+ description: "Apply Office.js filter criteria to table columns.",
2019
+ inputSchema: tableFilterSchema(),
2020
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2021
+ }, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
2022
+ registerMcpTool(mcp, "excel.table.preserve_filters", {
2023
+ title: "Preserve table filters",
2024
+ description: "Reapply provided filter criteria to a table.",
2025
+ inputSchema: tableFilterSchema(),
2026
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2027
+ }, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
2028
+ registerMcpTool(mcp, "excel.table.sort", {
2029
+ title: "Sort Excel table",
2030
+ description: "Apply table sort fields.",
2031
+ inputSchema: tableSortSchema(),
2032
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2033
+ }, async (args) => jsonResult(await runtime.sortTable({ ...tableSelector(args), fields: args.fields, matchCase: args.matchCase, method: args.method })));
2034
+ registerMcpTool(mcp, "excel.table.set_total_row", {
2035
+ title: "Set table total row",
2036
+ description: "Show or hide the table total row.",
2037
+ inputSchema: { ...tableSelectorSchema(), showTotals: z.boolean() },
2038
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2039
+ }, async (args) => jsonResult(await runtime.setTableTotalRow({ ...tableSelector(args), showTotals: args.showTotals })));
2040
+ registerMcpTool(mcp, "excel.table.set_style", {
2041
+ title: "Set table style",
2042
+ description: "Apply an Excel table style.",
2043
+ inputSchema: { ...tableSelectorSchema(), style: z.string() },
2044
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2045
+ }, async (args) => jsonResult(await runtime.setTableStyle({ ...tableSelector(args), style: args.style })));
2046
+ registerMcpTool(mcp, "excel.table.copy_structure", {
2047
+ title: "Copy table structure",
2048
+ description: "Copy table headers and optional style/totals to a new target table.",
2049
+ inputSchema: {
2050
+ ...tableSelectorSchema(),
2051
+ targetSheetName: z.string(),
2052
+ targetAddress: z.string(),
2053
+ newTableName: z.string().optional(),
2054
+ includeStyle: z.boolean().optional(),
2055
+ includeTotals: z.boolean().optional(),
2056
+ includeFilters: z.boolean().optional()
2057
+ },
2058
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2059
+ }, async (args) => jsonResult(await runtime.copyTableStructure({
2060
+ ...tableSelector(args),
2061
+ targetSheetName: args.targetSheetName,
2062
+ targetAddress: args.targetAddress,
2063
+ newTableName: args.newTableName,
2064
+ includeStyle: args.includeStyle,
2065
+ includeTotals: args.includeTotals,
2066
+ includeFilters: args.includeFilters
2067
+ })));
2068
+ registerMcpTool(mcp, "excel.table.validate_against_template", {
2069
+ title: "Validate table against template",
2070
+ description: "Compare current table metadata with a registered template table fingerprint.",
2071
+ inputSchema: { ...tableSelectorSchema(), templateId: z.string() },
2072
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2073
+ }, async (args) => jsonResult(await runtime.validateTableAgainstTemplate({ ...tableSelector(args), templateId: args.templateId })));
2074
+ }
2075
+ function registerFilterTools(mcp) {
2076
+ registerMcpTool(mcp, "excel.filter.get_filters", {
2077
+ title: "Get table filters",
2078
+ description: "Return filter metadata for a structured table.",
2079
+ inputSchema: tableSelectorSchema(),
2080
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2081
+ }, async (args) => {
2082
+ const result = await runtime.getTableInfo(tableSelector(args));
2083
+ const info = result.info;
2084
+ return jsonResult({ ok: Boolean(info), filters: info?.filters, result });
2085
+ });
2086
+ registerMcpTool(mcp, "excel.filter.apply", {
2087
+ title: "Apply table filters",
2088
+ description: "Apply filters to a structured table.",
2089
+ inputSchema: tableFilterSchema(),
2090
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2091
+ }, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
2092
+ registerMcpTool(mcp, "excel.filter.clear", {
2093
+ title: "Clear table filters",
2094
+ description: "Clear table filters.",
2095
+ inputSchema: tableSelectorSchema(),
2096
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2097
+ }, async (args) => jsonResult(await runtime.clearTableFilters(tableSelector(args))));
2098
+ registerMcpTool(mcp, "excel.filter.preserve_from_template", {
2099
+ title: "Preserve filters from template",
2100
+ description: "Apply provided template filter criteria to a table.",
2101
+ inputSchema: tableFilterSchema(),
2102
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2103
+ }, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
2104
+ registerMcpTool(mcp, "excel.filter.validate", {
2105
+ title: "Validate table filters",
2106
+ description: "Return current filter state for validation by the agent.",
2107
+ inputSchema: tableSelectorSchema(),
2108
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2109
+ }, async (args) => jsonResult(await runtime.getTableInfo(tableSelector(args))));
2110
+ }
2111
+ function registerSortTools(mcp) {
2112
+ registerMcpTool(mcp, "excel.sort.apply", {
2113
+ title: "Apply table sort",
2114
+ description: "Apply sort fields to a structured table.",
2115
+ inputSchema: tableSortSchema(),
2116
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2117
+ }, async (args) => jsonResult(await runtime.sortTable({ ...tableSelector(args), fields: args.fields, matchCase: args.matchCase, method: args.method })));
2118
+ registerMcpTool(mcp, "excel.sort.clear", {
2119
+ title: "Clear table sort",
2120
+ description: "Clear sort state on a structured table.",
2121
+ inputSchema: tableSelectorSchema(),
2122
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2123
+ }, async (args) => jsonResult(await runtime.clearTableSort(tableSelector(args))));
2124
+ registerMcpTool(mcp, "excel.sort.preserve_from_template", {
2125
+ title: "Preserve sort from template",
2126
+ description: "Apply provided template sort fields to a table.",
2127
+ inputSchema: tableSortSchema(),
2128
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2129
+ }, async (args) => jsonResult(await runtime.sortTable({ ...tableSelector(args), fields: args.fields, matchCase: args.matchCase, method: args.method })));
2130
+ }
2131
+ function registerPivotTools(mcp) {
2132
+ registerMcpTool(mcp, "excel.pivot.list", {
2133
+ title: "List PivotTables",
2134
+ description: "List PivotTables in the active workbook.",
2135
+ inputSchema: { workbookId: z.string() },
2136
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2137
+ }, async ({ workbookId }) => jsonResult(await runtime.listPivotTables(workbookId)));
2138
+ registerMcpTool(mcp, "excel.pivot.get_info", {
2139
+ title: "Get PivotTable info",
2140
+ description: "Return PivotTable metadata and source details where Office.js exposes them.",
2141
+ inputSchema: pivotSelectorSchema(),
2142
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2143
+ }, async (args) => jsonResult(await runtime.getPivotTableInfo(pivotSelector(args))));
2144
+ registerMcpTool(mcp, "excel.pivot.create", {
2145
+ title: "Create PivotTable",
2146
+ description: "Create a PivotTable from a source range or structured table at a destination cell.",
2147
+ inputSchema: {
2148
+ workbookId: z.string(),
2149
+ pivotTableName: z.string(),
2150
+ sourceSheetName: z.string().optional(),
2151
+ sourceAddress: z.string().optional(),
2152
+ sourceTableName: z.string().optional(),
2153
+ destinationSheetName: z.string(),
2154
+ destinationAddress: z.string(),
2155
+ rowFields: z.array(z.string()).optional(),
2156
+ columnFields: z.array(z.string()).optional(),
2157
+ filterFields: z.array(z.string()).optional(),
2158
+ dataFields: z
2159
+ .array(z.object({
2160
+ sourceFieldName: z.string(),
2161
+ name: z.string().optional(),
2162
+ summarizeBy: z.string().optional(),
2163
+ numberFormat: z.string().optional()
2164
+ }))
2165
+ .optional(),
2166
+ layout: z.record(z.string(), z.unknown()).optional(),
2167
+ refresh: z.boolean().optional()
2168
+ },
2169
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2170
+ }, async (args) => jsonResult(await runtime.createPivotTable(pivotCreateRequest(args))));
2171
+ registerMcpTool(mcp, "excel.pivot.refresh", {
2172
+ title: "Refresh PivotTable",
2173
+ description: "Refresh one PivotTable.",
2174
+ inputSchema: pivotSelectorSchema(),
2175
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2176
+ }, async (args) => jsonResult(await runtime.refreshPivotTable(pivotSelector(args))));
2177
+ registerMcpTool(mcp, "excel.pivot.refresh_all", {
2178
+ title: "Refresh all PivotTables",
2179
+ description: "Refresh all PivotTables in the workbook.",
2180
+ inputSchema: { workbookId: z.string() },
2181
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2182
+ }, async ({ workbookId }) => jsonResult(await runtime.refreshAllPivotTables(workbookId)));
2183
+ registerMcpTool(mcp, "excel.pivot.update_source", {
2184
+ title: "Update PivotTable source",
2185
+ description: "Return current support status for PivotTable source reassignment.",
2186
+ inputSchema: {
2187
+ workbookId: z.string(),
2188
+ pivotTableName: z.string(),
2189
+ sourceSheetName: z.string().optional(),
2190
+ sourceAddress: z.string().optional(),
2191
+ sourceTableName: z.string().optional(),
2192
+ destinationSheetName: z.string(),
2193
+ destinationAddress: z.string()
2194
+ },
2195
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2196
+ }, async (args) => jsonResult(runtime.updatePivotSource(pivotCreateRequest(args))));
2197
+ registerMcpTool(mcp, "excel.pivot.copy_from_template", {
2198
+ title: "Copy PivotTable from template",
2199
+ description: "Replay deterministic PivotTable metadata and layout from a template PivotTable through the transaction-backed add-in path.",
2200
+ inputSchema: {
2201
+ ...pivotSelectorSchema(),
2202
+ templatePivotTableName: z.string(),
2203
+ templateId: z.string().optional(),
2204
+ dimensions: z.array(z.enum(["metadata", "layout", "fields", "dataFields", "numberFormats", "filters", "refresh"])).optional(),
2205
+ strict: z.boolean().optional()
2206
+ },
2207
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2208
+ }, async (args) => {
2209
+ const request = {
2210
+ ...pivotSelector(args),
2211
+ templatePivotTableName: args.templatePivotTableName
2212
+ };
2213
+ if (args.templateId !== undefined) {
2214
+ request.templateId = args.templateId;
2215
+ }
2216
+ if (args.dimensions !== undefined) {
2217
+ request.dimensions = args.dimensions;
2218
+ }
2219
+ if (args.strict !== undefined) {
2220
+ request.strict = args.strict;
2221
+ }
2222
+ return jsonResult(await runtime.copyPivotFromTemplate(request));
2223
+ });
2224
+ registerMcpTool(mcp, "excel.pivot.delete", {
2225
+ title: "Delete PivotTable",
2226
+ description: "Delete a PivotTable through a transaction-backed backup path.",
2227
+ inputSchema: pivotSelectorSchema(),
2228
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2229
+ }, async (args) => jsonResult(await runtime.deletePivotTable(pivotSelector(args))));
2230
+ registerMcpTool(mcp, "excel.pivot.validate_source", {
2231
+ title: "Validate PivotTable source",
2232
+ description: "Validate PivotTable source metadata and optional expected source/layout fields.",
2233
+ inputSchema: {
2234
+ ...pivotSelectorSchema(),
2235
+ expectedFields: z.array(z.string()).optional(),
2236
+ expectedRowFields: z.array(z.string()).optional(),
2237
+ expectedColumnFields: z.array(z.string()).optional(),
2238
+ expectedFilterFields: z.array(z.string()).optional(),
2239
+ expectedDataFields: z.array(z.string()).optional(),
2240
+ expectedDataFieldSettings: z
2241
+ .array(z.object({
2242
+ sourceFieldName: z.string().optional(),
2243
+ name: z.string().optional(),
2244
+ summarizeBy: z.string().optional(),
2245
+ numberFormat: z.string().optional()
2246
+ }))
2247
+ .optional(),
2248
+ expectedLayout: z.record(z.string(), z.unknown()).optional()
2249
+ },
2250
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2251
+ }, async (args) => jsonResult(await runtime.validatePivotSource(pivotValidateSourceRequest(args))));
2252
+ registerMcpTool(mcp, "excel.pivot.get_capability_matrix", {
2253
+ title: "Get PivotTable capability matrix",
2254
+ description: "Return deterministic PivotTable support status for the active Excel host.",
2255
+ inputSchema: { workbookId: z.string().optional() },
2256
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2257
+ }, async ({ workbookId }) => jsonResult(runtime.getPivotCapabilityMatrix(workbookId)));
2258
+ registerMcpTool(mcp, "excel.pivot.get_fingerprint", {
2259
+ title: "Get PivotTable fingerprint",
2260
+ description: "Capture a deterministic PivotTable fingerprint from metadata Office.js exposes.",
2261
+ inputSchema: pivotSelectorSchema(),
2262
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2263
+ }, async (args) => jsonResult(await runtime.getPivotFingerprint(pivotSelector(args))));
2264
+ registerMcpTool(mcp, "excel.pivot.compare_fingerprint", {
2265
+ title: "Compare PivotTable fingerprint",
2266
+ description: "Compare two PivotTable fingerprints and return deterministic differences.",
2267
+ inputSchema: { ...pivotSelectorSchema(), targetPivotTableName: z.string() },
2268
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2269
+ }, async (args) => {
2270
+ const request = {
2271
+ ...pivotSelector(args),
2272
+ targetPivotTableName: args.targetPivotTableName
2273
+ };
2274
+ return jsonResult(await runtime.comparePivotFingerprint(request));
2275
+ });
2276
+ registerMcpTool(mcp, "excel.pivot.diff", {
2277
+ title: "Diff PivotTables",
2278
+ description: "Return a PivotTable diff based on captured fingerprint dimensions.",
2279
+ inputSchema: { ...pivotSelectorSchema(), targetPivotTableName: z.string() },
2280
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2281
+ }, async (args) => {
2282
+ const request = {
2283
+ ...pivotSelector(args),
2284
+ targetPivotTableName: args.targetPivotTableName
2285
+ };
2286
+ return jsonResult(await runtime.diffPivotTables(request));
2287
+ });
2288
+ registerMcpTool(mcp, "excel.pivot.repair_from_template", {
2289
+ title: "Repair PivotTable from template",
2290
+ description: "Repair a target PivotTable by replaying deterministic metadata from a template PivotTable, then diffing the result.",
2291
+ inputSchema: {
2292
+ ...pivotSelectorSchema(),
2293
+ templatePivotTableName: z.string(),
2294
+ templateId: z.string().optional(),
2295
+ dimensions: z.array(z.enum(["metadata", "layout", "fields", "dataFields", "numberFormats", "filters", "refresh"])).optional(),
2296
+ strict: z.boolean().optional()
2297
+ },
2298
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2299
+ }, async (args) => {
2300
+ const request = {
2301
+ ...pivotSelector(args),
2302
+ templatePivotTableName: args.templatePivotTableName
2303
+ };
2304
+ if (args.templateId !== undefined)
2305
+ request.templateId = args.templateId;
2306
+ if (args.dimensions !== undefined)
2307
+ request.dimensions = args.dimensions;
2308
+ if (args.strict !== undefined)
2309
+ request.strict = args.strict;
2310
+ return jsonResult(await runtime.repairPivotFromTemplate(request));
2311
+ });
2312
+ registerMcpTool(mcp, "excel.pivot.rebuild_with_source", {
2313
+ title: "Rebuild PivotTable with source",
2314
+ description: "Create a new PivotTable from the desired source and optionally replay a template PivotTable.",
2315
+ inputSchema: {
2316
+ workbookId: z.string(),
2317
+ pivotTableName: z.string(),
2318
+ sourceSheetName: z.string().optional(),
2319
+ sourceAddress: z.string().optional(),
2320
+ sourceTableName: z.string().optional(),
2321
+ destinationSheetName: z.string(),
2322
+ destinationAddress: z.string(),
2323
+ rowFields: z.array(z.string()).optional(),
2324
+ columnFields: z.array(z.string()).optional(),
2325
+ filterFields: z.array(z.string()).optional(),
2326
+ dataFields: z
2327
+ .array(z.object({
2328
+ sourceFieldName: z.string(),
2329
+ name: z.string().optional(),
2330
+ summarizeBy: z.string().optional(),
2331
+ numberFormat: z.string().optional()
2332
+ }))
2333
+ .optional(),
2334
+ layout: z.record(z.string(), z.unknown()).optional(),
2335
+ refresh: z.boolean().optional(),
2336
+ templatePivotTableName: z.string().optional(),
2337
+ replaceExisting: z.boolean().optional(),
2338
+ strict: z.boolean().optional()
2339
+ },
2340
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2341
+ }, async (args) => {
2342
+ const request = pivotCreateRequest(args);
2343
+ if (args.templatePivotTableName !== undefined)
2344
+ request.templatePivotTableName = args.templatePivotTableName;
2345
+ if (args.replaceExisting !== undefined)
2346
+ request.replaceExisting = args.replaceExisting;
2347
+ if (args.strict !== undefined)
2348
+ request.strict = args.strict;
2349
+ return jsonResult(await runtime.rebuildPivotWithSource(request));
2350
+ });
2351
+ }
2352
+ function registerChartTools(mcp) {
2353
+ registerMcpTool(mcp, "excel.chart.list", {
2354
+ title: "List charts",
2355
+ description: "List charts across worksheets.",
2356
+ inputSchema: { workbookId: z.string() },
2357
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2358
+ }, async ({ workbookId }) => jsonResult(await runtime.listCharts(workbookId)));
2359
+ registerMcpTool(mcp, "excel.chart.get_info", {
2360
+ title: "Get chart info",
2361
+ description: "Return chart metadata.",
2362
+ inputSchema: chartSelectorSchema(),
2363
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2364
+ }, async (args) => jsonResult(await runtime.getChartInfo(chartSelector(args))));
2365
+ registerMcpTool(mcp, "excel.chart.create", {
2366
+ title: "Create chart",
2367
+ description: "Create an Excel chart from a source range.",
2368
+ inputSchema: chartCreateSchema(),
2369
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2370
+ }, async (args) => jsonResult(await runtime.createChart(chartCreateRequest(args))));
2371
+ registerMcpTool(mcp, "excel.chart.update_data_source", {
2372
+ title: "Update chart data source",
2373
+ description: "Reset a chart data source to a new range.",
2374
+ inputSchema: { ...chartSelectorSchema(), sourceAddress: z.string(), seriesBy: z.enum(["Auto", "Columns", "Rows"]).optional() },
2375
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2376
+ }, async (args) => jsonResult(await runtime.updateChartDataSource(chartUpdateDataSourceRequest(args))));
2377
+ registerMcpTool(mcp, "excel.chart.copy_from_template", {
2378
+ title: "Copy chart from template",
2379
+ description: "Copy deterministic chart metadata from a template chart to a target chart.",
2380
+ inputSchema: { ...chartSelectorSchema(), templateSheetName: z.string(), templateChartName: z.string() },
2381
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2382
+ }, async (args) => jsonResult(await runtime.copyChartFromTemplate({
2383
+ ...chartSelector(args),
2384
+ templateSheetName: args.templateSheetName,
2385
+ templateChartName: args.templateChartName
2386
+ })));
2387
+ registerMcpTool(mcp, "excel.chart.refresh", {
2388
+ title: "Refresh chart",
2389
+ description: "Return current chart metadata; charts update from their source data through Excel.",
2390
+ inputSchema: chartSelectorSchema(),
2391
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2392
+ }, async (args) => jsonResult(await runtime.refreshChart(chartSelector(args))));
2393
+ registerMcpTool(mcp, "excel.chart.delete", {
2394
+ title: "Delete chart",
2395
+ description: "Delete a chart from a worksheet.",
2396
+ inputSchema: chartSelectorSchema(),
2397
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2398
+ }, async (args) => jsonResult(await runtime.deleteChart(chartSelector(args))));
2399
+ registerMcpTool(mcp, "excel.chart.validate_against_template", {
2400
+ title: "Validate chart against template",
2401
+ description: "Validate target and template chart metadata availability.",
2402
+ inputSchema: { ...chartSelectorSchema(), templateSheetName: z.string().optional(), templateChartName: z.string().optional() },
2403
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2404
+ }, async (args) => jsonResult(await runtime.validateChartAgainstTemplate(chartTemplateValidationRequest(args))));
2405
+ }
2406
+ function registerNamesTools(mcp) {
2407
+ registerMcpTool(mcp, "excel.names.list", {
2408
+ title: "List named items",
2409
+ description: "List workbook and worksheet scoped Excel named items.",
2410
+ inputSchema: { workbookId: z.string() },
2411
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2412
+ }, async ({ workbookId }) => jsonResult(await runtime.listNames(workbookId)));
2413
+ registerMcpTool(mcp, "excel.names.get", {
2414
+ title: "Get named item",
2415
+ description: "Get one workbook or worksheet scoped Excel named item.",
2416
+ inputSchema: nameSelectorSchema(),
2417
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2418
+ }, async (args) => jsonResult(await runtime.getName(nameSelector(args))));
2419
+ registerMcpTool(mcp, "excel.names.create", {
2420
+ title: "Create named item",
2421
+ description: "Create a workbook or worksheet scoped Excel name for a formula or range reference.",
2422
+ inputSchema: nameMutationSchema(),
2423
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2424
+ }, async (args) => jsonResult(await runtime.createName(nameCreateRequest(args))));
2425
+ registerMcpTool(mcp, "excel.names.update", {
2426
+ title: "Update named item",
2427
+ description: "Update a named item's formula/reference, comment, or visibility.",
2428
+ inputSchema: nameMutationSchema(),
2429
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2430
+ }, async (args) => jsonResult(await runtime.updateName(nameUpdateRequest(args))));
2431
+ registerMcpTool(mcp, "excel.names.delete", {
2432
+ title: "Delete named item",
2433
+ description: "Delete a workbook or worksheet scoped named item.",
2434
+ inputSchema: nameSelectorSchema(),
2435
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2436
+ }, async (args) => jsonResult(await runtime.deleteName(nameSelector(args))));
2437
+ }
2438
+ function registerRegionTools(mcp) {
2439
+ registerMcpTool(mcp, "excel.region.detect", {
2440
+ title: "Detect workbook regions",
2441
+ description: "Return registered regions plus named-range and used-range region candidates.",
2442
+ inputSchema: { workbookId: z.string() },
2443
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2444
+ }, async ({ workbookId }) => jsonResult(await runtime.detectRegions(workbookId)));
2445
+ registerMcpTool(mcp, "excel.region.register", {
2446
+ title: "Register workbook region",
2447
+ description: "Register a reusable workbook region, optionally creating a matching Excel named range.",
2448
+ inputSchema: {
2449
+ workbookId: z.string(),
2450
+ name: z.string(),
2451
+ sheetName: z.string(),
2452
+ address: z.string(),
2453
+ kind: z.enum(["data", "header", "formula", "output", "template", "table", "named-range", "other"]).optional(),
2454
+ description: z.string().optional(),
2455
+ templateId: z.string().optional(),
2456
+ createNamedRange: z.boolean().optional()
2457
+ },
2458
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2459
+ }, async (args) => jsonResult(await runtime.registerRegion(regionRegisterRequest(args))));
2460
+ registerMcpTool(mcp, "excel.region.list", {
2461
+ title: "List workbook regions",
2462
+ description: "List registered workbook regions.",
2463
+ inputSchema: { workbookId: z.string() },
2464
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2465
+ }, async ({ workbookId }) => jsonResult(runtime.listRegions(workbookId)));
2466
+ registerMcpTool(mcp, "excel.region.get", {
2467
+ title: "Get workbook region",
2468
+ description: "Get a registered region or resolve an Excel named range as a region.",
2469
+ inputSchema: regionSelectorSchema(),
2470
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2471
+ }, async (args) => jsonResult(await runtime.getRegion(regionSelector(args))));
2472
+ registerMcpTool(mcp, "excel.region.clear_values", {
2473
+ title: "Clear region values",
2474
+ description: "Clear values in a registered region while preserving formats.",
2475
+ inputSchema: regionSelectorSchema(),
2476
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2477
+ }, async (args) => jsonResult(await runtime.clearRegionValues(regionSelector(args))));
2478
+ registerMcpTool(mcp, "excel.region.write_values", {
2479
+ title: "Write region values",
2480
+ description: "Write values to a registered region while preserving formats.",
2481
+ inputSchema: { ...regionSelectorSchema(), values: z.array(z.array(z.any())) },
2482
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2483
+ }, async (args) => jsonResult(await runtime.writeRegionValues({ ...regionSelector(args), values: args.values })));
2484
+ registerMcpTool(mcp, "excel.region.fill", {
2485
+ title: "Fill workbook region",
2486
+ description: "Optionally clear then write values to a registered region while preserving formats.",
2487
+ inputSchema: { ...regionSelectorSchema(), values: z.array(z.array(z.any())), clearFirst: z.boolean().optional() },
2488
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2489
+ }, async (args) => {
2490
+ const request = { ...regionSelector(args), values: args.values };
2491
+ if (args.clearFirst !== undefined) {
2492
+ request.clearFirst = args.clearFirst;
2493
+ }
2494
+ return jsonResult(await runtime.fillRegion(request));
2495
+ });
2496
+ }
2497
+ function registerTaskTools(mcp) {
2498
+ registerMcpTool(mcp, "excel.task.create", {
2499
+ title: "Create Excel task",
2500
+ description: "Create a multi-agent workbook task with optional scope and assigned agent.",
2501
+ inputSchema: {
2502
+ workbookId: z.string(),
2503
+ goal: z.string(),
2504
+ role: z.string().optional(),
2505
+ priority: z.enum(["low", "normal", "high"]).optional(),
2506
+ assignedAgentId: z.string().optional(),
2507
+ allowedScopes: z.array(z.any()).optional(),
2508
+ dependencies: z.array(z.string()).optional()
2509
+ },
2510
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2511
+ }, async (args) => jsonResult(runtime.createTask({
2512
+ workbookId: args.workbookId,
2513
+ goal: args.goal,
2514
+ role: args.role,
2515
+ priority: args.priority,
2516
+ assignedAgentId: args.assignedAgentId,
2517
+ allowedScopes: args.allowedScopes,
2518
+ dependencies: args.dependencies
2519
+ })));
2520
+ registerMcpTool(mcp, "excel.task.claim", {
2521
+ title: "Claim Excel task",
2522
+ description: "Assign a task to an agent.",
2523
+ inputSchema: { taskId: z.string(), agentId: z.string() },
2524
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2525
+ }, async ({ taskId, agentId }) => jsonResult(runtime.claimTask(taskId, agentId)));
2526
+ registerMcpTool(mcp, "excel.task.update", {
2527
+ title: "Update Excel task",
2528
+ description: "Update task metadata, status, scope, or assignment.",
2529
+ inputSchema: {
2530
+ taskId: z.string(),
2531
+ goal: z.string().optional(),
2532
+ role: z.string().optional(),
2533
+ priority: z.enum(["low", "normal", "high"]).optional(),
2534
+ status: z.enum(["open", "claimed", "planning", "queued", "applying", "blocked", "completed", "failed", "cancelled"]).optional(),
2535
+ progress: z.number().min(0).max(100).optional(),
2536
+ currentStep: z.string().optional(),
2537
+ blockers: z.array(z.any()).optional(),
2538
+ assignedAgentId: z.string().optional(),
2539
+ allowedScopes: z.array(z.any()).optional(),
2540
+ dependencies: z.array(z.string()).optional(),
2541
+ errorMessage: z.string().optional()
2542
+ },
2543
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2544
+ }, async (args) => {
2545
+ const { taskId, ...patch } = args;
2546
+ return jsonResult(runtime.updateTask(taskId, patch));
2547
+ });
2548
+ registerMcpTool(mcp, "excel.task.set_progress", {
2549
+ title: "Set Excel task progress",
2550
+ description: "Update task progress and the current step shown in collaboration status.",
2551
+ inputSchema: {
2552
+ taskId: z.string(),
2553
+ progress: z.number().min(0).max(100),
2554
+ currentStep: z.string().optional()
2555
+ },
2556
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2557
+ }, async ({ taskId, progress, currentStep }) => jsonResult(runtime.setTaskProgress(taskId, progress, currentStep)));
2558
+ registerMcpTool(mcp, "excel.task.add_blocker", {
2559
+ title: "Add Excel task blocker",
2560
+ description: "Add an open blocker, warning, or informational note to a task.",
2561
+ inputSchema: {
2562
+ taskId: z.string(),
2563
+ severity: z.enum(["info", "warning", "blocked"]),
2564
+ message: z.string(),
2565
+ scope: z.any().optional()
2566
+ },
2567
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2568
+ }, async (args) => jsonResult(runtime.addTaskBlocker(args.taskId, {
2569
+ severity: args.severity,
2570
+ message: args.message,
2571
+ scope: args.scope
2572
+ })));
2573
+ registerMcpTool(mcp, "excel.task.resolve_blocker", {
2574
+ title: "Resolve Excel task blocker",
2575
+ description: "Mark a task blocker as resolved.",
2576
+ inputSchema: {
2577
+ taskId: z.string(),
2578
+ blockerId: z.string()
2579
+ },
2580
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2581
+ }, async ({ taskId, blockerId }) => jsonResult(runtime.resolveTaskBlocker(taskId, blockerId)));
2582
+ registerMcpTool(mcp, "excel.task.evaluate_schedule", {
2583
+ title: "Evaluate Excel task schedule",
2584
+ description: "Evaluate task readiness against dependencies, blockers, and active locks.",
2585
+ inputSchema: {
2586
+ workbookId: z.string().optional(),
2587
+ apply: z.boolean().optional(),
2588
+ lockMode: z.enum(["read", "write_values", "write_formulas", "write_styles", "format_layout", "table", "chart", "pivot", "structure", "workbook"]).optional()
2589
+ },
2590
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2591
+ }, async (args) => jsonResult(runtime.evaluateTaskSchedule({
2592
+ workbookId: args.workbookId,
2593
+ apply: args.apply,
2594
+ lockMode: args.lockMode
2595
+ })));
2596
+ registerMcpTool(mcp, "excel.task.resume_ready", {
2597
+ title: "Resume ready Excel tasks",
2598
+ description: "Apply scheduler decisions so blocked tasks with cleared dependencies can resume.",
2599
+ inputSchema: {
2600
+ workbookId: z.string().optional()
2601
+ },
2602
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2603
+ }, async ({ workbookId }) => jsonResult(runtime.resumeReadyTasks(workbookId)));
2604
+ for (const [name, status] of [
2605
+ ["excel.task.complete", "completed"],
2606
+ ["excel.task.fail", "failed"],
2607
+ ["excel.task.cancel", "cancelled"]
2608
+ ]) {
2609
+ registerMcpTool(mcp, name, {
2610
+ title: name.replace(/^excel\./, ""),
2611
+ description: `Mark a task as ${status}.`,
2612
+ inputSchema: { taskId: z.string(), errorMessage: z.string().optional() },
2613
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2614
+ }, async ({ taskId, errorMessage }) => jsonResult(runtime.updateTask(taskId, status === "failed" ? { status, errorMessage } : { status })));
2615
+ }
2616
+ registerMcpTool(mcp, "excel.task.list", {
2617
+ title: "List Excel tasks",
2618
+ description: "List multi-agent workbook tasks.",
2619
+ inputSchema: { workbookId: z.string().optional() },
2620
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2621
+ }, async ({ workbookId }) => jsonResult(runtime.listTasks(workbookId)));
2622
+ registerMcpTool(mcp, "excel.task.get", {
2623
+ title: "Get Excel task",
2624
+ description: "Return a task by ID.",
2625
+ inputSchema: { taskId: z.string() },
2626
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2627
+ }, async ({ taskId }) => jsonResult(runtime.getTask(taskId)));
2628
+ }
2629
+ function registerCollaborationTools(mcp) {
2630
+ registerMcpTool(mcp, "excel.collab.get_status", {
2631
+ title: "Get collaboration status",
2632
+ description: "Return agents, tasks, locks, transactions, conflicts, and recent collaboration events.",
2633
+ inputSchema: { workbookId: z.string().optional() },
2634
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2635
+ }, async ({ workbookId }) => jsonResult(runtime.getCollaborationStatus(workbookId)));
2636
+ for (const [name, method] of [
2637
+ ["excel.collab.list_agents", "listAgents"],
2638
+ ["excel.collab.list_tasks", "listTasks"],
2639
+ ["excel.collab.list_locks", "listLocks"],
2640
+ ["excel.collab.list_transactions", "listTransactions"],
2641
+ ["excel.collab.get_conflicts", "listConflicts"]
2642
+ ]) {
2643
+ registerMcpTool(mcp, name, {
2644
+ title: name.replace(/^excel\./, ""),
2645
+ description: "Return collaboration runtime state.",
2646
+ inputSchema: { workbookId: z.string().optional() },
2647
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2648
+ }, async ({ workbookId }) => jsonResult(runtime[method](workbookId)));
2649
+ }
2650
+ registerMcpTool(mcp, "excel.collab.get_recent_events", {
2651
+ title: "Get recent collaboration events",
2652
+ description: "Return recent collaboration events from the shared runtime.",
2653
+ inputSchema: { workbookId: z.string().optional() },
2654
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2655
+ }, async ({ workbookId }) => jsonResult(runtime.getCollaborationStatus(workbookId).events));
2656
+ }
2657
+ function registerLockTools(mcp) {
2658
+ registerMcpTool(mcp, "excel.lock.get_policy", {
2659
+ title: "Get lock lease policy",
2660
+ description: "Return runtime lock TTL and manual-lock policy.",
2661
+ inputSchema: {},
2662
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2663
+ }, async () => jsonResult(runtime.getLockPolicy()));
2664
+ registerMcpTool(mcp, "excel.lock.set_policy", {
2665
+ title: "Set lock lease policy",
2666
+ description: "Update runtime lock TTL and manual-lock policy.",
2667
+ inputSchema: {
2668
+ defaultTtlMs: z.number().int().positive().optional(),
2669
+ transactionTtlMs: z.number().int().positive().optional(),
2670
+ maxTtlMs: z.number().int().positive().optional(),
2671
+ allowManualLocks: z.boolean().optional()
2672
+ },
2673
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2674
+ }, async (args) => jsonResult(runtime.setLockPolicy(args)));
2675
+ registerMcpTool(mcp, "excel.lock.acquire", {
2676
+ title: "Acquire Excel lock",
2677
+ description: "Acquire explicit workbook/sheet/range/object locks for multi-agent planning or guarded work.",
2678
+ inputSchema: {
2679
+ workbookId: z.string(),
2680
+ scopes: z.array(z.any()),
2681
+ mode: z.enum(["read", "write_values", "write_formulas", "write_styles", "format_layout", "table", "chart", "pivot", "structure", "workbook"]),
2682
+ reason: z.string(),
2683
+ ownerAgentId: z.string().optional(),
2684
+ taskId: z.string().optional(),
2685
+ ttlMs: z.number().int().positive().optional()
2686
+ },
2687
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2688
+ }, async (args) => jsonResult(runtime.acquireLocks({
2689
+ workbookId: args.workbookId,
2690
+ scopes: args.scopes,
2691
+ mode: args.mode,
2692
+ reason: args.reason,
2693
+ ownerAgentId: args.ownerAgentId,
2694
+ taskId: args.taskId,
2695
+ ttlMs: args.ttlMs
2696
+ })));
2697
+ registerMcpTool(mcp, "excel.lock.renew", {
2698
+ title: "Renew Excel locks",
2699
+ description: "Extend active lock leases up to the runtime max TTL.",
2700
+ inputSchema: {
2701
+ lockIds: z.array(z.string()),
2702
+ ttlMs: z.number().int().positive().optional()
2703
+ },
2704
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2705
+ }, async ({ lockIds, ttlMs }) => jsonResult(runtime.renewLocks(lockIds, ttlMs)));
2706
+ registerMcpTool(mcp, "excel.lock.release", {
2707
+ title: "Release Excel locks",
2708
+ description: "Release active lock leases.",
2709
+ inputSchema: {
2710
+ lockIds: z.array(z.string())
2711
+ },
2712
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2713
+ }, async ({ lockIds }) => jsonResult(runtime.releaseLocks(lockIds)));
2714
+ }
2715
+ function registerConflictTools(mcp) {
2716
+ registerMcpTool(mcp, "excel.conflict.get_guidance", {
2717
+ title: "Get conflict guidance",
2718
+ description: "Return actionable conflict-resolution guidance for recent runtime conflicts.",
2719
+ inputSchema: { workbookId: z.string().optional() },
2720
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2721
+ }, async ({ workbookId }) => jsonResult(runtime.getConflictGuidance(workbookId)));
2722
+ registerMcpTool(mcp, "excel.conflict.explain", {
2723
+ title: "Explain conflict",
2724
+ description: "Return actionable resolution guidance for a supplied conflict record.",
2725
+ inputSchema: {
2726
+ conflict: z.any()
2727
+ },
2728
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2729
+ }, async ({ conflict }) => jsonResult(runtime.explainConflict(conflict)));
2730
+ registerMcpTool(mcp, "excel.conflict.get_telemetry", {
2731
+ title: "Get conflict telemetry",
2732
+ description: "Summarize repeated contention, hot scopes, tasks, agents, and wait outcomes.",
2733
+ inputSchema: {
2734
+ workbookId: z.string().optional(),
2735
+ windowSize: z.number().int().positive().optional()
2736
+ },
2737
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2738
+ }, async ({ workbookId, windowSize }) => jsonResult(runtime.getConflictTelemetry(workbookId, windowSize)));
2739
+ registerMcpTool(mcp, "excel.conflict.clear_telemetry", {
2740
+ title: "Clear conflict telemetry",
2741
+ description: "Clear conflict telemetry for one workbook or the whole runtime.",
2742
+ inputSchema: {
2743
+ workbookId: z.string().optional()
2744
+ },
2745
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2746
+ }, async ({ workbookId }) => jsonResult(runtime.clearConflictTelemetry(workbookId)));
2747
+ }
2748
+ function registerTransactionTools(mcp) {
2749
+ registerMcpTool(mcp, "excel.transaction.list", {
2750
+ title: "List Excel transactions",
2751
+ description: "List serialized workbook transactions.",
2752
+ inputSchema: { workbookId: z.string().optional() },
2753
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2754
+ }, async ({ workbookId }) => jsonResult(runtime.listTransactions(workbookId)));
2755
+ registerMcpTool(mcp, "excel.transaction.get", {
2756
+ title: "Get Excel transaction",
2757
+ description: "Return one serialized workbook transaction.",
2758
+ inputSchema: { transactionId: z.string() },
2759
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2760
+ }, async ({ transactionId }) => jsonResult(runtime.getTransaction(transactionId)));
2761
+ registerMcpTool(mcp, "excel.transaction.preview_rollback", {
2762
+ title: "Preview transaction rollback",
2763
+ description: "Check whether a transaction can be rolled back without overwriting later work.",
2764
+ inputSchema: { transactionId: z.string() },
2765
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2766
+ }, async ({ transactionId }) => jsonResult(runtime.previewTransactionRollback(transactionId)));
2767
+ registerMcpTool(mcp, "excel.transaction.rollback", {
2768
+ title: "Rollback transaction",
2769
+ description: "Rollback a transaction only when rollback preview has no later-overlap conflicts.",
2770
+ inputSchema: { transactionId: z.string(), confirmationToken: z.string().optional() },
2771
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2772
+ }, async ({ transactionId, confirmationToken }) => jsonResult(await runtime.rollbackTransaction(transactionId, confirmationToken)));
2773
+ registerMcpTool(mcp, "excel.transaction.preview_rollback_chain", {
2774
+ title: "Preview transaction rollback chain",
2775
+ description: "Find later dependent transactions that must be rolled back newest-first with the target transaction.",
2776
+ inputSchema: { transactionId: z.string() },
2777
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2778
+ }, async ({ transactionId }) => jsonResult(runtime.previewTransactionRollbackChain(transactionId)));
2779
+ registerMcpTool(mcp, "excel.transaction.rollback_chain", {
2780
+ title: "Rollback transaction chain",
2781
+ description: "Rollback a confirmed related transaction chain newest-first.",
2782
+ inputSchema: { transactionId: z.string(), confirmationToken: z.string().optional() },
2783
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2784
+ }, async ({ transactionId, confirmationToken }) => jsonResult(await runtime.rollbackTransactionChain(transactionId, confirmationToken)));
2785
+ }
2786
+ function registerPermissionsTools(mcp) {
2787
+ registerMcpTool(mcp, "excel.permissions.get", {
2788
+ title: "Get permissions",
2789
+ description: "Return current Open Workbook permission policy, scope, and locked regions.",
2790
+ inputSchema: {},
2791
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2792
+ }, async () => jsonResult(runtime.getPermissions()));
2793
+ registerMcpTool(mcp, "excel.permissions.set", {
2794
+ title: "Set permissions",
2795
+ description: "Update Open Workbook permission policy.",
2796
+ inputSchema: permissionSetSchema(),
2797
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2798
+ }, async (args) => jsonResult(runtime.setPermissions(permissionUpdate(args))));
2799
+ registerMcpTool(mcp, "excel.permissions.require_confirmation", {
2800
+ title: "Require confirmation",
2801
+ description: "Set destructive levels that require a confirmation token before apply.",
2802
+ inputSchema: { levels: z.array(z.enum(["none", "values", "format", "structure", "workbook"])) },
2803
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2804
+ }, async ({ levels }) => jsonResult(runtime.requireConfirmation(levels)));
2805
+ registerMcpTool(mcp, "excel.permissions.set_scope", {
2806
+ title: "Set permission scope",
2807
+ description: "Restrict mutations to a workbook, sheet names, or registered region names.",
2808
+ inputSchema: {
2809
+ workbookId: z.string().optional(),
2810
+ sheetNames: z.array(z.string()).optional(),
2811
+ regionNames: z.array(z.string()).optional()
2812
+ },
2813
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2814
+ }, async (args) => jsonResult(runtime.setPermissionScope(permissionScope(args))));
2815
+ registerMcpTool(mcp, "excel.permissions.allow_destructive_actions", {
2816
+ title: "Allow destructive actions",
2817
+ description: "Allow or block structure/workbook destructive actions.",
2818
+ inputSchema: { allow: z.boolean() },
2819
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2820
+ }, async ({ allow }) => jsonResult(runtime.allowDestructiveActions(allow)));
2821
+ registerMcpTool(mcp, "excel.permissions.allow_macro_execution", {
2822
+ title: "Allow macro execution",
2823
+ description: "Record macro execution permission. Macro execution is not implemented by the Office.js runtime.",
2824
+ inputSchema: { allow: z.boolean() },
2825
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2826
+ }, async ({ allow }) => jsonResult(runtime.allowMacroExecution(allow)));
2827
+ registerMcpTool(mcp, "excel.permissions.lock_regions", {
2828
+ title: "Lock regions",
2829
+ description: "Block future writes that overlap registered regions.",
2830
+ inputSchema: {
2831
+ workbookId: z.string(),
2832
+ regions: z.array(z.object({ regionName: z.string(), reason: z.string().optional() }))
2833
+ },
2834
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2835
+ }, async (args) => jsonResult(await runtime.lockRegions({ workbookId: args.workbookId, regions: args.regions })));
2836
+ registerMcpTool(mcp, "excel.permissions.unlock_regions", {
2837
+ title: "Unlock regions",
2838
+ description: "Unlock all or selected locked regions for a workbook.",
2839
+ inputSchema: { workbookId: z.string(), regionNames: z.array(z.string()).optional() },
2840
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2841
+ }, async (args) => {
2842
+ const request = { workbookId: args.workbookId };
2843
+ if (args.regionNames !== undefined) {
2844
+ request.regionNames = args.regionNames;
2845
+ }
2846
+ return jsonResult(runtime.unlockRegions(request));
2847
+ });
2848
+ }
2849
+ function registerCleanTools(mcp) {
2850
+ const rangeSchema = cleanRangeSchema();
2851
+ registerMcpTool(mcp, "excel.clean.detect_header_row", {
2852
+ title: "Detect header row",
2853
+ description: "Score likely header rows in a range without mutating the workbook.",
2854
+ inputSchema: { ...rangeSchema, maxRows: z.number().int().min(1).max(100).optional() },
2855
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2856
+ }, async (args) => jsonResult(await runtime.cleanDetectHeaderRow(cleanRangeArgs(args, { maxRows: args.maxRows }))));
2857
+ registerMcpTool(mcp, "excel.clean.normalize_headers", {
2858
+ title: "Normalize headers",
2859
+ description: "Normalize a header row to lowercase snake_case and deduplicate names.",
2860
+ inputSchema: { ...rangeSchema, headerRowIndex: z.number().int().min(0).optional() },
2861
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2862
+ }, async (args) => jsonResult(await runtime.cleanNormalizeHeaders(cleanRangeArgs(args, { headerRowIndex: args.headerRowIndex }))));
2863
+ registerMcpTool(mcp, "excel.clean.trim_whitespace", {
2864
+ title: "Trim whitespace",
2865
+ description: "Trim and collapse whitespace in string cells.",
2866
+ inputSchema: rangeSchema,
2867
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2868
+ }, async (args) => jsonResult(await runtime.cleanTrimWhitespace(cleanRangeArgs(args))));
2869
+ registerMcpTool(mcp, "excel.clean.remove_duplicates", {
2870
+ title: "Remove duplicate rows",
2871
+ description: "Compact duplicate rows in-place and blank removed rows to preserve range shape.",
2872
+ inputSchema: { ...rangeSchema, hasHeader: z.boolean().optional(), keyColumns: z.array(z.number().int().min(0)).optional() },
2873
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2874
+ }, async (args) => jsonResult(await runtime.cleanRemoveDuplicates(cleanRangeArgs(args, { hasHeader: args.hasHeader, keyColumns: args.keyColumns }))));
2875
+ for (const [name, method] of [
2876
+ ["excel.clean.parse_dates", "cleanParseDates"],
2877
+ ["excel.clean.parse_numbers", "cleanParseNumbers"],
2878
+ ["excel.clean.standardize_currency", "cleanStandardizeCurrency"]
2879
+ ]) {
2880
+ registerMcpTool(mcp, name, {
2881
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
2882
+ description: "Parse and standardize cell values in a range.",
2883
+ inputSchema: rangeSchema,
2884
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2885
+ }, async (args) => jsonResult(await runtime[method](cleanRangeArgs(args))));
2886
+ }
2887
+ registerMcpTool(mcp, "excel.clean.fill_missing_values", {
2888
+ title: "Fill missing values",
2889
+ description: "Fill blank cells using a fixed value, zero, previous value, or next value.",
2890
+ inputSchema: { ...rangeSchema, strategy: z.enum(["value", "zero", "previous", "next"]).optional(), value: z.any().optional() },
2891
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2892
+ }, async (args) => jsonResult(await runtime.cleanFillMissingValues(cleanRangeArgs(args, { strategy: args.strategy, value: args.value }))));
2893
+ registerMcpTool(mcp, "excel.clean.split_column", {
2894
+ title: "Split column",
2895
+ description: "Split one source column by delimiter and write the output to a target range.",
2896
+ inputSchema: { ...rangeSchema, columnIndex: z.number().int().min(0), delimiter: z.string().optional(), targetAddress: z.string() },
2897
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2898
+ }, async (args) => jsonResult(await runtime.cleanSplitColumn(cleanRangeArgs(args, { columnIndex: args.columnIndex, delimiter: args.delimiter, targetAddress: args.targetAddress }))));
2899
+ registerMcpTool(mcp, "excel.clean.merge_columns", {
2900
+ title: "Merge columns",
2901
+ description: "Merge selected source columns and write the output to a target range.",
2902
+ inputSchema: { ...rangeSchema, columnIndexes: z.array(z.number().int().min(0)), separator: z.string().optional(), targetAddress: z.string() },
2903
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2904
+ }, async (args) => jsonResult(await runtime.cleanMergeColumns(cleanRangeArgs(args, { columnIndexes: args.columnIndexes, separator: args.separator, targetAddress: args.targetAddress }))));
2905
+ registerMcpTool(mcp, "excel.clean.detect_outliers", {
2906
+ title: "Detect outliers",
2907
+ description: "Detect numeric outliers using z-score threshold without mutating the workbook.",
2908
+ inputSchema: { ...rangeSchema, columnIndex: z.number().int().min(0).optional(), threshold: z.number().positive().optional() },
2909
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2910
+ }, async (args) => jsonResult(await runtime.cleanDetectOutliers(cleanRangeArgs(args, { columnIndex: args.columnIndex, threshold: args.threshold }))));
2911
+ registerMcpTool(mcp, "excel.clean.fuzzy_match", {
2912
+ title: "Fuzzy match",
2913
+ description: "Compare cell text to lookup values and return similarity matches without mutating the workbook.",
2914
+ inputSchema: { ...rangeSchema, lookupValues: z.array(z.string()), threshold: z.number().min(0).max(1).optional() },
2915
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2916
+ }, async (args) => jsonResult(await runtime.cleanFuzzyMatch(cleanRangeArgs(args, { lookupValues: args.lookupValues, threshold: args.threshold }))));
2917
+ }
2918
+ function registerValidateTools(mcp) {
2919
+ const validationRangeSchema = {
2920
+ workbookId: z.string(),
2921
+ sheetName: z.string().optional(),
2922
+ address: z.string().optional()
2923
+ };
2924
+ registerMcpTool(mcp, "excel.validate.workbook", {
2925
+ title: "Validate workbook",
2926
+ description: "Run workbook-level health checks, including map availability and formula-error scanning over used ranges.",
2927
+ inputSchema: { workbookId: z.string() },
2928
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2929
+ }, async ({ workbookId }) => jsonResult(await runtime.validateWorkbook({ workbookId: workbookId })));
2930
+ registerMcpTool(mcp, "excel.validate.sheet", {
2931
+ title: "Validate sheet",
2932
+ description: "Validate one worksheet's used range and formula-error state.",
2933
+ inputSchema: { workbookId: z.string(), sheetName: z.string() },
2934
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2935
+ }, async ({ workbookId, sheetName }) => jsonResult(await runtime.validateSheet({ workbookId: workbookId, sheetName })));
2936
+ registerMcpTool(mcp, "excel.validate.template_consistency", {
2937
+ title: "Validate template consistency",
2938
+ description: "Compare a target sheet against a registered template fingerprint.",
2939
+ inputSchema: { workbookId: z.string(), templateId: z.string(), targetSheetName: z.string() },
2940
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2941
+ }, async (args) => jsonResult(await runtime.validateTemplateConsistency({
2942
+ workbookId: args.workbookId,
2943
+ templateId: args.templateId,
2944
+ targetSheetName: args.targetSheetName
2945
+ })));
2946
+ registerMcpTool(mcp, "excel.validate.formulas", {
2947
+ title: "Validate formulas",
2948
+ description: "Find formula errors in a workbook, sheet, or explicit range.",
2949
+ inputSchema: validationRangeSchema,
2950
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2951
+ }, async (args) => jsonResult(await runtime.validateFormulas(validationRangeArgs(args))));
2952
+ registerMcpTool(mcp, "excel.validate.styles", {
2953
+ title: "Validate styles",
2954
+ description: "Capture style fingerprint data or compare styles against a registered template.",
2955
+ inputSchema: {
2956
+ workbookId: z.string(),
2957
+ sheetName: z.string().optional(),
2958
+ templateId: z.string().optional(),
2959
+ targetSheetName: z.string().optional()
2960
+ },
2961
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2962
+ }, async (args) => {
2963
+ const request = {
2964
+ workbookId: args.workbookId
2965
+ };
2966
+ if (args.sheetName !== undefined) {
2967
+ request.sheetName = args.sheetName;
2968
+ }
2969
+ if (args.templateId !== undefined) {
2970
+ request.templateId = args.templateId;
2971
+ }
2972
+ if (args.targetSheetName !== undefined) {
2973
+ request.targetSheetName = args.targetSheetName;
2974
+ }
2975
+ return jsonResult(await runtime.validateStyles(request));
2976
+ });
2977
+ registerMcpTool(mcp, "excel.validate.tables", {
2978
+ title: "Validate tables",
2979
+ description: "Inspect structured table metadata, optionally against a registered template.",
2980
+ inputSchema: { workbookId: z.string(), tableName: z.string().optional(), templateId: z.string().optional() },
2981
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2982
+ }, async (args) => {
2983
+ const request = {
2984
+ workbookId: args.workbookId
2985
+ };
2986
+ if (args.tableName !== undefined) {
2987
+ request.tableName = args.tableName;
2988
+ }
2989
+ if (args.templateId !== undefined) {
2990
+ request.templateId = args.templateId;
2991
+ }
2992
+ return jsonResult(await runtime.validateTables(request));
2993
+ });
2994
+ registerMcpTool(mcp, "excel.validate.filters", {
2995
+ title: "Validate filters",
2996
+ description: "Inspect current table filter metadata for one table or the workbook table list.",
2997
+ inputSchema: { workbookId: z.string(), tableName: z.string().optional() },
2998
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
2999
+ }, async (args) => {
3000
+ const request = { workbookId: args.workbookId };
3001
+ if (args.tableName !== undefined) {
3002
+ request.tableName = args.tableName;
3003
+ }
3004
+ return jsonResult(await runtime.validateFilters(request));
3005
+ });
3006
+ registerMcpTool(mcp, "excel.validate.print_layout", {
3007
+ title: "Validate print layout",
3008
+ description: "Return print-layout validation status and current capability limitations.",
3009
+ inputSchema: { workbookId: z.string(), templateId: z.string().optional(), targetSheetName: z.string().optional() },
3010
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3011
+ }, async (args) => {
3012
+ const request = {
3013
+ workbookId: args.workbookId
3014
+ };
3015
+ if (args.templateId !== undefined) {
3016
+ request.templateId = args.templateId;
3017
+ }
3018
+ if (args.targetSheetName !== undefined) {
3019
+ request.targetSheetName = args.targetSheetName;
3020
+ }
3021
+ return jsonResult(runtime.validatePrintLayout(request));
3022
+ });
3023
+ registerMcpTool(mcp, "excel.validate.no_broken_references", {
3024
+ title: "Validate no broken references",
3025
+ description: "Search used ranges for #REF! broken-reference markers.",
3026
+ inputSchema: validationRangeSchema,
3027
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3028
+ }, async (args) => jsonResult(await runtime.validateNoBrokenReferences(validationRangeArgs(args))));
3029
+ registerMcpTool(mcp, "excel.validate.no_formula_errors", {
3030
+ title: "Validate no formula errors",
3031
+ description: "Assert that formula-error cells are absent from a workbook, sheet, or explicit range.",
3032
+ inputSchema: validationRangeSchema,
3033
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3034
+ }, async (args) => jsonResult(await runtime.validateNoFormulaErrors(validationRangeArgs(args))));
3035
+ registerMcpTool(mcp, "excel.validate.no_unintended_changes", {
3036
+ title: "Validate no unintended changes",
3037
+ description: "Compare snapshots or detect changes since a snapshot.",
3038
+ inputSchema: {
3039
+ workbookId: z.string(),
3040
+ snapshotId: z.string().optional(),
3041
+ leftSnapshotId: z.string().optional(),
3042
+ rightSnapshotId: z.string().optional()
3043
+ },
3044
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3045
+ }, async (args) => {
3046
+ const request = {
3047
+ workbookId: args.workbookId
3048
+ };
3049
+ if (args.snapshotId !== undefined) {
3050
+ request.snapshotId = args.snapshotId;
3051
+ }
3052
+ if (args.leftSnapshotId !== undefined) {
3053
+ request.leftSnapshotId = args.leftSnapshotId;
3054
+ }
3055
+ if (args.rightSnapshotId !== undefined) {
3056
+ request.rightSnapshotId = args.rightSnapshotId;
3057
+ }
3058
+ return jsonResult(await runtime.validateNoUnintendedChanges(request));
3059
+ });
3060
+ }
3061
+ function registerRepairTools(mcp) {
3062
+ registerMcpTool(mcp, "excel.repair.style_from_template", {
3063
+ title: "Repair style from template",
3064
+ description: "Repair target sheet styles from a registered template with a rollback backup.",
3065
+ inputSchema: templateRepairSchema(),
3066
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3067
+ }, async (args) => jsonResult(await runtime.repairStyleFromTemplate({
3068
+ workbookId: args.workbookId,
3069
+ templateId: args.templateId,
3070
+ targetSheetName: args.targetSheetName
3071
+ })));
3072
+ registerMcpTool(mcp, "excel.repair.formulas_from_template", {
3073
+ title: "Repair formulas from template",
3074
+ description: "Repair target sheet formulas from a registered template with a rollback backup.",
3075
+ inputSchema: templateRepairSchema(),
3076
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3077
+ }, async (args) => jsonResult(await runtime.repairFormulasFromTemplate({
3078
+ workbookId: args.workbookId,
3079
+ templateId: args.templateId,
3080
+ targetSheetName: args.targetSheetName
3081
+ })));
3082
+ registerMcpTool(mcp, "excel.repair.filters_from_template", {
3083
+ title: "Repair filters from template",
3084
+ description: "Return current filter repair capability status for a registered template.",
3085
+ inputSchema: templateRepairSchema(),
3086
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3087
+ }, async (args) => {
3088
+ const request = {
3089
+ workbookId: args.workbookId
3090
+ };
3091
+ if (args.templateId !== undefined) {
3092
+ request.templateId = args.templateId;
3093
+ }
3094
+ if (args.targetSheetName !== undefined) {
3095
+ request.targetSheetName = args.targetSheetName;
3096
+ }
3097
+ return jsonResult(runtime.repairFiltersFromTemplate(request));
3098
+ });
3099
+ registerMcpTool(mcp, "excel.repair.table_structure", {
3100
+ title: "Repair table structure",
3101
+ description: "Copy table headers and optional style/totals to a target range with rollback backup.",
3102
+ inputSchema: {
3103
+ ...tableSelectorSchema(),
3104
+ targetSheetName: z.string(),
3105
+ targetAddress: z.string(),
3106
+ newTableName: z.string().optional(),
3107
+ includeStyle: z.boolean().optional(),
3108
+ includeTotals: z.boolean().optional(),
3109
+ includeFilters: z.boolean().optional()
3110
+ },
3111
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3112
+ }, async (args) => jsonResult(await runtime.repairTableStructure({
3113
+ ...tableSelector(args),
3114
+ targetSheetName: args.targetSheetName,
3115
+ targetAddress: args.targetAddress,
3116
+ newTableName: args.newTableName,
3117
+ includeStyle: args.includeStyle,
3118
+ includeTotals: args.includeTotals,
3119
+ includeFilters: args.includeFilters
3120
+ })));
3121
+ for (const [name, repair] of [
3122
+ ["excel.repair.print_layout", "repairPrintLayout"],
3123
+ ["excel.repair.named_ranges", "repairNamedRanges"],
3124
+ ["excel.repair.formula_errors", "repairFormulaErrors"],
3125
+ ["excel.repair.merged_cells", "repairMergedCells"]
3126
+ ]) {
3127
+ registerMcpTool(mcp, name, {
3128
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
3129
+ description: "Return current repair capability status for this repair category.",
3130
+ inputSchema: { workbookId: z.string() },
3131
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3132
+ }, async ({ workbookId }) => jsonResult(runtime[repair]({ workbookId: workbookId })));
3133
+ }
3134
+ }
3135
+ function registerSnapshotTools(mcp) {
3136
+ for (const name of ["excel.snapshot.create", "excel.snapshot.refresh"]) {
3137
+ registerMcpTool(mcp, name, {
3138
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
3139
+ description: "Create or refresh a workbook snapshot.",
3140
+ inputSchema: name.endsWith(".refresh")
3141
+ ? { snapshotId: z.string(), reason: z.string().optional() }
3142
+ : snapshotInputSchema(),
3143
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3144
+ }, async (args) => {
3145
+ if (name.endsWith(".refresh")) {
3146
+ const existing = await runtime.getSnapshot(args.snapshotId);
3147
+ if (!existing.ok || !("snapshot" in existing)) {
3148
+ return jsonResult(existing);
3149
+ }
3150
+ return jsonResult(await runtime.createWorkbookSnapshot({
3151
+ workbookId: existing.snapshot.workbookId,
3152
+ reason: args.reason ?? `Refresh snapshot ${args.snapshotId}`,
3153
+ ranges: existing.snapshot.affectedRanges
3154
+ }));
3155
+ }
3156
+ return jsonResult(await runtime.createWorkbookSnapshot(snapshotRequest(args.workbookId, args.reason, args.ranges)));
3157
+ });
3158
+ }
3159
+ registerMcpTool(mcp, "excel.snapshot.get", {
3160
+ title: "Get snapshot",
3161
+ description: "Return a stored workbook snapshot.",
3162
+ inputSchema: { snapshotId: z.string() },
3163
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3164
+ }, async ({ snapshotId }) => jsonResult(runtime.getSnapshot(snapshotId)));
3165
+ registerMcpTool(mcp, "excel.snapshot.list", {
3166
+ title: "List snapshots",
3167
+ description: "List stored snapshots for a workbook.",
3168
+ inputSchema: { workbookId: z.string() },
3169
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3170
+ }, async ({ workbookId }) => jsonResult(runtime.listSnapshots(workbookId)));
3171
+ registerMcpTool(mcp, "excel.snapshot.compare", {
3172
+ title: "Compare snapshots",
3173
+ description: "Compare two workbook snapshots.",
3174
+ inputSchema: {
3175
+ leftSnapshotId: z.string(),
3176
+ rightSnapshotId: z.string()
3177
+ },
3178
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3179
+ }, async ({ leftSnapshotId, rightSnapshotId }) => jsonResult(runtime.compareSnapshots(leftSnapshotId, rightSnapshotId)));
3180
+ registerMcpTool(mcp, "excel.snapshot.invalidate", {
3181
+ title: "Invalidate snapshot",
3182
+ description: "Mark a snapshot as stale without deleting it.",
3183
+ inputSchema: { snapshotId: z.string() },
3184
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
3185
+ }, async ({ snapshotId }) => jsonResult(runtime.invalidateSnapshot(snapshotId)));
3186
+ registerMcpTool(mcp, "excel.snapshot.delete", {
3187
+ title: "Delete snapshot",
3188
+ description: "Delete a stored snapshot.",
3189
+ inputSchema: { snapshotId: z.string() },
3190
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3191
+ }, async ({ snapshotId }) => jsonResult(runtime.deleteSnapshot(snapshotId)));
3192
+ }
3193
+ function registerDiffTools(mcp) {
3194
+ for (const name of ["excel.diff.create", "excel.diff.summarize", "excel.diff.get_details", "excel.diff.export_json"]) {
3195
+ registerMcpTool(mcp, name, {
3196
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
3197
+ description: "Create or return a diff between two stored snapshots.",
3198
+ inputSchema: {
3199
+ leftSnapshotId: z.string(),
3200
+ rightSnapshotId: z.string()
3201
+ },
3202
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3203
+ }, async ({ leftSnapshotId, rightSnapshotId }) => {
3204
+ const diff = runtime.compareSnapshots(leftSnapshotId, rightSnapshotId);
3205
+ return jsonResult(name.endsWith("export_json") ? { ok: true, json: JSON.stringify(diff, null, 2) } : diff);
3206
+ });
3207
+ }
3208
+ registerMcpTool(mcp, "excel.diff.export_html", {
3209
+ title: "Export diff HTML",
3210
+ description: "Return a small HTML representation of a snapshot diff.",
3211
+ inputSchema: {
3212
+ leftSnapshotId: z.string(),
3213
+ rightSnapshotId: z.string()
3214
+ },
3215
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3216
+ }, async ({ leftSnapshotId, rightSnapshotId }) => {
3217
+ const diff = runtime.compareSnapshots(leftSnapshotId, rightSnapshotId);
3218
+ const escaped = escapeHtml(JSON.stringify(diff, null, 2));
3219
+ return jsonResult({ ok: true, html: `<html><body><pre>${escaped}</pre></body></html>` });
3220
+ });
3221
+ }
3222
+ function registerEventTools(mcp) {
3223
+ registerMcpTool(mcp, "excel.events.subscribe", {
3224
+ title: "Subscribe to Excel events",
3225
+ description: "Enable recent add-in event capture.",
3226
+ inputSchema: {},
3227
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
3228
+ }, async () => jsonResult(runtime.subscribeEvents()));
3229
+ registerMcpTool(mcp, "excel.events.unsubscribe", {
3230
+ title: "Unsubscribe from Excel events",
3231
+ description: "Disable recent add-in event capture.",
3232
+ inputSchema: {},
3233
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
3234
+ }, async () => jsonResult(runtime.unsubscribeEvents()));
3235
+ registerMcpTool(mcp, "excel.events.get_recent", {
3236
+ title: "Get recent Excel events",
3237
+ description: "Return recent add-in events observed by the backend.",
3238
+ inputSchema: { limit: z.number().int().positive().max(250).optional() },
3239
+ annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
3240
+ }, async ({ limit }) => jsonResult(runtime.getRecentEvents(limit)));
3241
+ registerMcpTool(mcp, "excel.events.clear", {
3242
+ title: "Clear Excel events",
3243
+ description: "Clear the in-memory event log.",
3244
+ inputSchema: {},
3245
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
3246
+ }, async () => jsonResult(runtime.clearEvents()));
3247
+ registerMcpTool(mcp, "excel.events.set_debounce", {
3248
+ title: "Set Excel event debounce",
3249
+ description: "Set the event debounce preference stored by the backend.",
3250
+ inputSchema: { debounceMs: z.number().int().min(0).max(60000) },
3251
+ annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
3252
+ }, async ({ debounceMs }) => jsonResult(runtime.setEventDebounce(debounceMs)));
3253
+ }
3254
+ async function readRangeSnapshot(workbookId, sheetName, address) {
3255
+ const operation = {
3256
+ kind: "range.read_full",
3257
+ operationId: makeId("op"),
3258
+ workbookId: workbookId,
3259
+ destructiveLevel: "none",
3260
+ reason: "MCP range read",
3261
+ target: {
3262
+ workbookId: workbookId,
3263
+ sheetName,
3264
+ address
3265
+ }
3266
+ };
3267
+ return runtime.applyBatch({
3268
+ workbookId: workbookId,
3269
+ mode: "apply",
3270
+ operations: [operation]
3271
+ });
3272
+ }
3273
+ function templateRepairSchema() {
3274
+ return {
3275
+ workbookId: z.string(),
3276
+ templateId: z.string(),
3277
+ targetSheetName: z.string(),
3278
+ repair: z.array(z.enum(["styles", "formulas", "dataRegions", "layout"])).optional()
3279
+ };
3280
+ }
3281
+ function tableSelectorSchema() {
3282
+ return {
3283
+ workbookId: z.string(),
3284
+ tableName: z.string()
3285
+ };
3286
+ }
3287
+ function tableSelector(args) {
3288
+ return {
3289
+ workbookId: args.workbookId,
3290
+ tableName: args.tableName
3291
+ };
3292
+ }
3293
+ function tableCreateRequest(args) {
3294
+ const request = {
3295
+ workbookId: args.workbookId,
3296
+ sheetName: args.sheetName,
3297
+ address: args.address,
3298
+ hasHeaders: args.hasHeaders
3299
+ };
3300
+ if (args.tableName !== undefined) {
3301
+ request.tableName = args.tableName;
3302
+ }
3303
+ if (args.values !== undefined) {
3304
+ request.values = args.values;
3305
+ }
3306
+ if (args.style !== undefined) {
3307
+ request.style = args.style;
3308
+ }
3309
+ if (args.showTotals !== undefined) {
3310
+ request.showTotals = args.showTotals;
3311
+ }
3312
+ return request;
3313
+ }
3314
+ function tableFilterSchema() {
3315
+ return {
3316
+ ...tableSelectorSchema(),
3317
+ filters: z.array(z.object({
3318
+ column: z.union([z.string(), z.number().int().min(0)]),
3319
+ criteria: z.record(z.string(), z.any())
3320
+ }))
3321
+ };
3322
+ }
3323
+ function tableSortSchema() {
3324
+ return {
3325
+ ...tableSelectorSchema(),
3326
+ fields: z.array(z.object({
3327
+ key: z.number().int().min(0),
3328
+ ascending: z.boolean().optional(),
3329
+ sortOn: z.enum(["Value", "CellColor", "FontColor", "Icon"]).optional(),
3330
+ color: z.string().optional(),
3331
+ dataOption: z.enum(["Normal", "TextAsNumber"]).optional()
3332
+ })),
3333
+ matchCase: z.boolean().optional(),
3334
+ method: z.enum(["PinYin", "StrokeCount"]).optional()
3335
+ };
3336
+ }
3337
+ function nameSelectorSchema() {
3338
+ return {
3339
+ workbookId: z.string(),
3340
+ name: z.string(),
3341
+ sheetName: z.string().optional()
3342
+ };
3343
+ }
3344
+ function nameMutationSchema() {
3345
+ return {
3346
+ ...nameSelectorSchema(),
3347
+ reference: z.string().optional(),
3348
+ formula: z.string().optional(),
3349
+ comment: z.string().optional(),
3350
+ visible: z.boolean().optional()
3351
+ };
3352
+ }
3353
+ function nameSelector(args) {
3354
+ const request = {
3355
+ workbookId: args.workbookId,
3356
+ name: args.name
3357
+ };
3358
+ if (args.sheetName !== undefined) {
3359
+ request.sheetName = args.sheetName;
3360
+ }
3361
+ return request;
3362
+ }
3363
+ function nameCreateRequest(args) {
3364
+ const request = nameSelector(args);
3365
+ if (args.reference !== undefined) {
3366
+ request.reference = args.reference;
3367
+ }
3368
+ if (args.formula !== undefined) {
3369
+ request.formula = args.formula;
3370
+ }
3371
+ if (args.comment !== undefined) {
3372
+ request.comment = args.comment;
3373
+ }
3374
+ if (args.visible !== undefined) {
3375
+ request.visible = args.visible;
3376
+ }
3377
+ return request;
3378
+ }
3379
+ function nameUpdateRequest(args) {
3380
+ return nameCreateRequest(args);
3381
+ }
3382
+ function regionSelectorSchema() {
3383
+ return {
3384
+ workbookId: z.string(),
3385
+ regionName: z.string()
3386
+ };
3387
+ }
3388
+ function regionSelector(args) {
3389
+ return {
3390
+ workbookId: args.workbookId,
3391
+ regionName: args.regionName
3392
+ };
3393
+ }
3394
+ function pivotSelectorSchema() {
3395
+ return {
3396
+ workbookId: z.string(),
3397
+ pivotTableName: z.string()
3398
+ };
3399
+ }
3400
+ function pivotSelector(args) {
3401
+ return {
3402
+ workbookId: args.workbookId,
3403
+ pivotTableName: args.pivotTableName
3404
+ };
3405
+ }
3406
+ function pivotValidateSourceRequest(args) {
3407
+ return {
3408
+ ...pivotSelector(args),
3409
+ ...(args.expectedFields !== undefined ? { expectedFields: args.expectedFields } : {}),
3410
+ ...(args.expectedRowFields !== undefined ? { expectedRowFields: args.expectedRowFields } : {}),
3411
+ ...(args.expectedColumnFields !== undefined ? { expectedColumnFields: args.expectedColumnFields } : {}),
3412
+ ...(args.expectedFilterFields !== undefined ? { expectedFilterFields: args.expectedFilterFields } : {}),
3413
+ ...(args.expectedDataFields !== undefined ? { expectedDataFields: args.expectedDataFields } : {}),
3414
+ ...(args.expectedDataFieldSettings !== undefined ? { expectedDataFieldSettings: args.expectedDataFieldSettings } : {}),
3415
+ ...(args.expectedLayout !== undefined ? { expectedLayout: args.expectedLayout } : {})
3416
+ };
3417
+ }
3418
+ function pivotCreateRequest(args) {
3419
+ const request = {
3420
+ workbookId: args.workbookId,
3421
+ pivotTableName: args.pivotTableName,
3422
+ destinationSheetName: args.destinationSheetName,
3423
+ destinationAddress: args.destinationAddress
3424
+ };
3425
+ if (args.sourceSheetName !== undefined) {
3426
+ request.sourceSheetName = args.sourceSheetName;
3427
+ }
3428
+ if (args.sourceAddress !== undefined) {
3429
+ request.sourceAddress = args.sourceAddress;
3430
+ }
3431
+ if (args.sourceTableName !== undefined) {
3432
+ request.sourceTableName = args.sourceTableName;
3433
+ }
3434
+ if (args.rowFields !== undefined) {
3435
+ request.rowFields = args.rowFields;
3436
+ }
3437
+ if (args.columnFields !== undefined) {
3438
+ request.columnFields = args.columnFields;
3439
+ }
3440
+ if (args.filterFields !== undefined) {
3441
+ request.filterFields = args.filterFields;
3442
+ }
3443
+ if (args.dataFields !== undefined) {
3444
+ request.dataFields = args.dataFields;
3445
+ }
3446
+ if (args.layout !== undefined) {
3447
+ request.layout = args.layout;
3448
+ }
3449
+ if (args.refresh !== undefined) {
3450
+ request.refresh = args.refresh;
3451
+ }
3452
+ return request;
3453
+ }
3454
+ function chartSelectorSchema() {
3455
+ return {
3456
+ workbookId: z.string(),
3457
+ sheetName: z.string(),
3458
+ chartName: z.string()
3459
+ };
3460
+ }
3461
+ function chartSelector(args) {
3462
+ return {
3463
+ workbookId: args.workbookId,
3464
+ sheetName: args.sheetName,
3465
+ chartName: args.chartName
3466
+ };
3467
+ }
3468
+ function chartCreateSchema() {
3469
+ return {
3470
+ workbookId: z.string(),
3471
+ sheetName: z.string(),
3472
+ chartName: z.string().optional(),
3473
+ sourceAddress: z.string(),
3474
+ chartType: z.string(),
3475
+ seriesBy: z.enum(["Auto", "Columns", "Rows"]).optional(),
3476
+ title: z.string().optional(),
3477
+ position: z.object({ startCell: z.string(), endCell: z.string().optional() }).optional(),
3478
+ style: z.number().int().optional()
3479
+ };
3480
+ }
3481
+ function chartCreateRequest(args) {
3482
+ const request = {
3483
+ workbookId: args.workbookId,
3484
+ sheetName: args.sheetName,
3485
+ sourceAddress: args.sourceAddress,
3486
+ chartType: args.chartType
3487
+ };
3488
+ if (args.chartName !== undefined) {
3489
+ request.chartName = args.chartName;
3490
+ }
3491
+ if (args.seriesBy !== undefined) {
3492
+ request.seriesBy = args.seriesBy;
3493
+ }
3494
+ if (args.title !== undefined) {
3495
+ request.title = args.title;
3496
+ }
3497
+ if (args.position !== undefined) {
3498
+ request.position = args.position;
3499
+ }
3500
+ if (args.style !== undefined) {
3501
+ request.style = args.style;
3502
+ }
3503
+ return request;
3504
+ }
3505
+ function chartUpdateDataSourceRequest(args) {
3506
+ const request = {
3507
+ ...chartSelector(args),
3508
+ sourceAddress: args.sourceAddress
3509
+ };
3510
+ if (args.seriesBy !== undefined) {
3511
+ request.seriesBy = args.seriesBy;
3512
+ }
3513
+ return request;
3514
+ }
3515
+ function chartTemplateValidationRequest(args) {
3516
+ const request = chartSelector(args);
3517
+ if (args.templateSheetName !== undefined) {
3518
+ request.templateSheetName = args.templateSheetName;
3519
+ }
3520
+ if (args.templateChartName !== undefined) {
3521
+ request.templateChartName = args.templateChartName;
3522
+ }
3523
+ return request;
3524
+ }
3525
+ function regionRegisterRequest(args) {
3526
+ const request = {
3527
+ workbookId: args.workbookId,
3528
+ name: args.name,
3529
+ sheetName: args.sheetName,
3530
+ address: args.address
3531
+ };
3532
+ if (args.kind !== undefined) {
3533
+ request.kind = args.kind;
3534
+ }
3535
+ if (args.description !== undefined) {
3536
+ request.description = args.description;
3537
+ }
3538
+ if (args.templateId !== undefined) {
3539
+ request.templateId = args.templateId;
3540
+ }
3541
+ if (args.createNamedRange !== undefined) {
3542
+ request.createNamedRange = args.createNamedRange;
3543
+ }
3544
+ return request;
3545
+ }
3546
+ function permissionSetSchema() {
3547
+ return {
3548
+ allowWrites: z.boolean().optional(),
3549
+ allowDestructiveActions: z.boolean().optional(),
3550
+ allowWorkbookActions: z.boolean().optional(),
3551
+ allowMacroExecution: z.boolean().optional(),
3552
+ requireConfirmationFor: z.array(z.enum(["none", "values", "format", "structure", "workbook"])).optional(),
3553
+ scope: z
3554
+ .object({
3555
+ workbookId: z.string().optional(),
3556
+ sheetNames: z.array(z.string()).optional(),
3557
+ regionNames: z.array(z.string()).optional()
3558
+ })
3559
+ .optional()
3560
+ };
3561
+ }
3562
+ function permissionUpdate(args) {
3563
+ const update = {};
3564
+ for (const key of ["allowWrites", "allowDestructiveActions", "allowWorkbookActions", "allowMacroExecution"]) {
3565
+ if (args[key] !== undefined) {
3566
+ update[key] = args[key];
3567
+ }
3568
+ }
3569
+ if (args.requireConfirmationFor !== undefined) {
3570
+ update.requireConfirmationFor = args.requireConfirmationFor;
3571
+ }
3572
+ if (args.scope !== undefined) {
3573
+ update.scope = permissionScope(args.scope);
3574
+ }
3575
+ return update;
3576
+ }
3577
+ function permissionScope(args) {
3578
+ const scope = {};
3579
+ if (args.workbookId !== undefined) {
3580
+ scope.workbookId = args.workbookId;
3581
+ }
3582
+ if (args.sheetNames !== undefined) {
3583
+ scope.sheetNames = args.sheetNames;
3584
+ }
3585
+ if (args.regionNames !== undefined) {
3586
+ scope.regionNames = args.regionNames;
3587
+ }
3588
+ return scope;
3589
+ }
3590
+ function cleanRangeSchema() {
3591
+ return {
3592
+ workbookId: z.string(),
3593
+ sheetName: z.string(),
3594
+ address: z.string()
3595
+ };
3596
+ }
3597
+ function cleanRangeArgs(args, extra) {
3598
+ return {
3599
+ workbookId: args.workbookId,
3600
+ sheetName: args.sheetName,
3601
+ address: args.address,
3602
+ ...compactOptional(extra)
3603
+ };
3604
+ }
3605
+ function compactOptional(input) {
3606
+ if (!input) {
3607
+ return {};
3608
+ }
3609
+ return Object.fromEntries(Object.entries(input).filter(([, value]) => value !== undefined));
3610
+ }
3611
+ function validationRangeArgs(args) {
3612
+ const request = {
3613
+ workbookId: args.workbookId
3614
+ };
3615
+ if (args.sheetName !== undefined) {
3616
+ request.sheetName = args.sheetName;
3617
+ }
3618
+ if (args.address !== undefined) {
3619
+ request.address = args.address;
3620
+ }
3621
+ return request;
3622
+ }
3623
+ async function repairTemplateFromArgs(args) {
3624
+ const request = {
3625
+ workbookId: args.workbookId,
3626
+ templateId: args.templateId,
3627
+ targetSheetName: args.targetSheetName
3628
+ };
3629
+ if (args.repair !== undefined) {
3630
+ request.repair = args.repair;
3631
+ }
3632
+ return runtime.repairSheetFromTemplate(request);
3633
+ }
3634
+ function registerRangeOperation(mcp, name, inputSchema, createOperation) {
3635
+ registerMcpTool(mcp, name, {
3636
+ title: name.replace(/^excel\./, "").replace(/\./g, " "),
3637
+ description: `Apply ${name} through the reversible batch pipeline.`,
3638
+ inputSchema,
3639
+ annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3640
+ }, async (args) => jsonResult(await applySingleOperation(args.workbookId, createOperation(args))));
3641
+ }
3642
+ function registerSheetOperation(mcp, name, inputSchema, createOperation) {
3643
+ registerRangeOperation(mcp, name, inputSchema, createOperation);
3644
+ }
3645
+ async function applySingleOperation(workbookId, operation) {
3646
+ return runtime.applyBatch({
3647
+ workbookId: workbookId,
3648
+ mode: "apply",
3649
+ operations: [
3650
+ {
3651
+ ...operation,
3652
+ operationId: makeId("op")
3653
+ }
3654
+ ]
3655
+ });
3656
+ }
3657
+ function targetFromArgs(args) {
3658
+ return {
3659
+ workbookId: args.workbookId,
3660
+ sheetName: args.sheetName,
3661
+ address: args.address
3662
+ };
3663
+ }
3664
+ function rangeMetadataRequest(args) {
3665
+ return {
3666
+ workbookId: args.workbookId,
3667
+ sheetName: args.sheetName,
3668
+ address: args.address
3669
+ };
3670
+ }
3671
+ function rangeSearchRequest(args) {
3672
+ const request = {
3673
+ workbookId: args.workbookId,
3674
+ sheetName: args.sheetName,
3675
+ address: args.address,
3676
+ text: args.text
3677
+ };
3678
+ if (args.completeMatch !== undefined) {
3679
+ request.completeMatch = args.completeMatch;
3680
+ }
3681
+ if (args.matchCase !== undefined) {
3682
+ request.matchCase = args.matchCase;
3683
+ }
3684
+ if (args.searchDirection !== undefined) {
3685
+ request.searchDirection = args.searchDirection;
3686
+ }
3687
+ return request;
3688
+ }
3689
+ function snapshotInputSchema() {
3690
+ return {
3691
+ workbookId: z.string(),
3692
+ reason: z.string().optional(),
3693
+ ranges: z
3694
+ .array(z.object({
3695
+ workbookId: z.string(),
3696
+ sheetName: z.string(),
3697
+ address: z.string()
3698
+ }))
3699
+ .optional()
3700
+ };
3701
+ }
3702
+ function snapshotRequest(workbookId, reason, ranges) {
3703
+ const request = {
3704
+ workbookId: workbookId
3705
+ };
3706
+ if (reason !== undefined) {
3707
+ request.reason = reason;
3708
+ }
3709
+ if (ranges !== undefined) {
3710
+ request.ranges = ranges;
3711
+ }
3712
+ return request;
3713
+ }
3714
+ async function selectSheetInfo(sheetName) {
3715
+ const result = await runtime.getWorkbookMap();
3716
+ const map = "map" in result ? result.map : undefined;
3717
+ const sheet = map?.sheets?.find((candidate) => candidate.name === sheetName);
3718
+ return { ok: Boolean(result.ok && sheet), sheet, result };
3719
+ }
3720
+ function escapeHtml(value) {
3721
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3722
+ }
3723
+ function registerMcpTool(mcp, name, config, callback) {
3724
+ if (!isToolExposed(name, catalogOptions)) {
3725
+ return;
3726
+ }
3727
+ mcp.registerTool(name, config, callback);
3728
+ }
3729
+ function jsonResult(value) {
3730
+ return {
3731
+ content: [
3732
+ {
3733
+ type: "text",
3734
+ text: JSON.stringify(value, null, 2)
3735
+ }
3736
+ ]
3737
+ };
3738
+ }
3739
+ function jsonResource(uri, value) {
3740
+ return {
3741
+ contents: [
3742
+ {
3743
+ uri,
3744
+ mimeType: "application/json",
3745
+ text: JSON.stringify(value, null, 2)
3746
+ }
3747
+ ]
3748
+ };
3749
+ }
3750
+ function resourceVariable(variables, name) {
3751
+ const value = variables[name];
3752
+ if (Array.isArray(value)) {
3753
+ return value.join("/");
3754
+ }
3755
+ return value ?? "";
3756
+ }
3757
+ function stripResourceSheetName(address) {
3758
+ const bangIndex = address.lastIndexOf("!");
3759
+ return bangIndex >= 0 ? address.slice(bangIndex + 1) : address;
3760
+ }
3761
+ function hasArg(name) {
3762
+ return process.argv.includes(name);
3763
+ }
3764
+ function readArg(name) {
3765
+ const prefix = `${name}=`;
3766
+ const inline = process.argv.find((arg) => arg.startsWith(prefix));
3767
+ if (inline) {
3768
+ return inline.slice(prefix.length);
3769
+ }
3770
+ const index = process.argv.indexOf(name);
3771
+ if (index >= 0) {
3772
+ return process.argv[index + 1];
3773
+ }
3774
+ return undefined;
3775
+ }
3776
+ function trimTrailingSlash(value) {
3777
+ return value.endsWith("/") ? value.slice(0, -1) : value;
3778
+ }
3779
+ //# sourceMappingURL=index.js.map