@components-kit/open-workbook 0.1.8 → 0.1.9

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.
@@ -14,6 +14,9 @@ const standalone = hasArg("--standalone") || process.env.OPEN_WORKBOOK_MCP_STAND
14
14
  const catalogOptions = {
15
15
  includePreview: process.env.OPEN_WORKBOOK_PREVIEW_TOOLS === "1"
16
16
  };
17
+ const toolProfile = readArg("--tool-profile") ?? process.env.OPEN_WORKBOOK_TOOL_PROFILE ?? "full";
18
+ const explicitToolNames = parseToolNameList(readArg("--tools") ?? process.env.OPEN_WORKBOOK_TOOLS);
19
+ const disabledToolNames = parseToolNameList(readArg("--disable-tools") ?? process.env.OPEN_WORKBOOK_DISABLE_TOOLS);
17
20
  const STYLE_DIMENSIONS = [
18
21
  "columnWidths",
19
22
  "rowHeights",
@@ -44,8 +47,99 @@ const STYLE_COPY_TOOL_DIMENSIONS = {
44
47
  "excel.style.copy_page_layout": "pageLayout",
45
48
  "excel.style.copy_hidden_rows_columns": "hiddenRowsColumns"
46
49
  };
50
+ const COMPACT_PROFILE_TOOLS = new Set([
51
+ "excel.runtime.get_status",
52
+ "excel.runtime.get_capabilities",
53
+ "excel.runtime.get_active_context",
54
+ "excel.runtime.get_selection",
55
+ "excel.runtime.connect_addin",
56
+ "excel.runtime.ping_addin",
57
+ "excel.runtime.set_active_workbook",
58
+ "excel.runtime.set_active_sheet",
59
+ "excel.workbook.list_open_workbooks",
60
+ "excel.workbook.get_workbook_info",
61
+ "excel.workbook.get_workbook_map",
62
+ "excel.workbook.get_summary",
63
+ "excel.workbook.get_used_range_summary",
64
+ "excel.workbook.detect_external_changes",
65
+ "excel.workbook.calculate",
66
+ "excel.workbook.save",
67
+ "excel.sheet.list",
68
+ "excel.sheet.get_info",
69
+ "excel.sheet.get_summary",
70
+ "excel.sheet.get_used_range",
71
+ "excel.lookup.search_workbook",
72
+ "excel.lookup.find_headers",
73
+ "excel.lookup.find_tables_by_columns",
74
+ "excel.lookup.find_entity",
75
+ "excel.lookup.resolve_range",
76
+ "excel.lookup.inspect_match",
77
+ "excel.range.get_summary",
78
+ "excel.range.read_compact",
79
+ "excel.range.find_errors",
80
+ "excel.table.list",
81
+ "excel.table.get_info",
82
+ "excel.table.get_schema",
83
+ "excel.table.read_compact",
84
+ "excel.batch.validate",
85
+ "excel.batch.preflight",
86
+ "excel.batch.dry_run",
87
+ "excel.batch.apply",
88
+ "excel.batch.submit",
89
+ "excel.batch.submit_chunked",
90
+ "excel.job.list",
91
+ "excel.job.get",
92
+ "excel.job.wait",
93
+ "excel.workflow.prepare_session",
94
+ "excel.workflow.create_formula_sheet",
95
+ "excel.workflow.create_template_report",
96
+ "excel.workflow.create_pivot_chart_summary",
97
+ "excel.workflow.repair_formula_errors",
98
+ "excel.workflow.preview_risky_edit",
99
+ "excel.plan.create",
100
+ "excel.plan.preview",
101
+ "excel.plan.apply",
102
+ "excel.plan.rollback",
103
+ "excel.compact.get_resource",
104
+ "excel.compact.list_resources",
105
+ "excel.compact.clear_resources",
106
+ "excel.compact.get_cache_status",
107
+ "excel.compact.clear_cache",
108
+ "excel.snapshot.create",
109
+ "excel.snapshot.get_compact",
110
+ "excel.snapshot.compare_compact",
111
+ "excel.diff.summarize",
112
+ "excel.diff.get_compact",
113
+ "excel.validate.compact",
114
+ "excel.validate.no_formula_errors",
115
+ "excel.validate.no_broken_references",
116
+ "excel.validate.no_unintended_changes",
117
+ "excel.collab.get_status",
118
+ "excel.transaction.list",
119
+ "excel.transaction.get",
120
+ "excel.transaction.wait",
121
+ "excel.transaction.preview_rollback",
122
+ "excel.transaction.rollback"
123
+ ]);
124
+ const READ_ONLY_PROFILE_TOOLS = new Set([...COMPACT_PROFILE_TOOLS].filter((name) => name.startsWith("excel.runtime.") ||
125
+ name.startsWith("excel.workbook.get_") ||
126
+ name === "excel.workbook.list_open_workbooks" ||
127
+ name.startsWith("excel.sheet.") ||
128
+ name.startsWith("excel.lookup.") ||
129
+ name.startsWith("excel.range.read_") ||
130
+ name === "excel.range.get_summary" ||
131
+ name === "excel.range.find_errors" ||
132
+ name.startsWith("excel.table.get_") ||
133
+ name === "excel.table.list" ||
134
+ name === "excel.table.read_compact" ||
135
+ name.startsWith("excel.compact.") ||
136
+ name.startsWith("excel.snapshot.get_") ||
137
+ name === "excel.snapshot.compare_compact" ||
138
+ name.startsWith("excel.diff.") ||
139
+ name.startsWith("excel.validate.") ||
140
+ name === "excel.collab.get_status"));
47
141
  const runtime = await createRuntimeFacade();
48
- const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.8";
142
+ const runtimeVersion = process.env.OPEN_WORKBOOK_VERSION ?? "0.1.9";
49
143
  const COMPACT_RESOURCE_LIMIT = 100;
50
144
  const COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES = 24_000;
51
145
  const compactResources = new Map();
@@ -381,14 +475,15 @@ function registerRuntimeTools(mcp) {
381
475
  title: "Get Open Workbook capabilities",
382
476
  description: "Return complete tool/resource/prompt catalog status and runtime capability metadata.",
383
477
  inputSchema: {
384
- includePreview: z.boolean().optional()
478
+ includePreview: z.boolean().optional(),
479
+ includeFullCatalog: z.boolean().optional()
385
480
  },
386
481
  annotations: {
387
482
  readOnlyHint: true,
388
483
  destructiveHint: false,
389
484
  openWorldHint: false
390
485
  }
391
- }, async ({ includePreview }) => jsonResult(runtime.getCapabilities(includePreview === undefined ? {} : { includePreview })));
486
+ }, async ({ includePreview, includeFullCatalog }) => jsonResult(runtimeCapabilities(includePreview, includeFullCatalog)));
392
487
  registerMcpTool(mcp, "excel.runtime.get_active_context", {
393
488
  title: "Get active Excel context",
394
489
  description: "Return active workbook context from the connected Excel add-in.",
@@ -434,6 +529,44 @@ function registerRuntimeTools(mcp) {
434
529
  }
435
530
  }, async ({ sheetName }) => jsonResult(await runtime.setActiveSheet(sheetName)));
