Scope Design
Every AI app accessing LedgerLou via MCP gets exactly the scopes it needs for its task — and no more. Scopes are the central security layer: they decide which tools an agent may call before even a single line is written to the general ledger.
The scope format
Scopes follow the format module:action. Each combination of module and action controls exactly what an agent is allowed to read or write:
| Scope | Allows |
|---|---|
journal:read | Read journal entries and chart-of-accounts tools |
journal:write | Execute journal entries and chart-of-accounts write operations |
bank:read | Read bank accounts and transactions |
bank:write | Reconcile and match bank transactions |
payables:read | Read vendors, incoming invoices and the AP inbox |
payables:write | Create vendors, incoming invoices, invoice-review cases |
receivables:read | Read customers and outgoing invoices |
receivables:write | Create customers, outgoing invoices, credit notes |
periods:read | Read period status and lock state |
periods:write | Soft-lock and reopen periods, request a hard lock |
reports:read | Trial balance, P&L, balance sheet, BWA, DATEV export |
config:read | Read tenant settings, users, API keys (not available via OAuth) |
config:write | Write tenant settings, migration, tax-code changes (not available via OAuth) |
admin | Global superscope — covers all modules and actions |
OAuth exclusion. The config:* scopes cannot be requested via the OAuth flow — managing users, API keys, and tenant-level accounting context belongs in the Dashboard or behind an admin API key. Third-party MCP clients that need migration or tax-code changes must use a tenant-issued admin API key.
Refresh tokens. OAuth clients receive both an access_token (30 min default) and a rotating refresh_token (30 days default) on every authorization-code exchange. Hitting /oauth/token with grant_type=refresh_token consumes the presented refresh token and returns a fresh access/refresh pair sharing the same token family. Replaying a consumed refresh token revokes the entire family, so a stolen copy cannot extend an attacker's session.
CIMD. LedgerLou also accepts a Client ID Metadata Document URL as client_id, so MCP clients that publish their metadata publicly do not need to register individually per tenant. The document is fetched once over HTTPS (5 s timeout, 256 KiB cap, SSRF guard), validated against a Zod schema, cached for 24 hours, and revalidated with If-None-Match. The admin superscope and config:* are never granted via CIMD — admin capabilities still require a tenant-issued admin key.
Recommended role separation
Instead of one universal key per AI integration, we recommend a clear role separation: one agent, one purpose, minimal scopes.
Reads data, builds reports, answers questions, and can also look up the chart of accounts. Cannot write anything — no risk of incorrect postings.
Creates journal entries, maintains vendors/customers, posts incoming and outgoing invoices, and reconciles bank transactions. No access to admin or tenant-configuration functions.
Scopes gate skill visibility
Beyond gating which tools a caller can invoke, scopes also gate which skills (MCP prompts) appear in prompts/list. A skill is only exposed when the caller holds all of its required scopes. This keeps the agent's palette focused and prevents it from loading playbooks it could not execute anyway.
| Skill | Required scopes |
|---|---|
process_incoming_invoice | payables:read, payables:write, journal:read, journal:write |
process_outgoing_invoice | receivables:read, receivables:write, journal:write, bank:read, bank:write |
reconcile_bank_transactions | bank:read, bank:write, journal:read |
tenant_setup_migration | admin — admin-only; invisible to OAuth clients because it needs config:write |
The admin superscope satisfies every skill's requirements. OAuth clients never see tenant_setup_migration because the migration capability mutates tenant-level configuration, which is reserved for the Dashboard and admin-keyed MCP clients.
Guardrails that have proven themselves
Every agent gets only the scopes it needs for its specific job. An analysis agent does not need write; a posting agent does not need admin.
Sensitive tools — especially write operations on the general ledger — should always require explicit user confirmation before the agent executes them.
Use a dedicated key/token per integration. That way, if there's a suspicion, a single key can be revoked without disrupting other integrations.
Never keep production keys in the frontend, in browser extensions, or in client-side code. MCP connections belong on the server, not in the user's app.
Key strategy in practice
- 1 Create a dedicated API key or OAuth client per integration — never share one key across systems.
- 2 Select scopes deliberately at connect time: grant only the modules the agent actually needs.
- 3 Rotate keys regularly — especially after team changes or when replacing an integration.
- 4 On suspicion of compromise, revoke immediately: Dashboard → Connected Apps → revoke connection.
LedgerLou manages GoBD-relevant bookkeeping data. An agent with overly broad scopes can, in a failure case, generate postings that are hard to undo — the general ledger is append-only. Clear scope design is therefore not a nice-to-have but part of the compliance architecture.