@components-kit/open-workbook-mcp-server 0.1.4 → 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/README.md +0 -1
- package/dist/index.js +662 -18
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
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.
|
|
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
|
|
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,
|
|
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
|
|
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,
|