Docs / All tools

All tools.

Every tool takes path: the .xlsx or .xlsm to operate on, absolute or ~/…. It must sit inside an allowed folder (see CLI options). Cells and ranges are A1 notation; sheet names are case-sensitive.

Write tools never touch the file. They accumulate as pending batches, each write returns a readback of what landed plus a review_url, and save_workbook is the only call that writes to disk. See Review before save.

Reading

Read-only. Hosts can run these without an approval prompt.

describe_workbookGet a structural map of the ACTIVE workbook without reading cell data.
find_rowsSearch the ACTIVE workbook's row labels and column headers by name and get exact positions back.
query_rowsFilter, project and aggregate the rows of ONE sheet of the ACTIVE workbook by column values — served by the calculation engine in milliseconds on any sheet size.
read_rangeRead evaluated cell values from a specific A1 range WITHOUT modifying anything.

Writing

Each call lands as one pending batch and returns a readback of what was written.

set_cellSet the value or formula of a single cell.
set_rangeSet a rectangular block of values starting at top_left (A1 address).
copy_rangeCopy a rectangular range to another location with Excel copy/paste semantics, executed against the LIVE grid: relative formula references shift by the paste offset exactly like Excel ($ anchors are respected — $B$5 stays put, B5 shifts), and cell formatting (number format, font, fill, alignment) travels with the cells.
clear_rangeEmpty the values AND formulas of cells in range.
run_scriptExecute a short JavaScript program against the workbook and land ALL its writes as one reviewable batch.

Formatting and layout

set_formatApply formatting to one OR MANY cell ranges in a single call.
set_column_widthSet the pixel width of one or more columns.
set_row_heightSet the pixel height of one or more rows.
merge_cellsMerge a rectangular range into a single visible cell.
unmerge_cellsUnmerge a previously merged range.
freeze_panesFreeze the top rows rows and left cols columns of a sheet so they stay visible while scrolling.
unfreeze_panesRemove any freeze panes from a sheet.
hide_rowsHide one or more rows.
show_rowsUnhide one or more rows.
hide_columnsHide one or more columns.
show_columnsUnhide one or more columns.
set_noteAttach a note (Excel-style cell comment) to a single cell — a small indicator that shows the given text on hover.
delete_noteRemove a cell's note, if it has one.
define_nameCreate (or replace) a workbook-scoped named range so formulas can reference a cell/range by a readable name instead of a raw address.

Rows, columns and sheets

Structural edits. Formula references shift the way they do in Excel.

insert_rowsInsert count empty rows starting at (and shifting down from) row before (1-indexed Excel row number).
delete_rowsDelete count rows starting at row start (1-indexed).
insert_columnsInsert count empty columns starting at (and shifting right from) column before (column letter like "C").
delete_columnsDelete count columns starting at column start (column letter like "C").
create_sheetCreate a new sheet (worksheet tab) in the workbook.
rename_sheetRename a sheet.
delete_sheetRemove a sheet from the workbook.

Batches and saving

Nothing touches the file until save_workbook.

list_batchesList this workbook's pending (unsaved) change batches with their mutation counts, and the review link.
reject_batchDrop one pending batch.
save_workbookWrite every pending batch into the .xlsx on disk through the surgical patcher: only the parts an edit touched are rewritten, everything else stays byte-identical.

Reference

describe_workbook read-only

Get a structural map of the ACTIVE workbook without reading cell data. Per sheet: the used range, the detected header row with its period/column headers mapped to column letters (e.g. {"BU": "FY2026E"}), and a table of contents of sections — contiguous row bands with their titles, like {"rows": "5-42", "title": "Revenue build"}. Also lists workbook defined names. Served from an in-memory index, so it's near-instant and returns in ONE call what would take many read_range probes. Call it once to orient on any sheet whose preview is truncated. Optional sheet filters to a single sheet.

ParameterTypeDescription
sheetstringOptional: limit the map to this sheet (case-sensitive).

find_rows read-only

