01What CellGuard is
CellGuard is an independent, deterministic validator for spreadsheet edits to financial models. When an AI assistant proposes a change to a workbook, CellGuard checks that change against a catalogue of 42 fixed, versioned rules (rules-1.1) and returns one of three verdicts before the edit is committed: block, flag or pass. Every verdict is sealed in a SHA-256 hash-chained, tamper-evident audit record.
Deterministic by construction. Identical inputs — the same workbook, edits, policy and engine version — always produce an identical verdict, for everyone, on every run. The validator is separate from whatever proposes the edit, so author and checker are never the same component.
What it does not do
- It never runs a language model and makes no LLM call in the verification path. The verdict is computed by rules, not inference.
- It never trains, fine-tunes or learns on your data, and it does not profile you. Per-client rules, where used, are written and version-pinned by humans.
- It does not send your workbooks or edits to any AI provider, and the verification path makes no outbound network egress. (Outside the verification path, the product makes a few operational calls that carry no model data, such as the website access-request email.)
- It is not a spreadsheet editor or an assistant. It does not write to your workbook; it evaluates a proposed change and returns a verdict for a human or agent to act on.
02Connect it as an MCP server
CellGuard is exposed as a remote Model Context Protocol (MCP) server. Point any MCP-capable client — Claude Code, Cursor, the Claude Agent SDK, or any host that supports remote MCP — at the HTTPS endpoint:
All transport is over TLS/HTTPS. Authentication is required; if it is not configured the server fails closed and returns 503 rather than accepting unauthenticated calls. Two authentication methods are supported:
OAuth 2.1
- The server publishes Protected Resource Metadata (RFC 9728) and Authorization Server Metadata (RFC 8414), so a compliant client can discover the endpoints automatically.
- Dynamic client registration (RFC 7591) is supported, with PKCE (S256) on the authorization-code exchange.
- In a client such as Claude Code, add the connector by its URL and complete the OAuth consent in the browser when prompted; the client stores and refreshes the token.
Bearer API key
- For headless or server-to-server use, send a bearer API key in the
Authorizationheader. Keys are compared in constant time. Request a key from [email protected].
Per-client setup
- Claude / Claude Code — add a remote MCP connector for
https://mcp.celldelta.ai/mcp. On Claude.ai (Pro/Max) you are taken to the CellGuard authorization screen: enter the approval secret you were given and approve, after which the token is used automatically. In Claude Code, complete the same OAuth consent in the browser when prompted. CLI:claude mcp add --transport http celldelta https://mcp.celldelta.ai/mcp - Cursor / VS Code — add CellGuard to your MCP configuration:
{ "mcpServers": { "celldelta": { "url": "https://mcp.celldelta.ai/mcp" } } }
- Claude Agent SDK or any other host — any client that supports remote MCP over HTTPS with OAuth 2.1 or a bearer key.
What access it has
CellGuard is read-mostly: it evaluates a proposed edit or an uploaded workbook and returns a verdict. It never writes to your workbook — your own agent applies the edit. The only state-changing tools are commit (appends a decision to the audit chain) and set_policy (changes thresholds), and both are recorded. It reads only the edits or workbook you pass it and reaches no external data source.
Browser-facing endpoints enforce a strict Origin allowlist, and both per-IP and per-key token-bucket rate limiting apply. Each deployment is single-tenant: it carries one shared approval credential, and sessions are isolated by an unguessable random session_id rather than by per-user identity.
Deployment modes
- Managed service — hosted by CellDelta. For a full-file check, your workbook is processed on our infrastructure to compute a verdict, then evicted. CellDelta acts as your processor under a Data Processing Agreement.
- Self-hosted — runs in your own cloud or on-premises. Your workbooks and verdicts never reach CellDelta, and no sub-processor receives your content.
Sending a workbook for a full-file check
For cross-sheet, recompute and balance checks, send the file over a side channel rather than pasting it into chat (a long base64 blob is slow and can corrupt the bytes). Call request_upload for a one-time URL, then from your code sandbox stream the raw file:
Pass the returned workbook_id to verify_workbook / verify_plan / analyze_workbook. On Claude.ai, allow the host once under Settings → Capabilities → Code execution and file creation → Additional allowed domains (add mcp.celldelta.ai), or use the no-setup browser upload page the tool returns. For a quick edit-level check with no file at all, use verify_ops.
03Verdicts & coverage
Every check resolves to one of three deterministic verdicts. They are control outputs for a human or agent to act on, not advice:
- block Do not apply the edit. A hard integrity failure (for example an overwritten formula, a ~100× magnitude slip, a broken balance or roll-forward, a circular reference). The agent should not engineer a workaround; it should show the reason and suggested fix and let the user decide. A user can override via policy.
- flag Proceed with care. A warning-tier finding the agent should surface and ask about before continuing (for example an inconsistent formula in a range, a sign-convention change, a domain-pack tie-out, a declared-identity violation).
- pass No rule fired within the stamped coverage. Note that a
passis only as wide as the coverage shown on the verdict.
verify_ops runs the local, edit-level checks only — it does not see cross-sheet dependency, recompute, balance or roll-forward, so a pass there is not full coverage. For those, send the file (request_upload → verify_workbook / verify_plan). On three real single-author finance models the measured false-positive rate via verify_plan (recompute on) was 0/90, Wilson 95% upper bound 4.09% — a signal on real models, not a multi-author benchmark and not a "zero false positives" claim.04The tools
The MCP server exposes the following tools. Each has a title and read-only / destructive hints set, so a host can render and gate them correctly. They operate solely on the workbook and edits you provide and reach no external data source.
Verify core
Session & upload lifecycle
session_id that scopes subsequent calls and the audit chain..xlsx over HTTPS. Returns a workbook_id to pass to the full-file tools.verify_verdict); an accept of ops verified as block is refused and must be an explicit override. This is the canonical record of what was done.Audit & evidence record
ops_digest per entry. Stores digests and metadata, not cell values.Policy & reference config
05A typical flow
CellGuard sits between a proposed edit and the workbook. The agent calls it before writing, presents the verdict faithfully, and only commits on a clean result.
- 1. The assistant proposes an edit to a financial model (a new value, a formula change, a batch of ops).
- 2. The agent calls CellGuard before writing. Use
verify_opsfor a fast, no-file, edit-level check; or upload the file (request_upload) and callverify_plan/verify_workbookfor full coverage including recompute and tie-outs. - 3. CellGuard returns a verdict. On block, do not apply the edit and do not work around it — show the reason and suggested fix and let the user decide. On flag, surface it and ask before proceeding. On pass, the edit is clear within the stamped coverage.
- 4. The edit is applied and recorded. Once the user accepts (or overrides), call
committo append the applied edit and the decision to the SHA-256 hash chain. The result is a reproducible record thataudit_logandevidence_packcan return.
Example prompts you can give your agent:
- "Before you change the WACC in this DCF, run the edit past CellGuard and show me the verdict."
- "Upload this model to CellGuard and verify the whole workbook before applying any changes."
- "Treat CellGuard as a gate: never write an edit to a financial model without a pass, and record every decision."
06Skills
A skill is a reusable instruction file that tells your AI client how to use the CellGuard MCP server effectively: when to trigger it, how to sequence tool calls, and what patterns to follow. Add it to your client once and it will know how to drive CellGuard without extra prompting.
CellGuard agent skill
---
name: cellguard
description: >
Use CellGuard whenever an AI is about to write, change, or delete a value or
formula in a financial spreadsheet or model (DCF, 3-statement, LBO, budget,
consolidation, FX, any .xlsx with financial logic). Call it BEFORE applying
the edit, present its verdict exactly as returned, and on a block verdict do
not apply the edit or build a workaround. CellGuard is an independent,
deterministic validator: it never runs a language model and never guesses.
---
# CellGuard: independent pre-write control for AI edits to financial models
## What it is
CellGuard is an INDEPENDENT, DETERMINISTIC validator for spreadsheet edits to
financial models. It never runs a language model and never guesses: the same
workbook, ops, policy, and engine version always produce the same verdict, for
everyone, on every run. The verdict is a CONTROL OUTPUT, not advice. You are the
author of the edit; CellGuard is the separate checker. Catalog: 42 named rules
(rules-1.1). Remote MCP endpoint: https://mcp.celldelta.ai/mcp.
## When to use this skill
Call CellGuard BEFORE you write, change, or delete any value or formula in a
financial model. Triggers include:
- Setting a cell value or replacing a formula (for example changing WACC, a
growth rate, a margin, a tax rate, a discount factor).
- Overwriting a formula with a hardcoded number.
- A batch of proposed edits to a model before any of them is applied.
- The user asks to "check", "validate", or "gate" an edit to a spreadsheet, or
asks for an audit-ready record of a change.
Run the check first, then act strictly on the verdict. Do not apply first and
verify after.
## Core workflow: verify, then decide, then record
### Step 1: verify (pick one path)
Path A. NO FILE (fast, edit-level only): use `verify_ops`. No upload, no
session. Pass each edit with its current value as `old_value`:
verify_ops(ops=[
{ "type": "set_value",
"params": {
"cell": "G3", "value": 0.28, "old_value": "=F3/E3"
} }
])
- ALWAYS include `params.old_value` (the cell's current formula or value) so the
formula-overwrite (including a formula blanked or replaced with text) and
magnitude/decimal checks can run.
- To also check SIGN convention, ASSUMPTION bounds, and magnitude peer-fit, add
`params.row_context`:
{ "type": "set_value",
"params": {
"cell": "B12", "value": 80, "old_value": -98,
"row_context": { "label": "Operating costs",
"peers": [-100, -98, -105] }
} }
- `verify_ops` runs LOCAL, edit-level checks only. It does NOT see cross-sheet
dependency, recompute, or balance / tie-outs. Read the stamped coverage
(`coverage: "local"`, plus `coverage_note`): a `pass` here is NOT full
coverage.
Path B. FULL FILE (cross-sheet dependency, recompute, balance, roll-forward):
send the workbook over the upload side channel, then verify against the whole
model.
1. Get a one-time upload URL:
request_upload() -> { "upload_url": "...", "how_to": "..." }
2. In your code sandbox, stream the raw .xlsx. Do NOT base64 it into chat: that
is slow and corrupts the bytes.
curl -sS -X POST '<upload_url>' --data-binary @model.xlsx
# -> {"workbook_id": "..."}
3. Verify the planned edits against the full model:
verify_workbook(
workbook_id="...",
ops=[ { "type": "set_value",
"params": { "cell": "G3", "value": 0.28,
"old_value": "=F3/E3", "sheet": "DCF" } } ]
)
For a stateful loop (several checks on one model, then commit to an audit chain),
open a session first and use `verify_plan`:
open_session(workbook_id="...") -> { "session_id": "...", ... }
verify_plan(session_id="...", ops=[ ... ])
To scope a model before checking it, call `analyze_workbook(workbook_id="...")`
(read-only Model-Health score, 0-100, plus findings; it does not itself block).
Claude.ai note: if the sandbox blocks outbound network, allowlist the host once
under Settings > Capabilities > Code execution and file creation > Additional
allowed domains (add `mcp.celldelta.ai`), or use the browser upload page
(`<server>/u`) returned in `how_to`.
### Step 2: decide (read the verdict, do not edit it)
Every verify call returns ONE tiered verdict with a stamped coverage. Present it
to the user clearly. You MAY phrase it in the user's language for readability,
but you MUST keep the verdict, the numbers, and the cell references EXACTLY as
returned. Do NOT soften, strengthen, add findings of your own, or second-guess
CellGuard itself. Do not claim an edit is safe beyond the stated coverage.
- block: a hard integrity failure (overwritten formula, ~100x magnitude slip,
broken balance or roll-forward, circular reference, declared-identity break).
Do NOT apply the edit and do NOT engineer a workaround. Show the reason and any
`suggested_fixes`, and let the user decide. The user can override via policy.
- flag (the warning tier, returned on the wire as `warn`): surface it and ASK
the user before proceeding.
- pass: no rule fired WITHIN the stamped coverage only. A `verify_ops` pass
(`coverage: "local"`) is not full coverage; for cross-sheet, recompute, and
balance, upload the file and use `verify_workbook` / `verify_plan`.
Deterministic fixes, when available, are in `verdict.suggested_fixes`. Use
`explain(check_type="...")` to tell the user what a check catches and its tier
(the detection method itself is intentionally not exposed).
### Step 3: record (commit the decision)
Only after the user accepts (or overrides), and after you have applied the edit
in your own tool, append the decision to the tamper-evident audit chain:
commit(session_id="...", ops=[ ... ],
decision="accept", # accept | override | reject
reason="user approved WACC change",
provenance="ai") # ai | human
`commit` requires a session (`open_session`). It appends a SHA-256 hash-chained
entry holding an ops digest plus metadata (no cell values). Each entry also
records the verdict of the verification that preceded it (`verify_verdict`:
block / warn / pass / unverified). A plain `decision="accept"` of ops the
session verified as block is refused — record an explicit `decision="override"`
with a reason instead, so the override is visible in the chain. Retrieve the
record with `audit_log(session_id="...")` (returns the chain and whether it is
cryptographically valid) or `evidence_pack(session_id="...")` for an
auditor-ready bundle mapped to control frameworks.
## Tool reference
VERIFY (the core pre-write checks)
- verify_ops(ops): no-file, edit-level check. Each op needs params.old_value;
optional params.row_context. Coverage: local. Use when you cannot upload the
file and want a fast pre-write check.
- verify_workbook(ops, workbook_id): full-model check of a plan against an
uploaded workbook (cross-sheet, recompute, balance). Use for full coverage
without managing a session.
- verify_plan(session_id, ops): full-model check inside a session, without
committing. Use in a stateful verify -> commit -> audit loop.
- analyze_workbook(workbook_id): read-only Model-Health audit (0-100 score,
findings). Does not block. Use to scope a model before checks.
UPLOAD AND SESSION (lifecycle)
- request_upload(): one-time presigned upload URL plus how_to. Always call before
any full-file tool, then curl the .xlsx to the URL.
- open_session(workbook_id): start a stateful session (returns session_id,
recompute availability, initial health). Use for verify_plan / commit / audit.
- commit(session_id, ops, decision, reason, provenance): apply edits in the
session and append a tamper-evident audit entry. decision: accept | override |
reject; provenance: ai | human. The entry records the verdict of the preceding
verification (verify_verdict); an accept of ops verified as block is refused —
use an explicit override with a reason. Use to record an accepted or
overridden edit.
- close_session(session_id): free session resources. Use when done.
AUDIT, EVIDENCE, CHANGE-CONTROL (the record)
- audit_log(session_id): the append-only hash chain plus validity flag. Use to
show the canonical record of what was done.
- evidence_pack(session_id): auditor-ready bundle (decision log plus metadata,
mapped to EU AI Act Art. 12, SOX/ICFR, DORA, EUC/model-risk). Use for a review
committee.
- model_diff(session_id, baseline_workbook_id): deterministic structural diff vs
the sign-off baseline (formulas frozen to literals, formulas removed,
aggregation basis changed AVERAGE/SUM, SUM ranges shrunk, volatile/external
refs introduced). Defaults to the workbook as opened if baseline_workbook_id is
omitted. Use for change-control. Reports facts, not a verdict.
POLICY AND REFERENCE (config and lookup)
- set_policy(session_id, policy): attach a versioned Policy Pack (tiers,
tightened bounds, two_person, conventions: custom label->concept and sign
conventions). Use to declare model identities or adjust thresholds.
- explain(check_type): what a check catches, its default tier, rules version.
Use to explain a fired check to the user.
- rules_catalog(): the full 42-rule catalog. Use to list available rules.
- capability_matrix(): surface x error-class x blocks-pre-commit, the named
out-of-scope boundary, and the measured false-positive rate with its Wilson
bound. Use to show what is gated before commit.
OPERATIONAL (observability, rarely driven by the agent)
- telemetry(session_id): per-session counters (plans/edits verified, errors
caught, decisions, dollars at risk in blocked edits). Session-scoped only.
- drift_report(session_id): per-session control evidence (volume, override rate,
dollars-at-risk-prevented, top firing rules). Session-scoped only.
## Patterns
Quick gate, no file (single edit):
verify_ops(ops=[{ "type":"set_value",
"params":{ "cell":"G3","value":0.28,"old_value":"=F3/E3" } }])
-> on pass: apply the edit; on flag: ask first; on block: do not apply, show
reason plus suggested_fixes.
Full-coverage gate with audit trail (recommended for real models):
request_upload() # -> upload_url
curl -sS -X POST '<upload_url>' --data-binary @model.xlsx # -> workbook_id
open_session(workbook_id="...") # -> session_id
verify_plan(session_id, ops=[ ... ]) # full verdict, nothing written yet
# user accepts -> apply the edit in your tool, then:
commit(session_id, ops=[ ... ], decision="accept", provenance="ai")
audit_log(session_id) # canonical hash-chained record
Change-control review since sign-off:
open_session(workbook_id="<current>")
model_diff(session_id, baseline_workbook_id="<signed-off baseline>")
-> report exactly what changed structurally; this is facts, not a verdict.
Declared identities / tighter bounds for a specific model:
set_policy(session_id, policy={
"identities": [ { "name": "balance",
"expr": "total_assets == total_liabilities",
"tier": "block" } ],
"conventions": { ... }, "tiers": { ... } })
verify_plan(session_id, ops=[ ... ])
Identity expressions accept cell refs or concept names (total_assets,
gross_profit, revenue, cogs, ...), resolved to the workbook's own rows.
Out of the box, with no policy at all, CellGuard already guards the
balance-sheet identity and the gross-profit identity (both sign
conventions); disable with policy default_identities: false.
## Important notes
- Verdict semantics are load-bearing. block = do not apply, no workaround; flag =
ask first; pass = clear within the stamped coverage only. The verdict is a
deterministic control output, not your opinion. Never invent, drop, soften, or
strengthen a finding, and never comment on or second-guess CellGuard itself.
- The middle tier shows as flag in product copy but the wire value is `warn`;
treat them as the same tier.
- Coverage is stamped on every verdict. `verify_ops` is edit-level only
(`coverage: "local"`): it does NOT see cross-sheet dependency, recompute, or
balance, so a local pass is NOT a clean full-model result. Upload the file and
use verify_workbook / verify_plan for those.
- Endpoint is https://mcp.celldelta.ai/mcp (note the /mcp path; the bare host is
only a status page). Auth is OAuth 2.1 plus PKCE or a bearer key; the server
fails closed (401 until authenticated, 503 if no auth is configured).
- Send files over the upload side channel, never base64 in chat: request_upload,
then `curl -sS -X POST '<upload_url>' --data-binary @file.xlsx` to get a
workbook_id. On Claude.ai add `mcp.celldelta.ai` under Settings > Capabilities >
Code execution and file creation > Additional allowed domains, or use the
returned browser upload page.
- Determinism: identical (workbook, ops, policy, engine version) always yields an
identical verdict. CellGuard never runs a model, never trains on your data, and
makes no outbound call in the verification path.
- Single-tenant per deployment: sessions are isolated by an unguessable
`session_id`, which is a session boundary, not a per-user authorization
boundary. Deploy separate instances for separate tenants.
- Finite by design. CellGuard catches structural and quantitative faults
(overwritten formulas, magnitude/decimal slips, sign flips, broken balance /
roll-forward / tie-outs, dependency and lookup faults, declared-identity
violations). It does NOT opine on business-logic correctness that recomputes to
a plausible-but-wrong number with no structural signal. A pass is never a
guarantee the model is correct.
07Data handling
- No model, no learning, no egress. Verification runs no language model, trains on nothing, and makes no outbound call in the verification path.
- Edits and workbooks are processed transiently.
verify_opsedits are evaluated in memory and never stored. An uploaded workbook sits in an in-memory or Redis artifact store under a 1-hour sliding time-to-live (refreshed on access), then is evicted. No cell values are persisted. - The audit log stores digests, not values. Each entry holds an
ops_digest— a one-way hash of the edits — plus metadata (timestamp, decision, provenance, reason, counts, versions). The chain is durably retained in an append-only store on the managed service for 24 months (configurable), then automatically purged; self-hosted deployments configure their own retention. - Data minimisation. No analytics, cookies or tracking. Client IP is processed for security and rate-limiting and kept for a short period.
- Single-tenant per deployment. Sessions are isolated by an unguessable random
session_id. This is a session boundary, not a per-user authorization boundary; deploy separate instances where you need separate tenants.
Full details are in the Privacy Policy, the Security overview and the Terms. A Data Processing Agreement is available on request.
08Troubleshooting
- 401 Unauthorized. The request is not authenticated yet. Complete the OAuth consent (enter the approval secret on the CellGuard screen), or send a valid bearer key in the
Authorizationheader. The endpoint returns401by design until you authenticate — expected, not an outage. - 503 Service Unavailable. The deployment is fail-closed because authentication is not configured on it. This is a server-side setting, not your client: on the managed service, contact us; self-hosted, set
CELLGUARD_API_KEYS. - Connected but no tools, or a call 404s. Confirm the URL ends in
/mcp(https://mcp.celldelta.ai/mcp). The bare host is only a status page. - Upload or
curlblocked in Claude.ai. Addmcp.celldelta.aiunder Settings → Capabilities → Code execution and file creation → Additional allowed domains, or use the browser upload page the tool returns. - A
passstampedcoverage: local. That isverify_ops(no file): it checks the edit only. For cross-sheet, recompute and balance, upload the file and callverify_workbook.
09Support
Questions about connecting CellGuard, requesting an API key, a Data Processing Agreement, or a self-hosted deployment:
Krzysztof Dalewski (CellDelta)
ul. Capri 4/18, 02‑762 Warsaw, Poland
[email protected]
MCP endpoint: mcp.celldelta.ai/mcp · Site: celldelta.ai