firma-mcp
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
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) |
| Production | Runs daily against the production KSeF API in the author's company. |
Merit Aktiva / 360 Księgowość |
| Production | Runs daily against the Polish endpoint. The Estonian and Finnish endpoints are untested. |
Mail (IMAP) |
| Production | Runs daily against one IMAP server (TLS, port 993, app passwords). Other servers are untested. |
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 |
| Recent messages in a mailbox: sender, subject, date, preview, attachment names | |
| Full text of one message | |
| Text of a PDF, DOCX or TXT attachment, or raw bytes as base64 | |
| merit | Booked purchase invoices, optionally only unpaid |
| merit | Issued sales invoices, optionally only unpaid |
| merit | Details of one invoice, optionally with its PDF |
| merit | Recorded incoming and outgoing payments |
| merit | Whether a payment of a given amount has already been made |
| ksef | Invoices received in KSeF; with Merit enabled, also whether each is already booked |
| ksef | XML of one KSeF invoice |
| ksef + merit | For unbooked KSeF invoices: the ledger account and VAT scheme used before for the same seller |
| fakturownia | Sales invoices (experimental) |
| fakturownia | Cost invoices (experimental) |
| fakturownia | Details of one invoice, optionally with its PDF (experimental) |
| 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-mcpThe 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/mcpIf 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:
Server address:
FIRMA_BASE_URL(for examplehttps://mcp.example.com) andFIRMA_DOMAIN(the same host, used by Caddy).Authentication (required, see below).
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:
On GitHub open Settings > Developer settings > OAuth Apps > New OAuth App.
Set Homepage URL to your
FIRMA_BASE_URLand Authorization callback URL to<FIRMA_BASE_URL>/auth/callback.Copy the client ID and a new client secret into
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET.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 theread:userscope.Generate
FIRMA_JWT_SIGNING_KEYwithopenssl 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.
In Claude open Customize > Connectors. On Team and Enterprise plans an Owner adds the connector under Organization settings > Connectors first.
Select Add, then Add custom connector.
Enter a name and the URL
https://<your domain>/mcp, then Continue.Keep the authentication settings Claude marks as Detected, and click Add.
Click Connect and sign in with the GitHub account you put on the allowlist.
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.
Open chatgpt.com/plugins.
Select the plus button, then Add custom MCP server.
Enter a name and a description.
Under Connection enter
https://<your domain>/mcp.Configure authentication (OAuth), review the risk warning and select I understand and want to continue.
Select Create as a plugin, review the discovered tools, and sign in with GitHub when asked.
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_salesandmerit_purchases(31 days) andmail_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:userscope), 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
.envonly. Keep the file out of version control (.gitignoredoes 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 systemLicence
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
Related MCP Connectors
Read-only MCP server for ClassQuill, a tutoring-business-management platform.
Read-only finance MCP server. Connects your banks, cards, payment apps, books and payroll into one ledger for your AI. Can't move money. Open source (AGPL-3.0), free during beta.
Read-only MCP server for Point (Tetra M Labs): demo ledger, trial balance, partners.
Read-only MCP server for interior design studios: projects, overviews, weekly activity. No writes.
Related MCP Servers
- AlicenseBqualityDmaintenanceA read-only MCP server for SimpleShop accounting data, enabling AI agents to find documents, download PDFs, inspect products, and review sales exports.5MIT
- AlicenseAqualityDmaintenanceA 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.442 npm1MIT
- AlicenseNot gradedqualityDmaintenanceRead-only MCP server for IMAP email access, enabling AI agents to read, search, and monitor email without sending or deleting messages.27 npmMIT
- FlicenseAqualityDmaintenanceRead-only MCP server for accessing personal finance data from Ogarni.AI, including receipts, categories, summaries, notifications, and more.20-