436
531
  }
532
+ function runtimeCapabilities(includePreview, includeFullCatalog) {
533
+ const capabilities = runtime.getCapabilities(includePreview === undefined ? {} : { includePreview });
534
+ if (toolProfile === "full" || includeFullCatalog === true) {
535
+ return capabilities;
536
+ }
537
+ const typed = capabilities;
538
+ const catalog = typed.catalog;
539
+ const exposedToolNames = exposedProfileToolNames();
540
+ return {
541
+ ...typed,
542
+ catalog: catalog
543
+ ? {
544
+ total: catalog.total,
545
+ stable: catalog.stable,
546
+ preview: catalog.preview,
547
+ planned: catalog.planned,
548
+ unsupported: catalog.unsupported,
549
+ profile: toolProfile,
550
+ exposed: exposedToolNames.length,
551
+ tools: exposedToolNames
552
+ }
553
+ : undefined,
554
+ resources: Array.isArray(typed.resources) ? { count: typed.resources.length } : typed.resources,
555
+ prompts: Array.isArray(typed.prompts) ? { count: typed.prompts.length } : typed.prompts
556
+ };
557
+ }
558
+ function exposedProfileToolNames() {
559
+ if (explicitToolNames !== undefined) {
560
+ return [...explicitToolNames].filter((name) => shouldExposeMcpTool(name)).sort();
561
+ }
562
+ if (toolProfile === "compact") {
563
+ return [...COMPACT_PROFILE_TOOLS].filter((name) => shouldExposeMcpTool(name)).sort();
564
+ }
565
+ if (toolProfile === "read-only" || toolProfile === "readonly") {
566
+ return [...READ_ONLY_PROFILE_TOOLS].filter((name) => shouldExposeMcpTool(name)).sort();
567
+ }
568
+ return [];
569
+ }
437
570
  function registerWorkbookTools(mcp) {
438
571
  registerMcpTool(mcp, "excel.workbook.list_open_workbooks", {
439
572
  title: "List open Excel workbooks",
@@ -2090,7 +2223,7 @@ async function workflowPreflight(workbookId, includePreview = true) {
2090
2223
  return {
2091
2224
  status,
2092
2225
  activeContext,
2093
- capabilities: runtime.getCapabilities({ includePreview }),
2226
+ capabilities: runtimeCapabilities(includePreview),
2094
2227
  workbookMap: await runtime.getWorkbookMap(),
2095
2228
  collaboration: runtime.getCollaborationStatus(resolvedWorkbookId),
2096
2229
  workbookId: resolvedWorkbookId
@@ -4098,13 +4231,13 @@ function registerSnapshotTools(mcp) {
4098
4231
  if (!existing.ok || !("snapshot" in existing)) {
4099
4232
  return jsonResult(existing);
4100
4233
  }
4101
- return jsonResult(await runtime.createWorkbookSnapshot({
4234
+ return jsonResult(compactSnapshotCreationResult(await runtime.createWorkbookSnapshot({
4102
4235
  workbookId: existing.snapshot.workbookId,
4103
4236
  reason: args.reason ?? `Refresh snapshot ${args.snapshotId}`,
4104
4237
  ranges: existing.snapshot.affectedRanges
4105
- }));
4238
+ })));
4106
4239
  }
4107
- return jsonResult(await runtime.createWorkbookSnapshot(snapshotRequest(args.workbookId, args.reason, args.ranges)));
4240
+ return jsonResult(compactSnapshotCreationResult(await runtime.createWorkbookSnapshot(snapshotRequest(args.workbookId, args.reason, args.ranges))));
4108
4241
  });
4109
4242
  }
4110
4243
  registerMcpTool(mcp, "excel.snapshot.get", {
@@ -4233,10 +4366,19 @@ function registerEventTools(mcp) {
4233
4366
  function registerCompactTools(mcp) {
4234
4367
  registerMcpTool(mcp, "excel.compact.get_resource", {
4235
4368
  title: "Get compact detail resource",
4236
- description: "Fetch a stored compact detail payload by resource id or excel://compact URI.",
4237
- inputSchema: { resourceId: z.string().optional(), resourceUri: z.string().optional() },
4369
+ description: "Fetch stored compact detail metadata by default. Set includePayload only when full detail is required.",
4370
+ inputSchema: {
4371
+ resourceId: z.string().optional(),
4372
+ resourceUri: z.string().optional(),
4373
+ mode: z.enum(["metadata", "preview", "page", "full"]).optional(),
4374
+ includePayload: z.boolean().optional(),
4375
+ maxPayloadBytes: z.number().int().min(0).optional(),
4376
+ maxEstimatedTokens: z.number().int().min(0).optional(),
4377
+ offset: z.number().int().min(0).optional(),
4378
+ limit: z.number().int().min(1).max(24000).optional()
4379
+ },
4238
4380
  annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
4239
- }, ({ resourceId, resourceUri }) => jsonResult(getCompactResource(resourceId ?? compactResourceIdFromUri(resourceUri ?? ""))));
4381
+ }, ({ resourceId, resourceUri, mode, includePayload, maxPayloadBytes, maxEstimatedTokens, offset, limit }) => jsonResult(getCompactResource(resourceId ?? compactResourceIdFromUri(resourceUri ?? ""), compactResourceReadOptions({ mode, includePayload, maxPayloadBytes, maxEstimatedTokens, offset, limit }))));
4240
4382
  registerMcpTool(mcp, "excel.compact.list_resources", {
4241
4383
  title: "List compact detail resources",
4242
4384
  description: "List stored compact detail resources without returning their payloads.",
@@ -4412,12 +4554,85 @@ function storeCompactResource(kind, payload, title) {
4412
4554
  }
4413
4555
  return resource;
4414
4556
  }
4415
- function getCompactResource(resourceId) {
4557
+ function getCompactResource(resourceId, options = {}) {
4416
4558
  const resource = compactResources.get(compactResourceIdFromUri(resourceId));
4417
4559
  if (!resource) {
4418
4560
  return { ok: false, error: { code: "COMPACT_RESOURCE_NOT_FOUND", message: "Compact detail resource was not found or has expired." } };
4419
4561
  }
4420
- return { ok: true, ...resource };
4562
+ const { payload, ...summary } = resource;
4563
+ const mode = options.mode ?? (options.includePayload === true ? "full" : "metadata");
4564
+ if (mode === "metadata") {
4565
+ return {
4566
+ ok: true,
4567
+ ...summary,
4568
+ payloadAvailable: true,
4569
+ payloadIncluded: false,
4570
+ message: "Use mode=preview or mode=page for bounded inspection, or mode=full/includePayload=true for full detail."
4571
+ };
4572
+ }
4573
+ if (mode === "preview" || mode === "page") {
4574
+ const serialized = JSON.stringify(payload, null, 2);
4575
+ const defaultLimit = mode === "preview" ? 4000 : COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES;
4576
+ const limit = Math.max(1, Math.min(options.limit ?? defaultLimit, COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES));
4577
+ const offset = Math.max(0, Math.min(options.offset ?? 0, serialized.length));
4578
+ const text = serialized.slice(offset, offset + limit);
4579
+ const nextOffset = offset + text.length < serialized.length ? offset + text.length : undefined;
4580
+ return {
4581
+ ok: true,
4582
+ ...summary,
4583
+ payloadAvailable: true,
4584
+ payloadIncluded: false,
4585
+ mode,
4586
+ format: "json-text",
4587
+ offset,
4588
+ limit,
4589
+ text,
4590
+ textBytes: Buffer.byteLength(text, "utf8"),
4591
+ totalCharacters: serialized.length,
4592
+ totalBytes: Buffer.byteLength(serialized, "utf8"),
4593
+ ...(nextOffset !== undefined ? { nextOffset } : {})
4594
+ };
4595
+ }
4596
+ const overBudget = (options.maxPayloadBytes !== undefined && resource.payloadBytes > options.maxPayloadBytes) ||
4597
+ (options.maxEstimatedTokens !== undefined && resource.estimatedTokens > options.maxEstimatedTokens);
4598
+ if (overBudget) {
4599
+ return {
4600
+ ok: true,
4601
+ ...summary,
4602
+ payloadAvailable: true,
4603
+ payloadIncluded: false,
4604
+ budgetExceeded: true,
4605
+ warnings: [
4606
+ {
4607
+ code: "COMPACT_RESOURCE_BUDGET_EXCEEDED",
4608
+ message: "Stored detail exceeded the requested response budget. Raise the budget or inspect a smaller resource."
4609
+ }
4610
+ ]
4611
+ };
4612
+ }
4613
+ return { ok: true, ...summary, payloadAvailable: true, payloadIncluded: true, payload };
4614
+ }
4615
+ function compactResourceReadOptions(options) {
4616
+ const normalized = {};
4617
+ if (options.mode !== undefined) {
4618
+ normalized.mode = options.mode;
4619
+ }
4620
+ if (options.includePayload !== undefined) {
4621
+ normalized.includePayload = options.includePayload;
4622
+ }
4623
+ if (options.maxPayloadBytes !== undefined) {
4624
+ normalized.maxPayloadBytes = options.maxPayloadBytes;
4625
+ }
4626
+ if (options.maxEstimatedTokens !== undefined) {
4627
+ normalized.maxEstimatedTokens = options.maxEstimatedTokens;
4628
+ }
4629
+ if (options.offset !== undefined) {
4630
+ normalized.offset = options.offset;
4631
+ }
4632
+ if (options.limit !== undefined) {
4633
+ normalized.limit = options.limit;
4634
+ }
4635
+ return normalized;
4421
4636
  }
4422
4637
  function listCompactResources(kind, limit = COMPACT_RESOURCE_LIMIT) {
4423
4638
  const resources = [...compactResources.values()]
@@ -4622,17 +4837,13 @@ async function compactRangeRead(args) {
4622
4837
  const sampleAddress = compactWindowAddress(args.address, sample.rowOffset, columnOffset, sample.rowCount, columnLimit);
4623
4838
  const sampleResult = await readRangeSnapshot(args.workbookId, args.sheetName, sampleAddress, facets);
4624
4839
  const sampleSnapshot = (sampleResult.data ?? [])[0]?.snapshot;
4840
+ const compactSnapshot = compactRangeSnapshotPayload(sampleSnapshot, facets);
4625
4841
  return {
4626
4842
  label: sample.label,
4627
4843
  rowOffset: sample.rowOffset,
4628
4844
  rowCount: sample.rowCount,
4629
4845
  address: sampleAddress,
4630
- fingerprint: sampleSnapshot?.fingerprint,
4631
- values: sampleSnapshot?.values,
4632
- formulas: sampleSnapshot?.formulas,
4633
- text: sampleSnapshot?.text,
4634
- numberFormat: sampleSnapshot?.numberFormat,
4635
- style: sampleSnapshot?.style,
4846
+ ...compactSnapshot,
4636
4847
  ok: sampleResult.ok,
4637
4848
  warnings: sampleResult.warnings ?? []
4638
4849
  };
@@ -4652,15 +4863,11 @@ async function compactRangeRead(args) {
4652
4863
  return withCompactTelemetry({ ok: false, ...summary, source: result }, { detailLevel: "compact", truncated, nextPage, maxPayloadBytes: args.maxPayloadBytes, maxEstimatedTokens: args.maxEstimatedTokens, resourceKind: "read", resourceTitle: "Compact range read error", budgetSummary: { ok: false, ...summary } });
4653
4864
  }
4654
4865
  const snapshot = (result.data ?? [])[0]?.snapshot;
4866
+ const compactSnapshot = compactRangeSnapshotPayload(snapshot, facets);
4655
4867
  return withCompactTelemetry({
4656
4868
  ok: true,
4657
4869
  ...summary,
4658
- fingerprint: snapshot?.fingerprint,
4659
- values: snapshot?.values,
4660
- formulas: snapshot?.formulas,
4661
- text: snapshot?.text,
4662
- numberFormat: snapshot?.numberFormat,
4663
- style: snapshot?.style,
4870
+ ...compactSnapshot,
4664
4871
  warnings: result.warnings ?? [],
4665
4872
  telemetry: result.telemetry
4666
4873
  }, {
@@ -4687,6 +4894,132 @@ async function tableSchema(selector) {
4687
4894
  }, { detailLevel: "summary" });
4688
4895
  });
4689
4896
  }
4897
+ function compactRangeSnapshotPayload(snapshot, facets) {
4898
+ if (!snapshot || typeof snapshot !== "object") {
4899
+ return {};
4900
+ }
4901
+ const matrixEntries = [
4902
+ ["values", snapshot.values],
4903
+ ["formulas", snapshot.formulas],
4904
+ ["text", snapshot.text],
4905
+ ["numberFormat", snapshot.numberFormat],
4906
+ ["style", snapshot.style]
4907
+ ];
4908
+ const requestedEntries = matrixEntries.filter(([name, value]) => compactFacetOutputRequested(name, facets) && Array.isArray(value));
4909
+ const bounds = compactMatrixBounds(requestedEntries.map(([, value]) => value));
4910
+ const output = {};
4911
+ if (snapshot.fingerprint !== undefined) {
4912
+ output.fingerprint = snapshot.fingerprint;
4913
+ }
4914
+ for (const [name, value] of requestedEntries) {
4915
+ output[name] = trimMatrixToBounds(value, bounds);
4916
+ }
4917
+ if (bounds.sourceRows > bounds.rows || bounds.sourceColumns > bounds.columns) {
4918
+ output.omittedEmpty = {
4919
+ trailingRows: Math.max(0, bounds.sourceRows - bounds.rows),
4920
+ trailingColumns: Math.max(0, bounds.sourceColumns - bounds.columns),
4921
+ sourceRows: bounds.sourceRows,
4922
+ sourceColumns: bounds.sourceColumns,
4923
+ returnedRows: bounds.rows,
4924
+ returnedColumns: bounds.columns
4925
+ };
4926
+ }
4927
+ return output;
4928
+ }
4929
+ function compactFacetOutputRequested(name, facets) {
4930
+ if (name === "numberFormat") {
4931
+ return facets.includes("numberFormat");
4932
+ }
4933
+ if (name === "style") {
4934
+ return facets.includes("style");
4935
+ }
4936
+ return facets.includes(name);
4937
+ }
4938
+ function compactMatrixBounds(matrices) {
4939
+ const sourceRows = matrices.reduce((max, matrix) => Math.max(max, matrix.length), 0);
4940
+ const sourceColumns = matrices.reduce((max, matrix) => Math.max(max, ...matrix.map((row) => Array.isArray(row) ? row.length : 0)), 0);
4941
+ let lastRow = -1;
4942
+ let lastColumn = -1;
4943
+ for (const matrix of matrices) {
4944
+ for (let rowIndex = 0; rowIndex < matrix.length; rowIndex += 1) {
4945
+ const row = matrix[rowIndex];
4946
+ if (!Array.isArray(row)) {
4947
+ continue;
4948
+ }
4949
+ for (let columnIndex = 0; columnIndex < row.length; columnIndex += 1) {
4950
+ if (!isCompactEmptyCell(row[columnIndex])) {
4951
+ lastRow = Math.max(lastRow, rowIndex);
4952
+ lastColumn = Math.max(lastColumn, columnIndex);
4953
+ }
4954
+ }
4955
+ }
4956
+ }
4957
+ return {
4958
+ sourceRows,
4959
+ sourceColumns,
4960
+ rows: lastRow + 1,
4961
+ columns: lastColumn + 1
4962
+ };
4963
+ }
4964
+ function trimMatrixToBounds(matrix, bounds) {
4965
+ if (bounds.rows === 0 || bounds.columns === 0) {
4966
+ return [];
4967
+ }
4968
+ return matrix.slice(0, bounds.rows).map((row) => Array.isArray(row) ? row.slice(0, bounds.columns) : []);
4969
+ }
4970
+ function isCompactEmptyCell(value) {
4971
+ if (value === undefined || value === null || value === "") {
4972
+ return true;
4973
+ }
4974
+ if (Array.isArray(value)) {
4975
+ return value.every(isCompactEmptyCell);
4976
+ }
4977
+ if (typeof value === "object") {
4978
+ return Object.keys(value).length === 0;
4979
+ }
4980
+ return false;
4981
+ }
4982
+ function compactTablePayload(table) {
4983
+ if (!table || typeof table !== "object") {
4984
+ return {};
4985
+ }
4986
+ const matrixEntries = [
4987
+ ["values", table.values],
4988
+ ["formulas", table.formulas],
4989
+ ["text", table.text],
4990
+ ["numberFormat", table.numberFormat]
4991
+ ];
4992
+ const requestedEntries = matrixEntries.filter(([, value]) => Array.isArray(value));
4993
+ const rowBounds = compactTableRowBounds(requestedEntries.map(([, value]) => value));
4994
+ const output = {};
4995
+ if (table.headers !== undefined) {
4996
+ output.headers = table.headers;
4997
+ }
4998
+ for (const [name, value] of requestedEntries) {
4999
+ output[name] = value.slice(0, rowBounds.rows);
5000
+ }
5001
+ if (rowBounds.sourceRows > rowBounds.rows) {
5002
+ output.omittedEmpty = {
5003
+ trailingRows: rowBounds.sourceRows - rowBounds.rows,
5004
+ sourceRows: rowBounds.sourceRows,
5005
+ returnedRows: rowBounds.rows
5006
+ };
5007
+ }
5008
+ return output;
5009
+ }
5010
+ function compactTableRowBounds(matrices) {
5011
+ const sourceRows = matrices.reduce((max, matrix) => Math.max(max, matrix.length), 0);
5012
+ let lastRow = -1;
5013
+ for (const matrix of matrices) {
5014
+ for (let rowIndex = 0; rowIndex < matrix.length; rowIndex += 1) {
5015
+ const row = matrix[rowIndex];
5016
+ if (Array.isArray(row) && row.some((cell) => !isCompactEmptyCell(cell))) {
5017
+ lastRow = Math.max(lastRow, rowIndex);
5018
+ }
5019
+ }
5020
+ }
5021
+ return { sourceRows, rows: lastRow + 1 };
5022
+ }
4690
5023
  async function compactTableRead(args) {
4691
5024
  const mode = args.mode ?? "window";
4692
5025
  const schemaResult = await runtime.getTableInfo(tableSelector(args));
@@ -4727,16 +5060,13 @@ async function compactTableRead(args) {
4727
5060
  rowLimit: sample.rowCount
4728
5061
  });
4729
5062
  const sampleTable = sampleResult.table;
5063
+ const compactTable = compactTablePayload(sampleTable);
4730
5064
  return {
4731
5065
  label: sample.label,
4732
5066
  rowOffset: sample.rowOffset,
4733
5067
  rowCount: sample.rowCount,
4734
5068
  ok: sampleResult.ok,
4735
- headers: sampleTable?.headers,
4736
- values: sampleTable?.values,
4737
- formulas: sampleTable?.formulas,
4738
- text: sampleTable?.text,
4739
- numberFormat: sampleTable?.numberFormat
5069
+ ...compactTable
4740
5070
  };
4741
5071
  }));
4742
5072
  return withCompactTelemetry({ ok: true, ...summary, samples }, {
@@ -4760,14 +5090,11 @@ async function compactTableRead(args) {
4760
5090
  rowLimit
4761
5091
  });
4762
5092
  const table = result.table;
5093
+ const compactTable = compactTablePayload(table);
4763
5094
  return withCompactTelemetry({
4764
5095
  ok: Boolean(result.ok),
4765
5096
  ...summary,
4766
- headers: table?.headers,
4767
- values: table?.values,
4768
- formulas: table?.formulas,
4769
- text: table?.text,
4770
- numberFormat: table?.numberFormat,
5097
+ ...compactTable,
4771
5098
  source: result.ok ? undefined : result
4772
5099
  }, {
4773
5100
  detailLevel: "compact",
@@ -5149,6 +5476,37 @@ function compactSnapshot(snapshotId, maxPayloadBytes, maxEstimatedTokens) {
5149
5476
  budgetSummary: summary
5150
5477
  });
5151
5478
  }
5479
+ function compactSnapshotCreationResult(result) {
5480
+ if (toolProfile === "full") {
5481
+ return result;
5482
+ }
5483
+ const snapshot = result?.snapshot;
5484
+ if (!snapshot) {
5485
+ return result;
5486
+ }
5487
+ const summary = snapshotSummary(snapshot);
5488
+ const stored = storeCompactResource("snapshot", result, `Snapshot detail: ${summary.snapshotId}`);
5489
+ return withCompactTelemetry({
5490
+ ok: result.ok,
5491
+ snapshot: summary
5492
+ }, {
5493
+ detailLevel: "summary",
5494
+ resourceUri: stored.uri,
5495
+ resourceKind: "snapshot",
5496
+ resourceTitle: `Snapshot detail: ${summary.snapshotId}`
5497
+ });
5498
+ }
5499
+ function snapshotSummary(snapshot) {
5500
+ return {
5501
+ snapshotId: snapshot.snapshotId,
5502
+ workbookId: snapshot.workbookId,
5503
+ createdAt: snapshot.createdAt,
5504
+ reason: snapshot.reason,
5505
+ affectedRangeCount: snapshot.affectedRanges?.length ?? 0,
5506
+ affectedRanges: snapshot.affectedRanges,
5507
+ payloadRangeCount: snapshot.payload?.rangeSnapshots?.length ?? snapshot.rangeSnapshots?.length ?? 0
5508
+ };
5509
+ }
5152
5510
  function compactSnapshotDiff(leftSnapshotId, rightSnapshotId, maxPayloadBytes, maxEstimatedTokens) {
5153
5511
  const result = runtime.compareSnapshots(leftSnapshotId, rightSnapshotId);
5154
5512
  const diff = result.diff;
@@ -6241,7 +6599,7 @@ function escapeHtml(value) {
6241
6599
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
6242
6600
  }
6243
6601
  function registerMcpTool(mcp, name, config, callback) {
6244
- if (!isToolExposed(name, catalogOptions)) {
6602
+ if (!shouldExposeMcpTool(name)) {
6245
6603
  return;
6246
6604
  }
6247
6605
  mcp.registerTool(name, config, async (args, extra) => {
@@ -6250,9 +6608,75 @@ function registerMcpTool(mcp, name, config, callback) {
6250
6608
  if (isWorkbookMutatingMcpTool(name)) {
6251
6609
  clearCompactCache(`mutation:${name}`);
6252
6610
  }
6253
- return decorated;
6611
+ return enforceCompactResultBudget(name, decorated);
6254
6612
  });
6255
6613
  }
6614
+ function enforceCompactResultBudget(toolName, result) {
6615
+ if (toolProfile === "full" || !isJsonTextResult(result)) {
6616
+ return result;
6617
+ }
6618
+ if (toolName === "excel.compact.get_resource") {
6619
+ return result;
6620
+ }
6621
+ const payloadBytes = Buffer.byteLength(result.content[0].text, "utf8");
6622
+ if (payloadBytes <= COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES) {
6623
+ return result;
6624
+ }
6625
+ try {
6626
+ const payload = JSON.parse(result.content[0].text);
6627
+ if (payload.resourceUri !== undefined && payload.budgetExceeded === true) {
6628
+ return result;
6629
+ }
6630
+ const stored = storeCompactResource(compactResourceKindForTool(toolName), payload, `Full result: ${toolName}`);
6631
+ const summary = compactResultSummary(toolName, payload, payloadBytes, stored);
6632
+ return jsonResult(summary);
6633
+ }
6634
+ catch {
6635
+ const stored = storeCompactResource("generic", result.content[0].text, `Full text result: ${toolName}`);
6636
+ return jsonResult({
6637
+ ok: true,
6638
+ toolName,
6639
+ detailLevel: "summary",
6640
+ budgetExceeded: true,
6641
+ payloadBytes,
6642
+ estimatedTokens: Math.ceil(payloadBytes / 4),
6643
+ resourcePayloadBytes: stored.payloadBytes,
6644
+ resourceEstimatedTokens: stored.estimatedTokens,
6645
+ resourceUri: stored.uri,
6646
+ warnings: [
6647
+ {
6648
+ code: "COMPACT_RESULT_BUDGET_EXCEEDED",
6649
+ message: "Tool result exceeded compact response budget and was stored behind resourceUri."
6650
+ }
6651
+ ]
6652
+ });
6653
+ }
6654
+ }
6655
+ function shouldExposeMcpTool(name) {
6656
+ if (!isToolExposed(name, catalogOptions)) {
6657
+ return false;
6658
+ }
6659
+ if (explicitToolNames !== undefined && !explicitToolNames.has(name)) {
6660
+ return false;
6661
+ }
6662
+ if (disabledToolNames?.has(name)) {
6663
+ return false;
6664
+ }
6665
+ if (explicitToolNames !== undefined) {
6666
+ return true;
6667
+ }
6668
+ if (toolProfile === "full") {
6669
+ return true;
6670
+ }
6671
+ if (toolProfile === "compact") {
6672
+ return COMPACT_PROFILE_TOOLS.has(name);
6673
+ }
6674
+ if (toolProfile === "read-only" || toolProfile === "readonly") {
6675
+ return READ_ONLY_PROFILE_TOOLS.has(name);
6676
+ }
6677
+ console.error(`Unknown OPEN_WORKBOOK_TOOL_PROFILE=${toolProfile}; falling back to full MCP tool surface.`);
6678
+ return true;
6679
+ }
6256
6680
  function jsonResult(value) {
6257
6681
  return {
6258
6682
  content: [
@@ -6294,9 +6718,18 @@ function addCompactMutationProof(result) {
6294
6718
  if (payload.compactProof !== undefined) {
6295
6719
  return result;
6296
6720
  }
6721
+ const compactProof = summarizeMutationProof(payload);
6722
+ if (toolProfile === "full") {
6723
+ return jsonResult({
6724
+ ...payload,
6725
+ compactProof
6726
+ });
6727
+ }
6728
+ const stored = storeCompactResource("mutation", payload, "Full mutation result");
6297
6729
  return jsonResult({
6298
- ...payload,
6299
- compactProof: summarizeMutationProof(payload)
6730
+ ...compactMutationPayload(payload),
6731
+ compactProof,
6732
+ resourceUri: stored.uri
6300
6733
  });
6301
6734
  }
6302
6735
  catch {
@@ -6333,6 +6766,196 @@ function summarizeMutationProof(payload) {
6333
6766
  estimatedTokens: Math.ceil(Buffer.byteLength(JSON.stringify(payload), "utf8") / 4)
6334
6767
  };
6335
6768
  }
6769
+ function compactMutationPayload(payload) {
6770
+ const output = {};
6771
+ for (const [key, value] of Object.entries(payload)) {
6772
+ if (key === "beforeSnapshot" || key === "afterSnapshot") {
6773
+ output[key] = value && typeof value === "object" ? snapshotSummary(value) : value;
6774
+ continue;
6775
+ }
6776
+ if (key === "backup") {
6777
+ output[key] = summarizeBackup(value);
6778
+ continue;
6779
+ }
6780
+ if (key === "diff") {
6781
+ output[key] = summarizeDiffResult(value);
6782
+ continue;
6783
+ }
6784
+ if (key === "planPreview") {
6785
+ output[key] = summarizePlanPreview(value);
6786
+ continue;
6787
+ }
6788
+ if (key === "applyResult") {
6789
+ output[key] = summarizeOperationResult(value);
6790
+ continue;
6791
+ }
6792
+ if (key === "beforeSnapshotResult") {
6793
+ output[key] = summarizeOperationResult(value);
6794
+ continue;
6795
+ }
6796
+ output[key] = value;
6797
+ }
6798
+ return output;
6799
+ }
6800
+ function summarizeBackup(value) {
6801
+ if (!value || typeof value !== "object") {
6802
+ return value;
6803
+ }
6804
+ const record = value;
6805
+ return {
6806
+ backupId: record.backupId,
6807
+ workbookId: record.workbookId,
6808
+ kind: record.kind,
6809
+ reason: record.reason,
6810
+ createdAt: record.createdAt,
6811
+ affectedRangeCount: record.affectedRanges?.length ?? 0,
6812
+ affectedRanges: record.affectedRanges,
6813
+ payloadRef: record.payloadRef
6814
+ };
6815
+ }
6816
+ function summarizeDiffResult(value) {
6817
+ if (!value || typeof value !== "object") {
6818
+ return value;
6819
+ }
6820
+ const record = value;
6821
+ const diff = record.diff ?? record;
6822
+ return {
6823
+ ok: record.ok,
6824
+ diffId: record.diffId,
6825
+ summary: diff.summary ?? {
6826
+ title: diff.title,
6827
+ changedRanges: Array.isArray(diff.changedRanges) ? diff.changedRanges.slice(0, 20) : undefined,
6828
+ changedRangesTruncated: Array.isArray(diff.changedRanges) && diff.changedRanges.length > 20,
6829
+ cellsChanged: diff.cellsChanged,
6830
+ formulasChanged: diff.formulasChanged,
6831
+ stylesChanged: diff.stylesChanged,
6832
+ tablesChanged: diff.tablesChanged,
6833
+ sheetsChanged: diff.sheetsChanged,
6834
+ destructiveLevel: diff.destructiveLevel
6835
+ }
6836
+ };
6837
+ }
6838
+ function summarizePlanPreview(value) {
6839
+ if (!value || typeof value !== "object") {
6840
+ return value;
6841
+ }
6842
+ const record = value;
6843
+ return {
6844
+ ok: record.ok,
6845
+ planId: record.planId,
6846
+ operationCount: record.operationCount ?? record.operations?.length,
6847
+ requiredBackupCount: record.requiredBackups?.length ?? 0,
6848
+ requiredBackups: record.requiredBackups,
6849
+ diffSummary: record.diffSummary ?? summarizeDiffResult(record.diff),
6850
+ confirmationRequired: record.confirmationRequired,
6851
+ confirmationToken: record.confirmationToken,
6852
+ warnings: Array.isArray(record.warnings) ? record.warnings.length : record.warnings
6853
+ };
6854
+ }
6855
+ function summarizeOperationResult(value) {
6856
+ if (!value || typeof value !== "object") {
6857
+ return value;
6858
+ }
6859
+ const record = value;
6860
+ return {
6861
+ ok: record.ok,
6862
+ transactionId: record.transactionId,
6863
+ transactionStatus: record.transactionStatus,
6864
+ rollbackAvailable: record.rollbackAvailable,
6865
+ backups: record.backups,
6866
+ backup: summarizeBackup(record.backup),
6867
+ warnings: Array.isArray(record.warnings) ? record.warnings.length : record.warnings,
6868
+ telemetry: record.telemetry,
6869
+ error: record.error
6870
+ };
6871
+ }
6872
+ function compactResourceKindForTool(toolName) {
6873
+ if (toolName.includes(".snapshot.")) {
6874
+ return "snapshot";
6875
+ }
6876
+ if (toolName.includes(".diff.")) {
6877
+ return "diff";
6878
+ }
6879
+ if (toolName.includes(".validate.")) {
6880
+ return "validation";
6881
+ }
6882
+ if (isWorkbookMutatingMcpTool(toolName)) {
6883
+ return "mutation";
6884
+ }
6885
+ if (toolName.includes(".read") || toolName.includes(".lookup.") || toolName.includes(".summary") || toolName.includes(".schema")) {
6886
+ return "read";
6887
+ }
6888
+ return "generic";
6889
+ }
6890
+ function compactResultSummary(toolName, payload, payloadBytes, stored) {
6891
+ return {
6892
+ ok: payload.ok ?? true,
6893
+ toolName,
6894
+ detailLevel: "summary",
6895
+ budgetExceeded: true,
6896
+ payloadBytes: Math.min(payloadBytes, COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES),
6897
+ estimatedTokens: Math.ceil(Math.min(payloadBytes, COMPACT_DEFAULT_RESOURCE_THRESHOLD_BYTES) / 4),
6898
+ resourcePayloadBytes: stored.payloadBytes,
6899
+ resourceEstimatedTokens: stored.estimatedTokens,
6900
+ resourceUri: stored.uri,
6901
+ summary: summarizePayloadForBudget(payload),
6902
+ warnings: [
6903
+ ...asWarningArray(payload.warnings),
6904
+ {
6905
+ code: "COMPACT_RESULT_BUDGET_EXCEEDED",
6906
+ message: "Tool result exceeded compact response budget and was stored behind resourceUri."
6907
+ }
6908
+ ]
6909
+ };
6910
+ }
6911
+ function summarizePayloadForBudget(payload) {
6912
+ return {
6913
+ transactionId: payload.transactionId,
6914
+ transactionStatus: payload.transactionStatus,
6915
+ taskId: payload.taskId,
6916
+ rollbackAvailable: payload.rollbackAvailable,
6917
+ backups: payload.backups,
6918
+ backup: summarizeBackup(payload.backup),
6919
+ snapshot: payload.snapshot && typeof payload.snapshot === "object" ? snapshotSummary(payload.snapshot) : undefined,
6920
+ diffSummary: payload.diffSummary ?? summarizeDiffResult(payload.diff),
6921
+ compactProof: payload.compactProof,
6922
+ telemetry: payload.telemetry,
6923
+ validation: validationSummary(payload),
6924
+ table: summarizeTablePayload(payload.table),
6925
+ source: summarizeSourcePayload(payload)
6926
+ };
6927
+ }
6928
+ function summarizeTablePayload(value) {
6929
+ if (!value || typeof value !== "object") {
6930
+ return undefined;
6931
+ }
6932
+ const table = value;
6933
+ return {
6934
+ info: table.info
6935
+ ? {
6936
+ workbookId: table.info.workbookId,
6937
+ tableName: table.info.tableName,
6938
+ rowCount: table.info.rowCount,
6939
+ columnCount: table.info.columnCount
6940
+ }
6941
+ : undefined,
6942
+ headerCount: Array.isArray(table.headers?.[0]) ? table.headers[0].length : undefined,
6943
+ rowCount: Array.isArray(table.values) ? table.values.length : undefined,
6944
+ hasValues: Array.isArray(table.values),
6945
+ hasFormulas: Array.isArray(table.formulas),
6946
+ hasText: Array.isArray(table.text),
6947
+ hasNumberFormat: Array.isArray(table.numberFormat)
6948
+ };
6949
+ }
6950
+ function summarizeSourcePayload(payload) {
6951
+ if (Array.isArray(payload.data)) {
6952
+ return {
6953
+ dataCount: payload.data.length,
6954
+ snapshotCount: payload.data.filter((item) => Boolean(item.snapshot)).length
6955
+ };
6956
+ }
6957
+ return undefined;
6958
+ }
6336
6959
  function validationSummary(payload) {
6337
6960
  const issueCount = typeof payload.issueCount === "number" ? payload.issueCount : undefined;
6338
6961
  const issues = Array.isArray(payload.issues) ? payload.issues : undefined;
@@ -6382,6 +7005,16 @@ function readArg(name) {
6382
7005
  }
6383
7006
  return undefined;
6384
7007
  }
7008
+ function parseToolNameList(value) {
7009
+ if (value === undefined || value.trim() === "") {
7010
+ return undefined;
7011
+ }
7012
+ const names = value
7013
+ .split(",")
7014
+ .map((item) => item.trim())
7015
+ .filter(Boolean);
7016
+ return names.length > 0 ? new Set(names) : undefined;
7017
+ }
6385
7018
  function trimTrailingSlash(value) {
6386
7019
  return value.endsWith("/") ? value.slice(0, -1) : value;
6387
7020
  }