cellrune 0.1.18

Bounded XLSX/XLSM reading, deterministic calculation, editing, and writing for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
# CellRune

CellRune is a headless Rust backend for bounded XLSX/XLSM reading and deterministic formula
calculation. It keeps the workbook read from disk immutable, returns recalculated values in a
separate snapshot, and can retain an exact package backing for explicit round-trip writing. Its
MCP integration is a local stdio workflow server, not a browser, UI, hosted service, or remote
transport.

## Rust installation

The CellRune Rust crate 0.1.18 requires Rust 1.88 or newer.

```bash
cargo add cellrune@0.1.18
```

Or add the dependency directly:

```toml
[dependencies]
cellrune = "0.1.18"
```

## Features

- reads `.xlsx` files from paths, byte slices, or `Read + Seek` streams;
- opens package-backed `.xlsx` and `.xlsm` documents with exact SHA-256 identity and bounded
  round-trip preservation;
- preserves sheet order, sparse cells, formulas, saved results, defined names, and relevant
  number-format metadata;
- exposes merged ranges and validated worksheet-owned Excel tables, including stable table and
  column IDs, complete filter/sort/formula/style metadata, and case-insensitive lookup indexes;
- resolves typed multi-area, 3-D, structured table, current-row, and spill references under
  cumulative reference and dependency budgets;
- inspects workbook and sheet-local defined names without running a calculation session, preserving
  rectangular, 3-D, ordered multi-area, empty, dynamic, external, invalid, and unsupported results;
- expands shared formulas while preserving absolute and relative references;
- returns typed formula values and stable per-cell calculation issues in one result snapshot;
- reports normalized per-workbook function demand and exposes the implemented function catalog;
- evaluates first-class and defined-name `LAMBDA` callables, including immediate invocation,
  `ISOMITTED`, `MAP`, `BYROW`, `BYCOL`, `REDUCE`, `SCAN`, and `MAKEARRAY`, under explicit
  recursion and iteration limits;
- evaluates audited 3-D aggregate references across workbook tab order, including hidden sheets and
  reverse-written endpoints, without expanding the sheet span into dependency edges;
- applies configurable limits to ZIP, XML, workbook, formula, dependency, text, and array work;
- never executes macros, never follows external links, and never reads the host clock for
  `TODAY()` or `NOW()`;
- returns stable error and issue codes for programmatic handling;
- materializes recalculated typed results into existing `.xlsx`/`.xlsm` packages with strict or
  explicit cache-invalidation policies and reports a verified output SHA-256 identity;
- creates canonical `.xlsx` workbooks and applies typed cell, formula, sheet, name, table rename,
  table-column rename, table-row resize, number-format, date-system, and calculation-property edits
  through `WorkbookDraft`;
- reads, queries, preserves, and explicitly authors SpreadsheetML phonetic annotations and default
  frozen panes without mixing presentation state into formula calculation;
- exposes the same versioned read/edit/calculate/write contract through typed Python and
  Node.js/TypeScript native packages;
- supports atomic typed edit batches, persistent parsed/dependency state, safe incremental
  recalculation, bounded result deltas, cooperative cancellation, stale-result rejection, and
  retained immutable change previews;
- provides a local stdio MCP server with high-level open, inspect, edit, preview, recalculate,
  range-read, delta, and verified Save As tools over the same interop session; and
- raw-copies unchanged package entries without exposing ZIP or XML implementation types.

## Usage

```rust
use cellrune::{
    CalculationCellResult, CalculationOptions, ReadOptions, calculate_workbook, read_xlsx_path,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let workbook = read_xlsx_path("input.xlsx", ReadOptions::default())?;
    let calculation = calculate_workbook(&workbook, CalculationOptions::default());

    for (cell, result) in calculation.cells() {
        match result {
            CalculationCellResult::Value(value) => println!("{cell:?}: {value:?}"),
            CalculationCellResult::Unavailable(issue) => {
                eprintln!("{cell:?}: {}", issue.code().as_str());
            }
        }
    }

    Ok(())
}
```

