FakturaXL MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@FakturaXL MCP Serverlist invoices from last month"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
FakturaXL MCP Server
Zdalny serwer MCP dla API FakturaXL, wystawiany pod URL przez Streamable HTTP. Obsługuje legacy Bearer z kluczem FakturaXL oraz opcjonalny OAuth 2.1 dla Cursor Desktop, ChatGPT i Claude.
Domyślnie serwer jest read-only. Wystawianie faktur włącza flaga ALLOW_WRITE=true
(patrz „Zapis: wystawianie faktur”).
Stack
Node.js (>=20) + TypeScript (ESM)
@modelcontextprotocol/sdkv1.x (McpServer+StreamableHTTPServerTransport)Express (endpoint HTTP)
fast-xml-parser(API FakturaXL komunikuje się XML-em)
Related MCP server: InvoiceNinja MCP Server
Instalacja
npm install
cp .env.example .env # Windows: copy .env.example .envUruchomienie
npm run dev # tryb deweloperski (tsx watch)
npm run build # kompilacja do dist/
npm start # uruchomienie z dist/Serwer nasłuchuje na http://localhost:<PORT>/mcp (domyślnie port 3000).
Health check: GET /health.
Konfiguracja (.env)
Zmienna | Domyślnie | Opis |
|
| Port serwera HTTP |
|
| Bazowy URL API |
|
| Timeout wywołań do FakturaXL |
|
|
|
|
| Zapis logów do |
|
| Liczba zaufanych proxy; za pojedynczym Traefikiem ustaw |
|
| Włącza OAuth 2.1 obok legacy Bearer |
| — | Publiczny origin serwera OAuth, np. |
|
| Szyfrowany magazyn klientów, grantów i kluczy FakturaXL |
| — | 32 bajty base64 do AES-256-GCM; wymagane przy OAuth |
| callbacki Cursor | Lista dodatkowych dokładnych callbacków DCR; zaufane callbacki ChatGPT i Claude są wbudowane |
|
| Ważność rozpoczętego formularza OAuth |
|
| Ważność jednorazowego kodu OAuth |
|
| Ważność access tokenu; |
|
| Ważność rotowanego refresh tokenu; |
|
| Limit prób formularza per IP i okno |
|
| Okno limitu prób formularza |
|
| Rejestruje narzędzia zapisu (wystawianie faktur) |
|
| Ważność tokenu podglądu z |
|
| Domyślna liczba wierszy zwracanych z list |
|
| Twardy górny limit zwracanych wierszy |
|
| Maks. zakres dat dla auto-chunkingu (potem przycięcie + ostrzeżenie) |
|
| TTL cache list słownikowych (klienci/produkty/...) |
|
| Rozmiar strony przy auto-paginacji (twardy max API = 500) |
|
| Liczba prób przy kodzie 2 (rate limit) |
|
| Budżet czasu jednego wywołania narzędzia; po nim dane częściowe + kursor |
|
| Obcięcie body w logach (pełne tylko przy błędzie) |
Warstwa praktyczna (agent-friendly)
Serwer nie jest cienkim wrapperem — ukrywa uciążliwości API przed agentem:
Auto-paginacja — pobiera wszystkie strony pod spodem; agent nie widzi
strona/na_stronie.Chunking dat — dowolny zakres jest wewnętrznie dzielony na okna ≤31 dni (limit API) i scalany. Powyżej
MAX_RANGE_MONTHSzakres jest przycinany z ostrzeżeniem.Kursor czasowy — szeroki zakres nie zmieści się w jednym wywołaniu (patrz „Limity API” poniżej), więc narzędzie pracuje do
TOOL_BUDGET_MSi zwraca dane częściowe zkursor_nastepny,ukonczonoipostep. Agent kontynuuje kolejnym wywołaniem z tym kursorem. Zawsze przetwarzane jest co najmniej jedno okno, więc kursor nie może się zapętlić.Natywny throttling — rate limiter respektuje odstępy z dokumentacji (dokumenty 5 s; produkty/klienci/stany 10 s; reszta 1 s) + retry z backoff na kod 2. Kolejka jest per token API, bo limit obowiązuje per konto — agenci różnych kont nie blokują się wzajemnie.
Filtrowanie po kliencie (którego API nie ma) — po nazwie (fuzzy) lub NIP, realizowane po stronie serwera.
Compact + summary +
has_more— listy zwracają skrócone rekordy, zawsze z agregatami (liczba, sumy per waluta, rozkład statusów/rodzajów) i informacją o obcięciu, żeby nie zalewać kontekstu agenta.Cache TTL — listy słownikowe (klienci/produkty/magazyny/działy) są cache'owane. Dokumenty nie są cache'owane, bo mogą być edytowane.
Limity API FakturaXL (zweryfikowane empirycznie)
Te ograniczenia determinują architekturę narzędzi dokumentów, dlatego zostały sprawdzone zapytaniami do produkcyjnego API, nie tylko odczytane z dokumentacji:
Ograniczenie | Wynik testu |
Maks. zakres | 32 dni przechodzi, 35 dni zwraca |
Odstęp między zapytaniami | Wymuszany: 3 s i 4 s → |
Odstęp dla | 2 s (z dokumentacji) — wpisany do rate limitera |
Zasięg odstępu | Cały endpoint, nie pojedyncze zapytanie — różne zakresy dat wysłane co 1 s też dostają |
| Podlegają temu samemu limitowi 31 dni ( |
| Maksymalnie 500; wysłanie 2000 zwraca |
Filtr po kliencie | Nie istnieje w API — konieczne pobranie całego okresu i filtrowanie po naszej stronie |
Konsekwencja: 2 lata dokumentów to ~24 okna × 5 s ≈ 120 s i nie ma sposobu, by to skrócić. Dlatego narzędzia dokumentów zwracają kursor zamiast wisieć do timeoutu klienta (zwykle 60 s).
Autoryzacja
Serwer obsługuje dwie zgodne wstecznie metody.
Legacy Bearer
Klient wysyła klucz FakturaXL bezpośrednio w każdym żądaniu:
Authorization: Bearer <TWOJ_KLUCZ_API_FAKTURAXL>Ta metoda działa zawsze, również po włączeniu OAuth. Klucz nie jest wtedy zapisywany przez serwer. Nieprawidłowy klucz daje błąd auth z API FakturaXL.
OAuth 2.1 dla Cursor Desktop, ChatGPT i Claude
OAuth jest domyślnie wyłączony. Po włączeniu klient MCP wykonuje discovery, Dynamic
Client Registration i PKCE S256, po czym otwiera formularz w przeglądarce. Użytkownik
podaje klucz FakturaXL, serwer sprawdza go odczytowym dokument_lista_dzialow.php,
szyfruje i zapisuje grant. Klient otrzymuje opaque access token; ten token nigdy nie
jest przekazywany do FakturaXL.
Provider akceptuje dokładne callbacki skonfigurowane w OAUTH_ALLOWED_REDIRECT_URIS
oraz oficjalne callbacki zdalnych konektorów:
ChatGPT:
https://chatgpt.com/connector/oauth/{callback_id}z jednym niepustym segmentem identyfikatora oraz starszyhttps://chatgpt.com/connector_platform_oauth_redirect;Claude:
https://claude.ai/api/mcp/auth_callbackihttps://claude.com/api/mcp/auth_callback.
Przykładowa konfiguracja Cursor nie zawiera sekretu:
{
"mcpServers": {
"fakturaxl": {
"url": "https://mcp.example.pl/mcp"
}
}
}Endpointy discovery i protokołu:
/.well-known/oauth-authorization-server/.well-known/oauth-protected-resource/mcp/register,/authorize,/token,/revoke/oauth/authorize/complete— obsługa formularza, nie endpoint publicznego API
Uruchomienie OAuth
Wygeneruj jeden trwały klucz szyfrujący. Jego zmiana uniemożliwi odszyfrowanie istniejącego magazynu:
node -e "console.log(require('node:crypto').randomBytes(32).toString('base64'))"Ustaw:
OAUTH_ENABLED=true
PUBLIC_BASE_URL=https://mcp.example.pl
OAUTH_STORE_KEY=<WYNIK_POLECENIA>PUBLIC_BASE_URL musi być originem bez ścieżki. Produkcja wymaga HTTPS; HTTP jest
akceptowane wyłącznie dla localhost. Access i refresh token są domyślnie bezterminowe;
refresh token nadal jest rotowany przy każdym odświeżeniu. Skradziony token pozostaje
ważny do wywołania /revoke albo usunięcia grantu, dlatego krótsze TTL są bezpieczniejszą
opcją dla publicznych wdrożeń.
Magazyn oauth-store.json jest w całości szyfrowany AES-256-GCM. Kody i tokeny są
dodatkowo zapisywane tylko jako SHA-256. Zapis używa pliku tymczasowego, fsync
i atomowego rename; zły klucz szyfrujący lub uszkodzony plik zatrzymuje start.
Implementacja plikowa obsługuje wielu użytkowników, ale zakłada jeden kontener. Nie skaluj tej wersji poziomo — wiele replik wymaga wspólnego magazynu transakcyjnego.
Dostępne narzędzia (odczyt)
Narzędzie | Opis |
| Faktury dla zakresu dat + filtr klienta/statusu/typu/rodzaju. Compact + summary + |
| Same agregaty (liczba, sumy per waluta, rozkład statusów/rodzajów) — bez wierszy. Też z kursorem; sumy z kolejnych wywołań są addytywne. |
| Pełne dane pojedynczego dokumentu po ID. |
| PDF dokumentu w base64. |
| Wyszukiwanie klienta po nazwie/NIP. |
| Lista klientów (opcjonalny filtr |
| Wyszukiwanie produktu po nazwie/kodzie/kodzie kreskowym. |
| Lista produktów (opcjonalny filtr |
| Ilości produktów w magazynach. |
| Lista magazynów. |
| Lista działów firmy. |
Przykład (agent): „faktury od K8 z ostatniego kwartału” → lista_dokumentow z
data_od/data_do i klient: "K8". Chunking, paginacja, throttling i mapowanie
nazwy klienta na klient_id dzieją się wewnętrznie.
Przy dłuższym zakresie odpowiedź zawiera ukonczono: false i kursor_nastepny.
Agent musi wtedy wywołać narzędzie ponownie z tym samym zakresem i kursor równym
tej wartości, aż ukonczono: true, a wyniki scalić (sumy dodać). Przy TOOL_BUDGET_MS=40000
jedno wywołanie obejmuje ~7 okien, czyli 2 lata to ~4 wywołania.
Świadomie pominięte: korekty, faktury zaliczkowe/końcowe, wysyłka e-mail, KSeF, relacje, tagi, zmiana statusu, usuwanie dokumentów.
Zapis: wystawianie faktur
Włączane flagą ALLOW_WRITE=true. Bez niej narzędzia zapisu nie są rejestrowane,
więc agent ich nie widzi — domyślne wdrożenie pozostaje read-only. GET /health
raportuje tryb (read-only / read-write).
Zakres: Faktura VAT (typ_faktury=0), przychodowa.
Narzędzie | Opis |
| Krok 1: waliduje dane, liczy kwoty, zwraca podgląd + |
| Krok 2: wystawia dokument przygotowany w kroku 1. Wymaga |
Dlaczego dwa kroki: wystawienie faktury jest nieodwracalne (zużywa numer z ciągłej numeracji), a MCP nie ma UI z przyciskiem potwierdzenia. Bariera siedzi więc w protokole — agent musi najpierw pokazać użytkownikowi podgląd z kwotami.
Token daje przy okazji idempotencję, której API FakturaXL nie ma: klient MCP
potrafi powtórzyć wywołanie po timeoucie, a zużyty draft zwraca pierwotny dokument
zamiast wystawiać drugi. Drafty żyją w pamięci procesu (TTL DRAFT_TTL_MS),
więc restart unieważnia te niepotwierdzone.
Kwoty w podglądzie liczymy lokalnie w groszach (float gubi grosze na sumach
pozycji), wyłącznie po to, żeby użytkownik zobaczył wartość przed wystawieniem.
Ostateczne kwoty nalicza FakturaXL — w teście E2E obie liczby były identyczne
(230.00 / 52.90 / 282.90).
Pułapki API przy zapisie (zweryfikowane sondą)
Ustalenie | Konsekwencja |
Dokument bez kraju nabywcy, dat, waluty i rodzaju płatności zwraca kody 11, 13, 15, 18 | Wszystkie te pola mają wartości domyślne (dziś, |
Odpowiedź zawiera wiele tagów |
|
| Dane nabywcy trzeba przepisać z |
API samo dopasowuje klienta po NIP i wpisuje | Duplikaty klientów nie powstają |
| Różne nazwy pól na wejściu i wyjściu |
Encja | CDATA jest zbędne, escapowanie |
Każda pozycja zakłada nowy produkt w katalogu ( | Wystawianie faktur zaśmieca listę produktów |
| 2 × 100 netto − 10% = netto 180.00, VAT 41.40, brutto 221.40 |
Logowanie
Logi są w formacie JSON (jedna linia = jedno zdarzenie). Token API jest zawsze
maskowany (widoczne tylko ostatnie 4 znaki). Trafiają na konsolę oraz — gdy
LOG_TO_FILE=true — do katalogu logs/.
Rejestrowane zdarzenia:
Zdarzenie | Kiedy i co zawiera |
| Każde żądanie HTTP: metoda JSON-RPC, nazwa narzędzia, czas trwania |
| Wywołanie narzędzia: argumenty, czas trwania, liczba zapytań do API, liczba rekordów, czy zwrócono kursor |
| Klient rozłączył się przed końcem odpowiedzi — tak objawia się timeout agenta |
| Błąd narzędzia lub transportu z czasem trwania |
| Raw request/response do FakturaXL (URL, nagłówki, body, status, czas) |
| Błędy komunikacji — z pełnym, nieobciętym body |
Body request/response jest obcinane do LOG_BODY_MAX_CHARS (odpowiedzi API sięgają
setek KB). Pełne body jest logowane tylko przy błędach. Kontenery mają ustawioną
rotację logów Dockera (max-size: 10m, max-file: 3).
Struktura
src/
index.ts # inicjalizacja OAuth i start procesu HTTP
app.ts # Express + Streamable HTTP + dual auth + logi żądań/rozłączeń
server.ts # buildServer(apiToken) - instancja McpServer per request
config.ts # konfiguracja z .env
logger.ts # logger JSON + maskowanie sekretów + obcinanie body
callContext.ts # kontekst wywołania narzędzia (licznik zapytań do API, czas)
auth/
runtime.ts # router OAuth SDK, metadata i trwały runtime
provider.ts # DCR, authorize, token, refresh, revoke i formularz
store.ts # szyfrowany, atomowy magazyn JSON
resolveCredential.ts # OAuth access token lub legacy klucz FakturaXL
validateApiToken.ts # odczytowa walidacja klucza przed wydaniem grantu
ui.ts # responsywny formularz autoryzacji
tools/runner.ts # wspólny wrapper narzędzia (kontekst pomiarowy + logi)
tools/register.ts # rejestracja narzędzi odczytu
tools/registerWrite.ts # rejestracja narzędzi zapisu (tylko przy ALLOW_WRITE)
fakturaxl/invoice.ts # schema faktury VAT, wyliczenia podglądu, mapowanie na XML
fakturaxl/drafts.ts # drafty faktur (dwa kroki + idempotencja)
fakturaxl/client.ts # XML->POST->JSON, mapowanie błędów, rate limiter + retry
fakturaxl/rateLimiter.ts # throttling per (token + endpoint) wg odstępów z dokumentacji
fakturaxl/pagination.ts # auto-paginacja (fetchAllPages)
fakturaxl/dates.ts # chunking zakresu dat na okna <=31 dni + cap 24 mies.
fakturaxl/cache.ts # cache TTL w pamięci (tylko listy słownikowe)
fakturaxl/service.ts # getDocuments z budżetem czasu i kursorem, findClients/Products
fakturaxl/compact.ts # compact mappery + agregacja (summary)
fakturaxl/codes.ts # mapa kodów zwracanych przez API
docs/
api-fakturaxl.md # dokumentacja API FakturaXL
diagram.md # architektura dual auth i przepływ sekretów
etap 1/backlog.md # wykonanie wystawiania faktur
etap 2/backlog.md # wykonanie OAuth 2.1
.github/workflows/
ci-cd.yml # build + push obrazu do GHCR i deploy przez SSH
Dockerfile # multi-stage: build / runtime (prod) / dev
docker-compose.dev.yml # lokalny dev (hot-reload, bez Traefika)
docker-compose.example.yml # szablon compose produkcyjnego (Traefik) do skopiowaniaDocker
Legacy Bearer pozostaje bezstanowy. Po włączeniu OAuth stan trafia do
./data/oauth-store.json, montowanego jako /app/data/oauth-store.json.
Katalog nie trafia do obrazu ani repozytorium.
Pliki:
Dockerfile— multi-stage (build→runtimeprodukcyjny, orazdevz hot-reload). Obraz produkcyjny nanode:22-alpine, uruchamiany jako usernode, z healthcheckiem/health.docker-compose.dev.yml— lokalny development, port3000:3000i bind mount./data.docker-compose.example.yml— produkcja za Traefikiem z TLS i bind mount./data.
Dev (lokalnie)
mkdir data
docker compose -f docker-compose.dev.yml up --build
# MCP: http://localhost:3000/mcp health: http://localhost:3000/healthNa Windowsie uprawnienia bind mountu obsługuje Docker Desktop. Na natywnym Linuksie
katalog musi należeć do UID/GID 1000, bo proces działa jako user node.
Serwer (produkcja, Traefik)
Wymaga istniejącej zewnętrznej sieci traefik-net. Na serwerze, w katalogu aplikacji
(u nas /home/user/myapps/fakturaxl-mcp-server/), tworzymy docker-compose.yml na
bazie szablonu, przygotowujemy katalog danych i ustawiamy .env:
cp docker-compose.example.yml docker-compose.yml
mkdir -p data
sudo chown 1000:1000 data
chmod 700 data
# .env: MCP_DOMAIN, TRAEFIK_CERTRESOLVER, OAUTH_ENABLED i OAUTH_STORE_KEY
docker compose up -dPlik jest tworzony z trybem 600. Backupuj data/oauth-store.json razem z bezpiecznie
przechowywanym OAUTH_STORE_KEY; sam plik bez klucza jest nieczytelny. Nie używaj
docker compose down -v jako procedury sprzątania danych i nie uruchamiaj drugiej repliki.
Router nasłuchuje na wskazanej domenie (entrypoint websecure, TLS przez
certresolver Traefika), kierując ruch na port 3000 kontenera.
W mcp.json użyj samego URL dla OAuth albo dodaj nagłówek
Authorization: Bearer <klucz> dla trybu legacy.
Test lokalny (MCP Inspector)
npx @modelcontextprotocol/inspectorW Inspectorze: transport Streamable HTTP, URL http://localhost:3000/mcp,
nagłówek Authorization: Bearer <klucz>.
Test OAuth w Cursor Desktop: włącz OAuth z PUBLIC_BASE_URL=http://localhost:3000,
zostaw w konfiguracji MCP wyłącznie url, uruchom ponownie Cursor i wybierz połączenie.
Przeglądarka powinna otworzyć formularz, a callback wrócić na
http://localhost:8787/callback. W ChatGPT i Claude dodaj zdalny konektor wskazujący
na publiczny URL /mcp; ich callbacki są rozpoznawane automatycznie.
Wdrożenie produkcyjne (CI/CD)
Wdrożenie jest automatyczne: każdy push do main uruchamia
.github/workflows/ci-cd.yml, który buduje obraz, wypycha go do GHCR i wchodzi na
serwer przez SSH, żeby zrobić docker compose pull && up -d. Ręcznie można go odpalić
przez workflow_dispatch (gh workflow run "Build & Deploy").
Jak to działa:
Build i push — obraz
ghcr.io/socialbuzzstudio/fakturaxl-mcp-serverw tagachlatesti wersji zpackage.json. Build używatarget: runtime— to obowiązkowe, bo ostatnim etapemDockerfilejestdevz hot-reloadem. Kompilacja TypeScript dzieje się w etapie build, więc błąd typów zatrzymuje CI. Publikacja idzie wbudowanymGITHUB_TOKEN(job mapackages: write) — żadnych własnych PAT-ów.Deploy —
appleboy/ssh-actionloguje się na serwer kluczem z sekretu, robidocker login ghcr.iotokenem ważnym tylko na czas tego przebiegu, pobiera obraz, restartuje kontener i się wylogowuje. Serwer nie przechowuje żadnych poświadczeń do rejestru.
Paczka w GHCR jest prywatna, mimo że repozytorium jest publiczne — w GHCR widoczność paczki jest niezależna od widoczności repo i domyślnie ustawiona na prywatną. Paczka dziedziczy uprawnienia dostępu z repo, dzięki czemu job deployu ma do niej dostęp bez dodatkowej konfiguracji. Uwaga: zmiana paczki na publiczną jest nieodwracalna.
Wymagane sekrety w repozytorium (ustawiane przez gh secret set, nie klikane w przeglądarce):
Sekret | Zawartość |
| IP serwera |
| użytkownik SSH używany do deployu |
| klucz prywatny osobnej pary wygenerowanej tylko pod CI/CD |
Klucz prywatny podaje się przez potok (Get-Content -Raw ... | gh secret set ...), a nie
przez --body — musi wejść w całości, z liniami BEGIN/END, inaczej deploy padnie na
„invalid format".
Bez Dockera (wdrożenie ręczne): npm ci && npm run build && npm start za reverse proxy
(nginx/Caddy/Traefik) z TLS, kierującym /mcp na port aplikacji.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- FlicenseCqualityBmaintenanceExposes read-only Aruba Fatturazione Elettronica API operations for managing electronic invoices, notifications, and providing fiscal document helpers.701
- FlicenseNot gradedqualityDmaintenanceEnables read-only access to InvoiceNinja data, including invoices, expenses, clients, and tax reports, for AI assistants like Claude.1
- AlicenseNot gradedqualityCmaintenanceEnables interaction with the sevDesk accounting API for managing contacts, invoices, credit notes, orders, vouchers, transactions, and parts.321MIT
- AlicenseNot gradedqualityDmaintenanceProvides read-only access to Xledger accounting data via GraphQL API for querying invoices, balances, projects, timesheets, and more.2MIT
Related MCP Connectors
Read-only NuMetric.work accounting & ERP data: statements, KPIs, reports, invoices, documents.
Create, validate, convert & extract compliant e-invoices (UBL, Factur-X, ZUGFeRD, XRechnung)
Bling ERP (SMB and e-commerce management, by Locaweb) via the official v3 API, sales orders, product
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/SocialBuzzStudio/fakturaxl-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server