Skip to main content
Glama
firma-mcp

firma-mcp

firma-mcp

A read-only Model Context Protocol server that lets an AI assistant look into a small Polish company's back office: invoices in KSeF, bookkeeping in Merit Aktiva, the company mailbox, and (experimentally) Fakturownia.

You run it on your own server, give it your own credentials, and add it to Claude or ChatGPT as a remote connector. One installation serves one company.

Polish version: README.pl.md

What it is for

It was built for a daily "morning brief": an assistant reads the new mail, the invoices that arrived in KSeF, and what is booked and paid in the accounting system, then reports what needs attention. Every tool only reads. Nothing here can send a message, issue an invoice, or change or delete anything in the connected systems.

Related MCP server: protonmail-mcp-server

Supported systems

System

Module

Status

What that means

KSeF 2.0 (Krajowy System e-Faktur)

ksef

Production

Runs daily against the production KSeF API in the author's company.

Merit Aktiva / 360 Księgowość

merit

Production

Runs daily against the Polish endpoint. The Estonian and Finnish endpoints are untested.

Mail (IMAP)

mail

Production

Runs daily against one IMAP server (TLS, port 993, app passwords). Other servers are untested.

Fakturownia

fakturownia

Experimental, off by default

Written from the public API documentation. Tested only against stubbed responses, never on a live account.

"Production" describes one company's installation. It is not a claim about anyone else's.

A module switches on only when all of its settings are present. The rest of the server works without it.

Fakturownia is the exception: it is off by default. Entering its credentials does not start it. You have to opt in with a separate setting, FAKTUROWNIA_ENABLED=true, because the module has never run against a live account. With the switch on and credentials missing, the server refuses to start.

Tools

Tool

Module

What it returns

mail_list

mail

Recent messages in a mailbox: sender, subject, date, preview, attachment names

mail_get

mail

Full text of one message

mail_attachment

mail

Text of a PDF, DOCX or TXT attachment, or raw bytes as base64

merit_purchases

merit

Booked purchase invoices, optionally only unpaid

merit_sales

merit

Issued sales invoices, optionally only unpaid

merit_invoice

merit

Details of one invoice, optionally with its PDF

merit_payments

merit

Recorded incoming and outgoing payments

payment_check

merit

Whether a payment of a given amount has already been made

ksef_received

ksef

Invoices received in KSeF; with Merit enabled, also whether each is already booked

ksef_invoice

ksef

XML of one KSeF invoice

booking_suggestions

ksef + merit

For unbooked KSeF invoices: the ledger account and VAT scheme used before for the same seller

fakturownia_sales

fakturownia

Sales invoices (experimental)

fakturownia_purchases

fakturownia

Cost invoices (experimental)

fakturownia_invoice

fakturownia

Details of one invoice, optionally with its PDF (experimental)

fakturownia_payment_status

fakturownia

Paid, remaining and overdue state of one invoice (experimental)

All tools carry the MCP annotation readOnlyHint: true. Mail is opened with IMAP EXAMINE and fetched without marking anything as read.

Tool descriptions and error messages are in English. A few returned values are in Polish, because they describe Polish bookkeeping and existing clients rely on them: payment directions (wpływ, wypływ, kompensata, nieznany) and the note for a seller without history (nowy kontrahent — brak historii). Data from the connected systems is passed through in whatever language it was written.

Run it with Docker Compose

You need a machine with Docker, a domain name pointing at it, and ports 80 and 443 open.

git clone https://github.com/zacharm-ewsoft/firma-mcp.git
cd firma-mcp
cp .env.example .env
chmod 600 .env
# edit .env: see "Configuration" below
docker compose up -d
docker compose logs -f firma-mcp

The Compose file starts two containers: the server, and Caddy, which obtains and renews a Let's Encrypt certificate for FIRMA_DOMAIN. The server is not published to the host; only Caddy is.

On a correct start the log shows which modules are enabled:

INFO firma_mcp module enabled: ksef (production)
INFO firma_mcp starting: auth=github modules=ksef url=https://mcp.example.com/mcp

If the configuration is incomplete the server prints what is missing and exits with code 2.

Prefer no Docker? deploy/ has an example nginx site and systemd unit. That is how the author's own installation runs.

