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/mcpto 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 pollinghttps://api.ondayzero.com/mcp/ar(dayzero-ar) — invoices, customers, credit memos, AR workflowshttps://api.ondayzero.com/mcp/ap(dayzero-ap) — bills, vendors, vendor credits, AP workflowshttps://api.ondayzero.com/mcp/reports(dayzero-reports) — reporting, analytics, ledgers, transaction readshttps://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):
{
"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_iddirectly 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 thenext_cursorvalue from a response ascursorto 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_invoicerequires Stripe Connect (or a demo business) — callget_invoice_create_requirementsfirst. Typical flow:create_invoice→finalize_invoice→send_invoiceorlink_invoice_payment.mark_invoice_paidis for manual paid status without a bank match. AP:approve_bill→mark_bill_received(needs expenseledger_id).get_aged_payablespairs withget_ar_snapshot;get_payment_priority_suggestionsuses 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.
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_reportto 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
- A DayZero account with API access enabled (required before OAuth can mint MCP tokens)
- 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:
{
"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 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
- The client connects to
/mcpand receives a401with aWWW-Authenticateheader pointing at/.well-known/oauth-protected-resource. - It reads RFC 9728 protected-resource metadata and discovers the authorization server.
- It reads RFC 8414 authorization-server metadata at
/.well-known/oauth-authorization-server. - It dynamically registers itself (RFC 7591) at
POST /oauth2/register— no pre-shared client credentials needed. - It opens your browser to
GET /oauth2/authorizewith PKCE (S256) and an optional RFC 8707resourceparameter (must match the MCP server URL). - 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)
- After you click Allow access, the client exchanges the authorization code at
POST /oauth2/mcp/tokenfor an MCP access token (plus a refresh token when the grant includesoffline_access). - 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 tomcp: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:
# 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=mcpand audience = the MCP resource URL — distinct from RESTaccess,refresh, andapitokens. - 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, loopbackhttp, and private-use app schemes (e.g.cursor://) are allowed;javascript:/data:/file:andhttpon non-loopback hosts are rejected. - PKCE (
S256) is mandatory — authorization codes cannot be exchanged without a valid code verifier. - DNS-rebinding protection validates the
Hostheader and allowlists theOriginheader on every MCP request (see Browser-based and hosted clients). Requests with an unrecognizedOriginare rejected with403. - 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/registerandPOST /oauth2/mcp/tokenare rate-limited per client IP and return429with aRetry-Afterheader 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:
- Acme LLC — admin
- 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?
- Authentication — managing API tokens
- First Request — getting started with the REST API
- Reports — available report types and formats