Skip to main content
Glama

mcp-henrri

Your Henrri invoicing, answered by asking.

A single Rust binary that puts the whole Henrri v1 API — customers, quotes, invoices, credit notes, payment schedules, deposits, payments, webhooks — in front of any MCP client.

Unofficial Rust MCP Henrri API Transports CI Release License

🇫🇷 Serveur MCP non officiel pour Henrri : clients, devis, factures, avoirs, échéances, acomptes et paiements, pilotés en langage naturel depuis Claude, Cursor ou tout client MCP.

IMPORTANT

This is not an official Henrri product. It is an independent, open-source project, not made, endorsed or supported by Henrri. It covers the published Henrri v1 specification operation by operation, as faithfully as the API allows, and documents where the API behaves differently from its documentation. See Disclaimer.


Ask your assistant "invoice Atelier Martin for three days of development at 600 € excl. tax", "who still owes me money?", "turn the signed quote D-26-11-3 into an invoice" — and get it done in the same Henrri your accountant reads.

> who still owes me money?

  list_payment_milestones   is_overdue = true · 7 milestones
                            ATELIER MARTIN    I-26-10-12   1 440,00 €   23 days late
                            LOVELACE          I-26-10-17     864,00 €    9 days late
                            …
                            overdue total     4 812,40 €

> invoice Atelier Martin for 3 days of development at 600 € excl. tax

  create_document           draft invoice 9632 for ATELIER MARTIN
  add_document_line         3 × Journée de développement · 600,00 € · VAT 20 %
  get_document_with_all     1 800,00 € excl. tax · 360,00 € VAT · 2 160,00 € incl. tax
                            finalize it? It locks the invoice for good and costs 8 credits.

Why

Henrri's API is a clean REST surface, but handed raw to a language model it goes wrong in predictable ways: a PUT erases every field it is not sent, a document is a draft until a call that can never be undone, finalizing costs credits, the monthly quota and the rate limiter both answer 429, and a model cannot tell a document's id from its number.

  • The whole API, nothing hidden. All 87 operations of the v1 specification: 85 routes, one tool each, and the two authentication routes driven by the client itself. A test checks the tool table against spec/operations.tsv, and CI checks that file against the live production and sandbox specifications every day.

  • Updates that update. Every update_* tool takes only what changes, reads the resource, merges, and writes back what Henrri accepts — no more customers losing their comment because their website changed.

  • Fail-closed where it cannot be undone. finalize_document, validate_quotation, the pay tools, cancel_deposit and every delete refuse to run without confirm: true, and the refusal states what would happen: the document, its total, its customer, the credits. HENRRI_READ_ONLY=1 withholds every write tool.

  • Errors a model can act on. Failures come back as tool results with isError: true and Henrri's RFC 9457 problem document: the stable errorCode (ALREADY_FINALIZED, LINES_MISSING, QUOTA_EXCEEDED…), the detail, the invalid fields, and the traceId for Henrri's support, plus a hint when the fix is not obvious.

  • The API's manners, handled once. Ten-minute tokens renewed ahead of expiry; the rate limiter, 503 and busy finalizations retried; the credit quota never retried; a POST never resent after it may have run; list filters sent as repeated keys; periods such as last month turned into Henrri's inclusive dates.

  • Webhooks, verified. Over HTTP the server receives Henrri's deliveries at /webhooks, checks their HMAC-SHA256 signature in constant time, refuses replays, and lets the model read what happened.

  • Written for a model to read. Structured output with schemas on all 89 tools, annotations on every one, four prompts that stop and ask before anything irreversible, and server instructions that say up front: ids not numbers, euros, drafts versus finalized.

Related MCP server: wefact-mcp

Install

Download the archive for your platform from the latest release, check it against its .sha256, and put mcp-henrri on your PATH:

# macOS on Apple silicon; swap the target for x86_64-apple-darwin,
# x86_64-unknown-linux-gnu or aarch64-unknown-linux-gnu
V=v0.1.0 T=aarch64-apple-darwin
curl -LO https://github.com/edouard-claude/henrri-mcp/releases/download/$V/mcp-henrri-$V-$T.tar.gz
tar xzf mcp-henrri-$V-$T.tar.gz
sudo mv mcp-henrri-$V-$T/mcp-henrri /usr/local/bin/

Windows: take the x86_64-pc-windows-msvc.zip archive.

