@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.
@@ -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.4";
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 recreating the table.",
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, createOperation) {
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: `Apply ${name} through the reversible batch pipeline.`,
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,