# MCP Server

> Connect AI assistants like Claude, ChatGPT, Cursor, and Codex to DayZero via the Model Context Protocol — sign in with OAuth 2.1 in your browser.

- **Category:** Integrations
- HTML version: https://www.ondayzero.com/docs/guides/mcp
- All guides: https://www.ondayzero.com/docs/guides/index.md
- Endpoint catalog: https://www.ondayzero.com/docs/reference/index.md

---

# MCP Server

DayZero exposes a **Model Context Protocol (MCP)** server that lets AI assistants — Claude, ChatGPT, Cursor, and other MCP-compatible clients — interact with your accounting data through natural language.

The server is mounted at `/mcp` and uses the **Streamable HTTP** transport. Authentication uses **OAuth 2.1** — your client discovers the auth server, registers itself, and opens a browser for you to sign in to DayZero and approve access. No tokens to copy.

> **Just want to connect?** Claude users can click **[Add DayZero to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=DayZero&connectorUrl=https%3A%2F%2Fapi.ondayzero.com%2Fmcp)** — claude.ai opens the connector dialog prefilled; review it, click **Add**, and sign in. Every other client: add `https://api.ondayzero.com/mcp` with no credentials and you'll be prompted to **sign in to DayZero in your browser** and approve access. See [Quick Connect](#quick-connect) below.

> **Note:** REST API keys (`dz_…`) from **Settings → Developers** authenticate the REST API only. They do **not** work on the MCP server — use the OAuth browser flow instead.

## Quick Connect

The fastest path per client. Every row uses the same server URL — `https://api.ondayzero.com/mcp` — and ends in the same browser OAuth sign-in. **Settings → Developers** inside DayZero has one-click **Add to Claude** / **Add to Cursor** buttons and copy-paste snippets for the rest.