Reading and calculation are separate operations. `calculate_workbook` does not modify the source
`WorkbookSnapshot` or its saved XLSX results. It attempts every formula in the workbook:
successfully calculated cells contain a typed `Value`, while a cell that cannot be calculated
contains a structured `Unavailable(CalculationIssue)`. One unavailable formula does not suppress
independent results; dependent formulas report `BlockedByUpstream` when applicable.
Volatile functions require deterministic inputs through `CalculationOptions`:
`with_today_serial` for `TODAY()` and `with_now_serial` for `NOW()`. Use
`with_arithmetic_semantics` and `with_financial_solver_semantics` to opt into the raw IEEE-754 and
extended-search behavior shipped through 0.1.2; the defaults select Excel-compatible cancellation
and Microsoft's function-specific solver budgets. Use
`supported_function_catalog` for the build's exact function surface and `scan_function_usage` to
summarize the functions used by a workbook. `scan_formula_capabilities` remains available as an
optional static inventory for migration planning and user-interface reporting; calculation does
not require it.

CellRune 0.1.14 adds exactly 19 official Excel-facing engineering names: `CONVERT`, `BESSELI`,
`BESSELJ`, `BESSELK`, `BESSELY`, `COMPLEX`, `IMABS`, `IMAGINARY`, `IMARGUMENT`, `IMCONJUGATE`,
`IMREAL`, `IMDIV`, `IMPOWER`, `IMPRODUCT`, `IMSUB`, `IMSUM`, `IMEXP`, `IMLN`, and `IMSQRT`.
`CONVERT` uses a typed, case-sensitive unit registry; the complex family shares one finite parser
and canonical formatter; and the four Bessel functions use budgeted first-party kernels.

The fixed-income wave on top of 0.1.14 adds exactly 26 official names: `ACCRINT`, `ACCRINTM`,
`COUPDAYBS`, `COUPDAYS`, `COUPDAYSNC`, `COUPNCD`, `COUPNUM`, `COUPPCD`, `DISC`, `DURATION`,
`INTRATE`, `MDURATION`, `ODDFPRICE`, `ODDFYIELD`, `ODDLPRICE`, `ODDLYIELD`, `PRICE`, `PRICEDISC`,
`PRICEMAT`, `RECEIVED`, `TBILLEQ`, `TBILLPRICE`, `TBILLYIELD`, `YIELD`, `YIELDDISC`, and
`YIELDMAT`. They share a typed day-count and coupon-schedule model and a safeguarded yield root
solver that charges the calculation budget and observes cancellation.

