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? Add https://api.ondayzero.com/mcp to Claude, ChatGPT, or Cursor with no credentials and you'll be prompted to sign in to DayZero in your browser and approve access. See Connecting Claude Desktop, Connecting Cursor, or Connecting ChatGPT 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.

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

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"
    }
  }
}

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
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.

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 / 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_invoicefinalize_invoicesend_invoice or link_invoice_payment. mark_invoice_paid is for manual paid status without a bank match. AP: approve_billmark_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_typesgenerate_report → poll get_job_status (use job_id) → optional get_report when the completed job returns a report_id.

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

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 Cursor

In Cursor, go to Settings > MCP Servers 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

In ChatGPT, go to Settings → Connectors → Create and add:

  • 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.

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 Metadatahttps://api.ondayzero.com/.well-known/oauth-protected-resource
  • RFC 8414 Authorization Server Metadatahttps://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:

  • Anthropichttps://claude.ai, https://claude.com
  • OpenAIhttps://chatgpt.com, https://chat.openai.com, https://platform.openai.com
  • Cursorhttps://cursor.com, https://cursor.sh
  • Local developmenthttp://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

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.

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). 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 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?