@components-kit/open-workbook-mcp-server 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -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.3";
48
+ const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.5";
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);
@@ -262,7 +263,7 @@ function registerPrompts(mcp) {
262
263
  "1. Locate errors with `excel.formula.find_errors` and `excel.validate.no_formula_errors`.",
263
264
  "2. Read formula patterns and dependency graph with `excel.formula.read_patterns`, `excel.formula.get_dependency_graph`, `trace_precedents`, and `trace_dependents`.",
264
265
  "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`.",
266
+ "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
267
  "5. Preview, apply, recalculate, and re-run formula validation before reporting success."
267
268
  ]);
268
269
  registerWorkflowPrompt(mcp, "excel.prompts.format_like_template", "Format like template", "Repair styling and layout consistency using registered template fingerprints.", promptArgs, (args) => [
@@ -1066,7 +1067,7 @@ function registerRangeTools(mcp) {
1066
1067
  sheetName: z.string(),
1067
1068
  address: z.string(),
1068
1069
  values: z.array(z.array(z.any()))
1069
- }, (args) => ({
1070
+ }, "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
1071
  kind: "range.write_values",
1071
1072
  workbookId: args.workbookId,
1072
1073
  target: targetFromArgs(args),
@@ -1080,7 +1081,7 @@ function registerRangeTools(mcp) {
1080
1081
  sheetName: z.string(),
1081
1082
  address: z.string(),
1082
1083
  formulas: z.array(z.array(z.string().nullable()))
1083
- }, (args) => ({
1084
+ }, "Write formulas through the reversible batch pipeline while preserving formats. Use this for cells beginning with = instead of excel.range.write_values.", (args) => ({
1084
1085
  kind: "range.write_formulas",
1085
1086
  workbookId: args.workbookId,
1086
1087
  target: targetFromArgs(args),
@@ -1094,7 +1095,7 @@ function registerRangeTools(mcp) {
1094
1095
  sheetName: z.string(),
1095
1096
  address: z.string(),
1096
1097
  numberFormat: z.array(z.array(z.string()))
1097
- }, (args) => ({
1098
+ }, "Write number formats through the reversible batch pipeline without changing values or formulas.", (args) => ({
1098
1099
  kind: "range.write_number_formats",
1099
1100
  workbookId: args.workbookId,
1100
1101
  target: targetFromArgs(args),
@@ -1269,6 +1270,509 @@ function registerBatchTools(mcp) {
1269
1270
  return jsonResult(await runtime.applyBatch(request));
1270
1271
  });
1271
1272
  }
1273
+ function registerWorkflowTools(mcp) {
1274
+ registerMcpTool(mcp, "excel.workflow.prepare_session", {
1275
+ title: "Prepare Excel workflow session",
1276
+ description: "Run the standard read-only discovery sequence before workbook mutations: runtime status, active context, capabilities, workbook map, and collaboration status.",
1277
+ inputSchema: {
1278
+ workbookId: z.string().optional(),
1279
+ includePreview: z.boolean().optional()
1280
+ },
1281
+ annotations: {
1282
+ readOnlyHint: true,
1283
+ destructiveHint: false,
1284
+ openWorldHint: false
1285
+ }
1286
+ }, async ({ workbookId, includePreview }) => {
1287
+ return jsonResult({
1288
+ ok: true,
1289
+ workflow: "excel.workflow.prepare_session",
1290
+ ...(await workflowPreflight(workbookId, includePreview ?? false))
1291
+ });
1292
+ });
1293
+ registerMcpTool(mcp, "excel.workflow.create_formula_sheet", {
1294
+ title: "Create formula sheet",
1295
+ description: "Create a sheet, write a compact values block, write formulas, apply number formats, and validate formulas in one workflow.",
1296
+ inputSchema: {
1297
+ workbookId: z.string(),
1298
+ sheetName: z.string(),
1299
+ activate: z.boolean().optional(),
1300
+ valuesAddress: z.string(),
1301
+ values: z.array(z.array(z.any())),
1302
+ formulasAddress: z.string(),
1303
+ formulas: z.array(z.array(z.string().nullable())),
1304
+ numberFormatAddress: z.string().optional(),
1305
+ numberFormat: z.array(z.array(z.string())).optional(),
1306
+ validateAddress: z.string().optional()
1307
+ },
1308
+ annotations: {
1309
+ readOnlyHint: false,
1310
+ destructiveHint: true,
1311
+ openWorldHint: false
1312
+ }
1313
+ }, async (args) => {
1314
+ const workbookId = args.workbookId;
1315
+ const preflight = await workflowPreflight(workbookId);
1316
+ const createSheet = await applySingleOperation(args.workbookId, {
1317
+ kind: "sheet.create",
1318
+ workbookId,
1319
+ sheetName: args.sheetName,
1320
+ activate: args.activate,
1321
+ destructiveLevel: "structure",
1322
+ reason: "Workflow create formula sheet"
1323
+ });
1324
+ const operations = [
1325
+ {
1326
+ kind: "range.write_values",
1327
+ operationId: makeId("op"),
1328
+ workbookId,
1329
+ target: { workbookId, sheetName: args.sheetName, address: args.valuesAddress },
1330
+ values: args.values,
1331
+ preserveFormats: true,
1332
+ destructiveLevel: "values",
1333
+ reason: "Workflow write formula sheet values"
1334
+ },
1335
+ {
1336
+ kind: "range.write_formulas",
1337
+ operationId: makeId("op"),
1338
+ workbookId,
1339
+ target: { workbookId, sheetName: args.sheetName, address: args.formulasAddress },
1340
+ formulas: args.formulas,
1341
+ preserveFormats: true,
1342
+ destructiveLevel: "values",
1343
+ reason: "Workflow write formula sheet formulas"
1344
+ }
1345
+ ];
1346
+ if (args.numberFormatAddress !== undefined && args.numberFormat !== undefined) {
1347
+ operations.push({
1348
+ kind: "range.write_number_formats",
1349
+ operationId: makeId("op"),
1350
+ workbookId,
1351
+ target: { workbookId, sheetName: args.sheetName, address: args.numberFormatAddress },
1352
+ numberFormat: args.numberFormat,
1353
+ preserveValues: true,
1354
+ destructiveLevel: "format",
1355
+ reason: "Workflow format formula sheet numbers"
1356
+ });
1357
+ }
1358
+ const applyResult = await runtime.applyBatch({ workbookId, mode: "apply", operations });
1359
+ const validation = await runtime.validateFormulas({
1360
+ workbookId,
1361
+ sheetName: args.sheetName,
1362
+ address: args.validateAddress ?? args.formulasAddress
1363
+ });
1364
+ return jsonResult({
1365
+ ok: Boolean(createSheet.ok && applyResult.ok && validation.ok),
1366
+ workflow: "excel.workflow.create_formula_sheet",
1367
+ preflight,
1368
+ createSheet,
1369
+ applyResult,
1370
+ validation,
1371
+ summary: {
1372
+ sheetName: args.sheetName,
1373
+ operationCount: operations.length + 1,
1374
+ transactionId: applyResult.transactionId,
1375
+ backupIds: applyResult.backups,
1376
+ formulaValidationOk: validation.ok,
1377
+ formulaIssueCount: validation.issueCount
1378
+ }
1379
+ });
1380
+ });
1381
+ registerMcpTool(mcp, "excel.workflow.create_template_report", {
1382
+ title: "Create template report",
1383
+ 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.",
1384
+ inputSchema: {
1385
+ workbookId: z.string(),
1386
+ templateId: z.string(),
1387
+ newSheetName: z.string(),
1388
+ clearDataRegions: z.boolean().optional(),
1389
+ fillRegions: z
1390
+ .array(z.object({
1391
+ address: z.string(),
1392
+ values: z.array(z.array(z.any()))
1393
+ }))
1394
+ .optional()
1395
+ },
1396
+ annotations: {
1397
+ readOnlyHint: false,
1398
+ destructiveHint: true,
1399
+ openWorldHint: false
1400
+ }
1401
+ }, async ({ workbookId, templateId, newSheetName, clearDataRegions, fillRegions }) => {
1402
+ const preflight = await workflowPreflight(workbookId);
1403
+ const templateResult = runtime.getTemplate(templateId);
1404
+ if (!templateResult.ok || !("template" in templateResult)) {
1405
+ return jsonResult({ ...templateResult, preflight });
1406
+ }
1407
+ const template = templateResult.template;
1408
+ const createSheet = await applySingleOperation(workbookId, {
1409
+ kind: "template.create_sheet_from_template",
1410
+ workbookId: workbookId,
1411
+ templateId: templateId,
1412
+ newSheetName,
1413
+ clearDataRegions: clearDataRegions ?? true,
1414
+ destructiveLevel: "structure",
1415
+ reason: "MCP workflow create template report"
1416
+ });
1417
+ const clearOperations = (clearDataRegions === false ? [] : template.dataRegions).map((address) => ({
1418
+ kind: "range.clear_values_keep_format",
1419
+ operationId: makeId("op"),
1420
+ workbookId: workbookId,
1421
+ destructiveLevel: "values",
1422
+ reason: `Workflow clear data region from template ${templateId}`,
1423
+ target: {
1424
+ workbookId: workbookId,
1425
+ sheetName: newSheetName,
1426
+ address
1427
+ }
1428
+ }));
1429
+ const clearResult = clearOperations.length > 0
1430
+ ? await runtime.applyBatch({ workbookId: workbookId, mode: "apply", operations: clearOperations })
1431
+ : { ok: true, skipped: true };
1432
+ const fillOperations = (fillRegions ?? []).map((region) => ({
1433
+ kind: "range.write_values",
1434
+ operationId: makeId("op"),
1435
+ workbookId: workbookId,
1436
+ destructiveLevel: "values",
1437
+ reason: "Workflow fill template region",
1438
+ target: {
1439
+ workbookId: workbookId,
1440
+ sheetName: newSheetName,
1441
+ address: region.address
1442
+ },
1443
+ values: region.values,
1444
+ preserveFormats: true
1445
+ }));
1446
+ const fillResult = fillOperations.length > 0
1447
+ ? await runtime.applyBatch({ workbookId: workbookId, mode: "apply", operations: fillOperations })
1448
+ : { ok: true, skipped: true };
1449
+ const styleCompare = await runtime.compareStyleFingerprints({
1450
+ workbookId: workbookId,
1451
+ sourceSheetName: template.sourceSheetName,
1452
+ targetSheetName: newSheetName
1453
+ });
1454
+ const styleRepair = await repairTemplateFromArgs({
1455
+ workbookId,
1456
+ templateId,
1457
+ targetSheetName: newSheetName,
1458
+ repair: ["styles"]
1459
+ });
1460
+ const validation = await runtime.validateSheetAgainstTemplate({
1461
+ workbookId: workbookId,
1462
+ templateId: templateId,
1463
+ targetSheetName: newSheetName
1464
+ });
1465
+ return jsonResult({
1466
+ ok: Boolean(createSheet.ok && clearResult.ok && fillResult.ok && validation.ok),
1467
+ workflow: "excel.workflow.create_template_report",
1468
+ preflight,
1469
+ templateId,
1470
+ sourceSheetName: template.sourceSheetName,
1471
+ targetSheetName: newSheetName,
1472
+ createSheet,
1473
+ clearResult,
1474
+ fillResult,
1475
+ styleCompare,
1476
+ styleRepair,
1477
+ validation,
1478
+ summary: {
1479
+ targetSheetName: newSheetName,
1480
+ clearedRegionCount: clearOperations.length,
1481
+ filledRegionCount: fillOperations.length,
1482
+ styleCompared: Boolean(styleCompare.ok),
1483
+ styleRepairAttempted: true,
1484
+ validated: Boolean(validation.ok)
1485
+ }
1486
+ });
1487
+ });
1488
+ registerMcpTool(mcp, "excel.workflow.create_pivot_chart_summary", {
1489
+ title: "Create pivot and chart summary",
1490
+ description: "Run the standard PivotTable and chart creation workflow: capability check, PivotTable create, refresh, chart create, chart source update/refresh, and PivotTable source validation.",
1491
+ inputSchema: {
1492
+ workbookId: z.string(),
1493
+ pivotTableName: z.string(),
1494
+ sourceSheetName: z.string().optional(),
1495
+ sourceAddress: z.string().optional(),
1496
+ sourceTableName: z.string().optional(),
1497
+ pivotDestinationSheetName: z.string(),
1498
+ pivotDestinationAddress: z.string(),
1499
+ rowFields: z.array(z.string()).optional(),
1500
+ columnFields: z.array(z.string()).optional(),
1501
+ filterFields: z.array(z.string()).optional(),
1502
+ dataFields: z
1503
+ .array(z.object({
1504
+ sourceFieldName: z.string(),
1505
+ name: z.string().optional(),
1506
+ summarizeBy: z.string().optional(),
1507
+ numberFormat: z.string().optional()
1508
+ }))
1509
+ .optional(),
1510
+ chartSheetName: z.string(),
1511
+ chartName: z.string(),
1512
+ chartSourceAddress: z.string(),
1513
+ chartType: z.string(),
1514
+ chartTitle: z.string().optional(),
1515
+ chartPosition: z.object({ startCell: z.string(), endCell: z.string().optional() }).optional()
1516
+ },
1517
+ annotations: {
1518
+ readOnlyHint: false,
1519
+ destructiveHint: true,
1520
+ openWorldHint: false
1521
+ }
1522
+ }, async (args) => {
1523
+ const workbookId = args.workbookId;
1524
+ const preflight = await workflowPreflight(workbookId);
1525
+ const pivotRequest = {
1526
+ workbookId,
1527
+ pivotTableName: args.pivotTableName,
1528
+ destinationSheetName: args.pivotDestinationSheetName,
1529
+ destinationAddress: args.pivotDestinationAddress,
1530
+ refresh: true
1531
+ };
1532
+ if (args.sourceSheetName !== undefined)
1533
+ pivotRequest.sourceSheetName = args.sourceSheetName;
1534
+ if (args.sourceAddress !== undefined)
1535
+ pivotRequest.sourceAddress = args.sourceAddress;
1536
+ if (args.sourceTableName !== undefined)
1537
+ pivotRequest.sourceTableName = args.sourceTableName;
1538
+ if (args.rowFields !== undefined)
1539
+ pivotRequest.rowFields = args.rowFields;
1540
+ if (args.columnFields !== undefined)
1541
+ pivotRequest.columnFields = args.columnFields;
1542
+ if (args.filterFields !== undefined)
1543
+ pivotRequest.filterFields = args.filterFields;
1544
+ if (args.dataFields !== undefined)
1545
+ pivotRequest.dataFields = args.dataFields;
1546
+ const capability = runtime.getPivotCapabilityMatrix(workbookId);
1547
+ const pivotCreate = await runtime.createPivotTable(pivotRequest);
1548
+ const pivotRefresh = await runtime.refreshPivotTable({ workbookId, pivotTableName: args.pivotTableName });
1549
+ const chartCreateRequest = {
1550
+ workbookId,
1551
+ sheetName: args.chartSheetName,
1552
+ chartName: args.chartName,
1553
+ sourceAddress: args.chartSourceAddress,
1554
+ chartType: args.chartType
1555
+ };
1556
+ if (args.chartTitle !== undefined)
1557
+ chartCreateRequest.title = args.chartTitle;
1558
+ if (args.chartPosition !== undefined)
1559
+ chartCreateRequest.position = args.chartPosition;
1560
+ const chartCreate = await runtime.createChart(chartCreateRequest);
1561
+ const chartUpdate = await runtime.updateChartDataSource({
1562
+ workbookId,
1563
+ sheetName: args.chartSheetName,
1564
+ chartName: args.chartName,
1565
+ sourceAddress: args.chartSourceAddress
1566
+ });
1567
+ const chartRefresh = await runtime.refreshChart({ workbookId, sheetName: args.chartSheetName, chartName: args.chartName });
1568
+ const validation = await runtime.validatePivotSource({
1569
+ workbookId,
1570
+ pivotTableName: args.pivotTableName,
1571
+ expectedRowFields: args.rowFields,
1572
+ expectedColumnFields: args.columnFields,
1573
+ expectedFilterFields: args.filterFields,
1574
+ expectedDataFields: Array.isArray(args.dataFields) ? args.dataFields.map((field) => field.sourceFieldName) : undefined,
1575
+ expectedDataFieldSettings: args.dataFields
1576
+ });
1577
+ return jsonResult({
1578
+ ok: Boolean(pivotCreate.ok && pivotRefresh.ok && chartCreate.ok && chartUpdate.ok && validation.ok),
1579
+ workflow: "excel.workflow.create_pivot_chart_summary",
1580
+ preflight,
1581
+ capability,
1582
+ pivotCreate,
1583
+ pivotRefresh,
1584
+ chartCreate,
1585
+ chartUpdate,
1586
+ chartRefresh,
1587
+ validation,
1588
+ summary: {
1589
+ pivotTableName: args.pivotTableName,
1590
+ chartName: args.chartName,
1591
+ refreshed: Boolean(pivotRefresh.ok && chartRefresh.ok),
1592
+ validated: Boolean(validation.ok)
1593
+ }
1594
+ });
1595
+ });
1596
+ registerMcpTool(mcp, "excel.workflow.repair_formula_errors", {
1597
+ title: "Repair formula errors",
1598
+ 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.",
1599
+ inputSchema: {
1600
+ workbookId: z.string(),
1601
+ sheetName: z.string(),
1602
+ errorAddress: z.string(),
1603
+ patternAddress: z.string().optional(),
1604
+ sourceAddress: z.string().optional(),
1605
+ targetAddress: z.string().optional(),
1606
+ direction: z.enum(["down", "right"]).optional(),
1607
+ formulasAddress: z.string().optional(),
1608
+ formulas: z.array(z.array(z.string().nullable())).optional()
1609
+ },
1610
+ annotations: {
1611
+ readOnlyHint: false,
1612
+ destructiveHint: true,
1613
+ openWorldHint: false
1614
+ }
1615
+ }, async (args) => {
1616
+ const workbookId = args.workbookId;
1617
+ const sheetName = args.sheetName;
1618
+ const errorAddress = args.errorAddress;
1619
+ const sourceAddress = args.sourceAddress;
1620
+ const targetAddress = (args.targetAddress ?? args.formulasAddress ?? errorAddress);
1621
+ const preflight = await workflowPreflight(workbookId);
1622
+ const beforeValidation = await runtime.validateFormulas({ workbookId, sheetName, address: errorAddress });
1623
+ const patterns = await runtime.readFormulaPatterns({
1624
+ workbookId,
1625
+ sheetName,
1626
+ address: (args.patternAddress ?? sourceAddress ?? errorAddress)
1627
+ });
1628
+ const dependencyGraph = await runtime.getFormulaDependencyGraph({ workbookId, sheetName, address: errorAddress });
1629
+ let repairResult;
1630
+ if (Array.isArray(args.formulas)) {
1631
+ repairResult = await runtime.applyBatch({
1632
+ workbookId,
1633
+ mode: "apply",
1634
+ operations: [
1635
+ {
1636
+ kind: "range.write_formulas",
1637
+ operationId: makeId("op"),
1638
+ workbookId,
1639
+ target: { workbookId, sheetName, address: targetAddress },
1640
+ formulas: args.formulas,
1641
+ preserveFormats: true,
1642
+ destructiveLevel: "values",
1643
+ reason: "Workflow repair formula errors with scoped formulas"
1644
+ }
1645
+ ]
1646
+ });
1647
+ }
1648
+ else if (sourceAddress !== undefined) {
1649
+ repairResult = await runtime.fillFormulaPattern({
1650
+ workbookId,
1651
+ sheetName,
1652
+ sourceAddress,
1653
+ targetAddress,
1654
+ direction: args.direction ?? "down"
1655
+ });
1656
+ }
1657
+ else {
1658
+ repairResult = {
1659
+ ok: false,
1660
+ error: {
1661
+ code: "FORMULA_REPAIR_SOURCE_REQUIRED",
1662
+ message: "Provide sourceAddress for pattern fill or formulas with formulasAddress for scoped formula repair.",
1663
+ retryable: false
1664
+ }
1665
+ };
1666
+ }
1667
+ const afterValidation = await runtime.validateFormulas({ workbookId, sheetName, address: targetAddress });
1668
+ return jsonResult({
1669
+ ok: Boolean(repairResult.ok && afterValidation.ok),
1670
+ workflow: "excel.workflow.repair_formula_errors",
1671
+ preflight,
1672
+ beforeValidation,
1673
+ patterns,
1674
+ dependencyGraph,
1675
+ repairResult,
1676
+ afterValidation,
1677
+ summary: {
1678
+ sheetName,
1679
+ errorAddress,
1680
+ sourceAddress,
1681
+ targetAddress,
1682
+ usedDirectFormulaWrite: Array.isArray(args.formulas),
1683
+ formulaValidationOk: afterValidation.ok,
1684
+ formulaIssueCount: afterValidation.issueCount
1685
+ }
1686
+ });
1687
+ });
1688
+ registerMcpTool(mcp, "excel.workflow.preview_risky_edit", {
1689
+ title: "Preview risky Excel edit",
1690
+ description: [
1691
+ "Run the full safe-edit workflow for scoped risky changes: before snapshot, plan preview, apply, after snapshot, diff, and rollback preview.",
1692
+ "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.",
1693
+ "Provide at least one scoped operation. The workflow applies by default; set apply=false only when the user asks for preview without mutation."
1694
+ ].join(" "),
1695
+ inputSchema: {
1696
+ workbookId: z.string(),
1697
+ operations: z.array(z.any()).min(1),
1698
+ reason: z.string().optional(),
1699
+ goal: z.string().optional(),
1700
+ ranges: z
1701
+ .array(z.object({
1702
+ workbookId: z.string().optional(),
1703
+ sheetName: z.string(),
1704
+ address: z.string()
1705
+ }))
1706
+ .optional(),
1707
+ apply: z.boolean().optional(),
1708
+ allowSparseOverwrite: z.boolean().optional(),
1709
+ confirmationToken: z.string().optional(),
1710
+ agentId: z.string().optional(),
1711
+ agentName: z.string().optional(),
1712
+ taskId: z.string().optional(),
1713
+ role: z.string().optional()
1714
+ },
1715
+ annotations: {
1716
+ readOnlyHint: false,
1717
+ destructiveHint: true,
1718
+ openWorldHint: false
1719
+ }
1720
+ }, async (args) => {
1721
+ const workbookId = args.workbookId;
1722
+ const preflight = await workflowPreflight(workbookId);
1723
+ const request = {
1724
+ workbookId,
1725
+ operations: workflowOperations(workbookId, args.operations, args.reason ?? args.goal)
1726
+ };
1727
+ const ranges = workflowRanges(workbookId, args.ranges);
1728
+ if (args.reason !== undefined) {
1729
+ request.reason = args.reason;
1730
+ }
1731
+ if (args.goal !== undefined) {
1732
+ request.goal = args.goal;
1733
+ }
1734
+ if (ranges !== undefined) {
1735
+ request.ranges = ranges;
1736
+ }
1737
+ if (args.apply !== undefined) {
1738
+ request.apply = args.apply;
1739
+ }
1740
+ if (args.allowSparseOverwrite !== undefined) {
1741
+ request.allowSparseOverwrite = args.allowSparseOverwrite;
1742
+ }
1743
+ if (args.confirmationToken !== undefined) {
1744
+ request.confirmationToken = args.confirmationToken;
1745
+ }
1746
+ if (args.agentId !== undefined) {
1747
+ request.agentId = args.agentId;
1748
+ }
1749
+ if (args.agentName !== undefined) {
1750
+ request.agentName = args.agentName;
1751
+ }
1752
+ if (args.taskId !== undefined) {
1753
+ request.taskId = args.taskId;
1754
+ }
1755
+ if (args.role !== undefined) {
1756
+ request.role = args.role;
1757
+ }
1758
+ const result = await runtime.previewRiskyEdit(request);
1759
+ return jsonResult({ ...result, preflight });
1760
+ });
1761
+ }
1762
+ async function workflowPreflight(workbookId, includePreview = true) {
1763
+ const status = runtime.getStatus();
1764
+ const activeContext = await runtime.getActiveContext();
1765
+ const activeWorkbookId = activeContext.activeWorkbook?.workbookId;
1766
+ const resolvedWorkbookId = workbookId ?? activeWorkbookId;
1767
+ return {
1768
+ status,
1769
+ activeContext,
1770
+ capabilities: runtime.getCapabilities({ includePreview }),
1771
+ workbookMap: await runtime.getWorkbookMap(),
1772
+ collaboration: runtime.getCollaborationStatus(resolvedWorkbookId),
1773
+ workbookId: resolvedWorkbookId
1774
+ };
1775
+ }
1272
1776
  function registerPlanTools(mcp) {
1273
1777
  registerMcpTool(mcp, "excel.plan.create", {
1274
1778
  title: "Create Excel plan",
@@ -1715,7 +2219,7 @@ function styleCopyRequest(args) {
1715
2219
  function registerFormulaTools(mcp) {
1716
2220
  registerMcpTool(mcp, "excel.formula.read_patterns", {
1717
2221
  title: "Read formula patterns",
1718
- description: "Capture formulas, R1C1 pattern hashes, and pattern groups for a sheet or range.",
2222
+ 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
2223
  inputSchema: formulaPatternSchema(),
1720
2224
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1721
2225
  }, async (args) => jsonResult(await runtime.readFormulaPatterns(formulaPatternRequest(args))));
@@ -1758,13 +2262,13 @@ function registerFormulaTools(mcp) {
1758
2262
  })));
1759
2263
  registerMcpTool(mcp, "excel.formula.repair_patterns", {
1760
2264
  title: "Repair formula patterns",
1761
- description: "Repair formulas from a registered template.",
2265
+ description: "Repair formulas from a registered template. If no template fits, use formula fill tools or scoped excel.range.write_formulas, then validate.",
1762
2266
  inputSchema: templateRepairSchema(),
1763
2267
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
1764
2268
  }, async (args) => jsonResult(await repairTemplateFromArgs({ ...args, repair: ["formulas"] })));
1765
2269
  registerMcpTool(mcp, "excel.formula.find_errors", {
1766
2270
  title: "Find formula errors",
1767
- description: "Find cells with formula errors in a sheet/range.",
2271
+ description: "Find cells with formula errors in a sheet/range before reading patterns and repairing formulas.",
1768
2272
  inputSchema: validationRangeSchema(),
1769
2273
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
1770
2274
  }, async (args) => jsonResult(await runtime.validateFormulas({
@@ -1988,7 +2492,7 @@ function registerTableTools(mcp) {
1988
2492
  }, async (args) => jsonResult(await runtime.resizeTable({ ...tableSelector(args), address: args.address })));
1989
2493
  registerMcpTool(mcp, "excel.table.reorder_columns", {
1990
2494
  title: "Reorder Excel table columns",
1991
- description: "Reorder columns in an existing structured table without clearing or recreating the table.",
2495
+ description: "Reorder columns in an existing structured table without clearing, recreating, or range-rewriting the table.",
1992
2496
  inputSchema: {
1993
2497
  ...tableSelectorSchema(),
1994
2498
  columnOrder: z.array(z.union([z.string(), z.number().int().min(0)]))
@@ -2000,7 +2504,7 @@ function registerTableTools(mcp) {
2000
2504
  })));
2001
2505
  registerMcpTool(mcp, "excel.table.append_rows", {
2002
2506
  title: "Append Excel table rows",
2003
- description: "Append one or more rows to a structured table.",
2507
+ description: "Append one or more rows to a structured table while preserving table style, filters, totals, and structured formulas.",
2004
2508
  inputSchema: {
2005
2509
  ...tableSelectorSchema(),
2006
2510
  values: z.array(z.array(z.any())),
@@ -2016,7 +2520,7 @@ function registerTableTools(mcp) {
2016
2520
  })));
2017
2521
  registerMcpTool(mcp, "excel.table.update_rows", {
2018
2522
  title: "Update Excel table rows",
2019
- description: "Update table rows by zero-based table-row index.",
2523
+ description: "Update specific table rows by zero-based table-row index without rewriting the whole table.",
2020
2524
  inputSchema: {
2021
2525
  ...tableSelectorSchema(),
2022
2526
  rows: z.array(z.object({ index: z.number().int().min(0), values: z.array(z.any()) }))
@@ -2037,7 +2541,7 @@ function registerTableTools(mcp) {
2037
2541
  }, async (args) => jsonResult(await runtime.clearTableFilters(tableSelector(args))));
2038
2542
  registerMcpTool(mcp, "excel.table.apply_filters", {
2039
2543
  title: "Apply table filters",
2040
- description: "Apply Office.js filter criteria to table columns.",
2544
+ description: "Apply Office.js filter criteria to table columns without reading or rewriting the full table body.",
2041
2545
  inputSchema: tableFilterSchema(),
2042
2546
  annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false }
2043
2547
  }, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
@@ -2049,7 +2553,7 @@ function registerTableTools(mcp) {
2049
2553
  }, async (args) => jsonResult(await runtime.applyTableFilters({ ...tableSelector(args), filters: args.filters })));
2050
2554
  registerMcpTool(mcp, "excel.table.sort", {
2051
2555
  title: "Sort Excel table",
2052
- description: "Apply table sort fields.",
2556
+ description: "Apply table sort fields while preserving the structured table, filters, formulas, and styles.",
2053
2557
  inputSchema: tableSortSchema(),
2054
2558
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
2055
2559
  }, async (args) => jsonResult(await runtime.sortTable({ ...tableSelector(args), fields: args.fields, matchCase: args.matchCase, method: args.method })));
@@ -3158,7 +3662,7 @@ function registerSnapshotTools(mcp) {
3158
3662
  for (const name of ["excel.snapshot.create", "excel.snapshot.refresh"]) {
3159
3663
  registerMcpTool(mcp, name, {
3160
3664
  title: name.replace(/^excel\./, "").replace(/\./g, " "),
3161
- description: "Create or refresh a workbook snapshot.",
3665
+ description: "Create or refresh a workbook snapshot. For risky edits, create before and after snapshots so diff tools can compare two snapshot IDs.",
3162
3666
  inputSchema: name.endsWith(".refresh")
3163
3667
  ? { snapshotId: z.string(), reason: z.string().optional() }
3164
3668
  : snapshotInputSchema(),
@@ -3192,7 +3696,7 @@ function registerSnapshotTools(mcp) {
3192
3696
  }, async ({ workbookId }) => jsonResult(runtime.listSnapshots(workbookId)));
3193
3697
  registerMcpTool(mcp, "excel.snapshot.compare", {
3194
3698
  title: "Compare snapshots",
3195
- description: "Compare two workbook snapshots.",
3699
+ description: "Compare two workbook snapshots after capturing before and after states.",
3196
3700
  inputSchema: {
3197
3701
  leftSnapshotId: z.string(),
3198
3702
  rightSnapshotId: z.string()
@@ -3216,7 +3720,7 @@ function registerDiffTools(mcp) {
3216
3720
  for (const name of ["excel.diff.create", "excel.diff.summarize", "excel.diff.get_details", "excel.diff.export_json"]) {
3217
3721
  registerMcpTool(mcp, name, {
3218
3722
  title: name.replace(/^excel\./, "").replace(/\./g, " "),
3219
- description: "Create or return a diff between two stored snapshots.",
3723
+ description: "Create or return a diff between two stored snapshots. Call after the second snapshot and before rollback preview.",
3220
3724
  inputSchema: {
3221
3725
  leftSnapshotId: z.string(),
3222
3726
  rightSnapshotId: z.string()
@@ -3697,10 +4201,19 @@ async function repairTemplateFromArgs(args) {
3697
4201
  }
3698
4202
  return runtime.repairSheetFromTemplate(request);
3699
4203
  }
3700
- function registerRangeOperation(mcp, name, inputSchema, createOperation) {
4204
+ function registerRangeOperation(mcp, name, inputSchema, descriptionOrCreateOperation, maybeCreateOperation) {
4205
+ const description = typeof descriptionOrCreateOperation === "string"
4206
+ ? descriptionOrCreateOperation
4207
+ : `Apply ${name} through the reversible batch pipeline.`;
4208
+ const createOperation = typeof descriptionOrCreateOperation === "function"
4209
+ ? descriptionOrCreateOperation
4210
+ : maybeCreateOperation;
4211
+ if (!createOperation) {
4212
+ throw new Error(`Missing operation factory for ${name}`);
4213
+ }
3701
4214
  registerMcpTool(mcp, name, {
3702
4215
  title: name.replace(/^excel\./, "").replace(/\./g, " "),
3703
- description: `Apply ${name} through the reversible batch pipeline.`,
4216
+ description,
3704
4217
  inputSchema,
3705
4218
  annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false }
3706
4219
  }, async (args) => jsonResult(await applySingleOperation(args.workbookId, createOperation(args))));
@@ -3720,6 +4233,137 @@ async function applySingleOperation(workbookId, operation) {
3720
4233
  ]
3721
4234
  });
3722
4235
  }
4236
+ function workflowOperations(workbookId, operations, reason) {
4237
+ return operations.map((operation, index) => workflowOperation(workbookId, operation, reason, index));
4238
+ }
4239
+ function workflowOperation(workbookId, operation, reason, index) {
4240
+ if (!operation || typeof operation !== "object") {
4241
+ throw new Error(`Workflow operation ${index + 1} must be an object.`);
4242
+ }
4243
+ const record = operation;
4244
+ if (typeof record.kind === "string" && !record.kind.startsWith("excel.")) {
4245
+ return {
4246
+ ...record,
4247
+ workbookId: (record.workbookId ?? workbookId),
4248
+ operationId: (record.operationId ?? makeId("op")),
4249
+ reason: record.reason ?? reason ?? "MCP workflow risky edit",
4250
+ destructiveLevel: record.destructiveLevel ?? workflowDestructiveLevel(record.kind)
4251
+ };
4252
+ }
4253
+ const toolName = String(record.tool ?? record.name ?? record.operation ?? record.kind ?? "");
4254
+ const args = (record.args ?? record.input ?? record.parameters ?? record);
4255
+ switch (toolName) {
4256
+ case "excel.range.write_values":
4257
+ case "range.write_values":
4258
+ assertWorkflowRangeArgs(toolName, args);
4259
+ return {
4260
+ kind: "range.write_values",
4261
+ operationId: makeId("op"),
4262
+ workbookId,
4263
+ target: workflowTarget(workbookId, args),
4264
+ values: args.values,
4265
+ preserveFormats: true,
4266
+ destructiveLevel: "values",
4267
+ reason: reason ?? "MCP workflow write values"
4268
+ };
4269
+ case "excel.range.write_formulas":
4270
+ case "range.write_formulas":
4271
+ assertWorkflowRangeArgs(toolName, args);
4272
+ return {
4273
+ kind: "range.write_formulas",
4274
+ operationId: makeId("op"),
4275
+ workbookId,
4276
+ target: workflowTarget(workbookId, args),
4277
+ formulas: args.formulas,
4278
+ preserveFormats: true,
4279
+ destructiveLevel: "values",
4280
+ reason: reason ?? "MCP workflow write formulas"
4281
+ };
4282
+ case "excel.range.write_number_formats":
4283
+ case "range.write_number_formats":
4284
+ assertWorkflowRangeArgs(toolName, args);
4285
+ return {
4286
+ kind: "range.write_number_formats",
4287
+ operationId: makeId("op"),
4288
+ workbookId,
4289
+ target: workflowTarget(workbookId, args),
4290
+ numberFormat: args.numberFormat,
4291
+ preserveValues: true,
4292
+ destructiveLevel: "format",
4293
+ reason: reason ?? "MCP workflow write number formats"
4294
+ };
4295
+ case "excel.range.clear_values_keep_format":
4296
+ case "range.clear_values_keep_format":
4297
+ assertWorkflowRangeArgs(toolName, args);
4298
+ return {
4299
+ kind: "range.clear_values_keep_format",
4300
+ operationId: makeId("op"),
4301
+ workbookId,
4302
+ target: workflowTarget(workbookId, args),
4303
+ destructiveLevel: "values",
4304
+ reason: reason ?? "MCP workflow clear values"
4305
+ };
4306
+ case "excel.sheet.create":
4307
+ case "sheet.create":
4308
+ if (typeof args.sheetName !== "string") {
4309
+ throw new Error(`${toolName} requires sheetName.`);
4310
+ }
4311
+ return {
4312
+ kind: "sheet.create",
4313
+ operationId: makeId("op"),
4314
+ workbookId,
4315
+ sheetName: args.sheetName,
4316
+ activate: args.activate,
4317
+ destructiveLevel: "structure",
4318
+ reason: reason ?? "MCP workflow sheet create"
4319
+ };
4320
+ default:
4321
+ throw new Error(`Unsupported workflow operation: ${toolName || "unknown"}. Use scoped range writes, clear-values-keep-format, sheet.create, or canonical ExcelOperation objects.`);
4322
+ }
4323
+ }
4324
+ function workflowRanges(workbookId, ranges) {
4325
+ if (!Array.isArray(ranges)) {
4326
+ return undefined;
4327
+ }
4328
+ return ranges.map((range, index) => {
4329
+ if (!range || typeof range !== "object") {
4330
+ throw new Error(`Workflow range ${index + 1} must be an object.`);
4331
+ }
4332
+ const record = range;
4333
+ if (typeof record.sheetName !== "string" || typeof record.address !== "string") {
4334
+ throw new Error(`Workflow range ${index + 1} requires sheetName and address.`);
4335
+ }
4336
+ return {
4337
+ workbookId: (record.workbookId ?? workbookId),
4338
+ sheetName: record.sheetName,
4339
+ address: record.address
4340
+ };
4341
+ });
4342
+ }
4343
+ function workflowTarget(workbookId, args) {
4344
+ return {
4345
+ workbookId: (args.workbookId ?? workbookId),
4346
+ sheetName: args.sheetName,
4347
+ address: args.address
4348
+ };
4349
+ }
4350
+ function assertWorkflowRangeArgs(toolName, args) {
4351
+ if (typeof args.sheetName !== "string" || typeof args.address !== "string") {
4352
+ throw new Error(`${toolName} requires sheetName and address so the workflow can snapshot a scoped range.`);
4353
+ }
4354
+ }
4355
+ function workflowDestructiveLevel(kind) {
4356
+ if (kind.startsWith("sheet.") || kind.startsWith("template.")) {
4357
+ return "structure";
4358
+ }
4359
+ if (kind.includes("style") || kind.includes("format")) {
4360
+ return "format";
4361
+ }
4362
+ if (kind.startsWith("range.")) {
4363
+ return "values";
4364
+ }
4365
+ return "workbook";
4366
+ }
3723
4367
  function targetFromArgs(args) {
3724
4368
  return {
3725
4369
  workbookId: args.workbookId,