| Client | Fastest path |
|--------|--------------|
| **Claude** (web, desktop & mobile) | **[Add DayZero to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=DayZero&connectorUrl=https%3A%2F%2Fapi.ondayzero.com%2Fmcp)** — one click, prefilled connector dialog |
| **Claude Code** | `claude mcp add --transport http dayzero https://api.ondayzero.com/mcp` — see [Connecting Claude Code](#connecting-claude-code) |
| **ChatGPT** | Enable **Developer mode**, then add the URL as a connector — see [Connecting ChatGPT](#connecting-chatgpt) |
| **Codex** (CLI, IDE extension & ChatGPT desktop app) | Two lines in `~/.codex/config.toml` — see [Connecting Codex](#connecting-codex) |
| **Cursor** | **Settings → MCP Servers → Add** with the URL above, or the **Add to Cursor** button in DayZero — see [Connecting Cursor](#connecting-cursor) |

On Claude **Team/Enterprise** plans only org owners can add custom connectors — send your admin the same link on the admin path:

```text
https://claude.ai/admin-settings/connectors?modal=add-custom-connector&connectorName=DayZero&connectorUrl=https%3A%2F%2Fapi.ondayzero.com%2Fmcp
```

## Domain Profiles For Power Users

DayZero keeps the unified server at `/mcp` for general use and also exposes focused domain profiles for multi-server MCP setups:

- `https://api.ondayzero.com/mcp/core` (`dayzero-core`) — discovery, search/fetch, snapshots, job polling
- `https://api.ondayzero.com/mcp/ar` (`dayzero-ar`) — invoices, customers, credit memos, AR workflows
- `https://api.ondayzero.com/mcp/ap` (`dayzero-ap`) — bills, vendors, vendor credits, AP workflows
- `https://api.ondayzero.com/mcp/reports` (`dayzero-reports`) — reporting, analytics, ledgers, transaction reads
- `https://api.ondayzero.com/mcp/writes` (`dayzero-writes`) — full write surface
- `https://api.ondayzero.com/mcp/o2c` (`dayzero-o2c`) — order to cash: pipeline deals, sales orders, customer contracts, job-costing projects, and bulk import for each
- `https://api.ondayzero.com/mcp/personal` (`dayzero-personal`) — personal connections: search and read the authenticated user's own linked Gmail (per-user, read-only; requires linking Gmail in Settings → Connected Accounts)

Each profile keeps `tools/list` scoped to that domain (well under strict hosted-client limits) while the full internal tool registry remains intact. Self-hosted deployments can force one profile process-wide with `MCP_DOMAIN_PROFILE` (for example `MCP_DOMAIN_PROFILE=dayzero-ar`).

### Multi-Server Setup Examples

Use multiple MCP server entries when you want separate focused tool surfaces in one client session.

**Claude Desktop (`claude_desktop_config.json`):**

```json
{
  "mcpServers": {
    "dayzero-core": {
      "url": "https://api.ondayzero.com/mcp/core"
    },
    "dayzero-ar": {
      "url": "https://api.ondayzero.com/mcp/ar"
    },
    "dayzero-ap": {
      "url": "https://api.ondayzero.com/mcp/ap"
    },
    "dayzero-reports": {
      "url": "https://api.ondayzero.com/mcp/reports"
    },
    "dayzero-writes": {
      "url": "https://api.ondayzero.com/mcp/writes"
    },
    "dayzero-o2c": {
      "url": "https://api.ondayzero.com/mcp/o2c"
    }
  }
}
```

**Cursor:** add one MCP server per profile in **Settings → MCP Servers** (same URLs above). Cursor will run OAuth for each server entry and keep tools separated by server.

**ChatGPT:** create one connector per profile under **Settings → Connectors → Create** using the profile URLs above.

**GitHub Copilot / VS Code MCP:** add each profile as a separate server in your MCP configuration (same URLs above) so tool discovery stays focused and below hosted limits.

## What You Can Do

The MCP server exposes **120+ curated tools** across core accounting domains. Tools marked **(write)** mutate data and require write permission; everything else is a read-only query. Call `list_mcp_capabilities` for a compact category map.

| Category | Highlights | Description |
|----------|------------|-------------|
| **Discovery** | `list_my_businesses`, `find_business`, `get_business_info`, `list_mcp_capabilities`, `search_entities` | Find businesses and entities without knowing IDs upfront; `find_business` fuzzy-searches by name |
| **Banking** | `list_bank_accounts`, `get_bank_balances` | See connected bank accounts and current/available balances |
| **Snapshots** | `get_daily_brief`, `get_ar_snapshot`, `get_aged_payables`, `get_ap_snapshot`, `get_payment_priority_suggestions`, `get_uncategorized_summary`, `get_month_end_package` | High-signal summaries — prefer these over paging huge lists |
| **Chart of Accounts** | `list_ledgers`, `get_ledger` | Browse and inspect ledger accounts |
| **Transactions** | `list_transactions`, `get_transaction`, `update_transaction`, `mark_reviewed`, `categorize_transaction`, `recategorize_by_counterparty`, `mark_transactions_reconciled` **(write)** | Search, inspect, categorize, and mark bank lines reconciled |
| **Invoices (AR)** | `create_invoice`, `update_invoice`, `finalize_invoice`, `send_invoice`, `mark_invoice_paid`, `void_invoice`, `list_invoice_payments`, `get_customer_statement`, credit memos, payments **(write)** | Full AR lifecycle, invoice payment history, and customer statement previews |
| **Bills (AP)** | `create_bill`, `approve_bill`, `mark_bill_received`, `update_bill`, `cancel_bill`, `list_bill_payments`, vendor credits, payments **(write)** | AP approval, receive, payment workflows, and bill payment history |
| **Estimates** | `list_estimates`, `get_estimate`, `create_estimate`, `send_estimate`, `update_estimate`, `accept_estimate`, `convert_estimate` **(write)** | Quote → accept → convert, then `create_invoice` |
| **Order-to-Cash** | `list_deals`, `create_deal`, `update_deal`, `convert_deal_to_order`, `list_orders`, `create_order`, `update_order`, `advance_order_status`, `attach_order_document`, `list_contracts`, `create_contract`, `update_contract`, `link_invoice_to_contract`, plus `bulk_create_*` / `bulk_update_*` for each **(write except list/get)** | The whole pre-invoice chain: pipeline deal → sales order → countersigned contract → invoice. `advance_order_status(target_status="countersigned")` is what turns an order into a live contract. Working from a signed PDF: `generate_upload_url` → `PUT` the file → `extract_document_entities` to read it → `create_order` with the line items → `attach_order_document` with the same `s3_key`. For more than a couple of rows always use the `bulk_*` variant — running them one at a time races on shared contract locks |
| **Projects (job costing)** | `list_projects`, `get_project`, `create_project`, `update_project`, `bulk_create_projects`, `bulk_update_projects` **(write except list/get)** | Jobs that orders, contracts and invoices hang off; assign invoices to them with `bulk_assign_invoices` |
| **Journal Entries** | `list_journal_entries`, `get_journal_entry`, `create_journal_entry` **(write)** | Read entry lists, inspect one entry, and create journal entries |
| **Customers & Vendors** | `list_*`, `get_*`, `create_*`, `update_*`, `export_vendors`, `export_customers`, `bulk_create_vendors`, `bulk_create_customers` **(write)** | CRUD-style contact management plus CSV export/import |
| **Counterparties** | `search_counterparty_registry`, `link_counterparty`, `unlink_counterparty`, `list_counterparty_defaults`, `export_counterparty_audit`, `create_counterparty_default`, `update_counterparty_default`, `delete_counterparty_default`, `bulk_create_counterparty_defaults`, `generate_upload_url` **(write for link/unlink, default rules, and uploads)** | Registry search, link/unlink, default rules (merchant → ledger), audit export, and CSV bulk import (use `generate_upload_url` for the `s3_key`) |
| **Credit Memos & Vendor Credits** | `list_*`, `get_*`, `create_*`, `issue_*`, `apply_*`, `unapply_*`, `void_*` **(write)** | Full credit lifecycle for AR credit memos and AP vendor credits — create, issue, apply to / unapply from invoices and bills, and void |
| **Delayed Charges** | `list_delayed_charges`, `get_delayed_charge` | Unbilled charges and credits |
| **Tags, Budgets, Tax, Products** | `list_tag_groups`, `list_budgets`, `get_budget_vs_actual`, `list_tax_*`, `list_products` | Supporting master data |
| **Close & Quality** | `get_close_checklist`, `get_close_score`, `list_close_blockers`, `list_anomalies`, `dismiss_anomaly`, `list_attention_items`, `mark_attention_read`, month-end packages | DayZero close intelligence: phased checklist, readiness score, explicit blockers, plus anomalies/inbox workflows |
| **Audit & Oversight** | `get_entity_audit_trail`, `get_user_activity`, `get_activity_feed`, `analyze_audit_log` | Change history for an entity, what a user did ("what did Quincy do today?"), the business-wide activity feed, and AI analysis of unusual activity |
| **Firm-wide** | `get_firm_client_overview`, `get_firm_user_activity` | For advisory-firm users: cross-client portfolio/AR-AP/work-queue overviews and one user's activity across all the firm's clients |
| **Reports** | `list_report_types`, `generate_report`, `get_report` | P&L, balance sheet, and other exports |
| **Async Jobs** | `get_job_status`, `trigger_bulk_transaction_upload`, `trigger_bulk_je_upload`, `trigger_teal_sync`, `trigger_month_end_close` **(write except get_job_status)** | Start long-running workflows immediately and poll by job id |
| **Reconciliation** | `list_reconciliations`, `get_reconciliation` | Bank reconciliation status |
| **Analytics** | `get_financial_overview`, `get_top_expenses`, `get_cash_flow_totals`, `get_profit_trends`, `get_transaction_summary`, `compare_periods` | Server-side aggregations for analytical questions |

You don't need to know your business ID upfront. The `business_id` parameter is optional on every tool — the server automatically resolves it:

- **One business** — selected automatically, zero friction.
- **Multiple businesses** — clients that support elicitation show a **scope picker** (firm-wide vs a specific business) as a native dropdown; other clients get your business list so the assistant can ask which one to use. Firm users with many clients can call `find_business("name")` to fuzzy-search for the right ID.
- **Explicit ID** — you can always pass a `business_id` directly if you prefer.

For cross-client questions ("across all our clients"), advisory-firm users should use the **firm-wide** tools (`get_firm_client_overview`, `get_firm_user_activity`) instead of looping per business.

The assistant remembers your selection for the rest of the conversation. Say *"switch to [business name]"* at any time to change.

> **Note:** All monetary amounts are in **cents** (e.g. $150.00 = `15000`). IDs are UUID v7 strings. Pagination is cursor-based — pass the `next_cursor` value from a response as `cursor` to get the next page. All date parameters use **YYYY-MM-DD** format.

**56 write tools** require the **write** permission (a viewer-role token cannot call them): journal entries, transactions, bills, invoices, payments, contacts, counterparty registry links, counterparty default rules, vendor/customer exports and bulk imports, credit memos, vendor credits, reconciliation, anomalies, attention, file uploads (`generate_upload_url`), and more. The assistant should confirm details with you before calling them.

**Bulk imports (end-to-end):** call `generate_upload_url(filename)` to get an `upload_url` and `s3_key`, HTTP `PUT` your CSV/Excel file to the `upload_url`, then call `bulk_create_vendors`, `bulk_create_customers`, or `bulk_create_counterparty_defaults` (or the `trigger_bulk_*` uploads) with that `s3_key`.

Every write tool accepts an optional **`dry_run=true`** parameter. When set, the server returns a preview of what would happen without committing changes — useful for confirmation flows.

<!-- BEGIN GENERATED TOOL MANIFEST -->
<!-- Generated by scripts/export_mcp_tool_manifest.py — do not edit by hand; run `make mcp-manifest`. -->

### Full tool reference

_Auto-generated from the live MCP registry: **225 tools** (108 write, 93 approval-gated, 40 exposed by default). Regenerate with `make mcp-manifest`._

| Tool | Access | Approval gate | Default set |
|------|--------|---------------|-------------|
| `accept_payment_suggestion` | write | confirm | — |
| `add_feedback_comment` | write | — | — |
| `advance_order_status` | write | confirm | — |
| `analyze_audit_log` | read | — | — |
| `apply_credit_memo` | write | confirm | — |
| `apply_vendor_credit` | write | confirm | — |
| `approve_bill` | write | confirm | ✓ |
| `approve_match` | write | confirm | — |
| `attach_order_document` | write | confirm | — |
| `attach_transaction_receipt` | write | confirm | — |
| `build_model` | write | — | — |
| `bulk_approve_matches` | write | confirm | — |
| `bulk_assign_invoices` | write | confirm | ✓ |
| `bulk_create_contracts` | write | confirm | — |
| `bulk_create_counterparty_defaults` | write | confirm | — |
| `bulk_create_customers` | write | confirm | — |
| `bulk_create_deals` | write | confirm | — |
| `bulk_create_invoices` | write | confirm | — |
| `bulk_create_orders` | write | confirm | — |
| `bulk_create_projects` | write | confirm | — |
| `bulk_create_vendors` | write | confirm | — |
| `bulk_mark_invoices_paid` | write | confirm | — |
| `bulk_reject_matches` | write | confirm | — |
| `bulk_retry_failed_invoices` | write | confirm | — |
| `bulk_update_contracts` | write | confirm | — |
| `bulk_update_counterparty` | write | confirm | — |
| `bulk_update_deals` | write | confirm | — |
| `bulk_update_invoices` | write | confirm | — |
| `bulk_update_projects` | write | confirm | — |
| `cancel_bill` | write | confirm | — |
| `categorize_transaction` | write | confirm | ✓ |
| `compare_periods` | read | — | — |
| `complete_reconciliation` | write | confirm | — |
| `convert_deal_to_order` | write | confirm | — |
| `create_bill` | write | confirm | ✓ |
| `create_contract` | write | confirm | — |
| `create_counterparty_default` | write | confirm | — |
| `create_credit_memo` | write | confirm | — |
| `create_customer` | write | confirm | — |
| `create_deal` | write | confirm | — |
| `create_feedback` | write | — | ✓ |
| `create_feedback_linear_ticket` | write | — | — |
| `create_invoice` | write | confirm | ✓ |
| `create_journal_entry` | write | confirm | — |
| `create_order` | write | confirm | — |
| `create_project` | write | confirm | — |
| `create_vendor` | write | confirm | — |
| `create_vendor_credit` | write | confirm | — |
| `delete_counterparty_default` | write | confirm | — |
| `delete_invoice` | write | confirm | — |
| `dismiss_anomaly` | write | confirm | — |
| `download_reconciliation_pdf` | read | — | — |
| `email_report` | write | — | — |
| `export_counterparty_audit` | read | — | — |
| `export_customers` | write | confirm | — |
| `export_dataset` | write | confirm | — |
| `export_vendors` | write | confirm | — |
| `extract_document_entities` | read | — | — |
| `fetch` | read | — | ✓ |
| `finalize_invoice` | write | confirm | — |
| `find_business` | read | — | ✓ |
| `generate_report` | write | — | ✓ |
| `generate_upload_url` | write | — | ✓ |
| `get_activity_feed` | read | — | — |
| `get_aged_payables` | read | — | ✓ |
| `get_ap_snapshot` | read | — | ✓ |
| `get_ar_snapshot` | read | — | ✓ |
| `get_balance_sheet` | read | — | — |
| `get_bank_balances` | read | — | — |
| `get_bill` | read | — | ✓ |
| `get_budget` | read | — | — |
| `get_budget_vs_actual` | read | — | — |
| `get_bulk_suggested_matches` | read | — | — |
| `get_business_info` | read | — | ✓ |
| `get_cash_flow` | read | — | — |
| `get_cash_flow_totals` | read | — | — |
| `get_close_checklist` | read | — | — |
| `get_close_score` | read | — | — |
| `get_contract` | read | — | — |
| `get_credit_line` | read | — | — |
| `get_credit_memo` | read | — | — |
| `get_credit_portfolio` | read | — | — |
| `get_customer` | read | — | — |
| `get_customer_balance` | read | — | — |
| `get_customer_statement` | read | — | — |
| `get_daily_brief` | read | — | ✓ |
| `get_deal` | read | — | — |
| `get_entity_audit_trail` | read | — | — |
| `get_feedback` | read | — | — |
| `get_feedback_attachment_url` | read | — | — |
| `get_feedback_dom_snapshot` | read | — | — |
| `get_feedback_screenshot` | read | — | — |
| `get_feedback_stats` | read | — | — |
| `get_financial_overview` | read | — | — |
| `get_firm_client_overview` | read | — | ✓ |
| `get_firm_user_activity` | read | — | — |
| `get_fixed_asset` | read | — | — |
| `get_invoice` | read | — | ✓ |
| `get_invoice_create_requirements` | read | — | — |
| `get_job_status` | read | — | ✓ |
| `get_journal_entry` | read | — | — |
| `get_ledger` | read | — | — |
| `get_match_review` | read | — | — |
| `get_month_end_package` | read | — | — |
| `get_order` | read | — | — |
| `get_payment_priority_suggestions` | read | — | — |
| `get_pnl_report` | read | — | — |
| `get_profit_trends` | read | — | — |
| `get_project` | read | — | — |
| `get_receipt_coverage` | read | — | — |
| `get_reconciliation` | read | — | — |
| `get_recurring_template` | read | — | — |
| `get_report` | read | — | — |
| `get_top_expenses` | read | — | — |
| `get_transaction` | read | — | ✓ |
| `get_transaction_suggested_matches` | read | — | — |
| `get_transaction_summary` | read | — | — |
| `get_trial_balance` | read | — | — |
| `get_uncategorized_summary` | read | — | — |
| `get_unmatched` | read | — | — |
| `get_user_activity` | read | — | — |
| `get_vendor` | read | — | — |
| `get_vendor_balance` | read | — | — |
| `get_vendor_credit` | read | — | — |
| `import_model_workbook` | read | — | — |
| `import_ramp_receipts` | write | confirm | — |
| `issue_credit_memo` | write | confirm | — |
| `issue_vendor_credit` | write | confirm | — |
| `link_bill_payment` | write | confirm | ✓ |
| `link_counterparty` | write | confirm | — |
| `link_invoice_payment` | write | confirm | ✓ |
| `link_invoice_to_contract` | write | confirm | — |
| `list_accounting_periods` | read | — | — |
| `list_anomalies` | read | — | — |
| `list_attention_items` | read | — | — |
| `list_auto_approvals` | read | — | — |
| `list_bank_accounts` | read | — | — |
| `list_bank_rules` | read | — | — |
| `list_bill_payments` | read | — | — |
| `list_bills` | read | — | ✓ |
| `list_budgets` | read | — | — |
| `list_close_blockers` | read | — | — |
| `list_contracts` | read | — | — |
| `list_counterparty_defaults` | read | — | — |
| `list_credit_lines` | read | — | — |
| `list_credit_loans` | read | — | — |
| `list_credit_memos` | read | — | — |
| `list_customers` | read | — | — |
| `list_deals` | read | — | — |
| `list_feedback` | read | — | — |
| `list_fixed_assets` | read | — | — |
| `list_invoice_payments` | read | — | — |
| `list_invoices` | read | — | ✓ |
| `list_jobs` | read | — | — |
| `list_journal_entries` | read | — | — |
| `list_ledgers` | read | — | — |
| `list_mcp_capabilities` | read | — | ✓ |
| `list_month_end_packages` | read | — | — |
| `list_my_businesses` | read | — | ✓ |
| `list_notifications` | read | — | — |
| `list_orders` | read | — | — |
| `list_products` | read | — | — |
| `list_projects` | read | — | — |
| `list_reconciliations` | read | — | — |
| `list_recurring_templates` | read | — | — |
| `list_report_types` | read | — | — |
| `list_reports` | read | — | — |
| `list_subscriptions` | read | — | — |
| `list_tag_groups` | read | — | — |
| `list_tags` | read | — | — |
| `list_tax_codes` | read | — | — |
| `list_transactions` | read | — | ✓ |
| `list_vendor_credits` | read | — | — |
| `list_vendors` | read | — | — |
| `mark_all_attention_read` | write | confirm | — |
| `mark_attention_read` | write | confirm | — |
| `mark_bill_received` | write | confirm | ✓ |
| `mark_invoice_paid` | write | confirm | ✓ |
| `mark_reviewed` | write | — | — |
| `mark_transactions_reconciled` | write | — | — |
| `match_transaction` | write | confirm | — |
| `read_gmail_message` | read | — | — |
| `read_transaction_receipt` | write | — | — |
| `recategorize_by_counterparty` | write | confirm | — |
| `refresh_model` | write | — | — |
| `reject_match` | write | confirm | — |
| `reject_payment_suggestion` | write | confirm | — |
| `remove_transaction_receipt` | write | confirm | — |
| `request_transaction_receipts` | write | confirm | — |
| `revoke_auto_approval` | write | — | — |
| `search` | read | — | ✓ |
| `search_counterparty_registry` | read | — | — |
| `search_entities` | read | — | ✓ |
| `search_gmail` | read | — | — |
| `search_tools` | read | — | ✓ |
| `send_invoice` | write | confirm | ✓ |
| `set_receipt_exempt` | write | — | — |
| `split_transaction_by_receipt` | write | confirm | — |
| `start_background_job` | write | confirm | — |
| `start_reconciliation` | write | confirm | — |
| `trigger_bulk_invoice_edit` | write | confirm | — |
| `trigger_bulk_invoice_upload` | write | confirm | ✓ |
| `trigger_bulk_je_upload` | write | confirm | ✓ |
| `trigger_bulk_transaction_upload` | write | confirm | ✓ |
| `trigger_month_end_close` | write | confirm | ✓ |
| `trigger_payment_matching` | write | — | — |
| `trigger_ramp_repair` | write | confirm | — |
| `trigger_teal_sync` | write | confirm | ✓ |
| `unapply_credit_memo` | write | confirm | — |
| `unapply_vendor_credit` | write | confirm | — |
| `unlink_counterparty` | write | confirm | — |
| `update_bill` | write | confirm | — |
| `update_contract` | write | confirm | — |
| `update_counterparty_default` | write | confirm | — |
| `update_customer` | write | confirm | — |
| `update_deal` | write | confirm | — |
| `update_feedback` | write | — | — |
| `update_invoice` | write | confirm | ✓ |
| `update_order` | write | confirm | — |
| `update_project` | write | confirm | — |
| `update_transaction` | write | confirm | ✓ |
| `update_vendor` | write | confirm | — |
| `void_credit_memo` | write | confirm | — |
| `void_invoice` | write | confirm | — |
| `void_vendor_credit` | write | confirm | — |

<!-- END GENERATED TOOL MANIFEST -->

### Approval gates (elicitation)

High-risk writes — payments (`link_invoice_payment`, `link_bill_payment`, `mark_invoice_paid`), voids (`void_invoice`, `void_credit_memo`, `void_vendor_credit`, `cancel_bill`), external sends (`send_invoice`, `send_estimate`), and irreversible state changes (`finalize_invoice`, `convert_estimate`) — pause for **in-band user approval** before committing, using the MCP **elicitation** capability. If your client supports elicitation (Claude Desktop, recent MCP hosts), you'll see a native confirm dialog summarizing the change (amounts formatted in dollars) with an approve/decline choice. Declining returns `{"approved": false, ...}` and commits nothing.

Clients without elicitation support are unaffected: the host's own tool-call consent plus the `dry_run` preview flow remain the gate, exactly as before.

### UI cards (MCP Apps)

Hosts that advertise [MCP Apps](https://modelcontextprotocol.io) / mcp-ui support receive rendered **HTML cards** as embedded `ui://dayzero/*` resources alongside the JSON payload on snapshot tools: `get_daily_brief`, `get_ar_snapshot`, `get_ap_snapshot`, `get_aged_payables`, `get_uncategorized_summary`, and `get_payment_priority_suggestions`. Cards show key figures, aging tables, and dollar-formatted amounts with automatic light/dark styling. Clients without UI support receive identical JSON-only responses.

> **Note:** `create_invoice` requires Stripe Connect (or a demo business) — call `get_invoice_create_requirements` first. Typical flow: `create_invoice` → `finalize_invoice` → `send_invoice` or `link_invoice_payment`. `mark_invoice_paid` is for manual paid status without a bank match. AP: `approve_bill` → `mark_bill_received` (needs expense `ledger_id`). `get_aged_payables` pairs with `get_ar_snapshot`; `get_payment_priority_suggestions` uses AI and may take a few seconds.

**Long-running workflows** return immediately with `{job_id, status: "started", poll_tool: "get_job_status"}`. Do not wait inline; poll `get_job_status(job_id)` until completion/failure.

**Reports** now follow: `list_report_types` → `generate_report` → poll `get_job_status` (use `job_id`) → optional `get_report` when the completed job returns a `report_id`.

### When a tool fails

A failed tool returns a **teaching error** rather than a bare message — the same
shape whether the failure came from the tool's own validation or from the data
underneath it:

```json
{
  "error": "You have access to multiple businesses. Please specify which one to use: …",
  "reason_code": "BUSINESS_SELECTION_REQUIRED",
  "how_to_fix": "Ask the user which of the businesses listed in the error to use, then pass its business_id.",
  "retryable": false,
  "did_you_mean": []
}
```

Read `how_to_fix` and `did_you_mean` before retrying, and don't retry at all
when `retryable` is `false`. Common `reason_code` values:

| `reason_code` | Meaning |
|---------------|---------|
| `BUSINESS_SELECTION_REQUIRED` | You have several businesses; pass the `business_id` of the one you want (the error lists them) |
| `FIRM_SELECTION_REQUIRED` | You belong to several advisory firms; pass `firm_id` |
| `NOT_FOUND` | No record with that id in this business — look it up first, and check `business_id` |
| `INVALID_ARGUMENT` / `VALIDATION_ERROR` / `INVALID_ENUM` / `INVALID_DATE_FORMAT` | Fix the named argument; `did_you_mean` lists the accepted values |
| `MISSING_REQUIRED_FIELD` | An update with no fields to change |
| `PERMISSION_DENIED` | Your role or grant doesn't cover this tool; access has to change first |
| `MISSING_SCOPE_READ` / `MISSING_SCOPE_WRITE` | Re-authorize with the missing OAuth scope |
| `PARTIAL_SUCCESS` | A bulk call saved some rows — retry only the ones in `data.failed` |
| `COMMIT_FAILED` | Nothing was saved; retry the whole call |
| `RATE_LIMITED` / `TIMEOUT` | Retryable — wait a moment and call again |
| `UNKNOWN_ERROR` | Unclassified; read `error` |

A bare `Error executing tool <name>` with no envelope means an internal fault,
not something to correct in the call — it is reported to DayZero automatically.

### Working with Large Datasets

List tools return a `total` field showing total matching records. Use this to decide how to proceed:

- **Analytical questions** ("what did we spend the most on?", "are we profitable?", "how much revenue last quarter?") — use the **analytics tools** (`get_financial_overview`, `get_top_expenses`, `get_cash_flow_totals`, `get_profit_trends`, `get_transaction_summary`). These compute summaries server-side and return small payloads regardless of data volume.
- **Under ~100 matching records** — paginate through them if you need individual details.
- **Over ~100 matching records** — narrow with filters (date range, search text, ledger_id, status) or use analytics tools. Don't page through thousands of records.
- **Full exports** — use `generate_report` to create a downloadable file.

**Strategy: summarize first, drill down second.** Start with aggregate tools to understand the big picture, then use list tools with filters to examine specific subsets.

## Prerequisites

1. A DayZero account with **API access enabled** (required before OAuth can mint MCP tokens)
2. A DayZero login (Clerk) for the browser consent flow

## Connecting Claude Desktop

The fastest path is the install link — **[Add DayZero to Claude](https://claude.ai/customize/connectors?modal=add-custom-connector&connectorName=DayZero&connectorUrl=https%3A%2F%2Fapi.ondayzero.com%2Fmcp)**. It opens claude.ai's **Add custom connector** dialog with the name and URL prefilled; review it, click **Add**, and sign in to DayZero when prompted. Connectors are account-level, so one add covers Claude on web, desktop, and mobile.

Prefer local config? Add the server with just its URL. Claude registers itself, opens a browser for you to sign in to DayZero and approve access, then stores the MCP OAuth token automatically:

```json
{
  "mcpServers": {
    "dayzero": {
      "url": "https://api.ondayzero.com/mcp"
    }
  }
}
```

Config file location:

**macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
**Windows:** `%APPDATA%\Claude\claude_desktop_config.json`

Restart Claude Desktop after saving. You should see DayZero tools appear in the tools menu.

## Connecting Claude Code

One command:

```bash
claude mcp add --transport http dayzero https://api.ondayzero.com/mcp
```

Then run `/mcp` inside Claude Code to complete the browser sign-in. To share the server with your whole team, commit a `.mcp.json` at the repo root instead:

```json
{
  "mcpServers": {
    "dayzero": {
      "type": "http",
      "url": "https://api.ondayzero.com/mcp"
    }
  }
}
```

## Connecting Cursor

The **Add to Cursor** button under **Settings → Developers** in DayZero opens Cursor with the server prefilled — confirm the install and sign in when the browser opens.

To add it manually, go to **Settings > MCP Servers** in Cursor and add a new server:

- **Name:** DayZero
- **URL:** `https://api.ondayzero.com/mcp`

Leave the headers blank — Cursor opens a browser for you to sign in and approve via OAuth.

## Connecting ChatGPT

ChatGPT requires **Developer mode** before you can add an unlisted MCP connector:

1. Enable it under **Settings → Apps → Advanced settings → Developer mode** (available on Plus and Pro; on Business, Enterprise, and Edu workspaces an admin may need to allow it — or an admin can add DayZero org-wide from the workspace admin console).
2. Create a new connector with **Name:** DayZero, **URL:** `https://api.ondayzero.com/mcp`.

ChatGPT discovers the OAuth endpoints, opens a browser for you to sign in to DayZero, and stores the token automatically. Connectors are account-level, so the same connection works in the ChatGPT web, desktop, and mobile apps.

## Connecting Codex

Codex CLI, the Codex IDE extension, and the ChatGPT desktop app share one MCP configuration. Add two lines to `~/.codex/config.toml`:

```toml
[mcp_servers.dayzero]
url = "https://api.ondayzero.com/mcp"
```

Or configure it in the ChatGPT desktop app under **Settings → MCP servers → Add server** — name `dayzero`, transport **Streamable HTTP**, URL above. Codex opens your browser to sign in to DayZero the first time it connects.

## Connecting Other MCP Clients

Any MCP client that supports **Streamable HTTP** transport can connect. The server implements the full MCP authorization spec, so compatible clients configure themselves automatically from the server URL alone:

- **RFC 9728 Protected Resource Metadata** — `https://api.ondayzero.com/.well-known/oauth-protected-resource`
- **RFC 8414 Authorization Server Metadata** — `https://api.ondayzero.com/.well-known/oauth-authorization-server`

These advertise the authorization server, supported scopes, PKCE methods, and the authorize / token / registration endpoints.

The canonical **resource URL** (RFC 8707 audience) is `https://api.ondayzero.com/mcp` — no trailing slash.

### Browser-based and hosted clients (Origin allowlist)

As part of the MCP spec's DNS-rebinding protection, the server validates the `Origin` header on every MCP request. Requests **without** an `Origin` header — desktop apps and CLIs such as Cursor, Claude Desktop, Codex CLI, and `mcp-remote` — always pass. Requests **with** an `Origin` header must match the allowlist, which covers:

- **Anthropic** — `https://claude.ai`, `https://claude.com`
- **OpenAI** — `https://chatgpt.com`, `https://chat.openai.com`, `https://platform.openai.com`
- **Cursor** — `https://cursor.com`, `https://cursor.sh`
- **Local development** — `http://localhost:*`, `http://127.0.0.1:*` (e.g. MCP Inspector)

A hosted client whose origin is not allowlisted receives `403 Invalid Origin header` on every MCP call **after** OAuth succeeds — the connector shows as connected but lists zero tools. If you're building or operating a hosted MCP client that should connect to DayZero, contact us so we can add your origin (self-hosted deployments can extend the list via the `MCP_ALLOWED_ORIGINS_EXTRA` environment variable).

## Environment URLs

| Environment | MCP server URL | Authorization server |
|-------------|----------------|----------------------|
| Production | `https://api.ondayzero.com/mcp` | `https://api.ondayzero.com` |
| Local dev | `http://localhost:8000/mcp` | `http://localhost:8000` |

Use the MCP server URL in your client config. Discovery endpoints live on the API host (not under `/mcp`).

## Authentication

MCP uses **OAuth 2.1** with browser consent. Tokens minted through this flow are **MCP-only** — they cannot call the REST API (`/api/v1/*`). Likewise, REST API keys and SPA session tokens cannot authenticate MCP tool requests.

### OAuth 2.1 flow

1. The client connects to `/mcp` and receives a `401` with a `WWW-Authenticate` header pointing at `/.well-known/oauth-protected-resource`.
2. It reads **RFC 9728** protected-resource metadata and discovers the authorization server.
3. It reads **RFC 8414** authorization-server metadata at `/.well-known/oauth-authorization-server`.
4. It **dynamically registers** itself (**RFC 7591**) at `POST /oauth2/register` — no pre-shared client credentials needed.
5. It opens your browser to `GET /oauth2/authorize` with **PKCE** (`S256`) and an optional **RFC 8707** `resource` parameter (must match the MCP server URL).
6. You sign in via Clerk and review the consent page, which shows:
   - The app name and human-readable scope descriptions
   - Your signed-in identity and accessible businesses (via `GET /oauth2/userinfo`)
   - Where you'll be redirected after approval (with loopback / non-HTTPS warnings when applicable)
7. After you click **Allow access**, the client exchanges the authorization code at `POST /oauth2/mcp/token` for an MCP access token (plus a refresh token when the grant includes `offline_access`).
8. The access token is sent as `Authorization: Bearer …` on every MCP request and refreshed automatically when it expires.

Your account must have API access enabled before tokens can be minted. If API access is disabled for your user, authorization fails with `access_denied`.

### OAuth scopes

Scopes appear on the consent page in plain English:

| Scope | What it allows |
|-------|----------------|
| `mcp:read` | View your accounting data — read transactions, balances, invoices, and reports |
| `mcp:write` | Make changes on your behalf — create and modify journal entries, invoices, bills, and other records |
| `offline_access` | Stay connected — keep this app authorized without signing in each time |
| `mcp:admin` | Act as DayZero staff — staff-only tools (feedback triage, admin repairs) and every client business. Staff accounts only. |

Write tools require both `mcp:write` **and** write permission on the business. A token with only `mcp:read` can query data but cannot call write tools.

`mcp:admin` is different from the other scopes: it is not advertised in `scopes_supported`, a client cannot obtain it by asking, and a superuser's token does not carry it by default. It is granted only when a DayZero staff member ticks **Act as DayZero staff** on the consent screen. Without it, a staff member's token behaves like any customer's, and the admin tools return `403` with a message pointing back to that box.

A few scope rules to be aware of:

- **Refresh tokens are only issued when the grant includes `offline_access`.** Without it, the client gets an access token only and must re-authorize when it expires.
- If a client requests no scopes at all, it is granted `mcp:read offline_access` (read-only, but able to stay connected).
- If a client registered with an explicit `scope`, requests are narrowed to that registered set — a read-only registration cannot later escalate to `mcp:write`.

If the token is missing or invalid, the server returns `401` with a `WWW-Authenticate` header pointing to the resource metadata endpoint.

### Revoking access

OAuth-connected MCP clients (e.g. Claude Desktop, Cursor) create a **connected app grant** tied to your account. Revoke a grant to immediately invalidate its refresh token:

```bash
# List connected apps (requires a DayZero session or REST OAuth token)
curl "https://api.ondayzero.com/api/v1/oauth/grants" \
  -H "Authorization: Bearer YOUR_SESSION_OR_API_TOKEN"

# Revoke a grant by ID
curl -X DELETE "https://api.ondayzero.com/api/v1/oauth/grants/GRANT_ID" \
  -H "Authorization: Bearer YOUR_SESSION_OR_API_TOKEN"
```

Revoking a grant blacklists **both** its refresh token and the most recently issued access token, so access is cut off immediately — not when the access token would naturally expire.

Revoking an MCP grant does **not** affect REST API keys — manage those separately under **Settings → Developers**.

### Security notes

- MCP tokens carry JWT `type=mcp` and audience = the MCP resource URL — distinct from REST `access`, `refresh`, and `api` tokens.
- The consent screen is a React page served at `GET /oauth2/authorize`; standalone error pages use Jinja2 templates. Both ship strict security headers (CSP nonce, `X-Frame-Options: DENY`, `Referrer-Policy: no-referrer`).
- Dynamic client registrations expire after ~90 days of inactivity; clients re-register automatically.
- Registered redirect URIs are validated: `https`, loopback `http`, and private-use app schemes (e.g. `cursor://`) are allowed; `javascript:`/`data:`/`file:` and `http` on non-loopback hosts are rejected.
- PKCE (`S256`) is mandatory — authorization codes cannot be exchanged without a valid code verifier.
- DNS-rebinding protection validates the `Host` header and allowlists the `Origin` header on every MCP request (see [Browser-based and hosted clients](#browser-based-and-hosted-clients-origin-allowlist)). Requests with an unrecognized `Origin` are rejected with `403`.
- Refresh tokens are **bound to the client they were issued to**; the refresh grant requires `client_id` (and the client secret for confidential clients) and rejects tokens presented by a different client.
- `POST /oauth2/register` and `POST /oauth2/mcp/token` are rate-limited per client IP and return `429` with a `Retry-After` header when exceeded.

## Example Conversation

Once connected, just start asking questions — no setup required:

**Single business (most common):**

> **You:** What invoices are overdue?
>
> **Assistant:** You have 3 overdue invoices totaling $4,250.00…

The server auto-detected your business behind the scenes. No IDs needed.

**Multiple businesses:**

> **You:** Show me last month's transactions
>
> **Assistant:** You have access to 2 businesses:
> 1. **Acme LLC** — admin
> 2. **Beta Corp** — viewer
>
> Which one would you like to use?
>
> **You:** Acme
>
> **Assistant:** Here are Acme LLC's transactions for last month…

After you pick, the assistant remembers your choice for the rest of the conversation. Say *"switch to Beta Corp"* at any time to change.

**More things you can ask:**

- *"List all bank accounts"*
- *"Show me the last 10 transactions from checking"*
- *"Generate a profit and loss report for Q1 2026"*
- *"Create a journal entry for $500 rent expense on March 1"*
- *"Search for anything related to Acme Corp"*
- *"Compare the operating budget to actuals this quarter"*
- *"What are our top expenses this year?"*
- *"Are we cash-flow positive this quarter?"*
- *"How have profit margins changed over the past 6 months?"*
- *"How many transactions came in last month?"*
- *"What should I look at today?"* (Daily Brief)
- *"Mark that Amazon transaction as reviewed"*
- *"Add a new vendor called Acme Hosting with email billing@acme.com"*
- *"Create a $250 bill from Acme Hosting due next Friday"*
- *"Finalize draft invoice #1042"*
- *"Email invoice #1042 to the customer"*
- *"How much do we owe vendors by aging bucket?"*
- *"Void invoice #1042"*
- *"Create a $25 credit memo for Acme and issue it"*
- *"Cancel the draft bill from Acme Hosting"*
- *"Mark these three bank transactions as reconciled"*
- *"What's blocking us from creating invoices via API?"*

## Troubleshooting

| Problem | Solution |
|---------|----------|
| Tools don't appear | Verify the URL is `https://api.ondayzero.com/mcp` (no trailing slash) and restart the client |
| Connected, but zero tools | The client's `Origin` isn't allowlisted — MCP calls get `403 Invalid Origin header` after OAuth. Claude, ChatGPT, and Cursor origins are supported; for other hosted clients see [Origin allowlist](#browser-based-and-hosted-clients-origin-allowlist) |
| Browser sign-in doesn't open | Ensure your MCP client supports OAuth 2.1 with dynamic client registration |
| `access_denied` during consent | Your account may not have API access enabled — ask a firm admin to re-enable it |
| Stuck re-authorizing | Your registered client may have expired — the client re-registers automatically; just retry the connection |
| 401 Unauthorized | Re-run the OAuth sign-in; REST API keys (`dz_…`) do not work on MCP |
| 403 on write tools | Your grant may only include `mcp:read` — re-authorize with write access, or check your business role |
| 403 Forbidden (business) | Your token doesn't have access to the specified `business_id` |
| 404 Not Found | Ensure the URL is exactly `/mcp` — not `/mcp/` with a redirect, `/mcp/mcp`, or `/api/mcp` |
| Unknown application / registration expired | Close the browser tab and reconnect from your MCP client so it re-registers |
| Connection timeout | Ensure the DayZero API is reachable from your network |
| `Session terminated` on every tool call | The MCP connector lost its Streamable HTTP session (common after a deploy or when the client doesn't re-initialize). Disconnect and reconnect the DayZero connector in your MCP client, or start a new chat. DayZero runs in **stateless** Streamable HTTP mode so new requests don't depend on a server-side session ID; if this persists after reconnecting, report it with the client name and timestamp. |

## What's Next?

- [Authentication](/docs/guides/authentication) — managing API tokens
- [First Request](/docs/guides/first-request) — getting started with the REST API
- [Reports](/docs/guides/reports) — available report types and formats
