informer-mcp
MCP server for the Informer bookkeeping API, providing direct access to relations, invoices, quotations, orders, receipts, products, and financial reports.
README
informer-mcp
A Model Context Protocol server for the Informer bookkeeping API (v2). It gives any MCP client direct access to your relations, sales and purchase invoices, quotations, orders, receipts, products and financial reports.
Every tool is derived from Informer's own OpenAPI document
(api.informer.eu/docs/v2). A copy ships with the
server so it works offline, and it keeps itself current — see
Keeping up with API changes.
Unofficial project. Not affiliated with or endorsed by Informer.
Quickstart
Paste this into any AI assistant that can install MCP servers:
Install the following MCP server: https://github.com/vladxyz/informer-mcp and run the local setup screen for the API keys.
It clones the repository, builds it, registers the server, and opens the page where your API credentials go. Nothing is asked for in the chat, and no key is ever pasted into a conversation.
To do it yourself, first build the server:
git clone https://github.com/vladxyz/informer-mcp.git
cd informer-mcp
npm install # also builds dist/
Then wire it into whichever client you use. All of them run the same thing: node, with
the path to dist/index.js.
Claude Code
claude mcp add informer -- node /absolute/path/to/informer-mcp/dist/index.js
Add --read-only at the end for a server that cannot change anything.
Claude Desktop
Build a bundle and open it:
npm run bundle # writes informer-mcp.mcpb
Settings → Extensions → Advanced settings → Install Extension… and pick the file. It carries its own dependencies, and the install dialog asks for an API key, a security code and a read-only switch — all three may be left empty, in which case the setup page opens on first run.
Settings → Connectors → Add custom connector is a different thing: it takes the URL of a remote MCP server. This one runs locally, so it installs as an extension.
Editing claude_desktop_config.json by hand works too — see
Any MCP client for the shape, and put the file at
~/Library/Application Support/Claude/ on macOS or %APPDATA%\Claude\ on Windows.
Codex and the ChatGPT desktop app
Codex CLI, the ChatGPT desktop app and the Codex IDE extension share one MCP configuration:
codex mcp add informer -- node /absolute/path/to/informer-mcp/dist/index.js
or in ~/.codex/config.toml:
[mcp_servers.informer]
command = "node"
args = ["/absolute/path/to/informer-mcp/dist/index.js"]
The .mcpb bundle is a Claude Desktop format; everywhere else use the command above.
Any MCP client
The server speaks MCP over stdio, which every client configures the same way — a command and its arguments:
{
"mcpServers": {
"informer": {
"command": "node",
"args": ["/absolute/path/to/informer-mcp/dist/index.js"]
}
}
}
On Windows, either double the backslashes or use forward slashes.
Credentials do not belong in this file: they live in ~/.informer-mcp.json, written by
the setup page. To pass them per client anyway, add an env block with
INFORMER_API_KEY and INFORMER_SECURITY_CODE.
First run
However you installed it, starting without credentials opens the setup page by itself.
Then ask "which administrations do you have access to?" to confirm — that calls
list_administrations and lists each alias with its company.
What you get
- 68 tools covering all 49 documented endpoints — read and write.
- Setup in the browser. Ask your assistant to open the setup page, or run
informer-mcp setup. It checks every key against the API, writes the config file, and the change takes effect without restarting anything. - Follows the API. When Informer publishes a new endpoint the server picks it up and adds the tool while your client stays connected — no reinstall, no restart.
- Several client administrations in one server. Bookkeepers can reach every
client's books from one connection, with an
administrationargument that is required whenever more than one is configured. - One question across the whole portfolio. Read-only tools accept a list of
aliases or
"all"and query them concurrently, returning results keyed by client. - Full request schemas. Create/update tools advertise the complete JSON Schema for their payload, so the model knows which fields exist and which are required before it sends anything.
- Read-only or read-write, your choice. A
--read-onlyflag hides every tool that changes anything, and individual clients can be pinned to read-only while the rest stay writable. Allow/deny lists narrow the surface further. - PDFs and attachments are decoded from base64 and can be written straight to disk.
- Resilient HTTP. Timeouts, retries with
Retry-Aftersupport, and Informer's Dutch validation errors surfaced verbatim (HTTP 422: invoice_date: ongeldig).
Requirements
- Node.js 20 or newer
- An InformerOnline account with API access
Setting up your credentials
Just ask, in the conversation:
"I want to change my Informer administrations" "Add a new client to Informer" "My Informer API key changed"
Your assistant calls the open_setup tool and the page opens. There is no config file to
find and nothing to edit by hand — and because the page is a browser form, your API key
never has to be typed into a chat. The same page from a terminal:
npm run setup # or: informer-mcp setup
Starting the server with no credentials at all opens it automatically, since that is
exactly the moment you need it. Set INFORMER_AUTO_SETUP=false to turn that off, or
INFORMER_OPEN_BROWSER=false on a headless machine to only print the URL. However it was
opened, there is only ever one page: asking again hands back the same URL.
What you see on the page
One card per administration, plus Add administration if you look after more than one:
┌─ Administration ────────────────────────────── Remove ─┐
│ ALIAS COMPANY NAME │
│ [ acme ] [ ACME BV ] │
│ Short handle you use Optional, shown in │
│ in prompts. tool descriptions. │
│ │
│ API KEY SECURITY CODE │
│ [ ••••••••••••••••• ] [ ••••••••••••••• ] │
│ │
│ ACCESS │
│ [ Read and write ▾ ] │
│ Read only hides every tool that changes this │
│ client's books. │
└────────────────────────────────────────────────────────┘
[ Add administration ] [ Verify & save ] ☐ Save without verifying
| Field | What to put in it |
|---|---|
| Alias | The short name you will say in prompts — "list open invoices for acme". Letters, digits, - and _. |
| Company name | Optional label, shown to the model so it knows acme is ACME BV. |
| API key | Created inside that administration at app.informer.eu/settings/api. |
| Security code | Shown in that administration's settings at app.informer.eu/settings/account. |
| Access | Read and write, or Read only to hide every tool that could change this client's books. |
Both credentials belong to one administration, so a bookkeeper adds one card per client. See Multiple client administrations.
What happens when you press Verify & save
- Each key/security-code pair is tried against the API, and the page shows you the company name it actually belongs to — so a key pasted into the wrong row is obvious before anything is stored.
- If a pair is rejected, nothing is written and the failing row is named. Tick Save without verifying to store it anyway, for instance when you are offline.
- On success the credentials are written to
~/.informer-mcp.jsonwith0600permissions. Opened throughopen_setup, the running server picks the change up immediately — a new administration is selectable in the very next message. Opened from a terminal, restart your client.
A few things the page deliberately does:
- it binds to
127.0.0.1only, and every run generates a random token that must be in the URL and in the save request, so another site in your browser cannot post to it; - it never sends stored keys back to the page — existing administrations show up with their credentials blank and are kept unless you type a new value;
- it refuses to save credentials the API rejects, unless you tick Save without verifying.
Nothing stops you from writing the file or the environment variables by hand; the page is a convenience, not a requirement.
Where the keys come from
The API authenticates with two headers, both required:
| Environment variable | Where to find it |
|---|---|
INFORMER_API_KEY |
app.informer.eu/settings/api |
INFORMER_SECURITY_CODE |
app.informer.eu/settings/account |
Both are scoped to one administration: the API key belongs to the administration it
was created in (GET /administration returns "the administration linked to this API
key") and the security code identifies that company. There is no endpoint that lists
administrations or switches between them.
A key grants full access to that administration's books. Treat it like a password: keep it in your environment, a secret manager, or a config file outside the repository.
Multiple client administrations
A bookkeeper with several clients needs one key/security-code pair per client
administration — an accountant user
with access to an administration can create them from its settings. Add them in the setup
page, or write ~/.informer-mcp.json (or any file named by INFORMER_CONFIG_FILE)
yourself:
{
"administrations": {
"acme": { "label": "ACME BV", "api_key": "...", "security_code": "..." },
"bakkerij": { "label": "Bakkerij de Bol", "api_key": "...", "security_code": "...", "mode": "read-only" }
}
}
With more than one administration configured, every tool requires an administration
argument, advertised as an enum of your aliases:
list_sales_invoices({ "administration": "acme", "filter": "open" })
There is deliberately no default. Booking an invoice into the wrong client's ledger is the one mistake that must not happen quietly, so a call without the argument is rejected by schema validation before any HTTP request is made — as is an alias you never configured.
list_administrations shows the configured aliases; pass verify: true to fetch each
company name from the API, which confirms both that the credentials work and that every
alias points at the company you think it does.
Querying several clients at once
Read-only tools also accept a list of aliases, or "all":
list_sales_invoices({ "administration": "all", "filter": "open", "records": 50 })
list_sales_invoices({ "administration": ["acme", "bakkerij"], "filter": "open" })
The administrations are queried concurrently (INFORMER_FANOUT_CONCURRENCY, four at a
time by default) and the answer is keyed by alias:
{
"administrations": ["acme", "bakkerij"],
"results": {
"acme": { "pagination": { "total": 3 }, "invoices": [ ... ] },
"bakkerij": { "error": "[bakkerij] HTTP 401: Authentication failed" }
}
}
Three properties worth knowing:
- One client failing does not sink the query. Its entry carries an
errorand the rest still return data. - The response budget is split evenly. Each administration gets
INFORMER_MAX_RESPONSE_CHARS / ncharacters, so one large client cannot crowd the others out; anything over its share comes back as{ "truncated": true, "partial": ... }. - Fan-out is read-only. Tools that write, and the PDF/attachment downloads, take a
single alias — their schema does not even offer the array or
"all", and the handler refuses them a second time. Creating the same invoice in twelve administrations is never an accident worth enabling.
A single administration still returns the API payload unwrapped, exactly as before.
With a single administration — the common case — nothing changes: set INFORMER_API_KEY
and INFORMER_SECURITY_CODE as usual and the argument stays optional.
Read-only or read-write
By default every tool is available. To take the writing tools away entirely, start the server with a flag:
informer-mcp --read-only # only the tools that read
informer-mcp --read-write # the default: create, update and delete too
INFORMER_READ_ONLY=true does the same thing, and the flag wins over the variable — so
you can register the same server twice in one client, once read-only for everyday
questions and once read-write for the sessions where you actually book something.
In read-only mode the write tools are not registered at all: they never appear in the tool list, so there is nothing for a model to reach for.
Per client
Individual administrations can be pinned in the config file, which is the useful shape when you may only look at some clients' books:
{
"administrations": {
"acme": { "api_key": "...", "security_code": "..." },
"bakkerij": { "api_key": "...", "security_code": "...", "mode": "read-only" }
}
}
"read_only": true works as a shorthand. The most restrictive setting wins:
| Server | Client | Result |
|---|---|---|
--read-write (default) |
unset | read-write |
--read-write |
"read-only" |
read-only |
--read-only |
unset | read-only |
--read-only |
"read-write" |
read-only — the flag clamps everything |
So a client marked read-only can never be written to by accident, and a session started
--read-only stays that way no matter what the config file says.
When some administrations are writable and others are not, the write tools stay
registered but their administration enum only offers the writable ones. Asking to
create an invoice in a read-only client is refused before any HTTP request:
Administration(s) bakkerij are configured as read-only, so this tool cannot change them.
Writable: acme, garage.
list_administrations reports the effective mode of each client, and the startup banner
summarises it: read-write: acme, garage.
Configuration
| Variable | Default | Purpose |
|---|---|---|
INFORMER_API_KEY |
— | API key for a single administration. |
INFORMER_SECURITY_CODE |
— | Security code for that administration. |
INFORMER_CONFIG_FILE |
~/.informer-mcp.json |
JSON file listing several administrations. Created by setup if absent. |
INFORMER_ADMINISTRATIONS |
— | The same JSON inline, as an environment variable. Overrides the file per alias. |
INFORMER_ADMINISTRATION_ALIAS |
default |
Alias for the single INFORMER_API_KEY pair. |
INFORMER_ADMINISTRATION_LABEL |
— | Human-readable name for that alias. |
INFORMER_ADMINISTRATION_MODE |
— | read-only or read-write for that alias. |
INFORMER_BASE_URL |
https://api.informer.eu/v2 |
Override the API root. |
INFORMER_READ_ONLY |
false |
true exposes only GET tools, for every administration. Same as --read-only. |
INFORMER_TOOLS |
(all) | Allowlist of tags and/or tool names, comma separated. |
INFORMER_EXCLUDE_TOOLS |
(none) | Denylist, applied after the allowlist. |
INFORMER_TIMEOUT_MS |
30000 |
Per-request timeout. |
INFORMER_MAX_RETRIES |
2 |
Retries for 408/429/5xx and network errors. |
INFORMER_MAX_RESPONSE_CHARS |
100000 |
Longer tool results are truncated with a notice. Split evenly across a fan-out query. |
INFORMER_FANOUT_CONCURRENCY |
4 |
How many administrations a fan-out query hits at the same time. |
INFORMER_AUTO_SETUP |
true |
false stops the setup page from opening when no credentials are configured. |
INFORMER_OPEN_BROWSER |
true |
false prints the setup URL instead of launching a browser. |
INFORMER_SPEC_MAX_AGE_HOURS |
24 |
How old the cached API description may get before a background refresh. 0 disables it. |
INFORMER_SPEC_CACHE |
~/.informer-mcp.spec.json |
Where the downloaded API description is cached. |
INFORMER_SPEC_URL |
Informer's published document | Override the API description to download. |
Filters accept either an OpenAPI tag or a tool name, and are matched case- and punctuation-insensitively:
# read-only access to invoicing data
INFORMER_TOOLS="Sales Invoices,Relations" node dist/index.js --read-only
# everything except deleting attachments
INFORMER_EXCLUDE_TOOLS=delete_sales_invoice_attachment node dist/index.js
Using it
Once connected, ask in plain language:
- "Which sales invoices from 2026 are still unpaid?" →
list_sales_invoiceswithfilter - "Create a draft invoice for ACME for 10 hours of consultancy at €125." →
get_sales_invoice_optionsfor valid ledger/VAT/template ids, thencreate_sales_invoice - "Download invoice 12345 as a PDF to my desktop." →
get_sales_invoice_pdfwithsave_path - "Show the balance sheet for period 6 of 2026." →
get_balance_report
Conventions worth knowing
- Pick the administration explicitly. With several clients configured, every tool
takes
administration: "<alias>".list_administrationsmaps aliases to companies, and read-only tools also accept a list or"all". - Dates are always
YYYY-MM-DD. - List tools are paginated through
page(default 1) andrecords(default 20), and return apaginationobject withtotalandpages. - Request payloads go in a single
bodyargument. Path and query parameters stay at the top level, soupdate_relationtakes{ "id": 42, "body": { ... } }. - Call the
*_optionstool first when creating documents.get_sales_invoice_options,get_quotation_optionsand friends return the valid ledger, VAT, template, currency and payment-condition ids for your administration. - Reports need explicit ranges.
get_balance_reportrequiresyear_from,year_toandperiod;get_column_balance_reportalso wants a ledger range.
PDFs and attachments
Informer returns files as base64 inside JSON. Tools that do this
(get_*_pdf, download_sales_invoice_attachment) take an optional save_path:
- with
save_path— the file is decoded and written to that path, and the tool returns{ saved_to, filename, bytes, mime_type }; - without it — the file comes back as an inline MCP resource with the right MIME type, which large documents can make expensive in context.
Uploading works the other way around: upload_sales_invoice_attachment takes
{ filename, file } where file is base64-encoded content (max 10 MB; PDF, PNG,
JPEG, GIF, DOC(X), XLS(X)).
Tool reference
npm run tools prints this list from the current spec; npm run tools -- --md
regenerates the tables below.
Besides the endpoint tools there are three server-provided ones:
| Tool | What it does |
|---|---|
list_administrations |
Which client administrations are configured, their companies, and which may be written to. |
open_setup |
Opens the local page for adding, changing or removing administrations and their credentials. |
refresh_api_spec |
Re-reads Informer's API description and updates the tools. |
<details> <summary><strong>All 68 endpoint tools, grouped by API area</strong></summary>
Administration
| Tool | Endpoint | Description |
|---|---|---|
get_administration |
GET /administration |
Get administration details |
Relations
| Tool | Endpoint | Description |
|---|---|---|
get_relation |
GET /relations/{id} |
Get a single relation |
update_relation |
PUT /relations/{id} |
Update a relation |
list_relations |
GET /relations |
Get a list of relations |
create_relation |
POST /relations |
Create a new relation |
Contacts
| Tool | Endpoint | Description |
|---|---|---|
get_contact |
GET /contact/{id} |
Get a single contact |
update_contact |
PUT /contact/{id} |
Update a contact |
create_contact |
POST /contact |
Create a new contact |
Sales Invoices
| Tool | Endpoint | Description |
|---|---|---|
get_sales_invoice |
GET /invoices/sales/{id} |
Get a single sales invoice |
update_sales_invoice |
PUT /invoices/sales/{id} |
Update a sales invoice |
list_sales_invoices |
GET /invoices/sales |
Get a list of sales invoices |
create_sales_invoice |
POST /invoices/sales |
Create a new sales invoice |
get_sales_invoice_options |
GET /invoices/sales/options |
Get sales invoice options |
get_sales_invoice_pdf |
GET /invoices/sales/pdf/{id} |
Get sales invoice PDF |
send_sales_invoice |
POST /invoices/sales/send/{id} |
Send a sales invoice |
upload_sales_invoice_attachment |
POST /invoices/sales/{id}/attachments |
Upload an invoice-specific attachment |
download_sales_invoice_attachment |
GET /invoices/sales/{id}/attachments/{attachment_id} |
Download an invoice attachment |
delete_sales_invoice_attachment |
DELETE /invoices/sales/{id}/attachments/{attachment_id} |
Delete an invoice-specific attachment |
Purchase Invoices
| Tool | Endpoint | Description |
|---|---|---|
get_purchase_invoice |
GET /invoices/purchase/{id} |
Get a single purchase invoice |
list_purchase_invoices |
GET /invoices/purchase |
Get a list of purchase invoices |
create_purchase_invoice |
POST /invoices/purchase |
Create a new purchase invoice |
get_purchase_invoice_options |
GET /invoices/purchase/options |
Get purchase invoice options |
get_purchase_invoice_pdf |
GET /invoices/purchase/pdf/{id} |
Get purchase invoice PDF |
Recurring Invoices
| Tool | Endpoint | Description |
|---|---|---|
get_recurring_invoice |
GET /invoices/recurring/{id} |
Get a single recurring invoice |
update_recurring_invoice |
PUT /invoices/recurring/{id} |
Update a recurring invoice |
list_recurring_invoices |
GET /invoices/recurring |
Get a list of recurring invoices |
create_recurring_invoice |
POST /invoices/recurring |
Create a new recurring invoice |
get_recurring_invoice_options |
GET /invoices/recurring/options |
Get recurring invoice options |
Sales Orders
| Tool | Endpoint | Description |
|---|---|---|
get_sales_order |
GET /orders/sales/{id} |
Get a single sales order |
update_sales_order |
PUT /orders/sales/{id} |
Update a sales order |
list_sales_orders |
GET /orders/sales |
Get a list of sales orders |
create_sales_order |
POST /orders/sales |
Create a new sales order |
get_sales_order_options |
GET /orders/sales/options |
Get sales order options |
get_sales_order_pdf |
GET /orders/sales/pdf/{id} |
Get sales order PDF |
send_sales_order |
POST /orders/sales/send/{id} |
Send a sales order |
Quotations
| Tool | Endpoint | Description |
|---|---|---|
get_quotation |
GET /quotations/{id} |
Get a single quotation |
update_quotation |
PUT /quotations/{id} |
Update a quotation |
list_quotations |
GET /quotations |
Get a list of quotations |
create_quotation |
POST /quotations |
Create a new quotation |
get_quotation_options |
GET /quotations/options |
Get quotation options |
get_quotation_pdf |
GET /quotations/pdf/{id} |
Get quotation PDF |
send_quotation |
POST /quotations/send/{id} |
Send a quotation |
Salesbook
| Tool | Endpoint | Description |
|---|---|---|
get_salesbook_invoice |
GET /salesbook/{id} |
Get a single salesbook invoice |
update_salesbook_invoice |
PUT /salesbook/{id} |
Update a salesbook invoice |
list_salesbook_invoices |
GET /salesbook |
Get a list of salesbook invoices |
create_salesbook_invoice |
POST /salesbook |
Create a new salesbook invoice |
get_salesbook_invoice_options |
GET /salesbook/options |
Get salesbook options |
get_salesbook_invoice_pdf |
GET /salesbook/pdf/{id} |
Get salesbook PDF |
Payment Conditions
| Tool | Endpoint | Description |
|---|---|---|
list_payment_conditions |
GET /payment-conditions |
Get all payment conditions |
Templates
| Tool | Endpoint | Description |
|---|---|---|
list_templates |
GET /templates |
Get all templates |
VAT
| Tool | Endpoint | Description |
|---|---|---|
list_vat_options |
GET /vat |
Get all VAT options |
Ledgers
| Tool | Endpoint | Description |
|---|---|---|
list_ledgers |
GET /ledgers |
Get all ledger accounts |
Costs
| Tool | Endpoint | Description |
|---|---|---|
list_cost_centres |
GET /costs |
Get all cost centre accounts |
Currencies
| Tool | Endpoint | Description |
|---|---|---|
list_currencies |
GET /currencies |
Get all currencies |
Journals
| Tool | Endpoint | Description |
|---|---|---|
list_journals |
GET /journals |
Get all journals |
Subscription types
| Tool | Endpoint | Description |
|---|---|---|
list_subscription_types |
GET /subscription-types |
Get all subscription types |
Attachments
| Tool | Endpoint | Description |
|---|---|---|
list_attachments |
GET /attachments |
Get all attachments |
Products
| Tool | Endpoint | Description |
|---|---|---|
list_products |
GET /products |
Get all products |
Receipts
| Tool | Endpoint | Description |
|---|---|---|
get_receipt |
GET /receipts/{id} |
Get a single receipt |
update_receipt |
PUT /receipts/{id} |
Update a receipt |
list_receipts |
GET /receipts |
Get a list of receipts |
create_receipt |
POST /receipts |
Create a new receipt |
Memorandum
| Tool | Endpoint | Description |
|---|---|---|
get_memorandum_entry |
GET /memorandum/{id} |
Get a single memorandum entry |
update_memorandum_entry |
PUT /memorandum/{id} |
Update a memorandum entry |
list_memorandum_entries |
GET /memorandum |
Get a list of memorandum entries |
create_memorandum_entry |
POST /memorandum |
Create a new memorandum entry |
Reports
| Tool | Endpoint | Description |
|---|---|---|
get_balance_report |
GET /reports/balance |
Get balance sheet |
get_column_balance_report |
GET /reports/column-balance |
Get column balance |
</details>
Tool naming
Names are derived from the HTTP method and path, not from prose, so they stay stable across spec updates:
| Pattern | Example |
|---|---|
GET /resources |
list_relations |
GET /resources/{id} |
get_relation |
POST /resources |
create_relation |
PUT /resources/{id} |
update_relation |
GET /resources/options |
get_sales_invoice_options |
GET /resources/pdf/{id} |
get_sales_invoice_pdf |
POST /resources/send/{id} |
send_quotation |
Endpoints the naming table does not recognise fall back to <verb>_<path slug>, so a
spec refresh never produces a broken tool.
Keeping up with API changes
The tools are generated from Informer's OpenAPI document, so when Informer adds an endpoint the only thing missing is a fresh copy of that document. The server can fetch it itself.
Three layers, in order of precedence:
- A downloaded copy, cached at
~/.informer-mcp.spec.json. - The bundled copy in
openapi/api-docs.json, which ships with the server and always works offline. - Neither is ever trusted blindly — a download must parse as an OpenAPI 3 document with at least one usable operation, or it is rejected and the current tools stay. A captive portal or a maintenance page cannot wipe your tool set.
On a schedule
Once a day, shortly after starting, the server checks for a newer document in the
background. Startup is never blocked and a failed check is logged and ignored.
INFORMER_SPEC_MAX_AGE_HOURS=0 turns it off.
On demand
The refresh_api_spec tool does the same thing when you ask for it — useful when an
endpoint you expect is missing, or an argument is rejected as unknown:
"Refresh the Informer API description and tell me what changed."
{
"adopted": true,
"api_version": "2.0.0",
"endpoints": 49,
"tools": 68,
"changes": {
"added": [{ "tool": "list_projects", "endpoint": "GET /projects" }],
"removed": [],
"changed": [{ "tool": "create_sales_invoice", "endpoint": "POST /invoices/sales",
"notes": ["body now requires: project_id"] }],
"unchanged": 66
},
"note": "The tool list has been updated; no restart is needed."
}
Pass dry_run to see that report without applying anything.
The diff is deliberately specific: it names the tools that appeared and disappeared, and
for the ones that changed it says what changed — a new argument, one that is gone, a
field that is now required. That is the part a bare path comparison misses, and it is
usually the part that would otherwise surface as a puzzling 422.
Adopting a document updates the running server: new tools are registered, withdrawn ones
are removed, changed ones are re-advertised, and a tools/list_changed notification goes
out so your client reloads the list mid-session.
The copy in the repository
npm run update-spec updates the bundled document and reports which paths came and
went. That is the one to run when you want the change committed for everyone who installs
the server; refresh_api_spec only affects your own machine.
Resources
The server also exposes the OpenAPI document itself as an MCP resource at
informer://openapi.json, which is handy when you want the model to check a field
definition without guessing.
Development
npm install # install + build
npm run setup # enter credentials in the browser
npm run bundle # package as informer-mcp.mcpb for one-click install
npm run dev # run from source with tsx
npm test # vitest
npm run typecheck # tsc --noEmit
npm run build # compile to dist/
npm run tools # print the tool surface
npm run update-spec # re-download openapi/api-docs.json and report added/removed paths
Project layout
openapi/api-docs.json vendored OpenAPI 3.0 document — the source of truth
src/openapi.ts spec → operations: tool names, JSON Schema conversion
src/client.ts HTTP client: auth headers, retries, error formatting
src/tools.ts operations → MCP tools, filtering, result formatting
src/server.ts server assembly (tools + openapi resource)
src/spec.ts download, validate, cache and diff the OpenAPI document
src/setup.ts local setup server: verify credentials, write the config file
src/setup-page.ts the HTML it serves
src/index.ts stdio entry point and CLI
manifest.json extension manifest: entry point and install-time settings
scripts/update-spec.mjs refresh the vendored spec
scripts/list-tools.ts print/regenerate the tool reference
scripts/bundle.mjs stage production dependencies and pack the .mcpb
Adding endpoints is normally not a code change at all — the running server picks them up
by itself, and npm run update-spec commits the same change to the bundled copy. Only
genuinely new URL shapes need a rule in the RESOURCES table in src/openapi.ts;
without one they still become tools, just with a duller name.
How schemas are converted
OpenAPI 3.0 is not quite JSON Schema. On the way to the MCP tool definition:
#/components/schemas/Xreferences become#/$defs/X, with only the transitive closure each operation actually needs inlined — so tool definitions stay small;nullable: truebecomes a["type", "null"]union;- path and query parameters become top-level properties, request bodies go under
body, andadditionalProperties: falsekeeps typos from reaching the API.
Arguments are validated against that schema before any HTTP call is made.
Safety notes
- This server can create, update and delete real bookkeeping records. Start with
--read-onlyif you only need reporting, pin individual clients with"mode": "read-only", and let your MCP client prompt for approval on write tools. - Credentials for several clients in one process means one misrouted call touches
someone else's books. The required
administrationargument, the enum of known aliases, the read-only restriction on fan-out, and the alias prefix on every error message ([acme] HTTP 422: ...) all exist for that reason. Keep the config file out of version control and readable only by you. - Tools are annotated with
readOnlyHint,destructiveHintandidempotentHint, so clients that use those hints can gate the risky ones. - Nothing is logged to stdout, and credentials are never echoed in tool output or
sent back to the setup page.
open_setupreturns a URL, never a key — an assistant has no way to read your credentials and no reason to ask you for them in a chat. - The API description is downloaded without credentials, and a document that does not parse as a usable OpenAPI 3 file is rejected rather than adopted.
License
MIT — see LICENSE, with one exception.
openapi/api-docs.json is Informer's own API description, retrieved unmodified from
their published document. It is their
work, carries no licence of its own, and is not covered by the MIT License that
applies to the rest of this repository — a licence only grants what the grantor owns.
It ships here so the server works offline and its tool surface is reproducible; at
runtime the server can fetch the current version instead. See NOTICE.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。