MCP (AI agents)
The LedgerBee MCP server gives AI agents (Claude, ChatGPT, Cursor, and other MCP clients) a curated tool set rather than an auto-generated wrapper around every REST endpoint. Transport is Streamable-HTTP; authorization is OAuth 2.1 with a browser sign-in. Add the server URL to your client and approve the sign-in — no API keys required.
One server per company
A LedgerBee MCP connection is scoped to exactly one company: the OAuth grant behind it binds to a single company, and every tool call runs against that company's books. Two URL forms exist:
| URL | Use |
|---|---|
https://api.ledgerbee.com/api/v1/mcp | Single company — binds to the company you pick at sign-in |
https://api.ledgerbee.com/api/v1/mcp/c/{appCompanyId} | One connection per company — the path pins which company the grant binds to |
The /c/{appCompanyId} discriminator exists for users with access to more
than one company. MCP clients dedupe connections by URL and server name, so
without it only one LedgerBee connection could be registered. With it, each
company gets a distinct URL + server name pair (ledgerbee-{company}), and an
agent can hold one connection per company side by side.
Marketplace → Connected apps in the LedgerBee app renders the setup below pre-filled for the company you're signed in to — URL, server name, and per-client commands, including a one-click Add to Cursor button.
Connect a client
Use the plain server URL — the connection binds to the company you pick at sign-in:
Code
Claude (Web / Desktop) — both share one connector list:
- Open claude.ai/customize/connectors (Settings → Connectors).
- Click Add custom connector and paste the server URL.
- Complete the LedgerBee sign-in and approve the requested access.
Claude Code — run this in your terminal, then run /mcp inside Claude
Code to complete the OAuth sign-in:
Code
Cursor — Add to Cursor,
or paste this into your mcp.json:
Code
VS Code — run this in your terminal (requires the VS Code CLI):
Code
Codex:
Code
Other clients — most accept the universal installer; the JSON snippet above also works in most clients' MCP config. The full client list is at modelcontextprotocol.io/clients.
Code
Register one connection per company with the company-pinned URL. The commands
below carry two placeholders — {appCompanyId} and the server name
ledgerbee-{company} — replace both, or copy the pre-filled commands from
Marketplace → Connected apps. To add the next company, switch company in
the app and reopen the card.
Code
Claude (Web / Desktop) — both share one connector list:
- Open claude.ai/customize/connectors (Settings → Connectors).
- Click Add custom connector and paste the company-pinned URL.
- Complete the LedgerBee sign-in and approve the requested access.
Claude Code — run this in your terminal, then run /mcp inside Claude
Code to complete the OAuth sign-in:
Code
Cursor — the one-click Add to Cursor button lives in the in-app card
(the deep link needs a concrete URL). Or paste this into your mcp.json:
Code
VS Code — run this in your terminal (requires the VS Code CLI):
Code
Codex:
Code
Other clients — most accept the universal installer; the JSON snippet above also works in most clients' MCP config. The full client list is at modelcontextprotocol.io/clients.
Code
Anonymous developer server
/api/v1/mcp/developer is an anonymous server that exposes a single tool,
read_development_docs, over the LedgerBee developer docs. Use it to verify
your client setup before connecting the tenant server:
Code
The developer MCP endpoint in the API Reference renders the same install card with copy-paste setup for popular clients.
Tools
Read tools — available with the mcp:read scope on a tenant server; some also require the listed domain read scope:
| Toolset | Resource | Tool | Operation | Additional scopes | Also requires |
|---|---|---|---|---|---|
| every connection | development_doc | read_development_docs | action | — | — |
| every connection | mcp_grant | request_permissions | action | — | user token |
| every connection | mcp_tool | list_mcp_tools | list | — | — |
| every connection | mcp_tool | call_mcp_tool | action | — | — |
context | accounting_period | get_accounting_period | get | journal-entries-read | — |
context | bookkeeping_rule | lookup_bookkeeping_rule | action | company-read | — |
context | journal_category | list_journal_categories | list | journal-categories-read | — |
context | ledger_account | list_ledger_accounts | list | accounts-read | — |
context | ledger_account | get_ledger_account | get | accounts-read, journal-entries-read | — |
context | vat_code | list_vat_codes | list | vat-codes-read | — |
ledger | journal_entry | list_journal_entries | list | — | user token |
ledger | journal_entry | get_journal_entry | get | journal-entries-read | — |
ledger | journal_entry | search_journal_entries | action | journal-entries-read | — |
ledger | vat_report | get_vat_breakdown | action | journal-entries-read, vat-codes-read | — |
ledger_analysis | journal_entry | find_counterparty_mispostings | action | customers-read, journal-entries-read, suppliers-read | — |
ledger_analysis | journal_entry | find_related_entries | action | journal-entries-read | — |
counterparties | counterparty | list_counterparties | list | customers-read or suppliers-read | — |
counterparties | counterparty | get_counterparty | get | customers-read or suppliers-read | — |
counterparties | counterparty_group | list_counterparty_groups | list | customers-read or suppliers-read | — |
counterparties | cvr_registration | lookup_cvr | action | company-read | — |
documents | document | list_documents | list | journal-entries-read | — |
documents | document | get_document | get | journal-entries-read | — |
bookkeeping | draft_journal_entry | get_draft_journal_entry | get | journal-entries-read | — |
staging | staged_write | list_staged_writes | list | — | user token |
staging | staged_write | get_staged_write | get | — | user token |
api | public_api | api_details | action | — | — |
api | public_api | api_read | action | — | — |
api | public_api | api_search | action | — | — |
reports | financial_report | get_financial_report | get | accounts-read | — |
reports | invoice | get_overdue_invoices | action | journal-entries-read, customers-read or suppliers-read | — |
banking | bank_account | list_bank_accounts | list | — | — |
banking | bank_reconciliation | list_bank_reconciliations | list | — | — |
banking | bank_transaction | list_bank_transactions | list | journal-entries-read | — |
banking | bank_transaction | get_bank_transaction | get | — | — |
bank_matching | bank_reconciliation | get_dismissed_suggestions | action | — | — |
bank_matching | bank_transaction | find_inbox_document_candidates | action | — | — |
bank_matching | bank_transaction | find_invoice_anchor_candidates | action | — | — |
bank_matching | bank_transaction | find_journal_entry_candidates | action | — | — |
bank_matching | business_entity | get_business_entity | get | — | — |
bank_matching | business_entity | resolve_business_entity | action | — | — |
subscriptions | subscription | list_subscriptions | list | subscriptions-read | Subscription licence |
subscriptions | subscription | get_subscription | get | subscriptions-read | Subscription licence |
payroll | payroll_run | list_payroll_runs | list | — | PayRoll licence, user token, payroll.read permission |
ui | page | inspect_page | action | — | user token |
ui | route | list_routes | list | — | — |
dashboards | dashboard | list_dashboards | list | — | user token |
dashboards | dashboard | get_dashboard | get | — | user token |
Write tools — available with the mcp:write scope, or the listed domain write scope. Writes always require an OAuth grant:
| Toolset | Resource | Tool | Operation | Additional scopes | Also requires |
|---|---|---|---|---|---|
| every connection | tool_gap | report_tool_gap | action | — | user token |
counterparties | counterparty | create_counterparty | create | customers-write or suppliers-write | user token |
counterparties | counterparty | update_counterparty | update | customers-write or suppliers-write | user token |
bookkeeping | draft_journal_entry | create_draft_journal_entry | create | journal-entries-write | user token |
bookkeeping | draft_journal_entry | update_draft_journal_entry | update | journal-entries-write | user token |
bookkeeping | draft_journal_entry | delete_draft_journal_entry | delete | journal-entries-write | user token |
bookkeeping | draft_journal_entry | book_journal_entry | action | journal-entries-write | user token |
staging | staged_write | approve_staged_write | action | — | user token |
staging | staged_write | cancel_staged_write | action | — | user token |
staging | staged_write | present_for_approval | action | — | user token |
api | public_api | api_write | action | — | user token |
bank_matching | bank_reconciliation | approve_bank_match | action | journal-entries-write | user token |
ui | page | fire_action | action | — | user token |
ui | page | highlight_rows | action | — | user token |
ui | page | spotlight | action | — | user token |
ui | route | navigate | action | — | user token |
dashboards | dashboard | create_dashboard | create | — | user token |
dashboards | dashboard | edit_dashboard_global_config | action | — | user token |
dashboards | dashboard | edit_dashboard_layout | action | — | user token |
dashboards | dashboard | edit_dashboard_metadata | action | — | user token |
dashboards | dashboard_widget | create_dashboard_widget | create | — | user token |
dashboards | dashboard_widget | update_dashboard_widget | update | — | user token |
dashboards | dashboard_widget | delete_dashboard_widget | delete | — | user token |
Every tool names the toolset it belongs to, the resource it acts on and its operation. A toolset is the job a tool serves; the tools marked every connection belong to none and are always advertised. list, get, create, update and delete tools are named <operation>_<resource>; an action is a lifecycle step or lookup with no CRUD shape, such as booking a draft. A renamed tool stops answering to its old name in the same release.
This table is the full catalog. Call tools/list on the server for the tools your connection advertises, with each tool’s parameters and description.
A connection advertises the tools of its enabled toolsets plus the tools that
belong to none. The toolsets are picked on the consent screen, which offers
the ones the requested permissions can serve and ticks the recommended set —
or, when a client re-authorizes a connection you already have, that
connection's own set. An admin changes it afterwards under Settings →
Integrations → OAuth clients → Connected apps, where each connection has a
toolset checkbox per toolset; the change reaches the client the next time it
reconnects. A toolset decides what the connection
is shown, not what it may do: a read outside the enabled set stays reachable
through call_mcp_tool, and what bounds a write is its scopes and the
company's write policy. The page-control and dashboard-editing tools are off
unless a connection enables ui or dashboards; their reads stay reachable
through call_mcp_tool, and their writes need the toolset ticked on a hosted
client. An agent asks for a toolset with request_permissions, the same call
it uses for a missing scope.
MCP scopes are OAuth-only and cannot be assigned to API keys. Write tools always require an OAuth grant with the matching scope.
Discovery
Two tools search the catalog and reach a tool by name, so an agent never has to guess whether a capability exists. Both are advertised to every connection.
list_mcp_toolssearches the full catalog by keyword, toolset or kind. Each row carries the tool's name, toolset, resource, operation and description, andinvokeVia:directwhen the tool is in this connection'stools/list,call_mcp_toolwhen it is a read outside it, orenable_toolsetwhen it is a write outside it, in which caseenableToolsetnames the toolset to ask for.blockedReasoncarries the messagetools/listputs on a tool the caller cannot invoke yet, ornullwhen no gate is visible at list time. Called with no arguments it returns the toolset index; a query that matches nothing returns the index and the closest names rather than an empty list.response_format: "detailed"adds each tool's input schema and annotations.call_mcp_toolruns one read tool by name, with the target's own arguments underarguments, and returns that tool's own result. The target is checked exactly as a direct call: its scopes, licence, rate limit and AI budget apply, and a refusal carries the same message. A write tool is refused with its toolset named. Neither discovery tool can be run through it.
A wrapped call is recorded in the MCP audit log as call_mcp_tool:<tool>,
with the target's route in targetOperation when the target is a facade
tool. The usage statistics report the target under its own name and count the
wrapped calls separately.
Direct mode
The curated tools above cover the common accounting tasks. For everything
else, the four tools on the public_api resource call the public REST API
directly, as the connection's own access token. Any route the grant can
already call over HTTP is reachable through them, and no other route is: the
facade grants nothing on its own.
- Search finds routes by keyword. Each result carries the method, the path
template, the required scopes, whether this connection can call it
(
callable), andcuratedToolwhen a dedicated tool already covers the route. - Details describes one route: path and query parameters, the request body schema, the success response shape, required scopes, and deprecation.
- Read performs a
GETand returns the status and JSON body. A body longer thanmaxBytes(default 64 KB) is cut, and the result names the route's paging parameters. - Write performs a
POST,PATCH,PUTorDELETE. It takes a one-linesummaryof the change, shown on the approval card and kept in the audit log.
The agent searches first and uses the curated tool whenever one is named: the curated tools validate input, stage the change for review in LedgerBee, and can be reversed. The facade does none of that.
Enabling writes
Direct writes are off by default. Turn them on under Inbox → Hivey → Permissions, where the Direct API writes row takes one of three values.
| Value | Effect on the write tool |
|---|---|
| Deny (default) | The call is refused. The tool is advertised with an "unavailable" marker that names this setting. |
| Approval | The request is stored and appears in the approval queue. It runs when you approve it, as the same grant that staged it. The tool returns executed: false and a chainId. |
| Auto | The request runs immediately and the tool returns executed: true with the route's status and body. No approval card is shown. |
The three read-side tools have no setting: a read the grant can perform over HTTP is not more dangerous when performed through MCP.
Scopes
Search, details and read require mcp:read; write requires mcp:write and a
user-consented grant. Every route also enforces its own scopes, exactly as it
does for an HTTP client: a read of /v1/customers needs customers-read, and
a write of POST /v1/customers needs customers-write. A call whose grant
lacks the route's scope is refused before any request is made, with the
missing scope named. Search reports the same check as callable, so the agent
can see which routes it can reach.
Idempotency
Every write call carries an idempotency key. When the agent passes
idempotencyKey, that key is sent; otherwise the server derives one from the
grant, the method, the route template and the request body. The result echoes
the key as idempotencyKey and sets replayed: true when the API served the
stored outcome of an earlier identical request instead of writing again. A
retry after a timeout therefore creates no duplicate.
Excluded routes
Routes that manage authentication, single sign-on, API keys and the MCP
endpoint itself (/v1/auth, /v1/oauth, /v1/api-key, /v1/scim,
/v1/portal-sso, /v1/mcp) are never reachable through the facade, whatever
scopes the grant holds, and neither is any write to /v1/webhook-endpoints: a
webhook endpoint created under prompt injection would be an exfiltration
channel. A request to one of them is refused with a message saying so.
Errors
A route that answers 400 or above does not fail the tool call. The result
carries error: true, the status, and the route's own error body, so the agent
can read a validation message and correct the request. The facade's own
refusals raise a tool error: an unknown path, an excluded route, a missing
scope, or direct writes set to Deny.
Audit
Every facade call is recorded in the MCP audit log with the tool name, the
grant, the outcome, and targetOperation, the route it hit, as
"POST /v1/customers". The loopback request carries the same request id as
the MCP call, so the API request log and the MCP audit row share one id.
Endpoints
| Endpoint | Auth | Purpose |
|---|---|---|
POST /api/v1/mcp | OAuth 2.1 bearer, Dynamic Client Registration grant (mcp:read scope) | Tenant-scoped server |
POST /api/v1/mcp/c/{appCompanyId} | same | Tenant-scoped server, company pinned in the URL |
POST /api/v1/mcp/developer | Anonymous (any bearer) | Developer/docs server |
Authorization (OAuth 2.1)
The tenant-scoped servers follow the MCP authorization spec. A compliant
client handles the whole flow on its own: it discovers the authorization
server via .well-known, registers itself, and opens the browser sign-in.
The metadata endpoints:
/.well-known/oauth-protected-resource(and a per-company variant)/.well-known/oauth-authorization-server/.well-known/openid-configuration/.well-known/jwks.json
Clients that self-register use Dynamic Client Registration. An MCP client is a DCR-registered connected app — the same record backs both terms:
Code
A partner app that LedgerBee registers for you (rather than self-registering via DCR) runs the same authorization flow. See Connected Apps.
See Authentication for the full OAuth flow.