@components-kit/open-workbook 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/LICENSE +21 -0
- package/README.md +80 -0
- package/assets/backend/dist/addin-rpc-client.d.ts +17 -0
- package/assets/backend/dist/addin-rpc-client.d.ts.map +1 -0
- package/assets/backend/dist/addin-rpc-client.js +66 -0
- package/assets/backend/dist/addin-rpc-client.js.map +1 -0
- package/assets/backend/dist/addin-websocket-server.d.ts +13 -0
- package/assets/backend/dist/addin-websocket-server.d.ts.map +1 -0
- package/assets/backend/dist/addin-websocket-server.js +199 -0
- package/assets/backend/dist/addin-websocket-server.js.map +1 -0
- package/assets/backend/dist/file-bridge.d.ts +2 -0
- package/assets/backend/dist/file-bridge.d.ts.map +1 -0
- package/assets/backend/dist/file-bridge.js +15 -0
- package/assets/backend/dist/file-bridge.js.map +1 -0
- package/assets/backend/dist/index.d.ts +4 -0
- package/assets/backend/dist/index.d.ts.map +1 -0
- package/assets/backend/dist/index.js +17 -0
- package/assets/backend/dist/index.js.map +1 -0
- package/assets/backend/dist/native-file-bridge-server.d.ts +28 -0
- package/assets/backend/dist/native-file-bridge-server.d.ts.map +1 -0
- package/assets/backend/dist/native-file-bridge-server.js +496 -0
- package/assets/backend/dist/native-file-bridge-server.js.map +1 -0
- package/assets/backend/dist/native-file-bridge.d.ts +20 -0
- package/assets/backend/dist/native-file-bridge.d.ts.map +1 -0
- package/assets/backend/dist/native-file-bridge.js +140 -0
- package/assets/backend/dist/native-file-bridge.js.map +1 -0
- package/assets/backend/dist/runtime-service.d.ts +3167 -0
- package/assets/backend/dist/runtime-service.d.ts.map +1 -0
- package/assets/backend/dist/runtime-service.js +6003 -0
- package/assets/backend/dist/runtime-service.js.map +1 -0
- package/assets/backend/dist/session-registry.d.ts +20 -0
- package/assets/backend/dist/session-registry.d.ts.map +1 -0
- package/assets/backend/dist/session-registry.js +53 -0
- package/assets/backend/dist/session-registry.js.map +1 -0
- package/assets/backend/dist/state-store.d.ts +26 -0
- package/assets/backend/dist/state-store.d.ts.map +1 -0
- package/assets/backend/dist/state-store.js +44 -0
- package/assets/backend/dist/state-store.js.map +1 -0
- package/assets/excel-addin/dist/connection.d.ts +26 -0
- package/assets/excel-addin/dist/connection.d.ts.map +1 -0
- package/assets/excel-addin/dist/connection.js +320 -0
- package/assets/excel-addin/dist/connection.js.map +1 -0
- package/assets/excel-addin/dist/excel-executor.d.ts +225 -0
- package/assets/excel-addin/dist/excel-executor.d.ts.map +1 -0
- package/assets/excel-addin/dist/excel-executor.js +2487 -0
- package/assets/excel-addin/dist/excel-executor.js.map +1 -0
- package/assets/excel-addin/dist/taskpane.d.ts +2 -0
- package/assets/excel-addin/dist/taskpane.d.ts.map +1 -0
- package/assets/excel-addin/dist/taskpane.js +28 -0
- package/assets/excel-addin/dist/taskpane.js.map +1 -0
- package/assets/excel-addin/manifest.xml +93 -0
- package/assets/excel-addin/public/taskpane.css +47 -0
- package/assets/excel-addin/public/taskpane.html +35 -0
- package/assets/excel-addin/scripts/dev-server.mjs +128 -0
- package/assets/instructions/open-workbook-excel/SKILL.md +52 -0
- package/assets/instructions/open-workbook-excel/references/multi-agent.md +43 -0
- package/assets/instructions/open-workbook-excel/references/performance.md +41 -0
- package/assets/instructions/open-workbook-excel/references/reliability.md +76 -0
- package/assets/instructions/open-workbook-excel/references/tool-selection.md +82 -0
- package/assets/instructions/open-workbook-excel/references/workflows.md +93 -0
- package/assets/mcp-server/dist/catalog.d.ts +5 -0
- package/assets/mcp-server/dist/catalog.d.ts.map +1 -0
- package/assets/mcp-server/dist/catalog.js +8 -0
- package/assets/mcp-server/dist/catalog.js.map +1 -0
- package/assets/mcp-server/dist/index.d.ts +3 -0
- package/assets/mcp-server/dist/index.d.ts.map +1 -0
- package/assets/mcp-server/dist/index.js +3779 -0
- package/assets/mcp-server/dist/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +721 -0
- package/dist/index.js.map +1 -0
- package/package.json +56 -0
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|