@components-kit/open-workbook 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -12
- package/assets/backend/dist/addin-websocket-server.js +5 -1
- package/assets/backend/dist/addin-websocket-server.js.map +1 -1
- package/assets/backend/dist/runtime-service.d.ts +362 -4
- package/assets/backend/dist/runtime-service.d.ts.map +1 -1
- package/assets/backend/dist/runtime-service.js +867 -9
- package/assets/backend/dist/runtime-service.js.map +1 -1
- package/assets/backend/dist/state-store.d.ts +2 -1
- package/assets/backend/dist/state-store.d.ts.map +1 -1
- package/assets/backend/dist/state-store.js +1 -0
- package/assets/backend/dist/state-store.js.map +1 -1
- package/assets/excel-addin/dist/excel-executor.js +1 -1
- package/assets/excel-addin/dist/taskpane.bundle.js +10 -10
- package/assets/excel-addin/manifest.xml +1 -1
- package/assets/excel-addin/scripts/dev-server.mjs +1 -1
- package/assets/instructions/open-workbook-excel/SKILL.md +22 -6
- package/assets/instructions/open-workbook-excel/references/performance.md +5 -1
- package/assets/instructions/open-workbook-excel/references/tool-selection.md +29 -6
- package/assets/instructions/open-workbook-excel/references/workflows.md +35 -7
- package/assets/mcp-server/dist/index.js +870 -18
- package/assets/mcp-server/dist/index.js.map +1 -1
- package/dist/index.js +4 -16
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
|
@@ -45,7 +45,7 @@ const STYLE_COPY_TOOL_DIMENSIONS = {
|
|
|
45
45
|
"excel.style.copy_hidden_rows_columns": "hiddenRowsColumns"
|
|
46
46
|
};
|
|
47
47
|
const runtime = await createRuntimeFacade();
|
|
48
|
-
const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.
|
|
48
|
+
const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.6";
|
|
49
49
|
const server = new McpServer({
|
|
50
50
|
name: "open-workbook",
|
|
51
51
|
version: runtimeVersion
|
|
@@ -56,6 +56,7 @@ registerBackupTools(server);
|
|
|
56
56
|
registerSheetTools(server);
|
|
57
57
|
registerRangeTools(server);
|
|
58
58
|
registerBatchTools(server);
|
|
59
|
+
registerWorkflowTools(server);
|
|
59
60
|
registerPlanTools(server);
|
|
60
61
|
registerTemplateTools(server);
|
|
61
62
|
registerStyleTools(server);
|
|
@@ -72,6 +73,7 @@ registerCollaborationTools(server);
|
|
|
72
73
|
registerLockTools(server);
|
|
73
74
|
registerConflictTools(server);
|
|
74
75
|
registerTransactionTools(server);
|
|
76
|
+
registerJobTools(server);
|
|
75
77
|
registerPermissionsTools(server);
|
|
76
78
|
registerCleanTools(server);
|
|
77
79
|
registerValidateTools(server);
|
|
@@ -262,7 +264,7 @@ function registerPrompts(mcp) {
|
|
|
262
264
|
"1. Locate errors with `excel.formula.find_errors` and `excel.validate.no_formula_errors`.",
|
|
263
265
|
"2. Read formula patterns and dependency graph with `excel.formula.read_patterns`, `excel.formula.get_dependency_graph`, `trace_precedents`, and `trace_dependents`.",
|
|
264
266
|
"3. If a template exists, compare with `excel.formula.validate_against_template`.",
|
|
265
|
-
"4. Create a repair plan using `excel.formula.repair_patterns`, `fill_down`, `fill_right`, or explicit `range.write_formulas`.",
|
|
267
|
+
"4. Create a repair plan using `excel.formula.repair_patterns`, `fill_down`, `fill_right`, or explicit `range.write_formulas`; never repair formulas by writing formula strings through `range.write_values`.",
|
|
266
268
|
"5. Preview, apply, recalculate, and re-run formula validation before reporting success."
|
|
267
269
|
]);
|
|
268
270
|
registerWorkflowPrompt(mcp, "excel.prompts.format_like_template", "Format like template", "Repair styling and layout consistency using registered template fingerprints.", promptArgs, (args) => [
|
|
@@ -1066,7 +1068,7 @@ function registerRangeTools(mcp) {
|
|
|
1066
1068
|
sheetName: z.string(),
|
|
1067
1069
|
address: z.string(),
|
|
1068
1070
|
values: z.array(z.array(z.any()))
|
|
1069
|
-
}, (args) => ({
|
|
1071
|
+
}, "Write constants only through the reversible batch pipeline. Use excel.range.write_formulas for formulas and excel.range.write_number_formats for display formats.", (args) => ({
|
|
1070
1072
|
kind: "range.write_values",
|
|
1071
1073
|
workbookId: args.workbookId,
|
|
1072
1074
|
target: targetFromArgs(args),
|
|
@@ -1080,7 +1082,7 @@ function registerRangeTools(mcp) {
|
|
|
1080
1082
|
sheetName: z.string(),
|
|
1081
1083
|
address: z.string(),
|
|
1082
1084
|
formulas: z.array(z.array(z.string().nullable()))
|
|
1083
|
-
}, (args) => ({
|
|
1085
|
+
}, "Write formulas through the reversible batch pipeline while preserving formats. Use this for cells beginning with = instead of excel.range.write_values.", (args) => ({
|
|
1084
1086
|
kind: "range.write_formulas",
|
|
1085
1087
|
workbookId: args.workbookId,
|
|
1086
1088
|
target: targetFromArgs(args),
|
|
@@ -1094,7 +1096,7 @@ function registerRangeTools(mcp) {
|
|
|
1094
1096
|
sheetName: z.string(),
|
|
1095
1097
|
address: z.string(),
|
|
1096
1098
|
numberFormat: z.array(z.array(z.string()))
|
|
1097
|
-
}, (args) => ({
|
|
1099
|
+
}, "Write number formats through the reversible batch pipeline without changing values or formulas.", (args) => ({
|
|
1098
1100
|
kind: "range.write_number_formats",
|
|
1099
1101
|
workbookId: args.workbookId,
|
|
1100
1102
|
target: targetFromArgs(args),
|
|
@@ -1117,6 +1119,35 @@ function registerRangeTools(mcp) {
|
|
|
1117
1119
|
destructiveLevel: "format",
|
|
1118
1120
|
reason: "MCP range write styles"
|
|
1119
1121
|
}));
|
|
1122
|
+
registerMcpTool(mcp, "excel.range.write_styles_many", {
|
|
1123
|
+
title: "Write many Excel range styles",
|
|
1124
|
+
description: "Apply styles to multiple ranges through one reversible batch transaction. Use this for grouped report styling instead of many parallel write_styles calls.",
|
|
1125
|
+
inputSchema: {
|
|
1126
|
+
workbookId: z.string(),
|
|
1127
|
+
reason: z.string().optional(),
|
|
1128
|
+
entries: z.array(z.object({
|
|
1129
|
+
sheetName: z.string(),
|
|
1130
|
+
address: z.string(),
|
|
1131
|
+
style: z.record(z.string(), z.any()),
|
|
1132
|
+
reason: z.string().optional()
|
|
1133
|
+
})).min(1)
|
|
1134
|
+
},
|
|
1135
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
1136
|
+
}, async ({ workbookId, reason, entries }) => {
|
|
1137
|
+
const typedWorkbookId = workbookId;
|
|
1138
|
+
const operations = styleEntriesToOperations(typedWorkbookId, entries, reason);
|
|
1139
|
+
const chunkSize = styleBatchChunkSize();
|
|
1140
|
+
if (operations.length > chunkSize) {
|
|
1141
|
+
return jsonResult(runtime.submitChunkedBatch({
|
|
1142
|
+
workbookId: typedWorkbookId,
|
|
1143
|
+
mode: "apply",
|
|
1144
|
+
operations,
|
|
1145
|
+
retryStrategy: "split_style_entries",
|
|
1146
|
+
progressMessage: reason ?? "Apply bulk range styles"
|
|
1147
|
+
}, { goal: reason ?? "Apply bulk range styles", retryStrategy: "split_style_entries" }));
|
|
1148
|
+
}
|
|
1149
|
+
return jsonResult(await runtime.applyBatch({ workbookId: typedWorkbookId, mode: "apply", operations }));
|
|
1150
|
+
});
|
|
1120
1151
|
for (const name of ["excel.range.clear", "excel.range.clear_values", "excel.range.clear_formats", "excel.range.clear_values_keep_format"]) {
|
|
1121
1152
|
registerRangeOperation(mcp, name, {
|
|
1122
1153
|
workbookId: z.string(),
|
|
@@ -1204,6 +1235,26 @@ function registerBatchTools(mcp) {
|
|
|
1204
1235
|
};
|
|
1205
1236
|
return jsonResult(await runtime.compileBatch(request));
|
|
1206
1237
|
});
|
|
1238
|
+
registerMcpTool(mcp, "excel.batch.preflight", {
|
|
1239
|
+
title: "Preflight Excel batch",
|
|
1240
|
+
description: "Estimate batch size and recommend apply, submit, or chunked submit before sending work to Excel.",
|
|
1241
|
+
inputSchema: {
|
|
1242
|
+
workbookId: z.string(),
|
|
1243
|
+
operations: z.array(z.any())
|
|
1244
|
+
},
|
|
1245
|
+
annotations: {
|
|
1246
|
+
readOnlyHint: true,
|
|
1247
|
+
destructiveHint: false,
|
|
1248
|
+
openWorldHint: false
|
|
1249
|
+
}
|
|
1250
|
+
}, async ({ workbookId, operations }) => {
|
|
1251
|
+
const request = {
|
|
1252
|
+
workbookId: workbookId,
|
|
1253
|
+
mode: "validate",
|
|
1254
|
+
operations: operations
|
|
1255
|
+
};
|
|
1256
|
+
return jsonResult(runtime.preflightBatch(request));
|
|
1257
|
+
});
|
|
1207
1258
|
registerMcpTool(mcp, "excel.batch.dry_run", {
|
|
1208
1259
|
title: "Dry-run Excel batch",
|
|
1209
1260
|
description: "Compile a batch and report backups, touched ranges, and estimated changes.",
|
|
@@ -1268,6 +1319,601 @@ function registerBatchTools(mcp) {
|
|
|
1268
1319
|
}
|
|
1269
1320
|
return jsonResult(await runtime.applyBatch(request));
|
|
1270
1321
|
});
|
|
1322
|
+
registerMcpTool(mcp, "excel.batch.submit", {
|
|
1323
|
+
title: "Submit Excel batch",
|
|
1324
|
+
description: "Queue a batch mutation and return transaction progress immediately instead of waiting for Excel execution to finish.",
|
|
1325
|
+
inputSchema: {
|
|
1326
|
+
workbookId: z.string(),
|
|
1327
|
+
operations: z.array(z.any()),
|
|
1328
|
+
confirmationToken: z.string().optional(),
|
|
1329
|
+
expectedTargetFingerprints: z.array(z.any()).optional(),
|
|
1330
|
+
agentId: z.string().optional(),
|
|
1331
|
+
agentName: z.string().optional(),
|
|
1332
|
+
taskId: z.string().optional(),
|
|
1333
|
+
role: z.string().optional()
|
|
1334
|
+
},
|
|
1335
|
+
annotations: {
|
|
1336
|
+
readOnlyHint: false,
|
|
1337
|
+
destructiveHint: true,
|
|
1338
|
+
openWorldHint: false
|
|
1339
|
+
}
|
|
1340
|
+
}, async ({ workbookId, operations, confirmationToken, expectedTargetFingerprints, agentId, agentName, taskId, role }) => {
|
|
1341
|
+
const request = {
|
|
1342
|
+
workbookId: workbookId,
|
|
1343
|
+
mode: "apply",
|
|
1344
|
+
operations: operations
|
|
1345
|
+
};
|
|
1346
|
+
if (confirmationToken !== undefined) {
|
|
1347
|
+
request.confirmationToken = confirmationToken;
|
|
1348
|
+
}
|
|
1349
|
+
if (expectedTargetFingerprints !== undefined) {
|
|
1350
|
+
request.expectedTargetFingerprints = expectedTargetFingerprints;
|
|
1351
|
+
}
|
|
1352
|
+
if (agentId !== undefined) {
|
|
1353
|
+
request.agentId = agentId;
|
|
1354
|
+
}
|
|
1355
|
+
if (agentName !== undefined) {
|
|
1356
|
+
request.agentName = agentName;
|
|
1357
|
+
}
|
|
1358
|
+
if (taskId !== undefined) {
|
|
1359
|
+
request.taskId = taskId;
|
|
1360
|
+
}
|
|
1361
|
+
if (role !== undefined) {
|
|
1362
|
+
request.role = role;
|
|
1363
|
+
}
|
|
1364
|
+
return jsonResult(runtime.submitBatch(request));
|
|
1365
|
+
});
|
|
1366
|
+
registerMcpTool(mcp, "excel.batch.submit_chunked", {
|
|
1367
|
+
title: "Submit chunked Excel batch",
|
|
1368
|
+
description: "Preflight a large batch, split safely chunkable operations, queue child transactions, and return one parent job.",
|
|
1369
|
+
inputSchema: {
|
|
1370
|
+
workbookId: z.string(),
|
|
1371
|
+
operations: z.array(z.any()),
|
|
1372
|
+
goal: z.string().optional(),
|
|
1373
|
+
confirmationToken: z.string().optional(),
|
|
1374
|
+
expectedTargetFingerprints: z.array(z.any()).optional(),
|
|
1375
|
+
agentId: z.string().optional(),
|
|
1376
|
+
agentName: z.string().optional(),
|
|
1377
|
+
taskId: z.string().optional(),
|
|
1378
|
+
role: z.string().optional()
|
|
1379
|
+
},
|
|
1380
|
+
annotations: {
|
|
1381
|
+
readOnlyHint: false,
|
|
1382
|
+
destructiveHint: true,
|
|
1383
|
+
openWorldHint: false
|
|
1384
|
+
}
|
|
1385
|
+
}, async ({ workbookId, operations, goal, confirmationToken, expectedTargetFingerprints, agentId, agentName, taskId, role }) => {
|
|
1386
|
+
const request = {
|
|
1387
|
+
workbookId: workbookId,
|
|
1388
|
+
mode: "apply",
|
|
1389
|
+
operations: operations
|
|
1390
|
+
};
|
|
1391
|
+
if (goal !== undefined) {
|
|
1392
|
+
request.progressMessage = goal;
|
|
1393
|
+
}
|
|
1394
|
+
if (confirmationToken !== undefined) {
|
|
1395
|
+
request.confirmationToken = confirmationToken;
|
|
1396
|
+
}
|
|
1397
|
+
if (expectedTargetFingerprints !== undefined) {
|
|
1398
|
+
request.expectedTargetFingerprints = expectedTargetFingerprints;
|
|
1399
|
+
}
|
|
1400
|
+
if (agentId !== undefined) {
|
|
1401
|
+
request.agentId = agentId;
|
|
1402
|
+
}
|
|
1403
|
+
if (agentName !== undefined) {
|
|
1404
|
+
request.agentName = agentName;
|
|
1405
|
+
}
|
|
1406
|
+
if (taskId !== undefined) {
|
|
1407
|
+
request.taskId = taskId;
|
|
1408
|
+
}
|
|
1409
|
+
if (role !== undefined) {
|
|
1410
|
+
request.role = role;
|
|
1411
|
+
}
|
|
1412
|
+
return jsonResult(runtime.submitChunkedBatch(request, { goal }));
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
function registerWorkflowTools(mcp) {
|
|
1416
|
+
registerMcpTool(mcp, "excel.workflow.prepare_session", {
|
|
1417
|
+
title: "Prepare Excel workflow session",
|
|
1418
|
+
description: "Run the standard read-only discovery sequence before workbook mutations: runtime status, active context, capabilities, workbook map, and collaboration status.",
|
|
1419
|
+
inputSchema: {
|
|
1420
|
+
workbookId: z.string().optional(),
|
|
1421
|
+
includePreview: z.boolean().optional()
|
|
1422
|
+
},
|
|
1423
|
+
annotations: {
|
|
1424
|
+
readOnlyHint: true,
|
|
1425
|
+
destructiveHint: false,
|
|
1426
|
+
openWorldHint: false
|
|
1427
|
+
}
|
|
1428
|
+
}, async ({ workbookId, includePreview }) => {
|
|
1429
|
+
return jsonResult({
|
|
1430
|
+
ok: true,
|
|
1431
|
+
workflow: "excel.workflow.prepare_session",
|
|
1432
|
+
...(await workflowPreflight(workbookId, includePreview ?? false))
|
|
1433
|
+
});
|
|
1434
|
+
});
|
|
1435
|
+
registerMcpTool(mcp, "excel.workflow.create_formula_sheet", {
|
|
1436
|
+
title: "Create formula sheet",
|
|
1437
|
+
description: "Create a sheet, write a compact values block, write formulas, apply number formats, and validate formulas in one workflow.",
|
|
1438
|
+
inputSchema: {
|
|
1439
|
+
workbookId: z.string(),
|
|
1440
|
+
sheetName: z.string(),
|
|
1441
|
+
activate: z.boolean().optional(),
|
|
1442
|
+
valuesAddress: z.string(),
|
|
1443
|
+
values: z.array(z.array(z.any())),
|
|
1444
|
+
formulasAddress: z.string(),
|
|
1445
|
+
formulas: z.array(z.array(z.string().nullable())),
|
|
1446
|
+
numberFormatAddress: z.string().optional(),
|
|
1447
|
+
numberFormat: z.array(z.array(z.string())).optional(),
|
|
1448
|
+
validateAddress: z.string().optional()
|
|
1449
|
+
},
|
|
1450
|
+
annotations: {
|
|
1451
|
+
readOnlyHint: false,
|
|
1452
|
+
destructiveHint: true,
|
|
1453
|
+
openWorldHint: false
|
|
1454
|
+
}
|
|
1455
|
+
}, async (args) => {
|
|
1456
|
+
const workbookId = args.workbookId;
|
|
1457
|
+
const preflight = await workflowPreflight(workbookId);
|
|
1458
|
+
const createSheet = await applySingleOperation(args.workbookId, {
|
|
1459
|
+
kind: "sheet.create",
|
|
1460
|
+
workbookId,
|
|
1461
|
+
sheetName: args.sheetName,
|
|
1462
|
+
activate: args.activate,
|
|
1463
|
+
destructiveLevel: "structure",
|
|
1464
|
+
reason: "Workflow create formula sheet"
|
|
1465
|
+
});
|
|
1466
|
+
const operations = [
|
|
1467
|
+
{
|
|
1468
|
+
kind: "range.write_values",
|
|
1469
|
+
operationId: makeId("op"),
|
|
1470
|
+
workbookId,
|
|
1471
|
+
target: { workbookId, sheetName: args.sheetName, address: args.valuesAddress },
|
|
1472
|
+
values: args.values,
|
|
1473
|
+
preserveFormats: true,
|
|
1474
|
+
destructiveLevel: "values",
|
|
1475
|
+
reason: "Workflow write formula sheet values"
|
|
1476
|
+
},
|
|
1477
|
+
{
|
|
1478
|
+
kind: "range.write_formulas",
|
|
1479
|
+
operationId: makeId("op"),
|
|
1480
|
+
workbookId,
|
|
1481
|
+
target: { workbookId, sheetName: args.sheetName, address: args.formulasAddress },
|
|
1482
|
+
formulas: args.formulas,
|
|
1483
|
+
preserveFormats: true,
|
|
1484
|
+
destructiveLevel: "values",
|
|
1485
|
+
reason: "Workflow write formula sheet formulas"
|
|
1486
|
+
}
|
|
1487
|
+
];
|
|
1488
|
+
if (args.numberFormatAddress !== undefined && args.numberFormat !== undefined) {
|
|
1489
|
+
operations.push({
|
|
1490
|
+
kind: "range.write_number_formats",
|
|
1491
|
+
operationId: makeId("op"),
|
|
1492
|
+
workbookId,
|
|
1493
|
+
target: { workbookId, sheetName: args.sheetName, address: args.numberFormatAddress },
|
|
1494
|
+
numberFormat: args.numberFormat,
|
|
1495
|
+
preserveValues: true,
|
|
1496
|
+
destructiveLevel: "format",
|
|
1497
|
+
reason: "Workflow format formula sheet numbers"
|
|
1498
|
+
});
|
|
1499
|
+
}
|
|
1500
|
+
const applyResult = await runtime.applyBatch({ workbookId, mode: "apply", operations });
|
|
1501
|
+
const validation = await runtime.validateFormulas({
|
|
1502
|
+
workbookId,
|
|
1503
|
+
sheetName: args.sheetName,
|
|
1504
|
+
address: args.validateAddress ?? args.formulasAddress
|
|
1505
|
+
});
|
|
1506
|
+
return jsonResult({
|
|
1507
|
+
ok: Boolean(createSheet.ok && applyResult.ok && validation.ok),
|
|
1508
|
+
workflow: "excel.workflow.create_formula_sheet",
|
|
1509
|
+
preflight,
|
|
1510
|
+
createSheet,
|
|
1511
|
+
applyResult,
|
|
1512
|
+
validation,
|
|
1513
|
+
summary: {
|
|
1514
|
+
sheetName: args.sheetName,
|
|
1515
|
+
operationCount: operations.length + 1,
|
|
1516
|
+
transactionId: applyResult.transactionId,
|
|
1517
|
+
backupIds: applyResult.backups,
|
|
1518
|
+
formulaValidationOk: validation.ok,
|
|
1519
|
+
formulaIssueCount: validation.issueCount
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
});
|
|
1523
|
+
registerMcpTool(mcp, "excel.workflow.create_template_report", {
|
|
1524
|
+
title: "Create template report",
|
|
1525
|
+
description: "Run the standard template report workflow: create sheet from template, clear/fill declared regions, compare style fingerprints, repair styles, and validate against the template.",
|
|
1526
|
+
inputSchema: {
|
|
1527
|
+
workbookId: z.string(),
|
|
1528
|
+
templateId: z.string(),
|
|
1529
|
+
newSheetName: z.string(),
|
|
1530
|
+
clearDataRegions: z.boolean().optional(),
|
|
1531
|
+
fillRegions: z
|
|
1532
|
+
.array(z.object({
|
|
1533
|
+
address: z.string(),
|
|
1534
|
+
values: z.array(z.array(z.any()))
|
|
1535
|
+
}))
|
|
1536
|
+
.optional()
|
|
1537
|
+
},
|
|
1538
|
+
annotations: {
|
|
1539
|
+
readOnlyHint: false,
|
|
1540
|
+
destructiveHint: true,
|
|
1541
|
+
openWorldHint: false
|
|
1542
|
+
}
|
|
1543
|
+
}, async ({ workbookId, templateId, newSheetName, clearDataRegions, fillRegions }) => {
|
|
1544
|
+
const preflight = await workflowPreflight(workbookId);
|
|
1545
|
+
const templateResult = runtime.getTemplate(templateId);
|
|
1546
|
+
if (!templateResult.ok || !("template" in templateResult)) {
|
|
1547
|
+
return jsonResult({ ...templateResult, preflight });
|
|
1548
|
+
}
|
|
1549
|
+
const template = templateResult.template;
|
|
1550
|
+
const createSheet = await applySingleOperation(workbookId, {
|
|
1551
|
+
kind: "template.create_sheet_from_template",
|
|
1552
|
+
workbookId: workbookId,
|
|
1553
|
+
templateId: templateId,
|
|
1554
|
+
newSheetName,
|
|
1555
|
+
clearDataRegions: clearDataRegions ?? true,
|
|
1556
|
+
destructiveLevel: "structure",
|
|
1557
|
+
reason: "MCP workflow create template report"
|
|
1558
|
+
});
|
|
1559
|
+
const clearOperations = (clearDataRegions === false ? [] : template.dataRegions).map((address) => ({
|
|
1560
|
+
kind: "range.clear_values_keep_format",
|
|
1561
|
+
operationId: makeId("op"),
|
|
1562
|
+
workbookId: workbookId,
|
|
1563
|
+
destructiveLevel: "values",
|
|
1564
|
+
reason: `Workflow clear data region from template ${templateId}`,
|
|
1565
|
+
target: {
|
|
1566
|
+
workbookId: workbookId,
|
|
1567
|
+
sheetName: newSheetName,
|
|
1568
|
+
address
|
|
1569
|
+
}
|
|
1570
|
+
}));
|
|
1571
|
+
const clearResult = clearOperations.length > 0
|
|
1572
|
+
? await runtime.applyBatch({ workbookId: workbookId, mode: "apply", operations: clearOperations })
|
|
1573
|
+
: { ok: true, skipped: true };
|
|
1574
|
+
const fillOperations = (fillRegions ?? []).map((region) => ({
|
|
1575
|
+
kind: "range.write_values",
|
|
1576
|
+
operationId: makeId("op"),
|
|
1577
|
+
workbookId: workbookId,
|
|
1578
|
+
destructiveLevel: "values",
|
|
1579
|
+
reason: "Workflow fill template region",
|
|
1580
|
+
target: {
|
|
1581
|
+
workbookId: workbookId,
|
|
1582
|
+
sheetName: newSheetName,
|
|
1583
|
+
address: region.address
|
|
1584
|
+
},
|
|
1585
|
+
values: region.values,
|
|
1586
|
+
preserveFormats: true
|
|
1587
|
+
}));
|
|
1588
|
+
const fillResult = fillOperations.length > 0
|
|
1589
|
+
? await runtime.applyBatch({ workbookId: workbookId, mode: "apply", operations: fillOperations })
|
|
1590
|
+
: { ok: true, skipped: true };
|
|
1591
|
+
const styleCompare = await runtime.compareStyleFingerprints({
|
|
1592
|
+
workbookId: workbookId,
|
|
1593
|
+
sourceSheetName: template.sourceSheetName,
|
|
1594
|
+
targetSheetName: newSheetName
|
|
1595
|
+
});
|
|
1596
|
+
const styleRepair = await repairTemplateFromArgs({
|
|
1597
|
+
workbookId,
|
|
1598
|
+
templateId,
|
|
1599
|
+
targetSheetName: newSheetName,
|
|
1600
|
+
repair: ["styles"]
|
|
1601
|
+
});
|
|
1602
|
+
const validation = await runtime.validateSheetAgainstTemplate({
|
|
1603
|
+
workbookId: workbookId,
|
|
1604
|
+
templateId: templateId,
|
|
1605
|
+
targetSheetName: newSheetName
|
|
1606
|
+
});
|
|
1607
|
+
return jsonResult({
|
|
1608
|
+
ok: Boolean(createSheet.ok && clearResult.ok && fillResult.ok && validation.ok),
|
|
1609
|
+
workflow: "excel.workflow.create_template_report",
|
|
1610
|
+
preflight,
|
|
1611
|
+
templateId,
|
|
1612
|
+
sourceSheetName: template.sourceSheetName,
|
|
1613
|
+
targetSheetName: newSheetName,
|
|
1614
|
+
createSheet,
|
|
1615
|
+
clearResult,
|
|
1616
|
+
fillResult,
|
|
1617
|
+
styleCompare,
|
|
1618
|
+
styleRepair,
|
|
1619
|
+
validation,
|
|
1620
|
+
summary: {
|
|
1621
|
+
targetSheetName: newSheetName,
|
|
1622
|
+
clearedRegionCount: clearOperations.length,
|
|
1623
|
+
filledRegionCount: fillOperations.length,
|
|
1624
|
+
styleCompared: Boolean(styleCompare.ok),
|
|
1625
|
+
styleRepairAttempted: true,
|
|
1626
|
+
validated: Boolean(validation.ok)
|
|
1627
|
+
}
|
|
1628
|
+
});
|
|
1629
|
+
});
|
|
1630
|
+
registerMcpTool(mcp, "excel.workflow.create_pivot_chart_summary", {
|
|
1631
|
+
title: "Create pivot and chart summary",
|
|
1632
|
+
description: "Run the standard PivotTable and chart creation workflow: capability check, PivotTable create, refresh, chart create, chart source update/refresh, and PivotTable source validation.",
|
|
1633
|
+
inputSchema: {
|
|
1634
|
+
workbookId: z.string(),
|
|
1635
|
+
pivotTableName: z.string(),
|
|
1636
|
+
sourceSheetName: z.string().optional(),
|
|
1637
|
+
sourceAddress: z.string().optional(),
|
|
1638
|
+
sourceTableName: z.string().optional(),
|
|
1639
|
+
pivotDestinationSheetName: z.string(),
|
|
1640
|
+
pivotDestinationAddress: z.string(),
|
|
1641
|
+
rowFields: z.array(z.string()).optional(),
|
|
1642
|
+
columnFields: z.array(z.string()).optional(),
|
|
1643
|
+
filterFields: z.array(z.string()).optional(),
|
|
1644
|
+
dataFields: z
|
|
1645
|
+
.array(z.object({
|
|
1646
|
+
sourceFieldName: z.string(),
|
|
1647
|
+
name: z.string().optional(),
|
|
1648
|
+
summarizeBy: z.string().optional(),
|
|
1649
|
+
numberFormat: z.string().optional()
|
|
1650
|
+
}))
|
|
1651
|
+
.optional(),
|
|
1652
|
+
chartSheetName: z.string(),
|
|
1653
|
+
chartName: z.string(),
|
|
1654
|
+
chartSourceAddress: z.string(),
|
|
1655
|
+
chartType: z.string(),
|
|
1656
|
+
chartTitle: z.string().optional(),
|
|
1657
|
+
chartPosition: z.object({ startCell: z.string(), endCell: z.string().optional() }).optional()
|
|
1658
|
+
},
|
|
1659
|
+
annotations: {
|
|
1660
|
+
readOnlyHint: false,
|
|
1661
|
+
destructiveHint: true,
|
|
1662
|
+
openWorldHint: false
|
|
1663
|
+
}
|
|
1664
|
+
}, async (args) => {
|
|
1665
|
+
const workbookId = args.workbookId;
|
|
1666
|
+
const preflight = await workflowPreflight(workbookId);
|
|
1667
|
+
const pivotRequest = {
|
|
1668
|
+
workbookId,
|
|
1669
|
+
pivotTableName: args.pivotTableName,
|
|
1670
|
+
destinationSheetName: args.pivotDestinationSheetName,
|
|
1671
|
+
destinationAddress: args.pivotDestinationAddress,
|
|
1672
|
+
refresh: true
|
|
1673
|
+
};
|
|
1674
|
+
if (args.sourceSheetName !== undefined)
|
|
1675
|
+
pivotRequest.sourceSheetName = args.sourceSheetName;
|
|
1676
|
+
if (args.sourceAddress !== undefined)
|
|
1677
|
+
pivotRequest.sourceAddress = args.sourceAddress;
|
|
1678
|
+
if (args.sourceTableName !== undefined)
|
|
1679
|
+
pivotRequest.sourceTableName = args.sourceTableName;
|
|
1680
|
+
if (args.rowFields !== undefined)
|
|
1681
|
+
pivotRequest.rowFields = args.rowFields;
|
|
1682
|
+
if (args.columnFields !== undefined)
|
|
1683
|
+
pivotRequest.columnFields = args.columnFields;
|
|
1684
|
+
if (args.filterFields !== undefined)
|
|
1685
|
+
pivotRequest.filterFields = args.filterFields;
|
|
1686
|
+
if (args.dataFields !== undefined)
|
|
1687
|
+
pivotRequest.dataFields = args.dataFields;
|
|
1688
|
+
const capability = runtime.getPivotCapabilityMatrix(workbookId);
|
|
1689
|
+
const pivotCreate = await runtime.createPivotTable(pivotRequest);
|
|
1690
|
+
const pivotRefresh = await runtime.refreshPivotTable({ workbookId, pivotTableName: args.pivotTableName });
|
|
1691
|
+
const chartCreateRequest = {
|
|
1692
|
+
workbookId,
|
|
1693
|
+
sheetName: args.chartSheetName,
|
|
1694
|
+
chartName: args.chartName,
|
|
1695
|
+
sourceAddress: args.chartSourceAddress,
|
|
1696
|
+
chartType: args.chartType
|
|
1697
|
+
};
|
|
1698
|
+
if (args.chartTitle !== undefined)
|
|
1699
|
+
chartCreateRequest.title = args.chartTitle;
|
|
1700
|
+
if (args.chartPosition !== undefined)
|
|
1701
|
+
chartCreateRequest.position = args.chartPosition;
|
|
1702
|
+
const chartCreate = await runtime.createChart(chartCreateRequest);
|
|
1703
|
+
const chartUpdate = await runtime.updateChartDataSource({
|
|
1704
|
+
workbookId,
|
|
1705
|
+
sheetName: args.chartSheetName,
|
|
1706
|
+
chartName: args.chartName,
|
|
1707
|
+
sourceAddress: args.chartSourceAddress
|
|
1708
|
+
});
|
|
1709
|
+
const chartRefresh = await runtime.refreshChart({ workbookId, sheetName: args.chartSheetName, chartName: args.chartName });
|
|
1710
|
+
const validation = await runtime.validatePivotSource({
|
|
1711
|
+
workbookId,
|
|
1712
|
+
pivotTableName: args.pivotTableName,
|
|
1713
|
+
expectedRowFields: args.rowFields,
|
|
1714
|
+
expectedColumnFields: args.columnFields,
|
|
1715
|
+
expectedFilterFields: args.filterFields,
|
|
1716
|
+
expectedDataFields: Array.isArray(args.dataFields) ? args.dataFields.map((field) => field.sourceFieldName) : undefined,
|
|
1717
|
+
expectedDataFieldSettings: args.dataFields
|
|
1718
|
+
});
|
|
1719
|
+
return jsonResult({
|
|
1720
|
+
ok: Boolean(pivotCreate.ok && pivotRefresh.ok && chartCreate.ok && chartUpdate.ok && validation.ok),
|
|
1721
|
+
workflow: "excel.workflow.create_pivot_chart_summary",
|
|
1722
|
+
preflight,
|
|
1723
|
+
capability,
|
|
1724
|
+
pivotCreate,
|
|
1725
|
+
pivotRefresh,
|
|
1726
|
+
chartCreate,
|
|
1727
|
+
chartUpdate,
|
|
1728
|
+
chartRefresh,
|
|
1729
|
+
validation,
|
|
1730
|
+
summary: {
|
|
1731
|
+
pivotTableName: args.pivotTableName,
|
|
1732
|
+
chartName: args.chartName,
|
|
1733
|
+
refreshed: Boolean(pivotRefresh.ok && chartRefresh.ok),
|
|
1734
|
+
validated: Boolean(validation.ok)
|
|
1735
|
+
}
|
|
1736
|
+
});
|
|
1737
|
+
});
|
|
1738
|
+
registerMcpTool(mcp, "excel.workflow.repair_formula_errors", {
|
|
1739
|
+
title: "Repair formula errors",
|
|
1740
|
+
description: "Run the standard formula repair workflow: preflight, validate/find errors, read formula patterns, inspect dependency graph, repair by filling or writing scoped formulas, and validate again.",
|
|
1741
|
+
inputSchema: {
|
|
1742
|
+
workbookId: z.string(),
|
|
1743
|
+
sheetName: z.string(),
|
|
1744
|
+
errorAddress: z.string(),
|
|
1745
|
+
patternAddress: z.string().optional(),
|
|
1746
|
+
sourceAddress: z.string().optional(),
|
|
1747
|
+
targetAddress: z.string().optional(),
|
|
1748
|
+
direction: z.enum(["down", "right"]).optional(),
|
|
1749
|
+
formulasAddress: z.string().optional(),
|
|
1750
|
+
formulas: z.array(z.array(z.string().nullable())).optional()
|
|
1751
|
+
},
|
|
1752
|
+
annotations: {
|
|
1753
|
+
readOnlyHint: false,
|
|
1754
|
+
destructiveHint: true,
|
|
1755
|
+
openWorldHint: false
|
|
1756
|
+
}
|
|
1757
|
+
}, async (args) => {
|
|
1758
|
+
const workbookId = args.workbookId;
|
|
1759
|
+
const sheetName = args.sheetName;
|
|
1760
|
+
const errorAddress = args.errorAddress;
|
|
1761
|
+
const sourceAddress = args.sourceAddress;
|
|
1762
|
+
const targetAddress = (args.targetAddress ?? args.formulasAddress ?? errorAddress);
|
|
1763
|
+
const preflight = await workflowPreflight(workbookId);
|
|
1764
|
+
const beforeValidation = await runtime.validateFormulas({ workbookId, sheetName, address: errorAddress });
|
|
1765
|
+
const patterns = await runtime.readFormulaPatterns({
|
|
1766
|
+
workbookId,
|
|
1767
|
+
sheetName,
|
|
1768
|
+
address: (args.patternAddress ?? sourceAddress ?? errorAddress)
|
|
1769
|
+
});
|
|
1770
|
+
const dependencyGraph = await runtime.getFormulaDependencyGraph({ workbookId, sheetName, address: errorAddress });
|
|
1771
|
+
let repairResult;
|
|
1772
|
+
if (Array.isArray(args.formulas)) {
|
|
1773
|
+
repairResult = await runtime.applyBatch({
|
|
1774
|
+
workbookId,
|
|
1775
|
+
mode: "apply",
|
|
1776
|
+
operations: [
|
|
1777
|
+
{
|
|
1778
|
+
kind: "range.write_formulas",
|
|
1779
|
+
operationId: makeId("op"),
|
|
1780
|
+
workbookId,
|
|
1781
|
+
target: { workbookId, sheetName, address: targetAddress },
|
|
1782
|
+
formulas: args.formulas,
|
|
1783
|
+
preserveFormats: true,
|
|
1784
|
+
destructiveLevel: "values",
|
|
1785
|
+
reason: "Workflow repair formula errors with scoped formulas"
|
|
1786
|
+
}
|
|
1787
|
+
]
|
|
1788
|
+
});
|
|
1789
|
+
}
|
|
1790
|
+
else if (sourceAddress !== undefined) {
|
|
1791
|
+
repairResult = await runtime.fillFormulaPattern({
|
|
1792
|
+
workbookId,
|
|
1793
|
+
sheetName,
|
|
1794
|
+
sourceAddress,
|
|
1795
|
+
targetAddress,
|
|
1796
|
+
direction: args.direction ?? "down"
|
|
1797
|
+
});
|
|
1798
|
+
}
|
|
1799
|
+
else {
|
|
1800
|
+
repairResult = {
|
|
1801
|
+
ok: false,
|
|
1802
|
+
error: {
|
|
1803
|
+
code: "FORMULA_REPAIR_SOURCE_REQUIRED",
|
|
1804
|
+
message: "Provide sourceAddress for pattern fill or formulas with formulasAddress for scoped formula repair.",
|
|
1805
|
+
retryable: false
|
|
1806
|
+
}
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
const afterValidation = await runtime.validateFormulas({ workbookId, sheetName, address: targetAddress });
|
|
1810
|
+
return jsonResult({
|
|
1811
|
+
ok: Boolean(repairResult.ok && afterValidation.ok),
|
|
1812
|
+
workflow: "excel.workflow.repair_formula_errors",
|
|
1813
|
+
preflight,
|
|
1814
|
+
beforeValidation,
|
|
1815
|
+
patterns,
|
|
1816
|
+
dependencyGraph,
|
|
1817
|
+
repairResult,
|
|
1818
|
+
afterValidation,
|
|
1819
|
+
summary: {
|
|
1820
|
+
sheetName,
|
|
1821
|
+
errorAddress,
|
|
1822
|
+
sourceAddress,
|
|
1823
|
+
targetAddress,
|
|
1824
|
+
usedDirectFormulaWrite: Array.isArray(args.formulas),
|
|
1825
|
+
formulaValidationOk: afterValidation.ok,
|
|
1826
|
+
formulaIssueCount: afterValidation.issueCount
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
registerMcpTool(mcp, "excel.workflow.preview_risky_edit", {
|
|
1831
|
+
title: "Preview risky Excel edit",
|
|
1832
|
+
description: [
|
|
1833
|
+
"Run the full safe-edit workflow for scoped risky changes: before snapshot, plan preview, apply, after snapshot, diff, and rollback preview.",
|
|
1834
|
+
"Use this when an edit must prove what changed and how it can be recovered instead of calling separate snapshot, batch, diff, and transaction tools.",
|
|
1835
|
+
"Provide at least one scoped operation. The workflow applies by default; set apply=false only when the user asks for preview without mutation."
|
|
1836
|
+
].join(" "),
|
|
1837
|
+
inputSchema: {
|
|
1838
|
+
workbookId: z.string(),
|
|
1839
|
+
operations: z.array(z.any()).min(1),
|
|
1840
|
+
reason: z.string().optional(),
|
|
1841
|
+
goal: z.string().optional(),
|
|
1842
|
+
ranges: z
|
|
1843
|
+
.array(z.object({
|
|
1844
|
+
workbookId: z.string().optional(),
|
|
1845
|
+
sheetName: z.string(),
|
|
1846
|
+
address: z.string()
|
|
1847
|
+
}))
|
|
1848
|
+
.optional(),
|
|
1849
|
+
apply: z.boolean().optional(),
|
|
1850
|
+
allowSparseOverwrite: z.boolean().optional(),
|
|
1851
|
+
confirmationToken: z.string().optional(),
|
|
1852
|
+
agentId: z.string().optional(),
|
|
1853
|
+
agentName: z.string().optional(),
|
|
1854
|
+
taskId: z.string().optional(),
|
|
1855
|
+
role: z.string().optional()
|
|
1856
|
+
},
|
|
1857
|
+
annotations: {
|
|
1858
|
+
readOnlyHint: false,
|
|
1859
|
+
destructiveHint: true,
|
|
1860
|
+
openWorldHint: false
|
|
1861
|
+
}
|
|
1862
|
+
}, async (args) => {
|
|
1863
|
+
const workbookId = args.workbookId;
|
|
1864
|
+
const preflight = await workflowPreflight(workbookId);
|
|
1865
|
+
const request = {
|
|
1866
|
+
workbookId,
|
|
1867
|
+
operations: workflowOperations(workbookId, args.operations, args.reason ?? args.goal)
|
|
1868
|
+
};
|
|
1869
|
+
const ranges = workflowRanges(workbookId, args.ranges);
|
|
1870
|
+
if (args.reason !== undefined) {
|
|
1871
|
+
request.reason = args.reason;
|
|
1872
|
+
}
|
|
1873
|
+
if (args.goal !== undefined) {
|
|
1874
|
+
request.goal = args.goal;
|
|
1875
|
+
}
|
|
1876
|
+
if (ranges !== undefined) {
|
|
1877
|
+
request.ranges = ranges;
|
|
1878
|
+
}
|
|
1879
|
+
if (args.apply !== undefined) {
|
|
1880
|
+
request.apply = args.apply;
|
|
1881
|
+
}
|
|
1882
|
+
if (args.allowSparseOverwrite !== undefined) {
|
|
1883
|
+
request.allowSparseOverwrite = args.allowSparseOverwrite;
|
|
1884
|
+
}
|
|
1885
|
+
if (args.confirmationToken !== undefined) {
|
|
1886
|
+
request.confirmationToken = args.confirmationToken;
|
|
1887
|
+
}
|
|
1888
|
+
if (args.agentId !== undefined) {
|
|
1889
|
+
request.agentId = args.agentId;
|
|
1890
|
+
}
|
|
1891
|
+
if (args.agentName !== undefined) {
|
|
1892
|
+
request.agentName = args.agentName;
|
|
1893
|
+
}
|
|
1894
|
+
if (args.taskId !== undefined) {
|
|
1895
|
+
request.taskId = args.taskId;
|
|
1896
|
+
}
|
|
1897
|
+
if (args.role !== undefined) {
|
|
1898
|
+
request.role = args.role;
|
|
1899
|
+
}
|
|
1900
|
+
const result = await runtime.previewRiskyEdit(request);
|
|
1901
|
+
return jsonResult({ ...result, preflight });
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
async function workflowPreflight(workbookId, includePreview = true) {
|
|
1905
|
+
const status = runtime.getStatus();
|
|
1906
|
+
const activeContext = await runtime.getActiveContext();
|
|
1907
|
+
const activeWorkbookId = activeContext.activeWorkbook?.workbookId;
|
|
1908
|
+
const resolvedWorkbookId = workbookId ?? activeWorkbookId;
|
|
1909
|
+
return {
|
|
1910
|
+
status,
|
|
1911
|
+
activeContext,
|
|
1912
|
+
capabilities: runtime.getCapabilities({ includePreview }),
|
|
1913
|
+
workbookMap: await runtime.getWorkbookMap(),
|
|
1914
|
+
collaboration: runtime.getCollaborationStatus(resolvedWorkbookId),
|
|
1915
|
+
workbookId: resolvedWorkbookId
|
|
1916
|
+
};
|
|
1271
1917
|
}
|
|
1272
1918
|
function registerPlanTools(mcp) {
|
|
1273
1919
|
registerMcpTool(mcp, "excel.plan.create", {
|
|
@@ -1715,7 +2361,7 @@ function styleCopyRequest(args) {
|
|
|
1715
2361
|
function registerFormulaTools(mcp) {
|
|
1716
2362
|
registerMcpTool(mcp, "excel.formula.read_patterns", {
|
|
1717
2363
|
title: "Read formula patterns",
|
|
1718
|
-
description: "Capture formulas, R1C1 pattern hashes, and pattern groups for a sheet or range.",
|
|
2364
|
+
description: "Capture formulas, R1C1 pattern hashes, and pattern groups for a sheet or range. Use before formula repair; this is diagnostic and does not repair formulas by itself.",
|
|
1719
2365
|
inputSchema: formulaPatternSchema(),
|
|
1720
2366
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
1721
2367
|
}, async (args) => jsonResult(await runtime.readFormulaPatterns(formulaPatternRequest(args))));
|
|
@@ -1758,13 +2404,13 @@ function registerFormulaTools(mcp) {
|
|
|
1758
2404
|
})));
|
|
1759
2405
|
registerMcpTool(mcp, "excel.formula.repair_patterns", {
|
|
1760
2406
|
title: "Repair formula patterns",
|
|
1761
|
-
description: "Repair formulas from a registered template.",
|
|
2407
|
+
description: "Repair formulas from a registered template. If no template fits, use formula fill tools or scoped excel.range.write_formulas, then validate.",
|
|
1762
2408
|
inputSchema: templateRepairSchema(),
|
|
1763
2409
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
1764
2410
|
}, async (args) => jsonResult(await repairTemplateFromArgs({ ...args, repair: ["formulas"] })));
|
|
1765
2411
|
registerMcpTool(mcp, "excel.formula.find_errors", {
|
|
1766
2412
|
title: "Find formula errors",
|
|
1767
|
-
description: "Find cells with formula errors in a sheet/range.",
|
|
2413
|
+
description: "Find cells with formula errors in a sheet/range before reading patterns and repairing formulas.",
|
|
1768
2414
|
inputSchema: validationRangeSchema(),
|
|
1769
2415
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
1770
2416
|
}, async (args) => jsonResult(await runtime.validateFormulas({
|
|
@@ -1988,7 +2634,7 @@ function registerTableTools(mcp) {
|
|
|
1988
2634
|
}, async (args) => jsonResult(await runtime.resizeTable({ ...tableSelector(args), address: args.address })));
|
|
1989
2635
|
registerMcpTool(mcp, "excel.table.reorder_columns", {
|
|
1990
2636
|
title: "Reorder Excel table columns",
|
|
1991
|
-
description: "Reorder columns in an existing structured table without clearing or
|
|
2637
|
+
description: "Reorder columns in an existing structured table without clearing, recreating, or range-rewriting the table.",
|
|
1992
2638
|
inputSchema: {
|
|
1993
2639
|
...tableSelectorSchema(),
|
|
1994
2640
|
columnOrder: z.array(z.union([z.string(), z.number().int().min(0)]))
|
|
@@ -2000,7 +2646,7 @@ function registerTableTools(mcp) {
|
|
|
2000
2646
|
})));
|
|
2001
2647
|
registerMcpTool(mcp, "excel.table.append_rows", {
|
|
2002
2648
|
title: "Append Excel table rows",
|
|
2003
|
-
description: "Append one or more rows to a structured table.",
|
|
2649
|
+
description: "Append one or more rows to a structured table while preserving table style, filters, totals, and structured formulas.",
|
|
2004
2650
|
inputSchema: {
|
|
2005
2651
|
...tableSelectorSchema(),
|
|
2006
2652
|
values: z.array(z.array(z.any())),
|
|
@@ -2016,7 +2662,7 @@ function registerTableTools(mcp) {
|
|
|
2016
2662
|
})));
|
|
2017
2663
|
registerMcpTool(mcp, "excel.table.update_rows", {
|
|
2018
2664
|
title: "Update Excel table rows",
|
|
2019
|
-
description: "Update table rows by zero-based table-row index.",
|
|
2665
|
+
description: "Update specific table rows by zero-based table-row index without rewriting the whole table.",
|
|
2020
2666
|
inputSchema: {
|
|
2021
2667
|
...tableSelectorSchema(),
|
|
2022
2668
|
rows: z.array(z.object({ index: z.number().int().min(0), values: z.array(z.any()) }))
|
|
@@ -2037,7 +2683,7 @@ function registerTableTools(mcp) {
|
|
|
2037
2683
|
}, async (args) => jsonResult(await runtime.clearTableFilters(tableSelector(args))));
|
|
2038
2684
|
registerMcpTool(mcp, "excel.table.apply_filters", {
|
|
2039
2685
|
title: "Apply table filters",
|
|
2040
|
-
description: "Apply Office.js filter criteria to table columns.",
|
|
2686
|
+
description: "Apply Office.js filter criteria to table columns without reading or rewriting the full table body.",
|
|
2041
2687
|
inputSchema: tableFilterSchema(),
|
|
2042
2688
|
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
|
|
2043
2689
|
}, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
|
|
@@ -2049,7 +2695,7 @@ function registerTableTools(mcp) {
|
|
|
2049
2695
|
}, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
|
|
2050
2696
|
registerMcpTool(mcp, "excel.table.sort", {
|
|
2051
2697
|
title: "Sort Excel table",
|
|
2052
|
-
description: "Apply table sort fields.",
|
|
2698
|
+
description: "Apply table sort fields while preserving the structured table, filters, formulas, and styles.",
|
|
2053
2699
|
inputSchema: tableSortSchema(),
|
|
2054
2700
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
2055
2701
|
}, async (args) => jsonResult(await runtime.sortTable({ ...tableSelector(args), fields: args.fields, matchCase: args.matchCase, method: args.method })));
|
|
@@ -2780,6 +3426,22 @@ function registerTransactionTools(mcp) {
|
|
|
2780
3426
|
inputSchema: { transactionId: z.string() },
|
|
2781
3427
|
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
2782
3428
|
}, async ({ transactionId }) => jsonResult(runtime.getTransaction(transactionId)));
|
|
3429
|
+
registerMcpTool(mcp, "excel.transaction.wait", {
|
|
3430
|
+
title: "Wait for Excel transaction",
|
|
3431
|
+
description: "Wait for a queued or applying workbook transaction to reach a terminal status.",
|
|
3432
|
+
inputSchema: {
|
|
3433
|
+
transactionId: z.string(),
|
|
3434
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
3435
|
+
pollMs: z.number().int().positive().optional()
|
|
3436
|
+
},
|
|
3437
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3438
|
+
}, async ({ transactionId, timeoutMs, pollMs }) => jsonResult(await runtime.waitTransaction(transactionId, timeoutMs, pollMs)));
|
|
3439
|
+
registerMcpTool(mcp, "excel.transaction.cancel", {
|
|
3440
|
+
title: "Cancel Excel transaction",
|
|
3441
|
+
description: "Cancel a queued workbook transaction before it starts applying in Excel.",
|
|
3442
|
+
inputSchema: { transactionId: z.string() },
|
|
3443
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
3444
|
+
}, async ({ transactionId }) => jsonResult(runtime.cancelTransaction(transactionId)));
|
|
2783
3445
|
registerMcpTool(mcp, "excel.transaction.preview_rollback", {
|
|
2784
3446
|
title: "Preview transaction rollback",
|
|
2785
3447
|
description: "Check whether a transaction can be rolled back without overwriting later work.",
|
|
@@ -2805,6 +3467,36 @@ function registerTransactionTools(mcp) {
|
|
|
2805
3467
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
2806
3468
|
}, async ({ transactionId, confirmationToken }) => jsonResult(await runtime.rollbackTransactionChain(transactionId, confirmationToken)));
|
|
2807
3469
|
}
|
|
3470
|
+
function registerJobTools(mcp) {
|
|
3471
|
+
registerMcpTool(mcp, "excel.job.list", {
|
|
3472
|
+
title: "List Excel jobs",
|
|
3473
|
+
description: "List parent jobs that group multiple queued workbook transactions.",
|
|
3474
|
+
inputSchema: { workbookId: z.string().optional() },
|
|
3475
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3476
|
+
}, async ({ workbookId }) => jsonResult(runtime.listJobs(workbookId)));
|
|
3477
|
+
registerMcpTool(mcp, "excel.job.get", {
|
|
3478
|
+
title: "Get Excel job",
|
|
3479
|
+
description: "Return one parent workbook job with aggregate progress across child transactions.",
|
|
3480
|
+
inputSchema: { jobId: z.string() },
|
|
3481
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3482
|
+
}, async ({ jobId }) => jsonResult(runtime.getJob(jobId)));
|
|
3483
|
+
registerMcpTool(mcp, "excel.job.wait", {
|
|
3484
|
+
title: "Wait for Excel job",
|
|
3485
|
+
description: "Wait for a parent workbook job to reach a terminal status.",
|
|
3486
|
+
inputSchema: {
|
|
3487
|
+
jobId: z.string(),
|
|
3488
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
3489
|
+
pollMs: z.number().int().positive().optional()
|
|
3490
|
+
},
|
|
3491
|
+
annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
3492
|
+
}, async ({ jobId, timeoutMs, pollMs }) => jsonResult(await runtime.waitJob(jobId, timeoutMs, pollMs)));
|
|
3493
|
+
registerMcpTool(mcp, "excel.job.cancel", {
|
|
3494
|
+
title: "Cancel Excel job",
|
|
3495
|
+
description: "Cancel queued child transactions for a parent workbook job.",
|
|
3496
|
+
inputSchema: { jobId: z.string() },
|
|
3497
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
3498
|
+
}, async ({ jobId }) => jsonResult(runtime.cancelJob(jobId)));
|
|
3499
|
+
}
|
|
2808
3500
|
function registerPermissionsTools(mcp) {
|
|
2809
3501
|
registerMcpTool(mcp, "excel.permissions.get", {
|
|
2810
3502
|
title: "Get permissions",
|
|
@@ -3158,7 +3850,7 @@ function registerSnapshotTools(mcp) {
|
|
|
3158
3850
|
for (const name of ["excel.snapshot.create", "excel.snapshot.refresh"]) {
|
|
3159
3851
|
registerMcpTool(mcp, name, {
|
|
3160
3852
|
title: name.replace(/^excel\./, "").replace(/\./g, " "),
|
|
3161
|
-
description: "Create or refresh a workbook snapshot.",
|
|
3853
|
+
description: "Create or refresh a workbook snapshot. For risky edits, create before and after snapshots so diff tools can compare two snapshot IDs.",
|
|
3162
3854
|
inputSchema: name.endsWith(".refresh")
|
|
3163
3855
|
? { snapshotId: z.string(), reason: z.string().optional() }
|
|
3164
3856
|
: snapshotInputSchema(),
|
|
@@ -3192,7 +3884,7 @@ function registerSnapshotTools(mcp) {
|
|
|
3192
3884
|
}, async ({ workbookId }) => jsonResult(runtime.listSnapshots(workbookId)));
|
|
3193
3885
|
registerMcpTool(mcp, "excel.snapshot.compare", {
|
|
3194
3886
|
title: "Compare snapshots",
|
|
3195
|
-
description: "Compare two workbook snapshots.",
|
|
3887
|
+
description: "Compare two workbook snapshots after capturing before and after states.",
|
|
3196
3888
|
inputSchema: {
|
|
3197
3889
|
leftSnapshotId: z.string(),
|
|
3198
3890
|
rightSnapshotId: z.string()
|
|
@@ -3216,7 +3908,7 @@ function registerDiffTools(mcp) {
|
|
|
3216
3908
|
for (const name of ["excel.diff.create", "excel.diff.summarize", "excel.diff.get_details", "excel.diff.export_json"]) {
|
|
3217
3909
|
registerMcpTool(mcp, name, {
|
|
3218
3910
|
title: name.replace(/^excel\./, "").replace(/\./g, " "),
|
|
3219
|
-
description: "Create or return a diff between two stored snapshots.",
|
|
3911
|
+
description: "Create or return a diff between two stored snapshots. Call after the second snapshot and before rollback preview.",
|
|
3220
3912
|
inputSchema: {
|
|
3221
3913
|
leftSnapshotId: z.string(),
|
|
3222
3914
|
rightSnapshotId: z.string()
|
|
@@ -3697,10 +4389,19 @@ async function repairTemplateFromArgs(args) {
|
|
|
3697
4389
|
}
|
|
3698
4390
|
return runtime.repairSheetFromTemplate(request);
|
|
3699
4391
|
}
|
|
3700
|
-
function registerRangeOperation(mcp, name, inputSchema,
|
|
4392
|
+
function registerRangeOperation(mcp, name, inputSchema, descriptionOrCreateOperation, maybeCreateOperation) {
|
|
4393
|
+
const description = typeof descriptionOrCreateOperation === "string"
|
|
4394
|
+
? descriptionOrCreateOperation
|
|
4395
|
+
: `Apply ${name} through the reversible batch pipeline.`;
|
|
4396
|
+
const createOperation = typeof descriptionOrCreateOperation === "function"
|
|
4397
|
+
? descriptionOrCreateOperation
|
|
4398
|
+
: maybeCreateOperation;
|
|
4399
|
+
if (!createOperation) {
|
|
4400
|
+
throw new Error(`Missing operation factory for ${name}`);
|
|
4401
|
+
}
|
|
3701
4402
|
registerMcpTool(mcp, name, {
|
|
3702
4403
|
title: name.replace(/^excel\./, "").replace(/\./g, " "),
|
|
3703
|
-
description
|
|
4404
|
+
description,
|
|
3704
4405
|
inputSchema,
|
|
3705
4406
|
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
|
|
3706
4407
|
}, async (args) => jsonResult(await applySingleOperation(args.workbookId, createOperation(args))));
|
|
@@ -3720,6 +4421,157 @@ async function applySingleOperation(workbookId, operation) {
|
|
|
3720
4421
|
]
|
|
3721
4422
|
});
|
|
3722
4423
|
}
|
|
4424
|
+
function workflowOperations(workbookId, operations, reason) {
|
|
4425
|
+
return operations.map((operation, index) => workflowOperation(workbookId, operation, reason, index));
|
|
4426
|
+
}
|
|
4427
|
+
function workflowOperation(workbookId, operation, reason, index) {
|
|
4428
|
+
if (!operation || typeof operation !== "object") {
|
|
4429
|
+
throw new Error(`Workflow operation ${index + 1} must be an object.`);
|
|
4430
|
+
}
|
|
4431
|
+
const record = operation;
|
|
4432
|
+
if (typeof record.kind === "string" && !record.kind.startsWith("excel.")) {
|
|
4433
|
+
return {
|
|
4434
|
+
...record,
|
|
4435
|
+
workbookId: (record.workbookId ?? workbookId),
|
|
4436
|
+
operationId: (record.operationId ?? makeId("op")),
|
|
4437
|
+
reason: record.reason ?? reason ?? "MCP workflow risky edit",
|
|
4438
|
+
destructiveLevel: record.destructiveLevel ?? workflowDestructiveLevel(record.kind)
|
|
4439
|
+
};
|
|
4440
|
+
}
|
|
4441
|
+
const toolName = String(record.tool ?? record.name ?? record.operation ?? record.kind ?? "");
|
|
4442
|
+
const args = (record.args ?? record.input ?? record.parameters ?? record);
|
|
4443
|
+
switch (toolName) {
|
|
4444
|
+
case "excel.range.write_values":
|
|
4445
|
+
case "range.write_values":
|
|
4446
|
+
assertWorkflowRangeArgs(toolName, args);
|
|
4447
|
+
return {
|
|
4448
|
+
kind: "range.write_values",
|
|
4449
|
+
operationId: makeId("op"),
|
|
4450
|
+
workbookId,
|
|
4451
|
+
target: workflowTarget(workbookId, args),
|
|
4452
|
+
values: args.values,
|
|
4453
|
+
preserveFormats: true,
|
|
4454
|
+
destructiveLevel: "values",
|
|
4455
|
+
reason: reason ?? "MCP workflow write values"
|
|
4456
|
+
};
|
|
4457
|
+
case "excel.range.write_formulas":
|
|
4458
|
+
case "range.write_formulas":
|
|
4459
|
+
assertWorkflowRangeArgs(toolName, args);
|
|
4460
|
+
return {
|
|
4461
|
+
kind: "range.write_formulas",
|
|
4462
|
+
operationId: makeId("op"),
|
|
4463
|
+
workbookId,
|
|
4464
|
+
target: workflowTarget(workbookId, args),
|
|
4465
|
+
formulas: args.formulas,
|
|
4466
|
+
preserveFormats: true,
|
|
4467
|
+
destructiveLevel: "values",
|
|
4468
|
+
reason: reason ?? "MCP workflow write formulas"
|
|
4469
|
+
};
|
|
4470
|
+
case "excel.range.write_number_formats":
|
|
4471
|
+
case "range.write_number_formats":
|
|
4472
|
+
assertWorkflowRangeArgs(toolName, args);
|
|
4473
|
+
return {
|
|
4474
|
+
kind: "range.write_number_formats",
|
|
4475
|
+
operationId: makeId("op"),
|
|
4476
|
+
workbookId,
|
|
4477
|
+
target: workflowTarget(workbookId, args),
|
|
4478
|
+
numberFormat: args.numberFormat,
|
|
4479
|
+
preserveValues: true,
|
|
4480
|
+
destructiveLevel: "format",
|
|
4481
|
+
reason: reason ?? "MCP workflow write number formats"
|
|
4482
|
+
};
|
|
4483
|
+
case "excel.range.clear_values_keep_format":
|
|
4484
|
+
case "range.clear_values_keep_format":
|
|
4485
|
+
assertWorkflowRangeArgs(toolName, args);
|
|
4486
|
+
return {
|
|
4487
|
+
kind: "range.clear_values_keep_format",
|
|
4488
|
+
operationId: makeId("op"),
|
|
4489
|
+
workbookId,
|
|
4490
|
+
target: workflowTarget(workbookId, args),
|
|
4491
|
+
destructiveLevel: "values",
|
|
4492
|
+
reason: reason ?? "MCP workflow clear values"
|
|
4493
|
+
};
|
|
4494
|
+
case "excel.sheet.create":
|
|
4495
|
+
case "sheet.create":
|
|
4496
|
+
if (typeof args.sheetName !== "string") {
|
|
4497
|
+
throw new Error(`${toolName} requires sheetName.`);
|
|
4498
|
+
}
|
|
4499
|
+
return {
|
|
4500
|
+
kind: "sheet.create",
|
|
4501
|
+
operationId: makeId("op"),
|
|
4502
|
+
workbookId,
|
|
4503
|
+
sheetName: args.sheetName,
|
|
4504
|
+
activate: args.activate,
|
|
4505
|
+
destructiveLevel: "structure",
|
|
4506
|
+
reason: reason ?? "MCP workflow sheet create"
|
|
4507
|
+
};
|
|
4508
|
+
default:
|
|
4509
|
+
throw new Error(`Unsupported workflow operation: ${toolName || "unknown"}. Use scoped range writes, clear-values-keep-format, sheet.create, or canonical ExcelOperation objects.`);
|
|
4510
|
+
}
|
|
4511
|
+
}
|
|
4512
|
+
function workflowRanges(workbookId, ranges) {
|
|
4513
|
+
if (!Array.isArray(ranges)) {
|
|
4514
|
+
return undefined;
|
|
4515
|
+
}
|
|
4516
|
+
return ranges.map((range, index) => {
|
|
4517
|
+
if (!range || typeof range !== "object") {
|
|
4518
|
+
throw new Error(`Workflow range ${index + 1} must be an object.`);
|
|
4519
|
+
}
|
|
4520
|
+
const record = range;
|
|
4521
|
+
if (typeof record.sheetName !== "string" || typeof record.address !== "string") {
|
|
4522
|
+
throw new Error(`Workflow range ${index + 1} requires sheetName and address.`);
|
|
4523
|
+
}
|
|
4524
|
+
return {
|
|
4525
|
+
workbookId: (record.workbookId ?? workbookId),
|
|
4526
|
+
sheetName: record.sheetName,
|
|
4527
|
+
address: record.address
|
|
4528
|
+
};
|
|
4529
|
+
});
|
|
4530
|
+
}
|
|
4531
|
+
function workflowTarget(workbookId, args) {
|
|
4532
|
+
return {
|
|
4533
|
+
workbookId: (args.workbookId ?? workbookId),
|
|
4534
|
+
sheetName: args.sheetName,
|
|
4535
|
+
address: args.address
|
|
4536
|
+
};
|
|
4537
|
+
}
|
|
4538
|
+
function assertWorkflowRangeArgs(toolName, args) {
|
|
4539
|
+
if (typeof args.sheetName !== "string" || typeof args.address !== "string") {
|
|
4540
|
+
throw new Error(`${toolName} requires sheetName and address so the workflow can snapshot a scoped range.`);
|
|
4541
|
+
}
|
|
4542
|
+
}
|
|
4543
|
+
function workflowDestructiveLevel(kind) {
|
|
4544
|
+
if (kind.startsWith("sheet.") || kind.startsWith("template.")) {
|
|
4545
|
+
return "structure";
|
|
4546
|
+
}
|
|
4547
|
+
if (kind.includes("style") || kind.includes("format")) {
|
|
4548
|
+
return "format";
|
|
4549
|
+
}
|
|
4550
|
+
if (kind.startsWith("range.")) {
|
|
4551
|
+
return "values";
|
|
4552
|
+
}
|
|
4553
|
+
return "workbook";
|
|
4554
|
+
}
|
|
4555
|
+
function styleEntriesToOperations(workbookId, entries, reason) {
|
|
4556
|
+
return entries.map((entry) => ({
|
|
4557
|
+
kind: "range.write_styles",
|
|
4558
|
+
operationId: makeId("op"),
|
|
4559
|
+
workbookId,
|
|
4560
|
+
target: {
|
|
4561
|
+
workbookId,
|
|
4562
|
+
sheetName: entry.sheetName,
|
|
4563
|
+
address: entry.address
|
|
4564
|
+
},
|
|
4565
|
+
style: entry.style,
|
|
4566
|
+
preserveValues: true,
|
|
4567
|
+
destructiveLevel: "format",
|
|
4568
|
+
reason: entry.reason ?? reason ?? "MCP bulk range write styles"
|
|
4569
|
+
}));
|
|
4570
|
+
}
|
|
4571
|
+
function styleBatchChunkSize() {
|
|
4572
|
+
const value = Number(process.env.OPEN_WORKBOOK_STYLE_BATCH_CHUNK_SIZE ?? 25);
|
|
4573
|
+
return Number.isFinite(value) && value > 0 ? Math.round(value) : 25;
|
|
4574
|
+
}
|
|
3723
4575
|
function targetFromArgs(args) {
|
|
3724
4576
|
return {
|
|
3725
4577
|
workbookId: args.workbookId,
|