Repository navigation
Conversation
read_range without `range` returned the whole used range in one reply, up to the 1,000,000-cell limit: a 15,000-row export of 30 columns is 6.6 MB of JSON, far more than an agent can take in one tool result, and there was no way to ask for less but to compute ranges by hand. sheet.read takes `maxCells`: it reads whole rows up to that many cells (at least one row) and, when the range is longer, adds `truncated: true`, `totalRows` and `next`, the sheet-qualified range of the remaining rows to pass back as `range`. Without `maxCells` nothing changes. The MCP read_range tool passes maxCells 5,000 unless the caller gives one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every tool result was pretty-printed, so read_range put each cell value on a line of its own: 100 rows of 30 columns took 44,368 characters on 3,238 lines, against 22,132 on one line compact. Agents parse the JSON either way; compact halves what a range read costs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validate the engine parameter before empty-sheet handling so invalid values cannot bypass paging. Cover engine and MCP callers and document the positive integer requirement. Co-Authored-By: OpenAI Codex <noreply@openai.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reading an export is the first thing an agent does with a workbook, and the engine is fast at it, but the MCP reply wasn't usable at size.
read_rangewithoutrangereturned the whole used range in one reply (up to the 1,000,000-cell limit), and every tool result was pretty-printed, one cell value per line. On a synthetic 15,000 × 30 CSV (generated data),read_range {}was one text block of 6,582,075 characters on 480,038 lines, with nothing telling the agent how to ask for less. MCP clients cap tool output, so in practice the agent got a truncated or spilled-to-disk result.What changed
Two independent commits: pagination first, compact tool-result JSON second. Pagination can land without compact JSON if indented output should be retained, but must include the follow-up commit that rejects invalid maxCells values.
1. Paging (
MCP: read_range returns long ranges in pages)sheet.readtakesmaxCells, a positive integer; zero, negative and non-integer values return an error, including on an empty sheet. It reads whole rows up to that many cells (at least one row, so a row wider thanmaxCellsstill comes back whole). When the range is longer it addstruncated: true,totalRows(rows of the whole range) andnext, the remaining rows as a sheet-qualified range ('My Data'!A334:AD15001). Passingnextback asrangereads on from there whatever the active sheet is. WithoutmaxCellsthe command behaves exactly as before, so the control channel and CLI are unchanged.read_rangetool exposesmaxCellsand passes 5,000 when the caller gives none. The 1,000,000-cell limit still applies to what is read.paramstext, the tool description,docs/mcp.mdanddocs/control-protocol.mddescribe it.2. Compact JSON (
MCP: tool results as compact JSON)ToolResult::jsonusesserde_json::to_stringinstead ofto_string_pretty. Agents parse the JSON either way; indented, 100 rows × 30 columns took 44,368 characters on 3,238 lines, compact 22,132 on one.docs/mcp.mdsaid "pretty JSON" and now says compact.Not included: an ISO date option for
read_range(dates come back as serials unlessformatted, where numbers become text too), and documenting inwrite_rangethat"'0010"or a Text format keeps a code with leading zeros as text.Verification (Linux x86_64, original baseline fc45b7f)
cargo test -p gridcraft-engine --lib tests::reading_a_range_in_pages: 10 rows × 3 columns on a sheet namedMy Datawith another sheet active.maxCells: 7givesA1:C2,totalRows10 andnext'My Data'!A3:C10; readingnextreturns the 8 remaining rows with notruncated; a positivemaxCellssmaller than a row gives one row; nomaxCellsgives the whole range. Onmain:left: "A1:C10",right: "A1:C2". Passes here.cargo test -p gridcraft-mcp tests::read_range_pages_long_ranges: 2,000 rows × 3 throughwrite_range, thenread_range {}givesA1:C1666,truncated,totalRows2000,nextSheet1!A1667:C2000; the next call reads the rest;maxCells: 6000reads everything. Onmain:left: ("A1:C2000", Null, Null). Passes here.cargo test -p gridcraft-mcp tests::tool_results_are_compact_json: the text of aread_rangeresult has no line break. Fails onmain, passes here.gridcraft-cli mcpon the synthetic 15,000 × 30 CSV:main(fc45b7f)read_range {}A1:AD15001A1:AD166,truncated,totalRows15001,nextlarge!A167:AD15001read_range {range: "A1:AD101"}cargo fmt --all -- --check,cargo clippy -p gridcraft-engine -p gridcraft-mcp --all-targets -- -D warnings,cargo test -p gridcraft-engine -p gridcraft-mcp -p gridcraft-cliLimits and questions
read_range {}returning the whole used range now gets the first page withtruncated: true, and needsmaxCellsornextto read the rest.Independent verification against updated main (84acf62, 2026-10-13)
Before the invalid-maxCells follow-up, the independent review reran
cargo fmt --all -- --check,cargo clippy -p gridcraft-engine -p gridcraft-mcp --all-targets -- -D warningsandcargo test -p gridcraft-engine -p gridcraft-mcpon the branch and its clean merge with 84acf62; all exited 0. The added regression tests failed with production code from 84acf62 and passed on both branch and merged trees. These checks are separate from the historical fc45b7f measurements above.Follow-up verification: invalid maxCells (2026-10-13)
sheet_read_rejects_invalid_max_cellsfails without the fix, including on an empty sheet, and passes with it.read_range_rejects_invalid_max_cellswrites 2,000 rows by 3 columns and confirms that the default page contains 1,666 rows. Without the fix,maxCells: -1returns all 2,000 rows instead of an error. With the fix, negative integers and zero fail; non-integer MCP arguments fail as JSON-RPC invalid params.maxCells: 1still returns one complete row.cargo fmt --all -- --check,cargo clippy -p gridcraft-engine -p gridcraft-mcp --all-targets -- -D warnings, andcargo test -p gridcraft-engine -p gridcraft-mcppass: 245 engine tests and 22 MCP tests, no failures. These checks ran on the branch after the follow-up, separately from the earlier review of its merged tree.Conflicts
Against upstream/main at 84acf62, git merge-tree is clean. #884 (my open conditional-format PR) conflicts only where the engine tests are inserted in tests.rs; retain both tests. #870, #480, #885 and #1064 merge cleanly. The review covered 100 listed open PRs plus #480, not every older PR.