git clone https://github.com/edouard-claude/henrri-mcp
cd henrri-mcp
cargo build --release
# the binary is at target/release/mcp-henrri
cargo install --git https://github.com/edouard-claude/henrri-mcp
docker run --rm -i \
  -e HENRRI_USERNAME -e HENRRI_PASSWORD \
  ghcr.io/edouard-claude/henrri-mcp:latest

The image speaks stdio by default; add --http 0.0.0.0:8080 (with HENRRI_HTTP_TOKEN) to serve HTTP.

Get API access

Henrri gives API access with its developer offer. There are two environments, with separate accounts:

Environment

API root

Credits

Production

https://api.henrri.io

charged on issuance, monthly quota

Sandbox

https://api-sandbox.henrri.io

unlimited, throwaway data

Three ways to authenticate, in this order of precedence:

  1. A developer client — HENRRI_CLIENT_ID and HENRRI_CLIENT_SECRET, exchanged at POST /v1/users/authenticate and renewed through POST /v1/users/refresh-token.

  2. An API user — HENRRI_USERNAME and HENRRI_PASSWORD, the OAuth2 password grant on POST /v1/users/token (this is what the sandbox hands out, e.g. xxxx@sandbox.henrri.net).

  3. A ready-made token — HENRRI_ACCESS_TOKEN, used as is and never renewed (ten minutes).

Then check everything works:

HENRRI_ENVIRONMENT=sandbox HENRRI_USERNAME=… HENRRI_PASSWORD=… mcp-henrri --check
environment  sandbox
base url     https://api-sandbox.henrri.io
credentials  API user (OAuth2 password grant)
toolsets     account, customers, documents, catalog, payments, revenues, webhooks
tools        89
token        ok
hello world  "Hello World"
user         me@sandbox.henrri.net (16079), role admin
company      My company (947), plan discovery
credits      used 100 of unlimited, unlimited left

ready.

Configure

Everything is read from the environment, once, at startup. A missing or malformed variable stops the process with a message naming it.

Variable

Default

HENRRI_USERNAME / HENRRI_PASSWORD

An API user. HENRRI_PASSWORD_FILE for Docker secrets.

HENRRI_CLIENT_ID / HENRRI_CLIENT_SECRET

Or a developer client. HENRRI_CLIENT_SECRET_FILE too.

HENRRI_ACCESS_TOKEN

Or a bearer token, never renewed.

HENRRI_ENVIRONMENT

production

production or sandbox (staging).

HENRRI_BASE_URL

per environment

Override the API root.

HENRRI_TOOLSETS

all

account,customers,documents,catalog,payments,revenues,webhooks

HENRRI_READ_ONLY

0

1 withholds every tool that is not readOnlyHint.

HENRRI_TIMEOUT_SECS

30

Per-request timeout.

HENRRI_PDF_TIMEOUT_SECS

120

Timeout of the PDF routes, which render on demand.

HENRRI_MAX_RETRIES

3

Retries on the rate limiter, 503 and busy finalizations.

HENRRI_DOWNLOAD_DIR

per-user cache

Where PDFs are saved (~/Library/Caches/mcp-henrri/downloads, ~/.cache/…, %LOCALAPPDATA%\…). Created 0700, refused if others can write to it; no shared temp fallback.

HENRRI_WEBHOOK_SECRET

The signing secret from Henrri's webhook settings.

HENRRI_HTTP_BIND

Serve HTTP on this address instead of stdio (same as --http).

HENRRI_HTTP_TOKEN

Bearer token the HTTP transport requires; mandatory off loopback.

HENRRI_LOG

info

Log filter. Logs go to stderr, never to stdout.

Copy .env.example to .env for local work; it is git-ignored.

Connect a client

In claude_desktop_config.json:

{
  "mcpServers": {
    "henrri": {
      "command": "/usr/local/bin/mcp-henrri",
      "env": {
        "HENRRI_USERNAME": "you@example.com",
        "HENRRI_PASSWORD": "…",
        "HENRRI_ENVIRONMENT": "production"
      }
    }
  }
}
claude mcp add henrri \
  -e HENRRI_USERNAME=you@example.com -e HENRRI_PASSWORD=… \
  -- /usr/local/bin/mcp-henrri

.cursor/mcp.json or .vscode/mcp.json:

{
  "servers": {
    "henrri": {
      "type": "stdio",
      "command": "/usr/local/bin/mcp-henrri",
      "env": { "HENRRI_USERNAME": "you@example.com", "HENRRI_PASSWORD": "…" }
    }
  }
}

