Norman Finance MCP Server
Official<div align="center">
<a href="https://norman.finance/?utm_source=mcp_server">
<img width="140px" src="https://github.com/user-attachments/assets/d2cb1df3-69f1-460e-b675-beb677577b06" alt="Norman" />
</a>
<h1>Norman MCP Server</h1>
<p>AI-powered bookkeeping, accounting, and taxes for European businesses, inside your AI assistant.<br/>
Norman connects invoicing, transactions, receipts, ledgers, reports, taxes, and company workflows to any MCP-compatible AI.</p>
<br/>
<p>
<img src="https://img.shields.io/badge/Protocol-MCP-black?style=flat-square" alt="MCP" />
<img src="https://img.shields.io/badge/Transport-Streamable_HTTP-black?style=flat-square" alt="Streamable HTTP" />
<img src="https://img.shields.io/badge/Auth-OAuth_2.1-black?style=flat-square" alt="OAuth 2.1" />
<img src="https://img.shields.io/badge/License-MIT-black?style=flat-square" alt="MIT" />
</p>
<code>https://mcp.norman.finance/mcp</code>
<br/><br/>
<strong>Claude</strong> · <strong>ChatGPT</strong> · <strong>Gemini</strong> · <strong>Grok</strong> · <strong>Perplexity</strong> · <strong>Cursor</strong> · <strong>Any MCP Client</strong>
</div>
<br/>
<div align="center">
<a href="https://github.com/user-attachments/assets/10718781-34f2-4640-9253-be4c82de6159"></a>
</div>
<br/>
---
<br/>
### What you can do
**Invoicing** — Create and edit invoices, quotes, and recurring schedules, including document templates, line discounts, units, service dates, payment links, and ZUGFeRD e-invoices; cancel an issued invoice with a Stornorechnung, credit part of it with a Rechnungskorrektur, or make a delivery note (Lieferschein) from an invoice or an approved quote
**Client emails and reminders** — Send invoices with the company's own email wording and CC recipients, see their delivery status, switch automatic payment reminders on for an invoice, and set the reminder rule (days and dunning fees per level)
**Bookkeeping** — Categorize transactions, match receipts, and verify entries
**Automation rules** — "Always book Telekom to Internet costs": preview, create, and manage rules that categorize matching transactions automatically
**Workflows** — "Close August for me": Norman runs month-end close, bank reconciliation, VAT-return readiness, invoice-to-payment and client document requests on its own servers, stops only to ask you something, and can run them by itself every month; answer its questions and see what the agents did this week from your AI client
**Client Management** — Maintain your client database and contact details
**Products & Services** — Keep a catalog of what you sell, with prices, VAT rates and units, and fill invoice lines from it
**Tax Filing** — Generate Finanzamt previews, file VAT returns, and track deadlines
**Company Overview** — Check your balance, revenue, and financial health at a glance
**Company Formation** — Found a German **GmbH or UG**: collect the founders' data, check the name against the Handelsregister, generate the founding documents (Musterprotokoll, Gesellschafterliste), match with a notary, and track every step through to registration
**Documents** — Upload and attach receipts, invoices, and supporting files
`create_attachment.file_url`, bulk `file_urls`, and structured document imports
accept HTTP(S) download links, including unexpired presigned S3 URLs with query
parameters. Pass the entire link unchanged; the file host must accept the request
without additional authorization headers. The URL downloader has no domain allowlist,
URL-length cap, or explicit file-size cap. Its connect/read timeout is 30 seconds
(not a total download deadline). The separate browser upload route defaults to
50 MiB, configurable with `MCP_UPLOAD_MAX_SIZE`; API file validation still applies.
URL download failures return an `error` message and `code`: `download_http_error`
(with `http_status`), `download_timeout`, `download_network_error`,
`download_storage_error`, or `download_error`. A 403 from the file host can mean an
expired or rejected signature; obtain a fresh link and check the host response.
Bulk uploads include `download_errors` for failed URLs, indexed within `file_urls`
followed by URL entries from the deprecated `file_paths` alias; successfully fetched
files are still uploaded. Structured imports report errors per document. Download
errors omit signed URLs, and temporary downloads are cleaned up after use or cancellation.
Norman is built as a multi-market European accounting platform. Market-specific capabilities are added as Norman expands; current German coverage includes SKR03/SKR04, DATEV, ELSTER, ZUGFeRD, and GmbH/UG workflows.
<br/>
#### Invoice appearance and updates
Use `list_invoice_templates` to discover the templates, appearance controls and the active company's plan access. Use `get_invoice_settings` to read its logo and saved defaults.
`create_invoice`, `create_recurring_invoice` and `create_offer` accept `document_design`, for example:
```json
{"template": "sovereign", "logoSize": 75, "textSize": "medium", "spacing": "compact", "tableBorders": "grid"}
```
Omit `document_design`, `font`, and `color_schema` to inherit saved branding. On create, a partial design uses that template's defaults. Paid templates require an active subscription.
Use `update_invoice` for an invoice or quote, `update_recurring_invoice` for a schedule, and `update_invoice_settings` for future document defaults. Their typed `changes` object accepts camelCase or snake_case field names. Unset fields stay unchanged; explicit `false`, `0`, empty strings and valid nulls keep their meaning. A partial document design keeps the document's other saved controls; changing its template starts from that template's defaults. Keep existing line IDs when editing lines. Rates use minor currency units; the API calculates totals. Only set `isToSend` when sending is intended.
Use `cancel_invoice`, `create_credit_note` and `create_delivery_note` for documents derived from an existing invoice or an approved quote; each links back to its source, and a cancelled invoice is read-only afterwards. `duplicate_invoice` copies an invoice or quote into a fresh draft without a link. API keys need `read_invoices` for template/settings reads and `write_invoices` for edits. Settings updates use the invoice-specific endpoint and cannot edit other company fields. Deploy the matching invoice API endpoints before deploying this MCP version.
#### Client emails and payment reminders
`send_invoice` and `send_invoice_overdue_reminder` use the company's email template when `subject` and `body` are left out; `additional_emails` go out as CC, and the company's own copy follows its setting unless `is_send_to_company` is given. `list_invoice_emails` returns every email about a document with its delivery status and the reminder the rule sends next. A send that fails at once returns an error with the reason; otherwise `email.status` is `sent`, or `queued` while Norman retries. A dunning fee is shown in the reminder with the total and is paid by bank transfer: a reminder with a fee has no online payment button.
Automatic reminders are off until `autoReminders` is true on the invoice (`create_invoice`, `create_recurring_invoice`, or `update_invoice`, also after the invoice was issued); `remindersPaused` stops them for one invoice and `skip_invoice_reminder` stops one planned reminder. `remindersActive` on an invoice says whether Norman reminds by itself right now. The company's rule and wording live in `get_invoice_email_settings` / `update_invoice_email_settings` and `list_invoice_email_templates` / `save_invoice_email_template` / `reset_invoice_email_template`. `settings_on_overdue` is kept for older integrations and sends nothing. These tools need the invoice email endpoints of the API to be live before this MCP version is deployed.
### 💬 Try asking
Once connected, talk to your books in plain language:
- *"Prepare and file my UStVA for last month."*
- *"Send a €1,200 invoice to ACME for consulting."*
- *"What did I spend on software this quarter?"*
- *"Find tax deductions I might have missed."*
- *"Which invoices are overdue? Send reminders."*
<br/>
### Interactive UI inside your AI assistant
Norman is more than a collection of background tools. In MCP Apps-compatible
ChatGPT and Claude clients, Norman can render focused accounting workspaces
directly inside the conversation. You can filter and inspect the underlying
data, move between related views, and use **Ask AI** to continue the discussion
with the current accounting context.
| Interactive workspace | Use case |
|:--|:--|
| **Norman Inbox** | See current bank balances, bookkeeping tasks and quick chat actions. Review blocked workflows and pending automation approvals before explicitly approving or dismissing. |
| **Document Review** | Review uploaded invoices and receipts, find documents that still need a transaction match, and inspect linked records. |
| **Reconciliation Cockpit** | Find transactions with missing documents, missing categories, or accounts from a previous SKR before month-end or year-end close. |
| **Ledger Explorer** | Browse the chart of accounts, inspect balances, and drill into the postings behind an account. |
| **Tax Preview & Submission** | Review the Finanzamt test PDF, tax lines, period, total, and readiness checks before filing. Submission is a separate explicit action and stays disabled until the user confirms the preview. |
Try prompts such as:
- *"Open my Norman Inbox and show what needs my attention."*
- *"Open my Document Review for the last 60 days."*
- *"Show my Reconciliation Cockpit and highlight missing documents or categories."*
- *"Open the Ledger Explorer and show the postings for account 1200."*
- *"Open my VAT return for July, generate the Finanzamt test preview, and explain anything I should review before submission."*
The Norman API remains the source of truth. Opening or filtering a workspace
does not change accounting data. Binding actions, including tax submission,
remain separate MCP tool calls with their normal confirmation and permission
checks. Clients without MCP Apps support receive the same underlying results as
structured or text tool output.
The SDK 2 HTTP transport limits MCP JSON request bodies to 4 MiB. Send larger
documents through `file_url` or the upload page's `file_ref`, rather than inline
base64. The separate multipart upload route keeps its own 50 MiB default,
configurable with `MCP_UPLOAD_MAX_SIZE`.
<br/>
### 🏢 Starting a company
Found a German **GmbH or UG (haftungsbeschränkt)** end-to-end — Norman collects the data, prepares the documents, and hands off to a notary:
- *"I want to start a GmbH in Berlin — walk me through it."*
- *"Found a UG for me and two co-founders, split the shares 60/40."*
- *"Is 'Wunderbar Robotics' still free in the Handelsregister?"*
- *"Reword my business purpose so it's ready for the register."*
- *"Generate the Musterprotokoll and find me a notary who does online notarization."*
- *"What's left before my company is officially registered?"*
> Choosing GmbH/UG also sets your Norman account to the corporate **SKR04** chart of accounts, so bookkeeping and taxes are ready from day one. The documents are drafts to prepare the notary appointment — not legal advice.
<br/>
<details open>
<summary>
<h3>👀 See it in action</h3>
</summary>
<br/>
<table>
<tr>
<td align="center">
<p><strong>Filing a VAT return</strong></p>
<img src="https://github.com/user-attachments/assets/00bdf6df-1e37-4ecd-9f12-2747d8f53484" alt="Filing VAT tax report" width="400">
</td>
<td align="center">
<p><strong>Transaction insights</strong></p>
<img src="https://github.com/user-attachments/assets/534c7aac-4fed-4b28-8a5e-3a3411e13bca" alt="Transaction insights" width="400">
</td>
</tr>
<tr>
<td align="center">
<p><strong>Syncing Stripe payments</strong></p>
<img src="https://github.com/user-attachments/assets/2f13bc4e-6acb-4b39-bddc-a4a1ca6787f0" alt="Syncing Stripe payments" width="400">
</td>
<td align="center">
<p><strong>Receipts from Gmail</strong></p>
<img src="https://github.com/user-attachments/assets/2380724b-7a79-45a4-93bd-ddc13a175525" alt="Creating transactions from Gmail receipts" width="200">
</td>
</tr>
<tr>
<td align="center">
<p><strong>Chasing overdue invoices</strong></p>
<img src="https://github.com/user-attachments/assets/d59ed22a-5e75-46f6-ad82-db2f637cf7a2" alt="Managing overdue invoices" width="300">
</td>
<td align="center">
<p><strong>Sending payment reminders</strong></p>
<img src="https://github.com/user-attachments/assets/26cfb8e9-4725-48a9-b413-077dfb5902e7" alt="Sending payment reminders" width="350">
</td>
</tr>
</table>
</details>
<br/>
---
<br/>
## 🚀 Get Started
Before connecting, [create a free Norman account](https://app.norman.finance/sign-up?utm_source=mcp_server) if you don't have one yet. Log in with your Norman credentials via OAuth — your password never touches the AI.
<details>
<summary><strong>Claude Connectors</strong></summary>
<br/>
1. Go to [Claude Connectors](https://claude.ai/new#settings/customize-connectors)
2. Click **Add**
3. Find and connect: **Norman Finance**
MCP Apps-compatible Claude hosts can open Norman's [interactive accounting
workspaces](#interactive-ui-inside-your-ai-assistant) directly in the
conversation. Other Claude clients receive the same data as normal tool output.
</details>
<details>
<summary><strong>Claude Code</strong></summary>
<br/>
Norman is available as a [Claude Code plugin](https://code.claude.com/docs/en/plugins) with built-in skills.
```bash
/plugin marketplace add norman-finance/norman-mcp-server
/plugin install norman-finance@norman-finance
```
Or install directly from GitHub:
```bash
claude /plugin install github:norman-finance/norman-mcp-server
```
</details>
<details>
<summary><strong>ChatGPT Plugins</strong></summary>
<br/>
1. Install it from the official [ChatGPT Plugins Directory](https://chatgpt.com/plugins/plugin_asdk_app_6981ec32565481919b1c5a1627b1e330).
The plugin includes Norman's [interactive accounting
workspaces](#interactive-ui-inside-your-ai-assistant), including Document
Review, Reconciliation, Ledger Explorer, and the explicit tax preview and
submission flow.
</details>
<details>
<summary><strong>Gemini</strong></summary>
<br/>
**Gemini CLI extension**
```bash
gemini extensions install https://github.com/norman-finance/norman-mcp-server
```
Start Gemini CLI and authenticate the remote server when prompted, or run:
```text
/mcp auth norman-finance
```
**Gemini Spark custom app**
Create a Spark, add a custom app, and use
`https://mcp.norman.finance/mcp` as its MCP server URL. Availability depends on
your Gemini account and region. See Google's [custom app
guide](https://support.google.com/gemini/answer/17209137).
</details>
<details>
<summary><strong>Perplexity</strong></summary>
<br/>
1. Open **Account settings → Connectors**
2. Click **+ Custom Connector** and select **Remote**
3. Enter **Norman Finance** and `https://mcp.norman.finance/mcp`
4. Save the connector and complete Norman OAuth
Organization administrators can share the remote connector with their team.
</details>
<details>
<summary><strong>Grok</strong></summary>
<br/>
**Grok web**
1. Go to [Grok Connectors](https://grok.com/connectors)
2. Click **New Connector → Custom**
3. Enter `https://mcp.norman.finance/mcp` and complete Norman OAuth
**Grok CLI**
```bash
grok mcp add --transport http norman-finance https://mcp.norman.finance/mcp
```
Grok CLI also discovers this repository's `.mcp.json` automatically.
</details>
<details>
<summary><strong>Cursor</strong></summary>
<br/>
[](https://cursor.com/en-US/install-mcp?name=norman-finance&config=eyJ1cmwiOiJodHRwczovL21jcC5ub3JtYW4uZmluYW5jZS9tY3AifQ%3D%3D)
</details>
<details>
<summary><strong>Replit</strong></summary>
<br/>
[](https://replit.com/integrations?mcp=eyJkaXNwbGF5TmFtZSI6Ik5vcm1hbiBNQ1AgU2VydmVyIiwiYmFzZVVybCI6Imh0dHBzOi8vbWNwLm5vcm1hbi5maW5hbmNlL21jcCJ9)
</details>
<details>
<summary><strong>OpenClaw</strong></summary>
<br/>
**Option 1 — Remote with OAuth**
Run in OpenClaw:
```
mcp add https://mcp.norman.finance/mcp
```
You'll be prompted to log in with your Norman account on first use.
**Option 2 — Skills only**
```bash
git clone https://github.com/norman-finance/norman-mcp-server.git
cp -r norman-mcp-server/skills/* ~/.openclaw/skills/
openclaw gateway restart
```
**Option 3 — Local stdio**
```bash
pip install norman-mcp-server
```
```bash
openclaw mcp add norman -- norman-mcp --transport stdio
```
Set your credentials as environment variables (`NORMAN_EMAIL`, `NORMAN_PASSWORD`) before starting the gateway.
</details>
<details>
<summary><strong>n8n</strong></summary>
<br/>
1. Create an **MCP OAuth2 API** credential
2. Enable **Dynamic Client Registration**
3. Set Server URL: `https://mcp.norman.finance/`
4. Click **Connect my account** and log in with Norman
5. Add an **MCP Client Tool** node to your AI Agent workflow
6. Set the URL to `https://mcp.norman.finance/mcp` and select the credential
</details>
<details>
<summary><strong>Any MCP Client</strong></summary>
<br/>
Add a remote HTTP MCP server with URL:
```
https://mcp.norman.finance/mcp
```
</details>
<br/>
---
<br/>
## Skills
Ready-to-use skills compatible with **Claude Code**, **OpenClaw**, and the [Agent Skills](https://agentskills.io) standard.
| Skill | What it does |
|:--|:--|
| `financial-overview` | Full dashboard — balance, transactions, invoices, and tax status |
| `create-invoice` | Step-by-step invoice creation and sending |
| `manage-clients` | List, create, and update client records |
| `manage-products` | List, create, update, and archive catalog products; fill invoice lines from them |
| `tax-report` | Review, preview, and file tax reports with the Finanzamt |
| `categorize-transactions` | Categorize and verify bank transactions |
| `find-receipts` | Find missing receipts from Gmail or email and attach them |
| `overdue-reminders` | Identify overdue invoices and send payment reminders |
| `expense-report` | Expense breakdown by category, top vendors, and trends |
| `tax-deduction-finder` | Scan transactions for missed deductions and suggest fixes |
| `monthly-reconciliation` | Full monthly close — transactions, invoices, receipts, and taxes |
| `run-workflow` | Start a Norman workflow, answer its questions, and schedule it to run by itself |
| `company-incorporation` | Found a German GmbH/UG — data, documents, name check, and notary hand-off |
<br/>
> **Claude Code** — `/plugin marketplace add norman-finance/norman-mcp-server`
>
> **Claude Code (local)** — `claude --plugin-dir ./norman-mcp-server`
>
> **OpenClaw** — `cp -r skills/* ~/.openclaw/skills/ && openclaw gateway restart`
<br/>
---
<br/>
<p align="center">
Have a feature idea? <a href="../../issues"><strong>Share your suggestion →</strong></a>
</p>
<br/>
<p align="center">
<a href="https://mcpbeat.com/mcp-servers/norman/mcp-server/"><img src="https://mcpbeat.com/badge/norman/mcp-server.svg" alt="mcpbeat"></a>
<a href="https://glama.ai/mcp/servers/@norman-finance/norman-mcp-server"><img src="https://glama.ai/mcp/servers/@norman-finance/norman-mcp-server/badge" alt="Norman Finance MCP server" width="200" /></a>
</p>
<p align="center">
<br/>
<a href="https://norman.finance/?utm_source=mcp_server">
<img width="80px" src="https://github.com/user-attachments/assets/d2cb1df3-69f1-460e-b675-beb677577b06" alt="Norman" />
</a>
<br/><br/>
<sub>Make business effortless</sub>
</p>
<!-- mcp-name: finance.norman/mcp-server -->
### Mixed VAT and documented input tax
See [the VAT item workflow](docs/vat-item-workflows.md) for item-level treatments, fixed documented EUR input VAT, refunds and manual VAT-only corrections. Requires the corresponding API migrations and calculation updates.
### Norman Inbox and MCP Events (development)
`open_norman_inbox` declares global/sidebar and thread entrypoints for hosts that
support [plugin extensions](https://developers.openai.com/plugins/build/extensions).
MCP Apps hosts can render the same self-contained UI; other clients can call
`get_norman_inbox_data` and `get_norman_approval_data` for structured results.
Workflow questions use actual blocking state. Pending approval totals include all
pages; tax reviews are a bounded list. Source failures are shown as unavailable.
The Inbox also shows latest synced bank balances grouped by their original
currency, overdue unpaid invoices, unattached invoice/receipt documents and
unfinalized transactions with status `UNVERIFIED`. These task counts cover all
history. Transaction totals cover the previous and current calendar months, with
the date range displayed. Counts come from pagination totals, not
the single record requested with each counter. Overdue invoices use Norman's
stored overdue status; bank snapshots may lag the bank and are not profit.
Task buttons delegate work to the conversation with the selected company and
scope. The assistant can complete supported, evidence-backed document matching,
categorization and finalization through existing tools, then report verified
results and genuine blockers. Financial overview is analysis-only; invoice
follow-ups prepare drafts. External messages, payments, tax filing and automation
approvals require a separate decision. The iframe sends a chat request rather
than calling write tools itself; clients without messaging show a copyable request.
Button-generated messages use readable references to the Inbox selection. Internal
company and record IDs stay in structured app context, which is acknowledged before
sending. When that context is unavailable, the request asks the user to confirm the
company and relevant record; copyable requests also omit IDs.
Below the financial overview, **You can also ask Norman** shows three contextual
tasks with the remaining suggestions under **Explore more tasks**. Their order
stays stable for the selected company while the panel refreshes. Spending analysis,
tax readiness and accountant handover checks are read-only; invoice creation is
restricted to an unsent draft, and tax previews never authorize filing.
The additive `capabilities` object contains `ledger` and `taxPreview` discovery
hints. One company detail read enables Ledger suggestions only for SMEs with an
SKR03/SKR04 chart. A preview suggestion requires an existing prepared Autopilot
run with a report ID; a missing suggestion does not mean no tax reports exist.
Unknown eligibility hides specialized suggestions while general tasks remain
usable. Raw company details are omitted, and the host and API still enforce
tool availability and permissions.
The header shows the selected company's name, legal form and country. Its picker
loads non-archived companies on demand through `list_companies`, then switches
with `switch_company` and re-reads the Inbox. Switching clears old decisions,
confirmation and chat context before loading the replacement. An ambiguous timeout
keeps actions unavailable until the requested company is verified; the switch is
never automatically retried. The additive `company` profile reuses the existing
company lookup and excludes tax IDs, bank details and members.
On mobile, the Inbox has a bounded inner scroll area, uses host safe-area insets,
and condenses empty states and explanatory text. Standard host context controls
theme and dimensions, with legacy OpenAI globals and OS theme fallbacks. Sidebar
icons include explicit light and dark variants. Unbounded inline hosts retain
natural height; previously cached Inbox resource URIs remain available.
The review card uses existing approve/dismiss/undo tools and refreshes actual
results. Approval requires a checkbox and re-reads current values and planned
actions immediately before executing. A changed review requires confirmation
again. The backend remains responsible for atomic execution and permissions.
Non-transaction approvals whose current target is unavailable can be discussed or
dismissed; they cannot be approved from this card.
The visible Inbox refreshes through `get_norman_inbox_data` 30 seconds after a
completed read and backs off to 60, 120 and at most 300 seconds while nothing on
screen changes; any interaction or change resets it to 30. Focus or becoming
visible refreshes only once half the current interval has passed. It pauses while
hidden/offscreen, during actions, on teardown, and after an expired session until
the user acts. It keeps the selected page and typed workflow answer (a draft is
bound to its question; if the question changes, sending needs confirmation); an
unchanged approval keeps explicit consent, while changed current values or
planned actions clear it. This uses the portable Apps `tools/call` bridge: iframe support for
SDK 2 resource subscriptions is not assumed.
Hosted SDK 2 clients can separately opt into resource invalidations by setting
`NORMAN_MCP_INBOX_LIVE=1` on a **single-process** MCP deployment using the
streamable-http transport (SSE starts one lifespan per connection, so the flag is
ignored there with a warning). Credential-only stdio does not enable this feature. Existing tools and legacy refresh continue
working when the setting is absent.
1. Call `watch_norman_inbox` with an approval `page` (default 1). It returns an
opaque `resourceUri`, `expiresAt` (Unix seconds), and observation interval.
2. Open SDK 2 `subscriptions/listen` for that exact URI. After acknowledgment,
call `resources/read` for the current snapshot; refetch on each
`notifications/resources/updated`. Events contain only the opaque URI.
3. Reopen the watch after expiry, reconnect, token refresh, restart, or an
attempted `switch_company`. Watches last at most five minutes and never
outlive their MCP bearer token. There is no replay; always refetch on reconnect.
Read and listen both check the exact OAuth grant, client and selected company.
Observers resolve only that grant's current Norman token and pin its company.
An expired Norman token (one hour) is refreshed through that grant; a 401 that
survives the refresh, or a 403, closes the watch. Local grant revocation, company changes and expiry
end its stream within one second. The observer reads only while a stream is
connected, with 30 seconds between observation cycles. Each watch observes one
approval page, workflow state, bounded tax reviews, discovery capabilities and the Inbox overview; it is not a complete
company event log and may miss intermediate changes. Financial snapshots are
never cached in the change bus. There are at most 128 leases/streams, four leases
and four open streams per OAuth grant, four concurrent snapshot reads (normally
ten API requests per snapshot) and two per grant. The grant-pinned reader
permits only GET requests.
Tokens refreshed from one authorization
share its grant, so refreshing does not raise these limits.
Leases and the SDK subscription bus are in memory. Use one process/replica;
multiple replicas require shared lease state, OAuth state and a distributed
subscription bus before this feature can be enabled reliably. This read-only
feed is separate from the durable webhook events below.
The opt-in `workflow.attention_required` event implements the
[draft MCP Events contract](https://developers.openai.com/plugins/build/mcp-events)
on SDK 2 / protocol `2026-07-28`. It watches one explicitly selected company and
workflow through the Norman API and sends signed webhooks when the active run
becomes blocked. It does not advance workflows, send invoice reminders or file
taxes. Current delivery waits 30 seconds between source observation cycles; it does not yet
consume a backend event stream and can miss transitions between observations.
To enable events on a hosted **single-worker** Streamable HTTP deployment, set:
- `NORMAN_MCP_EVENTS_DB`: an absolute SQLite path on a persistent private volume.
- `NORMAN_MCP_EVENTS_KEY`: a Fernet encryption key from your secret manager. Keep
the same key across restarts; changing it makes stored subscriptions unreadable.
Start with `norman-mcp --transport streamable-http --public-url https://your-host`.
Both settings are required. Without them, events are not advertised. SQLite state
contains encrypted authentication references, callback URLs, signing keys and
pending payloads. Persist the existing `MCP_OAUTH_STATE_FILE` on a private volume
as well: delivery after restart needs valid OAuth tokens and Norman token mappings.
The worker reads each run with the subscriber's own grant and company; an
expired Norman access token (one hour) is refreshed through that grant, and a
grant that cannot be refreshed suspends delivery until the client refreshes the
subscription. Nothing falls back to other credentials. Multiple worker
processes/replicas require coordinated OAuth storage and a distributed delivery
lease before enabling events.
Subscriptions use `events/list`, `events/subscribe` and `events/unsubscribe`, with
arguments `{ "company_id": "UUID", "run_id": "UUID" }` and webhook delivery
`{ "mode": "webhook", "url": "https://public-callback", "secret": "whsec_…" }`.
The receiver must echo the signed verification challenge. Subscription lifetime
is capped at one hour and authentication expiry; renew before `refreshBefore`.
Secret rotation accepts the previous signing key for five minutes. Each retry
keeps the same `eventId`; receivers should deduplicate by it. Permanent callback
failures stop retries until the subscription is renewed, and HTTP 410 removes
the subscription. Callback addresses are validated and DNS-pinned to public HTTPS
endpoints; redirects are not followed. Each callback, verification included, has
15 seconds in total and runs in a small dedicated pool.
Host support, plugin submission and production activation need separate
verification. Local protocol and browser fixtures do not establish ChatGPT
catalog availability or a successful production OAuth/webhook connection.
TDQS
Scored across 34 tools
Most tools have distinct purposes, but there is some overlap between create_transaction and categorize_transaction, and between send_invoice and send_invoice_overdue_reminder, which could cause minor confusion. However, descriptions clarify their specific roles, and the majority of tools target unique resources or actions.
All tool names follow a consistent snake_case pattern with a clear verb_noun structure, such as create_client, list_invoices, and update_transaction. This uniformity makes the tool set predictable and easy to navigate.
With 34 tools, the count is borderline high for a finance server, potentially overwhelming for an agent. While the domain is broad (clients, invoices, transactions, taxes, attachments), some tools might be redundant or overly specialized, making the set feel heavy.
The tool set provides comprehensive coverage for financial management, including full CRUD operations for clients, invoices, transactions, and attachments, plus tax reporting, validation, and email functionalities. There are no obvious gaps, and tools support end-to-end workflows.