Configuration

Everything is set through environment variables. .env.example lists every variable with a note on where to get its value. In short:

  1. Server address: FIRMA_BASE_URL (for example https://mcp.example.com) and FIRMA_DOMAIN (the same host, used by Caddy).

  2. Authentication (required, see below).

  3. Modules: fill in the sections for the systems you use and leave the others empty. For the experimental Fakturownia module also set FAKTUROWNIA_ENABLED=true.

Authentication

There is no unauthenticated mode. Without valid authentication settings the server refuses to start.

GitHub sign-in (default, needed for claude.ai and ChatGPT). Both assistants connect to remote MCP servers with OAuth, so the server needs an identity provider. It uses a GitHub OAuth app that you create yourself:

  1. On GitHub open Settings > Developer settings > OAuth Apps > New OAuth App.

  2. Set Homepage URL to your FIRMA_BASE_URL and Authorization callback URL to <FIRMA_BASE_URL>/auth/callback.

  3. Copy the client ID and a new client secret into GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET.

  4. Put your GitHub user name in FIRMA_ALLOWED_GITHUB_LOGINS (several names are separated with commas). Anybody with a GitHub account can complete the sign-in; this allowlist, checked on every tool call, is what limits the server to you. An account that is not listed is refused, and the server does not start with an empty list. The sign-in asks GitHub only for the read:user scope.

  5. Generate FIRMA_JWT_SIGNING_KEY with openssl rand -hex 32.

Shared token (FIRMA_AUTH_MODE=token), optional. One long secret sent as Authorization: Bearer <token>. It is meant for testing and for your own scripts, not for the assistants: connect Claude and ChatGPT with the GitHub sign-in above. ChatGPT cannot use a token at all, and claude.ai supports request headers only as a limited beta. The server rejects a token shorter than 32 characters at start-up.

Connect to Claude

Steps follow Anthropic's documentation (Add a connector that isn't in the directory), read in November 2026.

  1. In Claude open Customize > Connectors. On Team and Enterprise plans an Owner adds the connector under Organization settings > Connectors first.

  2. Select Add, then Add custom connector.

  3. Enter a name and the URL https://<your domain>/mcp, then Continue.

  4. Keep the authentication settings Claude marks as Detected, and click Add.

  5. Click Connect and sign in with the GitHub account you put on the allowlist.

  6. In a conversation, use the + button and Connectors to turn the connector on.

Claude Code can also connect with the shared token (for testing):

claude mcp add --transport http firma https://<your domain>/mcp \
  --header "Authorization: Bearer <FIRMA_AUTH_TOKEN>"

Connect to ChatGPT

Steps follow OpenAI's documentation (Connect and test your plugin), read in November 2026. Your plan and workspace settings decide whether you may add custom MCP servers.

  1. Open chatgpt.com/plugins.

  2. Select the plus button, then Add custom MCP server.

  3. Enter a name and a description.

  4. Under Connection enter https://<your domain>/mcp.

  5. Configure authentication (OAuth), review the risk warning and select I understand and want to continue.

  6. Select Create as a plugin, review the discovered tools, and sign in with GitHub when asked.

  7. In a new conversation type @ and pick the plugin.

OpenAI's documentation states that custom MCP servers do not need search and fetch tools, so this server does not add them. They are only required for a server to act as a "company knowledge" source.

What has actually been tested

Current state (13 November 2026). This code was reorganised from a private installation into the modular form you see here. After that reorganisation:

  • Checked on live systems: on a test installation, ksef_received (7 days), merit_sales and merit_purchases (31 days) and mail_list (1 day) returned the same results as the original installation for the same parameters. These calls used the shared-token mode, locally on the server. The same installation served HTTPS through nginx with a Let's Encrypt certificate.

  • Not yet checked live: the GitHub sign-in from start to finish (including the read:user scope), the refusal of an account that is not on the allowlist during a real sign-in (covered by unit tests only), and connecting to Claude and to ChatGPT.

The live check ran on FastMCP 4.1.8, which is the version pinned in pyproject.toml.

Stated separately on purpose.

Automated tests (run in CI on every push). 239 unit tests. KSeF, Merit and Fakturownia are tested against stubbed HTTP responses and mail against a fake IMAP mailbox. All test data is made up.