Tip: a bookkeeper who only reads can run with HENRRI_READ_ONLY=1; an assistant that only chases payments can run with HENRRI_TOOLSETS=customers,payments.

Tools

89 tools in seven toolsets. mcp-henrri --list-tools prints the ones your configuration exposes. R reads, W writes, D is destructive or irreversible and needs confirm: true (so do the two pay tools and validate_quotation).

documents — quotes, invoices, credit notes, lines, PDFs

Tool

Route

list_documents

R

GET /v1/documents — kinds, state, period, search, min_id

list_documents_table

R

GET /v1/documents/with-selected-fields

create_document

W

POST /v1/documents — an empty draft

get_document

R

GET /v1/documents/{id}

get_document_with_all

R

GET /v1/documents/{id}/with-all

get_document_all_included

R

GET /v1/documents/{id}/all-included

update_document

W

PUT /v1/documents/{id} — partial, merged

delete_document

D

DELETE /v1/documents/{id}

finalize_document

D

POST /v1/documents/{id}/finalize — 8 or 2 credits

validate_quotation

W

POST /v1/documents/{id}/validate — 2 credits, needs confirm

transform_quote_to_invoice

W

POST /v1/documents/{id}/transform-to-invoice

get_document_tax_details

R

GET /v1/documents/{id}/tax-details

get_document_display

R

GET /v1/documents/{id}/display

get_payment_terms_display

R

GET /v1/documents/{id}/paymenttermsdisplay

set_payment_terms_display

W

PUT /v1/documents/{id}/paymenttermsdisplay — partial

list_document_payment_milestones

R

GET /v1/documents/{documentId}/paymentmilestones

get_document_pdf

R

GET /v1/documents/{id}/pdf — saved to disk, Factur-X option

create_document_pdf_url

W

POST /v1/documents/{id}/pdf/url

download_document_pdf_file

R

GET /v1/documents/{id}/pdf/files/{guid}

next_quote_batch

W

GET /v1/documents/next-quote-batch — moves a server-side cursor

list_document_types

R

GET /v1/documenttypes

list_document_lines

R

GET /v1/documents/{documentId}/lines

add_document_line

W

POST /v1/documents/{documentId}/lines — kind resolved to the type id

get_document_line

R

GET /v1/documents/{documentId}/lines/{id}

update_document_line

W

PUT /v1/documents/{documentId}/lines/{id} — partial, merged

delete_document_line

D

DELETE /v1/documents/{documentId}/lines/{id}

move_document_line

W

POST /v1/documents/{documentId}/lines/{id}/move

list_document_line_types

R

GET /v1/documentlinetypes

payments — milestones, deposits, payments

Tool

Route

list_payment_milestones

R

GET /v1/paymentmilestones — paid, overdue, document

list_unpaid_milestones_table

R

GET /v1/paymentmilestones/with-selected-fields

create_payment_milestone

W

POST /v1/paymentmilestones

get_payment_milestone

R

GET /v1/paymentmilestones/{id}

update_payment_milestone

W

PUT /v1/paymentmilestones/{id} — partial

delete_payment_milestone

D

DELETE /v1/paymentmilestones/{id}

pay_payment_milestone

W

POST /v1/paymentmilestones/{id}/pay — needs confirm

pay_payment_milestones

W

POST /v1/paymentmilestones/pay — needs confirm

get_milestone_payment

R

GET /v1/paymentmilestones/{id}/payment

create_deposit_request

W

POST /v1/paymentmilestones/{id}/deposit-request

create_deposit_settlement

W

POST /v1/paymentmilestones/{id}/deposit-settlement

cancel_deposit

D

DELETE /v1/paymentmilestones/{id}/deposit

list_payment_modes

R

GET /v1/paymentmodes

list_payments

R

GET /v1/payments

get_payment

R

GET /v1/payments/{id}

list_payment_settled_milestones

R

GET /v1/payments/{id}/milestones

customers — customers and contacts

Tool

Route

list_customers

R

GET /v1/customers

list_customers_table

R

GET /v1/customers/with-selected-fields

list_best_customers

R

GET /v1/customers/best-sales

list_last_used_customers

R

GET /v1/customers/last-used

create_customer

W

POST /v1/customers — contact rules and SIRET/BCE checked first

get_customer

R

GET /v1/customers/{id}

