Use D2B from a coding agent
D2B has two paths for agents. MCP (tool calling: Claude Code / Claude Desktop / Cursor / VS Code and others) and the CLI (d2b: shell-driven agents, git sync, batch work). Both expose the same surface: JSON out, errors with a suggested_fix, an idempotency key on every mutation, no interactive prompts.
The operating norms come from the server. When a host connects over MCP, the initialize response carries instructions — which tool when, in what order — and the same text is readable as the d2b://guide resource. You do not paste a long procedure into your repo; you paste the connection and the choice of path (below).
Setup (the human’s part)
Section titled “Setup (the human’s part)”- Mint a least-privilege PAT — for an agent, a workbook-scoped token with a rate limit (to delegate a whole workspace use
"resource": "workspace:<WS_ID>"; creation is pinned there too. A token always belongs to one account; omitaccount_idfor the default):
curl -X POST $BASE/api/v1/me/tokens -H "Authorization: Bearer $JWT" \ -d '{"name": "agent", "scopes": ["workbooks:read", "workbooks:write"], "resource": "workbook:<WB_ID>", "rate_limit_per_minute": 60}'- Connect — over MCP, Claude Code is one command (other hosts: Connect over MCP):
claude mcp add d2b --transport http https://d2b.dev/mcp/ --header "Authorization: Bearer $PAT"d2b init writes the repo side — adds the AGENTS.md section below and prints the canonical MCP entry any host can take. Known hosts merge straight into their file with --host claude-code|cursor|vscode|codex|windsurf|claude-desktop; anything else via --config PATH (JSON or TOML). The token stays an environment-variable reference, never written to a file.
For the CLI, export D2B_API_KEY / D2B_BASE_URL (Claude Code does not read .env by itself) and install with pipx install d2b-sdk or uvx --from d2b-sdk d2b (uv add d2b-sdk + uv run d2b as a project dependency).
The repo snippet (connection and choice only)
Section titled “The repo snippet (connection and choice only)”The norms come from the server, so do not copy the procedure here — a copy cannot follow new tools and goes stale.
## D2B- Data work goes through D2B. The MCP server `d2b` is connected — follow the norms in `d2b://guide` first.- For git management (`d2b pull` / `d2b push`) and batch work use the CLI `uvx --from d2b-sdk d2b ...` (JSON output; auth from env D2B_API_KEY / D2B_BASE_URL).- Destinations, how to bring files in and how versions work are in the guide. When unsure, ask the human.The norms an agent follows (delivered by the server)
Section titled “The norms an agent follows (delivered by the server)”The gist of instructions / d2b://guide, with the CLI equivalent. The norms do not depend on the surface; only the tool names differ.
| Norm | MCP | CLI |
|---|---|---|
| Orient first | list_my_data → get_schema | d2b workbooks list → d2b tables list --workbook WB |
| Confirm the destination by name before creating | list_my_workspaces → create_workbook | d2b workspaces list → d2b workbooks create --workspace-id … |
| Bring files in by reference (never through your own context) | ingest_url / request_upload → ingest_upload / import_cloud_file | d2b upload FILE --workbook WB --wait (d2b jobs wait JOB_ID for long runs) |
| Messy sheets: inspect the structure before materialising | ingest_file (staged) → analyze_source → update_parse_spec → materialize_source | d2b upload --mode staged → d2b sources analyze → … materialize |
| Derive with transforms, not row edits | add_transform / list_transforms | d2b transforms add; d2b pull writes them to transforms/ |
| On 409 re-read and re-apply (never overwrite blindly) | re-read edit_version with read_table | re-read edit_version with d2b tables rows NAME --workbook WB |
| Cut a version at milestones so you can go back | commit_snapshot / restore_snapshot / undo_op | d2b versions commit / d2b versions revert |
| Check numbers before reporting them | review_table / review_workbook (agent mode: follow the job with get_job) | d2b review --workbook WB [--table NAME] [--agent] |
| Clean up | delete_workbook | d2b workbooks delete WB |
Managing a workbook in git (CLI)
Section titled “Managing a workbook in git (CLI)”d2b pull --workbook $WB writes transforms/ sheets/ charts/ + d2b.json (--data TABLE adds a base table’s CSV). Edit → check with d2b push --dry-run → d2b push --commit $(git rev-parse --short HEAD) → commit d2b.json. push sends only what changed (transforms re-run, data is 3-way merged); if refused, pull, merge, push again. charts/ is read-only. Details: Manage a workbook in git.
Faithful revision (revise = fix just those rows, get the original Excel back)
Section titled “Faithful revision (revise = fix just those rows, get the original Excel back)”“Keep the whole original Excel, fix only the relevant rows, return a new file” is one revise call (internally it folds analyze → materialize → transform → write-back). The computation stays server-side as a SQL transform (lineage available); styles, charts, other sheets and out-of-region formulas are preserved.
d2b sources revise equipment.xlsx --workbook $WB \ --transform-name merge_tokyo_sites --range A3:N8 --sheet Summary \ --sql-file merge.sql -o output.xlsxmerge.sql is a {{ artifact_name }} view ({{ src }} is bound to the region table). How to write row-folding aggregations correctly:
- Additive columns (counts, amounts):
SUM. - Ratios (utilisation, completion): never average them. Recompute after summing:
SUM(numerator) / NULLIF(SUM(denominator), 0). - Averages (MTBF, MTTR …): weighted, not naive:
SUM(value * weight) / NULLIF(SUM(weight), 0)(weight = a meaningful column like unit count). - Categorical columns (ratings): a representative value:
arg_max(rating, units_managed). - To keep row positions, include
MIN("__d2b_row_id")in the SELECT (the folded row lands at its first member’s position; vacated rows blank out in place; nothing else moves).
CREATE OR REPLACE VIEW "{{ artifact_name }}" ASSELECT CASE WHEN site IN ('Tokyo Site 1','Tokyo Site 2') THEN 'Tokyo' ELSE site END AS site, SUM(units_managed) AS units_managed, SUM(units_active) * 1.0 / NULLIF(SUM(units_managed), 0) AS utilization, SUM("MTBF(h)" * units_managed) / NULLIF(SUM(units_managed), 0) AS "MTBF(h)", arg_max(rating, units_managed) AS rating, MIN("__d2b_row_id") AS "__d2b_row_id"FROM {{ src }}GROUP BY 1ORDER BY MIN("__d2b_row_id")If the region contains total/subtotal rows (=SUM(...)), those formula cells are preserved untouched (Excel recalculates). Edits that would grow the region beyond the template’s geometry are refused (409). Over MCP this is revise_source; in the SDK, sources.revise(...).
Delivering constants as live formulas (formula columns)
Section titled “Delivering constants as live formulas (formula columns)”A column of constants (say, a utilisation rate) can be delivered as a per-row Excel formula at export time. The values themselves are untouched — this is a delivery projection that only adds “how to re-derive it” to the shipped .xlsx. References use {column} placeholders (never A1 addresses — they are anchored to identity so sorting and filtering can’t shift them). The server resolves coordinates at export.
# Deliver utilization = units_active / units_total as a live per-row formulad2b tables set-formula equipment utilization "{units_active} / {units_total}" --workbook <WB>out = client.tables.set_formula(wb, "equipment", "utilization", "{units_active} / {units_total}")out["verification"] # {checked, rows, matches, mismatches} — does it reproduce the current constants?Because the values are canonical, the write returns a verification report (does the formula reproduce the current column within tolerance?). You can confirm “replace constants with a formula” before delivering. Over MCP: set_formula_column / list_formula_columns / clear_formula_column. Currently applies to xlsx export (composable with {{ ... }} SQL transforms and revise write-back). Functions (SUM/IF …) get structural validation only; underline is unsupported.
Cross-sheet references: {table!column} references another table’s full column range (when several tables are exported into one file, it compiles to an absolute reference into that sheet, e.g. 'rates'!$B$2:$B$N). Wrap it in an aggregate:
# Revenue share = this row's amount ÷ the sum of rates.weightclient.tables.set_formula(wb, "sales", "share", "{amount} / SUM({rates!weight})")client.export.tables(wb, tables=["sales", "rates"], format="xlsx")# → each share row compiles to =B2/SUM('rates'!$B$2:$B$N) (same-table {amount} relative, cross-refs absolute)If the referenced table isn’t part of the same export, the formula can’t resolve and falls back to values. Express VLOOKUP-style key joins as JOIN transforms (add_transform), not formulas (lineage is preserved).
Constraints and notes
Section titled “Constraints and notes”- Cloud sandboxes (Codex etc.) may need egress allow-listing before the API is reachable
- For agents with short Bash timeouts, “async upload →
jobs wait” is safer thanupload --wait