Search the ACTIVE workbook's row labels and column headers by name and get exact positions back — e.g. query "total revenue" returns each matching row's sheet, 1-indexed row number, containing section, and one evaluated sample cell ("BT209 = =BT194 → 39001.1") read live from the grid. Matching is case-insensitive substring plus word-prefix ("tot rev" matches "Total revenue"). Near-instant. Use this — NOT exploratory read_range probes — whenever you need to LOCATE a row or header ("where is Subscriber ARPU?", "which column is FY2027E?"); then read_range the exact range you need. Searches only the active workbook, not reference workbooks.

ParameterTypeDescription
query requiredstringLabel text to find, e.g. "total revenue".
max_resultsintegerMax matches to return (default 20).
sheetstringOptional: search only this sheet (case-sensitive).

query_rows read-only

Filter, project and aggregate the rows of ONE sheet of the ACTIVE workbook by column values — served by the calculation engine in milliseconds on any sheet size. This is the tool for "which rows have AWS in the Owner column", "sites with capacity >= 100 MW online after 2026-01-01", or "total capacity by market": one call, instead of scanning with read_range or writing a script. Columns are column letters ("DB") or header texts from the sheet's header row ("Capacity (MW)", case-insensitive, unique substrings accepted); the header row is auto-detected (override with header_row). where lists predicates that are ANDed: op is one of eq, ne, contains, not_contains, starts_with, ends_with, gt, gte, lt, lte, between (value = [low, high]), in / not_in (value = list), empty, not_empty. Text compares case-insensitively and trimmed, numbers numerically, dates as ISO strings ("2026-01-01") against date-formatted cells. Returns the matching rows — 1-indexed row plus the requested columns keyed by column letter (date-formatted numbers come back as ISO dates) — up to limit (default 50, max 500), with matched = the total count and truncated. With group_by, returns one entry per distinct value with count and each requested aggregate (sum / count / min / max / avg) instead of rows. Read-only, evaluated values (set include_formulas for formulas too). Row numbers in the reply are exact — use them directly in formulas or read_range.