update_customer

W

PUT /v1/customers/{id} — partial, merged

delete_customer

D

DELETE /v1/customers/{id}

get_customer_address

R

GET /v1/customers/{id}/address

list_customer_payment_milestones

R

GET /v1/customers/{id}/paymentmilestones

list_customer_contacts

R

GET /v1/customers/{id}/contacts

add_customer_contact

W

POST /v1/customers/{id}/contacts

get_customer_contact

R

GET /v1/customers/{id}/contacts/{contactId}

update_customer_contact

W

PUT /v1/customers/{id}/contacts/{contactId} — partial

delete_customer_contact

D

DELETE /v1/customers/{id}/contacts/{contactId}

catalog — items, categories, text items, units

Tool

Route

list_items

R

GET /v1/items

list_items_table

R

GET /v1/items/with-selected-fields

list_most_used_items

R

GET /v1/items/most-used

list_best_selling_items

R

GET /v1/items/best-sales

create_item

W

POST /v1/items

get_item

R

GET /v1/items/{id}

update_item

W

PUT /v1/items/{id} — partial, merged

delete_item

D

DELETE /v1/items/{id}

list_item_categories

R

GET /v1/itemcategories

list_text_items

R

GET /v1/textitems

create_text_item

W

POST /v1/textitems

get_text_item

R

GET /v1/textitems/{id}

update_text_item

W

PUT /v1/textitems/{id} — partial

delete_text_item

D

DELETE /v1/textitems/{id}

list_units

R

GET /v1/units

create_unit

W

POST /v1/units

get_unit

R

GET /v1/units/{id}

account, revenues and webhooks

Tool

Route

ping

R

GET /v1/secures/hello-world

whoami

R

GET /v1/users/me

get_user

R

GET /v1/users/{id}

get_user_address

R

GET /v1/users/{id}/address

get_my_company

R

GET /v1/users/companies

get_company

R

GET /v1/companies/{id}

get_company_address

R

GET /v1/companies/{id}/address

get_api_usage

R

the X-Credits-* and X-RateLimit-* headers

get_yearly_revenue

R

GET /v1/revenues/{year}

get_monthly_revenue

R

GET /v1/revenues/{year}/months

list_webhook_event_types

R

the specification's 12 webhook events and the test ping

verify_webhook_signature

R

HMAC-SHA256 of {timestamp}.{rawBody}, constant time

list_received_webhook_events

R

deliveries received at /webhooks (HTTP transport)

POST /v1/users/authenticate and POST /v1/users/refresh-token are not tools: the client calls them itself, as does the OAuth2 token endpoint, POST /v1/users/token.

Resources and prompts

Resource

henrri://me

The user and the company.

henrri://reference

Document types, line types, payment modes, item categories, units.

henrri://usage

Credits used and left, rate limit.

henrri://webhooks/events

The webhook event catalogue.

henrri://webhooks/received

The deliveries received.

henrri://documents/{document_id}

A document with all its elements.

henrri://documents/{document_id}/pdf

A document's PDF, as a blob.

henrri://customers/{customer_id}

A customer with its payment milestones.

henrri://revenues/{year}

A year's revenue, total and monthly.

Prompt

draft_invoice

From "who" and "what" to a reviewed draft, finalized only on a yes.

chase_unpaid

What is overdue, per customer, with reminder messages to send yourself. Read-only.

revenue_report

A year's revenue, month by month, best customers and items. Read-only.

quote_to_invoice

Check the quote, record the signature, transform, review, finalize on a yes.

Henrri, as it actually behaves