CellRune 0.1.16 added `XLOOKUP`, `DATEVALUE`, `TIMEVALUE`,
`NETWORKDAYS.INTL`, and `WORKDAY.INTL`. The deterministic source catalog contains 416 official
names and 417 accepted entries including the non-official OOXML dummy-function marker. Their
fixed grammar, lookup modes, calendar rules, wildcard behavior, and array-result boundaries are
documented in [llms.txt](https://github.com/emulette/cellrune/blob/main/llms.txt).

The regex functions target PCRE2 semantics with bounded compile, matching, capture, and output
work. CellRune's prebuilt Python, Node.js, and MCP artifacts pin the bundled PCRE2 10.46 engine;
Rust consumers use `pcre2-sys` linkage policy and can set `PCRE2_SYS_STATIC=1` to select its bundled
source build.

`INDEX` follows Excel's zero-index reference behavior: a zero row or column selects the complete
corresponding column or row, and zero for both selects the complete input range. Scalar formulas
apply legacy implicit intersection, while array formulas can materialize the selected rectangle.
Unary and binary array operators that combine whole-column references use one common extent: the
greatest populated row among the columns those operands reference, with a one-row minimum for an
otherwise empty sheet. Cells in other columns of the same sheet do not widen it, so the value
depends only on the cells the expression's own dependency rectangles cover and a full and an
incremental recalculation agree. Missing cells within that extent are blanks, and directly
resolved source arrays plus operator outputs are charged to its cumulative array-cell budget. Function calls keep function-defined
argument boundaries: each array argument is evaluated under its own bounded context, and a
function's return does not establish a whole-column operator extent by itself. When another direct
whole-column operand has established such an enclosing context, the returned array is charged to
that context before the operator consumes it.

Direct 3-D references are accepted by `SUM`, `AVERAGE`, `AVERAGEA`, `COUNT`, `COUNTA`, `MAX`,
`MAXA`, `MIN`, `MINA`, `PRODUCT`, `STDEV.P`, `STDEV.S`, `VAR.P`, and `VAR.S` (including the
catalogued legacy aliases). The span follows workbook tab order, so hidden sheets participate and
reversed endpoint spelling produces the same set. Excel-defined direct 3-D error contexts remain
values: `INDEX` and `VLOOKUP` return `#VALUE!`, while `OFFSET` returns `#REF!`. Other direct 3-D
consumers remain an explicit `UnsupportedSheetRange` capability issue. A bare 3-D reference or one
composed through an array operator returns `#VALUE!`; it does not silently inherit an enclosing
aggregate's collection policy. The static capability scan and evaluator share this policy.

For repeated programmatic edits, use `WorkbookCalculationSession` instead of rebuilding stateless
calculation state after every cell:

```rust
use cellrune::{
    CalculationOptions, CancellationToken, CellAddress, CellValue, EditBatch, FiniteNumber,
    RecalculationMode, SheetId, WorkbookCalculationSession, WorkbookChange,
};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut session = WorkbookCalculationSession::create();
    let sheet = SheetId::new(1)?;
    let receipt = session.apply_changes(
        0,
        EditBatch::new([WorkbookChange::set_cell_value(
            sheet,
            CellAddress::from_a1("A1")?,
            CellValue::Number(FiniteNumber::new(42.0)?),
        )]),
    )?;
    let delta = session.recalculate(
        RecalculationMode::Auto,
        CalculationOptions::default(),
        CancellationToken::new(),
    )?;
    assert_eq!(delta.result_revision(), receipt.result_revision());

    Ok(())
}
```

`Auto` evaluates a proven dirty subset and falls back to the same full-workbook calculation
semantics when formula, name, sheet, option, dynamic-reference, or spill topology is uncertain.
Forced incremental mode fails closed instead of guessing. Sessions use optimistic semantic
revisions for atomic edits, retain bounded result-delta history, and reject stale calculations.
A batch whose accepted operations make no semantic change keeps the current revision, topology,
and installed calculation instead of forcing a redundant recalculation.
Long-running work can be prepared outside the session lock with `prepare_recalculation`, cancelled
through a request-owned `CancellationToken`, and installed only if its source revision is still
current.

Use targeted calculation when a headless service needs only a few outputs. Rust exposes
`calculate_targets` and `WorkbookCalculationSession::calculate_targets`; Python and Node expose
the same operation without requiring a prior full calculation:

```python
result = workbook.calculate_targets([
    {"sheet": "Sheet1", "start": "B1"},
    {"sheet": "Sheet1", "start": "D1", "end": "D10"},
])
```

```javascript
const result = await workbook.calculateTargets([
  { sheet: "Sheet1", start: "B1" },
  { sheet: "Sheet1", start: "D1", end: "D10" },
]);
```

The separate partial result contains only requested cells, deduplicated in sheet-ID and row-major
order, with typed values/issues, revision, fingerprint, provenance, options, and work counts.
Only target formulas and required precedents are parsed/evaluated; layout metadata is inspected
across the source workbook. A current complete cache with matching options can supply results.
Partial requests preserve dirty state, full results, delta history, previews, and save requirements.
Defaults bound input targets to 1,024, returned cells to 10,000, and evaluator invocations to
100,000; dynamic retries count toward the work budget. Request limits can be set with `limits`.
Known array followers calculate their entire anchor. For an undeclared spill whose owner has
never been calculated, include its anchor in the request; an unknown follower alone cannot locate
an arbitrary formula elsewhere in the workbook. Saved formula results are never used as current
calculation values. See [llms.txt](https://github.com/emulette/cellrune/blob/main/llms.txt) for
the complete Rust types and transport contract.

Undeclared spills outside the dependency scope are not discovered. Use declared spill ranges or
full calculation when independently anchored spills may overlap. A reproducible comparison of
small and complete scopes is recorded in the
[targeted calculation benchmark](https://github.com/emulette/cellrune/blob/main/release-tests/integration/benches/targeted_calculation.md).

The 0.1.16 change-preview workflow captures an immutable base/candidate transaction
without mutating the live workbook. Retention belongs to `cellrune-interop::WorkbookSession`, which
allows one active preview calculation and one published preview. Publication is two phase: a failed,
cancelled, stale, or oversized replacement leaves the previous published preview available. A
successful semantic mutation, commit, discard, or session close drops the published preview.
Preview details use core-owned opaque cursors; a cancelled or resource-limited pre-commit remains
retryable, while a stale or successful commit consumes its preview. The complete Rust and binding
call shapes are documented in
[llms.txt](https://github.com/emulette/cellrune/blob/main/llms.txt).

Table authoring uses stable identities rather than positional names. Construct
`WorkbookChange::rename_table`, `rename_table_column`, or `resize_table_rows` and include it in the
same atomic `EditBatch` as other workbook changes. Renames update cell formulas, defined names,
calculated-column formulas, and totals-row formulas through one typed source-span rewrite path.
Resize preserves table identity and filter/sort orientation, materializes calculated and totals
cells, and supports expanding a header-only empty table. Through
`WorkbookCalculationSession`, formula rewriting and table materialization are bounded by
`SessionLimits`. A successful `EditReceipt` lists the affected stable table IDs; any invalid
target, collision, rewrite error, resource limit, or cancellation rolls the whole batch back.

Use `analyze_defined_name`, `analyze_defined_name_with_options`, or
`analyze_defined_name_cancellable` to inspect a defined name against an immutable
`WorkbookSnapshot`. The typed result distinguishes a single rectangle, a 3-D span, ordered
multi-area geometry, a valid empty reference, dynamic formulas, constants, external targets,
invalid definitions, unsupported expressions, and missing names.

`open_xlsx_document_*` retains the exact input package for writing.
`write_recalculated_xlsx_bytes`, `write_recalculated_xlsx`, and
`write_recalculated_xlsx_path` bind a calculation to that exact input, update typed formula
caches, remove stale calculation chains, preserve unrelated package content, and reopen the output
before reporting success. Their `WriteReport::output_hash()` returns an `OutputHash`: the SHA-256
of the exact verified output archive bytes, deliberately distinct from the input identity. Strict
mode rejects incomplete calculations without producing an artifact; cache invalidation is an
explicit opt-in policy. `write_preserved_xlsx_bytes` remains available for an unchanged
preservation copy.

`WorkbookDraft::new` creates a canonical workbook, while
`WorkbookDraft::from_document` retains the source package for preservation-aware edits. Calculate
the draft's current `workbook()` and pass both objects to `write_xlsx_draft_bytes`,
`write_xlsx_draft`, or `write_xlsx_draft_path`. A mutation that changes workbook semantics advances
the semantic revision, so a calculation made before the latest effective edit is rejected. An
accepted no-op keeps the revision unchanged. Path writes are Save As operations and never replace
an existing destination unless replacement is explicitly enabled. Canonical drafts can author
dynamic-array formulas with `WorkbookDraft::set_cell_dynamic_formula`; calculation resolves their
spill region, detects occupied targets, and materializes followers for writing. Existing
document-backed dynamic formulas can be recalculated without changing their metadata, while adding
or replacing one is rejected until source metadata-index merging is implemented.

Package-backed documents expose phonetic annotations and frozen panes through
`XlsxDocument::presentation()`. `WorkbookDraft` provides atomic `set_annotated_text`,
`set_phonetics`, `clear_phonetics`, `set_frozen_pane`, and `clear_frozen_pane` mutations.
Phonetic base ranges are zero-based half-open UTF-16 code-unit ranges. Presentation-only changes
have a separate revision and reuse an otherwise current calculation. Source rich-text phonetic
editing, RTL pane authoring, and `PHONETIC()` calculation remain explicit unsupported boundaries.

Runnable examples are shipped in the crate package under `examples/` and live at
`crates/cellrune/examples/` in this repository. From the repository root, run one with
`cargo run -p cellrune --example <name> -- [arguments]`; from an extracted crate package, omit
`-p cellrune`. See the public
[llms.txt reference](https://github.com/emulette/cellrune/blob/main/llms.txt) for the complete
example inventory and a condensed public API reference.

## Language bindings

Python uses the mainstream PyO3 + maturin native-extension path. Node.js and TypeScript use napi-rs
over stable Node-API with Promise-backed native work and exact-version platform packages. Neither
binding requires a consumer Rust toolchain when installed from a wheel or prebuilt npm artifact.

The 0.1.18 release line targets Python 3.10 through 3.14 and Node.js 22 or newer. Install the
bindings with:

```bash
python -m pip install "cellrune==0.1.18"
npm install "@cellrune/node@0.1.18"
```

The bindings expose the same versioned read, edit, calculate, and write contract. Native package
availability remains platform-specific; package managers must select a wheel or exact-version npm
platform package compatible with the current runtime.
Python `inspect_defined_name` and Node.js `inspectDefinedName` expose the typed defined-name query.
The existing `apply_changes`/`applyChanges` v1 shapes are unchanged; the separate
`apply_changes_v2`/`applyChangesV2` methods add stable-ID table rename, table-column rename, and
table-row resize plus `changed_table_ids`/`changedTableIds` receipts.

In the current bindings, Python exposes synchronous
`preview_changes`, `preview_changes_page`, `commit_preview`, and `discard_preview`; the long
native preview operation releases the GIL. Node.js exposes Promise-backed `previewChanges`, then
synchronous `previewChangesPage`, `commitPreview`, and `discardPreview`. Python DTO fields use
snake case and integer IDs; Node DTO fields use camel case and `bigint` IDs. A successful Python
write report has `output_sha256`; the Node `WriteReport` has `outputSha256`.
[llms.txt](https://github.com/emulette/cellrune/blob/main/llms.txt)
defines the shared lifecycle and pagination semantics.

Python workbooks are context managers:

```python
from cellrune import Workbook

with Workbook.create() as workbook:
    workbook.set_number("Sheet1", "A1", 41.0)
    workbook.set_formula("Sheet1", "B1", "=A1+1")
    workbook.calculate()
    # 0.1.2-compatible calculation remains available when required:
    workbook.calculate(
        arithmetic_semantics="ieee_754",
        financial_solver_semantics="extended_search",
    )
    workbook.save("output.xlsx")
```

In a Node.js ES module, close the workbook in `finally`:

```js
import { Workbook } from "@cellrune/node";

const workbook = Workbook.create();
try {
  workbook.setNumber("Sheet1", "A1", 41);
  workbook.setFormula("Sheet1", "B1", "=A1+1");
  await workbook.calculate();
  await workbook.calculate({
    arithmeticSemantics: "ieee_754",
    financialSolverSemantics: "extended_search",
  });
  await workbook.save("output.xlsx");
} finally {
  workbook.close();
}
```

Python and Node.js `close()` calls are idempotent. Once `close()` returns, the binding-owned native
session has been released. An active calculation is cooperatively cancelled and a published preview
is discarded; subsequent operations fail with the stable `interop.session.closed` code.

## Local MCP

`cellrune-mcp` is a local stdio-only MCP `2025-11-25` server for AI hosts. It exposes a finite set
of high-level workbook workflow tools; spreadsheet functions remain formulas inside the workbook
and are not registered one by one as MCP tools. Start it with one or more explicit filesystem
roots:

```bash
cargo run --locked -p cellrune-mcp -- \
  --root /absolute/path/to/approved/workbooks
```

Its 17 tools are `workbook_create`, `workbook_open`, `workbook_close`, `workbook_summary`,
`workbook_read_range`, `workbook_function_usage`, `workbook_scan_capabilities`,
`workbook_apply_changes`, `workbook_apply_changes_v2`, `workbook_recalculate`, `workbook_calculate_targets`,
`workbook_changes_since`, `workbook_save_as`, `workbook_preview_changes`,
`workbook_preview_changes_page`, `workbook_commit_preview`, and `workbook_discard_preview`.
The v2 edit tool adds stable-ID table rename, table-column rename, and table-row resize while
retaining the v1 edit shapes. The four preview tools are the retained immutable transaction
workflow: preview returns a summary and ID, page returns a byte-bounded core-cursor page, and
commit or discard consumes the interop-owned preview. `workbook_save_as` returns the shared write
report, including its lowercase `output_sha256` output identity.

`workbook_calculate_targets` accepts `session_id`, `targets`, optional `options`, and optional
`limits`. Its limits are capped at the defaults above. The entire partial response must fit
`--max-response-bytes`; an oversized response fails without installing state, so callers can
retry with fewer targets. It uses the existing request cancellation and session lifetime controls.

The server also publishes read-only JSON resources at `cellrune://support/functions` and the
`cellrune://sessions/{session_id}/summary` resource template. Operators can set
`--max-sessions`, `--session-ttl-seconds`, `--max-response-bytes`, `--max-workbook-bytes`, and
`--log-level`; run `cellrune-mcp --help` for their defaults. Values outside the server's compiled
policy limits are rejected at startup.

`cellrune-mcp` is not published to a package registry. Prebuilt bundles for Linux, macOS, and
Windows are attached to each [GitHub release](https://github.com/emulette/cellrune/releases),
alongside their license materials and build provenance.

An MCP client can launch a release binary with configuration equivalent to:

```json
{
  "mcpServers": {
    "cellrune": {
      "command": "/absolute/path/to/cellrune-mcp",
      "args": ["--root", "/absolute/path/to/approved/workbooks"]
    }
  }
}
```

The server canonicalizes configured roots at startup. Every workbook path supplied to a tool must
be absolute and resolve inside one of those roots. The server bounds workbook/session/response
resources, writes protocol traffic only to stdout, writes diagnostics only to stderr, and never
provides a remote transport. Inputs are opened through an approved-root capability and read from
the same file handle under the configured archive-byte ceiling. Existing destinations are
protected unless the server starts with `--allow-overwrite` and a request also sets
`replace_existing`. Save As retains an open destination-directory capability from validation
through atomic installation, so renaming or replacing the ambient parent path cannot redirect a
write outside the approved root. Resource lists use byte-bounded cursor pagination. Preview pages
likewise return the longest complete interop prefix that fits the response limit and use opaque
cursors bound to the preview and detail section. At session capacity, create/open may evict the
least-recently-used idle session; active sessions are never evicted. TTL expiry and LRU eviction
drop the interop session, which discards its retained preview rather than maintaining a separate
MCP preview cache. Give the server the narrowest practical root; another process with write access
inside that root can still change workbook inputs and contents.

Tool results carry untrusted content. Cell text, sheet names, and defined names come from the
workbook and are returned verbatim, so a crafted workbook can place text that reads as an
instruction into a tool result. That is the same trust boundary as any other document a model
reads: the server does not rewrite workbook content, and the consuming application is responsible
for treating tool output as data rather than as instructions.

To inspect the local server before client integration:

```bash
npx --yes @modelcontextprotocol/inspector@1.0.0 \
  cargo run --locked -p cellrune-mcp -- \
  --root /absolute/path/to/approved/workbooks
```

## Scope

CellRune supports ordinary Transitional SpreadsheetML workbooks and a scoped set of Excel formula
syntax and functions. Unsupported formulas are returned as explicit per-cell calculation issues;
other formulas continue to calculate.

The following are outside the current scope:

- browser, frontend/UI, WebAssembly, hosted-service, and remote-MCP transports;
- `.xls`, `.xlsb`, `.ods`, and CSV;
- macro, add-in, external-workbook, query, or data-connection execution;
- 3-D references outside the audited direct-consumer policy above;
- data-table calculation; and
- iterative calculation and automatic host-time inputs.

[`docs/NUMERICS.md`](https://github.com/emulette/cellrune/blob/main/docs/NUMERICS.md)
records where calculated values differ from Excel and why, and documents the two calculation
options added in 0.1.3: `ArithmeticSemantics` and `FinancialSolverSemantics`, which default to
Excel's behavior and can be set to `Ieee754` and `ExtendedSearch` for what 0.1.2 did.

## Verification

Run the standard Rust test suite from the repository root:

```bash
cargo test --workspace --all-features --locked
```

Formula compatibility is regression-tested against committed Excel-saved and Apache POI
workbooks. See the
[`conformance/README.md`](https://github.com/emulette/cellrune/blob/main/conformance/README.md)
reference for the fixture layout, classifications, focused audit command, and maintenance policy.

## License

CellRune is dual-licensed under either the [MIT License](https://github.com/emulette/cellrune/blob/main/LICENSE-MIT)
or the [Apache License, Version 2.0](https://github.com/emulette/cellrune/blob/main/LICENSE-APACHE),
at your option. You need to comply with only one of them, not both. Apache-2.0 includes an
explicit patent grant; MIT does not. Both license texts are included in the source distribution.

Versions 0.1.0 through 0.1.2 were published under the MIT License alone and remain available
under those terms. The dual license applies from version 0.1.3 onward. Dependency license
information is provided in `THIRD_PARTY_LICENSES.md`.

## Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion
in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above,
without any additional terms or conditions.