ParameterTypeDescription
sheet requiredstringSheet name (case-sensitive).
aggregatearray of objectWith group_by: aggregates to compute per group (e.g. sum of Capacity). Groups are ordered by the first aggregate, descending.
aggregate[].column requiredstring
aggregate[].fn requiredstring: "sum" | "count" | "min" | "max" | "avg"
columnsarray of stringColumns to return (letters or header texts). Default: the header-row columns, up to 40.
end_rowintegerLast data row to scan (default: the sheet's last row).
group_bystringGroup matching rows by this column's value and return one entry per value instead of rows.
header_rowinteger1-indexed header row, when auto-detection picks the wrong one.
include_formulasbooleanReturn {v, f} for formula cells instead of the bare value.
limitintegerMax rows (or groups) to return. Default 50.
order_byobjectSort matches by a column before applying limit (numbers before text, blanks last).
order_by.column requiredstring
order_by.descboolean
start_rowintegerFirst data row to scan (default: the row after the header).
wherearray of objectPredicates, all of which must hold. Omit for every row.
where[].column requiredstringColumn letter or header text.
where[].op requiredstring: "eq" | "ne" | "contains" | "not_contains" | "starts_with" | "ends_with" | "gt" | "gte" | "lt" | "lte" | "between" | "in" | "not_in" | "empty" | "not_empty"
where[].valueanyComparison value: string, number, ISO date, or a list for between / in / not_in. Omit for empty / not_empty.

read_range read-only

Read evaluated cell values from a specific A1 range WITHOUT modifying anything. Use this to verify your own work, confirm where the assumption block actually landed before composing dependent formulas, or pull current values you need to reference. Returns each non-empty cell's value AND formula (if any) in A1 = value form. Costs one tool turn but is the cheapest way to prevent the formula-pointing-at-wrong-row class of bug on long sheets. Range limited to 500 cells per call. To LOCATE a row/section/header by NAME, don't probe with this — use find_rows or describe_workbook first, then read the exact range.

ParameterTypeDescription
range requiredstringA1 range, e.g. "G45:K45", "A1:Z10", or a single cell "B7".
sheet requiredstringSheet name (case-sensitive).

set_cell pending batch

Set the value or formula of a single cell. cell is an Excel A1-style address (case-insensitive, e.g. "A1", "B17", "AA42"). For formulas, pass them in formula starting with '=' (e.g. "=SUM(A1:A10)") — do NOT put formulas in value.

ParameterTypeDescription
cell requiredstringA1 address. Examples: "A1", "B17", "AA42".
sheet requiredstringSheet name (case-sensitive).
formulastring | nullExcel-style formula starting with '='. References inside use A1 notation. Example: "=SUM(A1:A10)".
valueanyLiteral value (string, number, boolean, or null). Leave null when using formula.

set_range pending batch

Set a rectangular block of values starting at top_left (A1 address). values is a 2D array — outer rows, inner cells. Strings starting with '=' inside values are treated as formulas; formula references must use A1 notation pointing at the FINAL placement of cells (e.g. if top_left is "A9" and the value at row index 2 / col 0 should reference the data row just above your block at A8, write "=A8" — not "=A1" or "=A11"). **Off-by-one guard: if formulas will reference rows created by THIS call, split the work — write labels + hardcoded values first, read the row_map in this call's result, then write formulas in a second set_range against those verified row numbers.** Composing formulas in the same call that lays out their target rows is the top source of off-by-one references. Use this for bulk writes; prefer it over many set_cell calls. **null and empty string "" inside values are treated as PRESERVE — the cell is left untouched, NOT cleared.** This means you can ship [["Gross Profit", "", "", ""]] to set only the label in column A without wiping the data in B–D. To actually clear cells, use clear_range. **Writing to a sheet name that doesn't exist CREATES that sheet automatically** (near-miss spellings of an existing sheet are rejected as probable typos) — so organize multi-section builds across purposefully named sheets (e.g. Data / Assumptions / Model / Notes) instead of piling everything onto one long sheet; a new tab costs nothing.

ParameterTypeDescription
sheet requiredstring
top_left requiredstringA1 address of the top-left cell, e.g. "A9".
values requiredarray of array2D array — outer = rows, inner = cells. Strings beginning with '=' are formulas.

copy_range pending batch

Copy a rectangular range to another location with Excel copy/paste semantics, executed against the LIVE grid: relative formula references shift by the paste offset exactly like Excel ($ anchors are respected — $B$5 stays put, B5 shifts), and cell formatting (number format, font, fill, alignment) travels with the cells. STRONGLY PREFER this over reading a range and re-typing shifted formulas yourself — it cannot make off-by-one mistakes and costs a fraction of the tokens. Ideal for: extending a projection by one more period (copy the last full year column block to the next column), duplicating a section as a template, or seeding a new sheet from an existing block. Blank source cells leave the destination untouched (they do NOT clear it). Result reports the evaluated destination values so you can verify. Limited to 5000 cells per call.

ParameterTypeDescription
dest requiredstringA1 address of the destination TOP-LEFT cell, e.g. "BU5". The copy lands in a rectangle of the same size starting here.
sheet requiredstringSource sheet name (case-sensitive).
source requiredstringA1 range to copy, e.g. "BT5:BT120" or a single cell "C7".
dest_sheetstring | nullDestination sheet if different from sheet. Formula references shift the same way; sheet-qualified refs keep pointing at their original sheets.
modestring: "all" | "values" | "formats""all" (default): shifted formulas + values + formats. "values": paste evaluated values only — use to freeze a calculation as hardcodes. "formats": formatting only, contents untouched.

clear_range pending batch

Empty the values AND formulas of cells in range. Use this when you want to delete content (not just overwrite with new values). range is an A1 range like "A1", "A1:F1", or "B2:D10". Cell formatting is preserved.

ParameterTypeDescription
range requiredstring
sheet requiredstring

run_script pending batch

Execute a short JavaScript program against the workbook and land ALL its writes as one reviewable batch. STRONGLY PREFER this over long set_range payloads or many tool turns whenever the edit is loop-shaped: per-row conditional transforms ("for every row where margin < 0 …"), repeating formula patterns across many rows/columns, address arithmetic, or edits that depend on reading existing cells first. One run_script call replaces dozens of tool turns and cannot drift out of alignment mid-payload, because it computes addresses programmatically.

API (plain synchronous JavaScript, strict mode — no async/await, no imports, no network, no DOM):
sheets() -> array of sheet names
sheet(name) -> handle for one sheet (name must match exactly; the name arg may be omitted ONLY if the workbook has a single sheet). Handle methods:
.get("B7") -> {value, formula} — evaluated value as of BEFORE this script, overlaid with your own literal writes
.values("A2:C50") -> 2D array of values (rows × cols)
.set("B7", x) — write a value; strings starting with '=' are formulas; null clears the cell's content
.setValues("A9", [[...],[...]]) — bulk write with set_range semantics: null / "" preserve the existing cell
.format("A1:F1", {bold: true, number_format: "0.0%", background_color: "#1F4E79", font_color: "#FFFFFF"}) — same keys as set_format
.clear("B2:D10") — empty values + formulas, formatting preserved
.usedRange() -> "A1:Q48" or null
log(...) — up to 100 entries of up to 4,000 characters each (64 KB in total), returned to you in the result as script_logs. A cut entry ends with … [+N chars]. Logs are for debugging and small extracts; for bulk reads use query_rows / read_range, not a script that logs rows.

Rules: formulas you write are NOT evaluated during the script — .get on a cell you just wrote a formula into returns {value: null, formula}; the automatic readback after the script (cells + row_map) carries the evaluated results, so verify there. Follow the same conventions as set_range: write raw numbers (never pre-formatted strings), write formulas for derived values, compose against row_map. Limits: 5-second CPU cap, and at most 20,000 WRITTEN cells per script (set/setValues/format/clear combined). Reads are not budgeted: .values() over a whole region (even 7,000 rows × 100 columns) is the right way to scan, far cheaper than many read_range turns — do not narrow reads to save budget. Workbook size is not a limit either: the script is fed per sheet from the calc engine, so name every sheet you READ as a string literal (sheet("NA Data Center Supply")) — only named sheets are loaded; reading one you did not name errors; calling sheets() loads all of them. sheet("NewName") on a name that doesn't exist AUTO-CREATES that sheet when the script's writes land — use it to organize builds across purposeful tabs. Other structural ops (insert/delete rows or columns, sheet delete/rename, merges, freeze) are NOT in the script API — use the dedicated tools for those. The script's writes flow into the same pending batch as every other tool: the user reviews and can reject them.

ParameterTypeDescription
script requiredstringJavaScript source, executed as a function body. Example: const s = sheet("Model"); const rows = s.values("A2:C50"); rows.forEach((r, i) => { if (r[2] !== null && r[2] < 0) s.set("D" + (i + 2), "=C" + (i + 2) + "*-1"); });

set_format pending batch

Apply formatting to one OR MANY cell ranges in a single call. STRONGLY PREFER the bulk operations form when you have more than one format to apply — it's one tool call instead of N, dramatically faster. Each operation's format is a partial object; only specified properties are applied, others are left as-is. Use this for: bold headers, currency / percent / number formatting on data columns, italic notes, alignment, font color/size/family, **text wrap via wrap_text** (long methodology notes / descriptions in a fixed-width column — wrap beats restructuring the layout around overflow), and **cell fill via background_color** (e.g. dark-blue header bars, yellow assumption highlights — see the financial-model color conventions). v1 limitation: cell borders are not yet supported.

Two equivalent shapes:
• Single range: { sheet, range, format }
• Bulk: { sheet, operations: [ { range, format }, ... ] }

ParameterTypeDescription
sheet requiredstring
formatobjectPartial format (only when using single-range form).
format.background_colorstring | nullCell fill color, e.g. "#1F4E79" for a dark-blue header bar or "#FFFF00" for a yellow assumption highlight. Pass null to clear. **Always pair light/white font_color with a contrasting background_color — white-on-white reads as invisible text.**
format.boldboolean
format.font_colorstringCSS color, e.g. "#000000" or "red".
format.font_familystringFont family name, e.g. "Calibri", "Arial", "Aptos Narrow".
format.font_sizenumber
format.horizontal_alignstring: "left" | "center" | "right"
format.italicboolean
format.number_formatstringExcel-style number format. Common: "$#,##0.00" currency, "0.0%" or "0.00%" percent, "#,##0" integers with commas, "#,##0.00" decimals, "@" plain text.
format.strikebooleanStrikethrough.
format.underlineboolean
format.vertical_alignstring: "top" | "middle" | "bottom"
format.wrap_textbooleanWrap long text within the cell instead of overflowing. Pair with a set_column_width + vertical_align "top" for description/notes columns.
operationsarray of objectBulk form — array of {range, format} pairs. Use this whenever you have ≥2 formats to apply. Example: [{"range":"A1","format":{"bold":true,"font_size":16}},{"range":"B8:F8","format":{"number_format":"#,##0"}},{"range":"B9:F9","format":{"number_format":"0.0%"}}].
operations[].format requiredobject
operations[].range requiredstring
rangestringA1 range (only when using single-range form). Examples: "A1", "B2:B10", "A1:F1".

set_column_width pending batch

Set the pixel width of one or more columns. **Always prefer the bulk operations form when you need multiple widths** (e.g. label column A wide, data columns narrower) — one call instead of N. The flat single-width form is kept for trivial cases. Default Excel column is ~64px; a wide column for labels is typically 180–220.

ParameterTypeDescription
sheet requiredstring
columnsstringSingle-op form (omit operations). Comma-separated column letters.
operationsarray of objectBulk form. Each entry sets a width on a group of columns. Use this when you need different widths for different column groups.
operations[].columns requiredstringComma-separated column letters, e.g. "A" or "B,C,D,E,F".
operations[].width requirednumberPixel width.
widthnumberSingle-op form pixel width.

set_row_height pending batch

Set the pixel height of one or more rows. **Always prefer the bulk operations form when you need multiple heights** (e.g. title row tall, body rows short) — one call instead of N. The flat single-height form is kept for trivial cases. Default row is ~24.

ParameterTypeDescription
sheet requiredstring
heightnumberSingle-op form pixel height.
operationsarray of objectBulk form. Each entry sets a height on a group of rows.
operations[].height requirednumberPixel height.
operations[].rows requiredstringComma-separated 1-indexed row numbers, e.g. "1" or "4,5,6,7,8".
rowsstringSingle-op form (omit operations). Comma-separated 1-indexed row numbers.

merge_cells pending batch

Merge a rectangular range into a single visible cell (e.g. for section headers). range is an A1 range like "A1:F1".

ParameterTypeDescription
range requiredstring
sheet requiredstring

unmerge_cells pending batch

Unmerge a previously merged range.

ParameterTypeDescription
range requiredstring
sheet requiredstring

freeze_panes pending batch

Freeze the top rows rows and left cols columns of a sheet so they stay visible while scrolling. Classic financial-model setup is freeze_rows: 1, freeze_cols: 1 (lock the header row and label column). Pass 0 to disable that axis.

ParameterTypeDescription
freeze_cols requiredintegerNumber of left columns to freeze.
freeze_rows requiredintegerNumber of top rows to freeze.
sheet requiredstring

unfreeze_panes pending batch

Remove any freeze panes from a sheet.

ParameterTypeDescription
sheet requiredstring

hide_rows pending batch

Hide one or more rows. rows is a comma-separated list of 1-indexed row numbers (e.g. "3" or "3,5,12"). Hidden rows still hold data but don't display in the workbook.

ParameterTypeDescription
rows requiredstringComma-separated 1-indexed row numbers.
sheet requiredstring

show_rows pending batch

Unhide one or more rows. Same rows format as hide_rows.

ParameterTypeDescription
rows requiredstring
sheet requiredstring

hide_columns pending batch

Hide one or more columns. columns is a comma-separated list of column letters (e.g. "D" or "D,F,H").

ParameterTypeDescription
columns requiredstringComma-separated column letters.
sheet requiredstring

show_columns pending batch

Unhide one or more columns. Same columns format as hide_columns.

ParameterTypeDescription
columns requiredstring
sheet requiredstring

set_note pending batch

Attach a note (Excel-style cell comment) to a single cell — a small indicator that shows the given text on hover. Use this for methodology callouts, source citations, or caveats that would clutter the cell's own value. Overwrites any existing note on that cell.

ParameterTypeDescription
cell requiredstringA1 address, e.g. "B7".
sheet requiredstring
text requiredstring

delete_note pending batch

Remove a cell's note, if it has one. No-op if the cell has no note.

ParameterTypeDescription
cell requiredstringA1 address, e.g. "B7".
sheet requiredstring

define_name pending batch

Create (or replace) a workbook-scoped named range so formulas can reference a cell/range by a readable name instead of a raw address — e.g. name GrowthRate for the assumption cell, then write =Revenue*(1+GrowthRate). The name persists in the saved .xlsx. name must be a valid Excel name (letters, digits, underscores; can't look like a cell reference such as "A1"). ref must be sheet-qualified, e.g. "Model!B22" or "Model!B5:B12" (absolute $ signs optional — they're added automatically). Calling again with the same name replaces its target.

ParameterTypeDescription
name requiredstringDefined name, e.g. "GrowthRate" or "Revenue". Must not look like a cell address.
ref requiredstringSheet-qualified target, e.g. "Model!B22" or "Assumptions!B5:B12".
sheetstringOptional. Only needed if ref is a bare range without a sheet prefix; supplies the sheet for it.

insert_rows pending batch

Insert count empty rows starting at (and shifting down from) row before (1-indexed Excel row number). Existing content at row before and below shifts down. Use this to make space in the middle of a model. Charts and conditional-formatting references in unrelated areas of the workbook may not auto-adjust to the new layout.

ParameterTypeDescription
before requiredinteger1-indexed row number to insert before.
sheet requiredstring
countinteger

delete_rows pending batch

Delete count rows starting at row start (1-indexed). Content below shifts up. Permanent — the removed rows are gone.

ParameterTypeDescription
sheet requiredstring
start requiredinteger
countinteger

insert_columns pending batch

Insert count empty columns starting at (and shifting right from) column before (column letter like "C"). Existing content shifts right.

ParameterTypeDescription
before requiredstringColumn letter to insert before, e.g. "C".
sheet requiredstring
countinteger

delete_columns pending batch

Delete count columns starting at column start (column letter like "C"). Content to the right shifts left. Permanent.

ParameterTypeDescription
sheet requiredstring
start requiredstring
countinteger

create_sheet pending batch

Create a new sheet (worksheet tab) in the workbook. Use a clear human-readable name like "Assumptions", "Q4 2024", "Inputs". The new sheet starts empty — write cells with set_cell / set_range afterwards. NOTE: writes to a not-yet-existing sheet name auto-create it, so an explicit create_sheet is only needed when you want to set a tab_color or create an empty tab ahead of time.

ParameterTypeDescription
name requiredstringSheet name (must be unique in the workbook).
tab_colorstring | nullOptional CSS color for the tab, e.g. "#22c55e".

rename_sheet pending batch

Rename a sheet. Cross-sheet formula references in other sheets will be updated automatically by the workbook engine.

ParameterTypeDescription
new_name requiredstring
old_name requiredstring

delete_sheet pending batch

Remove a sheet from the workbook. Permanent — every cell on this sheet is lost. Cross-sheet references in other sheets will become #REF!.

ParameterTypeDescription
name requiredstring

list_batches read-only

List this workbook's pending (unsaved) change batches with their mutation counts, and the review link.

No parameters beyond path.

reject_batch pending batch

Drop one pending batch. The remaining batches are replayed onto a fresh model, so later edits that depended on it may change.

ParameterTypeDescription
batch_id requiredstring

save_workbook writes the file

Write every pending batch into the .xlsx on disk through the surgical patcher: only the parts an edit touched are rewritten, everything else stays byte-identical. This is the ONLY tool that modifies the file. When the server runs with --review-required, this returns the review link instead and the user saves from there.

ParameterTypeDescription
asstringOptional: write the result to this path instead (a copy), leaving the original untouched. Pending batches stay pending against the original.

Found a mistake? Open an issue or email support@gridpath.dev.