Found against the sandbox while building this server, and handled so the model never meets them. The tests in tests/api_contract.rs pin each one.

  • A PUT erases what it is not sent. Updating a customer's website without its comment deletes the comment. Update tools read, merge and write back.

  • Some fields read back as "" and are refused as "". vatNumber, siret… An unchanged empty string is left out of the write.

  • A customer PUT carrying contacts is refused (PUT_WITH_CUSTOMER); a line PUT carrying its nested item, its type or its computed totals is refused too. They are dropped.

  • A body-less POST without Content-Length gets an IIS 411. transform-to-invoice, deposit-request, pdf/url… carry an explicit empty body.

  • A comma-separated list filter is not an error, it is an empty page (the documentation says 400). Lists are sent as repeated keys.

  • The due date is ignored when a milestone is created, and honoured when it is updated: create_payment_milestone sets it in a second call.

  • confirmFirstOfMonth is refused in the finalize body although the FIRST_ACCOUNTING_DOCUMENT_MONTH error asks for it: it is sent as a query parameter.

  • A new unit is answered with id: 0: create_unit finds it by its name.

  • The company-wide milestone lists only show finalized documents, and a finalized document points at a frozen copy of its customer (parentCustomerId): a draft's schedule is read through the document, a customer's through the copy.

  • The city follows the postal code. Henrri rewrites it ("Paris 01"); a city that does not match the code is blanked. Change both together.

  • refresh_token is refused on /v1/users/token; an API user is simply granted again.

  • Two different 429s. The per-minute rate limiter is retried; QUOTA_EXCEEDED (the monthly credits) is not.

  • PDF rendering is slow, and broken on the sandbox at the time of writing (500 or no answer); the PDF routes get their own, longer timeout.

Safety

  • Irreversible calls are fail-closed: without confirm: true nothing leaves the process, and the refusal says what would happen. The server instructions and the prompts tell the model to show the person and get a yes first.

  • HENRRI_READ_ONLY=1 registers no write tool at all: read-only is decided by each tool's readOnlyHint, not by a list that could drift.

  • Secrets never reach a log or a Debug output; logs go to stderr.

  • PDFs are written only inside HENRRI_DOWNLOAD_DIR, under a sanitized file name.

  • Over HTTP, /mcp requires HENRRI_HTTP_TOKEN (compared in constant time) and the server refuses to listen off loopback without one; on loopback, Host is validated against DNS rebinding.

  • Webhook deliveries are accepted only with a valid signature, a timestamp within five minutes, and once per delivery id.

Run it over HTTP

HENRRI_HTTP_TOKEN=$(openssl rand -hex 32) \
HENRRI_WEBHOOK_SECRET=… \
mcp-henrri --http 127.0.0.1:8080
# MCP at http://127.0.0.1:8080/mcp, webhooks at /webhooks, health at /healthz

The Streamable HTTP transport serves many sessions, which share one Henrri token and one webhook inbox. Behind a reverse proxy, bind 0.0.0.0 (a token is then mandatory) and point Henrri's webhook callback at https://<your-host>/webhooks. Henrri sends each delivery once, with no retry: the receiver answers 204 immediately, duplicates included.

Verify

cargo fmt --check
cargo clippy --all-targets -- -D warnings
cargo test                      # unit, contract (fake Henrri), stdio and HTTP protocol tests

Against the real sandbox, with your sandbox credentials in .env (it builds a draft quote and deletes it; nothing is finalized):

set -a; source .env; set +a
HENRRI_SANDBOX_TESTS=1 cargo test --test sandbox

Has the API moved? This prints the live operations, in the format of spec/operations.tsv (CI runs it daily against production and sandbox):

scripts/spec-operations.sh | diff - spec/operations.tsv

Releasing

Bump version in Cargo.toml and server.json, add a CHANGELOG.md section, then push a vX.Y.Z tag. The release workflow checks the three agree, builds binaries for five targets, publishes the multi-arch image to GHCR and the release notes from the changelog.

Contributing

Issues and pull requests are welcome. Keep the rule the code follows: one route, one tool, and a test for every behaviour the real API forces on it. See CONTRIBUTING.md.

Disclaimer

Henrri is a trademark of its owner. This project is unofficial: it is not affiliated with, endorsed by or supported by Henrri. It is built from Henrri's public v1 API specification and tested against Henrri's sandbox; behaviour may change when the API does. Finalizing documents in production has fiscal consequences and costs API credits: you remain responsible for what is issued in your name. Provided under the MIT license, without warranty.

License

MIT © 2026 Edouard Claude

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    B
    maintenance
    Enables natural language interaction with the Zenskar billing platform, covering customers, contracts, invoices, payments, accounting, products, plans, and more.
    100
    40 npm
    1
    MIT
  • A
    license
    B
    quality
    B
    maintenance
    Enables natural language interaction with the WeFact invoicing platform, allowing users to manage debtors, invoices, products, subscriptions, and perform various administrative tasks via MCP-compatible clients.
    18
    1
    AGPL 3.0
  • A
    license
    B
    quality
    D
    maintenance
    MCP server for Invoice Ninja v5 API. Enables AI assistants to manage clients, invoices, quotes, payments, and time tracking through natural language.
    32
    22 npm
    2
    MIT