MCP Inspector. The official MCP Inspector (version 2.15.0, CLI mode) was run against a local instance in token mode: it listed all 15 tools with their read-only annotations and called a tool successfully. Without a token it was refused.

OAuth surface, locally. A local instance in GitHub mode was probed with plain HTTP requests. An unauthenticated request gets 401 with a WWW-Authenticate header pointing at the protected resource metadata. The authorization server metadata advertises PKCE S256, dynamic client registration and client ID metadata documents. Registration succeeds for the redirect URIs of Claude (https://claude.ai/api/mcp/auth_callback) and ChatGPT (https://chatgpt.com/connector_platform_oauth_redirect, https://chatgpt.com/connector/oauth/...) and is rejected for other hosts. This used a dummy GitHub client, so the round trip through GitHub itself was not part of this test.

Claude. The private installation this project was extracted from is used every day as a custom connector in claude.ai, with the same GitHub sign-in component and the same KSeF, Merit and mail logic. The reorganised code in this repository has not itself been connected to claude.ai yet.

ChatGPT. Not tested with a live ChatGPT account. Support rests on OpenAI's documented requirements (Streamable HTTP at /mcp, OAuth 2.1 with PKCE and client registration, read-only annotations) and on the local OAuth test above.

Docker Compose. The image builds and the two-container stack was started locally: Caddy served HTTPS with a local certificate and proxied to the server. Obtaining a real Let's Encrypt certificate on a public domain was not tested.

Not tested. Fakturownia against a live account. Merit outside Poland. IMAP servers other than the one in use. Running with many mailboxes or very large accounts.

Security notes

  • Read-only by construction. The Merit facade exposes only read calls, the Fakturownia client only sends GET, and tests fail if a write call is added to either. The upstream credentials themselves may still allow writing, so store them carefully and prefer restricted credentials where the system offers them (KSeF does: grant the token only the permission to browse invoices).

  • Authentication is mandatory and the server fails closed on incomplete configuration.

  • Secrets live in .env only. Keep the file out of version control (.gitignore does this) and readable only by the service user.

  • What the assistant reads, its provider sees. Mail and invoice contents returned by a tool are sent to Anthropic or OpenAI as part of the conversation. Decide whether that is acceptable for your data before connecting a mailbox.

  • Prompt injection. An e-mail or an invoice can contain text written to manipulate an assistant. Because this server cannot write anywhere, such text cannot make it act, but it can still mislead a summary. Treat the assistant's reading of untrusted mail accordingly.

  • Logs contain tool names, the caller and timings, not tool arguments or results. An unexpected error is logged with its traceback.

  • Rate limiting. The nginx example includes a request limit. The Caddy setup does not.

  • The repository's history is scanned for secrets with gitleaks in CI.

To report a vulnerability see SECURITY.md.

Need a different system?

Adding a system means writing one module; docs/adding-a-module.md walks through it using the Fakturownia module as the example.

We also build integrations to order, for example wFirma, inFakt, iFirma or a bank feed. Contact: biuro@infortel.pl

Development

python3.12 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest
ruff check . && ruff format --check .

Layout:

src/firma_mcp/
  server.py      builds the server from the enabled modules
  config.py      core settings (address, authentication)
  auth.py        GitHub OAuth and shared-token modes
  toolkit.py     the wrapper every tool call goes through
  modules/       one package per external system

Licence

MIT

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      B
      quality
      D
      maintenance
      A read-only MCP server for SimpleShop accounting data, enabling AI agents to find documents, download PDFs, inspect products, and review sales exports.
      5
      MIT
    • A
      license
      A
      quality
      D
      maintenance
      A read-only MCP server that connects to Proton Mail via Proton Bridge, enabling AI assistants to search, list, and read emails securely without leaving your machine.
      4
      42 npm
      1
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      Read-only MCP server for IMAP email access, enabling AI agents to read, search, and monitor email without sending or deleting messages.
      27 npm
      MIT
    • F
      license
      A
      quality
      D
      maintenance
      Read-only MCP server for accessing personal finance data from Ogarni.AI, including receipts, categories, summaries, notifications, and more.
      20
      -