smartbill-mcp

smartbill-mcp

An MCP server for the SmartBill Cloud API. It lets an MCP client issue and manage Romanian invoices, proformas and payments, download document PDFs, and read VAT rates, series and stock levels.

Category
Visit Server

README

smartbill-mcp

An MCP server for the SmartBill Cloud API. It lets an MCP client issue and manage Romanian invoices, proformas and payments, download document PDFs, and read VAT rates, series and stock levels.

It runs two ways:

  • stdio, single account, on your machine — credentials come from the environment.
  • HTTP, multi-tenant, hosted — credentials arrive with each request, so one deployment serves any number of SmartBill accounts.

Credentials

You need a SmartBill Cloud account with API access. The token is generated in Contul meu → Integrari → API; the username is the email you log in with. A SmartBill token is a bearer credential with full account access and no scoping — anything holding it can issue and delete real fiscal documents.

Running over stdio

npm install
npm run build
cp .env.example .env   # then fill it in
Variable Required Description
SMARTBILL_USERNAME yes Account email.
SMARTBILL_TOKEN yes API token from SmartBill Cloud.
SMARTBILL_VAT_CODE yes Your company CIF, used as the default cif on every call.
SMARTBILL_INVOICE_SERIES no Default invoice series, e.g. FF.
SMARTBILL_ESTIMATE_SERIES no Default proforma series.
SMARTBILL_RECEIPT_SERIES no Default receipt (chitanta) series.
SMARTBILL_DOWNLOAD_DIR no Where PDFs are written. Default ./smartbill-downloads.
SMARTBILL_BASE_URL no Override the API base URL.

The series defaults are optional but convenient: with them set, tools can be called with just a document number. Without them, pass seriesName explicitly.

claude mcp add smartbill \
  --env SMARTBILL_USERNAME=you@example.com \
  --env SMARTBILL_TOKEN=your-api-token \
  --env SMARTBILL_VAT_CODE=RO12345678 \
  --env SMARTBILL_INVOICE_SERIES=FF \
  -- node /absolute/path/to/smartbill-mcp/dist/index.js

The token is read once at startup, turned into an Authorization: Basic header inside the HTTP client, and never enters the model's context — no tool takes a credential argument, so it cannot surface in a tool result.

Running over HTTP (multi-tenant)

npm run build
npm run start:http     # or: node dist/index.js --http
Variable Default Description
MCP_TRANSPORT Set to http instead of passing --http.
PORT 80 Port to listen on.
HOST 0.0.0.0 Interface to bind.
MCP_PATH /mcp Base path the endpoint is mounted at.
MCP_ALLOWED_HOSTS Comma-separated Host values to accept (DNS rebinding protection). Unset accepts any.

No SMARTBILL_* secrets are read in this mode — the server holds none. GET /health answers without credentials, for platform health checks:

{ "status": "ok", "version": "0.1.0", "commit": "9f2c1ab..." }

commit is the git SHA the image was built from, baked in via the BUILD_SHA build argument ("dev" outside a built image). It exists because a redeploy leaves the outgoing container serving: a check that only asks for a 200 is answered by the container being replaced. The deploy workflow waits for commit to equal the SHA it just built, so it tests the new container rather than racing it.

The setup page

Opening the deployment's root in a browser serves a self-service page that explains what the server does, points at the SmartBill integrations page for the three values it needs, and generates the connector URL.

The page is entirely self-contained — no external scripts, styles or fonts, no fetch, no <form>. The URL is assembled in the browser from what the user types, so the token never reaches the server until it is used for a real MCP call. test/http.test.ts asserts that, so a future edit can't quietly add a third-party subresource that could observe the field.

Non-browser callers (Accept without text/html) still get JSON at the root, so health checks and monitoring are unaffected.

Credentials in the URL

Each caller's credentials travel as one base64url segment of username:token:cif:

https://smartbill.example.com/mcp/<credentials>?invoiceSeries=FF

Generate the URL rather than assembling it by hand:

npm run make-url -- https://smartbill.example.com/mcp you@example.com TOKEN RO12345678 FF PF CH

The optional invoiceSeries, estimateSeries and receiptSeries query parameters set that tenant's series defaults, matching the stdio env vars.

This is the form to use with clients that only accept a URL — including claude.ai's Add custom connector dialog, which has fields for a URL and OAuth credentials but none for custom headers.

The URL is the secret. Anyone holding it can issue and delete fiscal documents on that account, and URLs leak more readily than headers do: they land in reverse-proxy access logs, error pages and crash traces. Mitigate it:

  • Turn off request-path logging on whatever proxy sits in front (Traefik/Coolify). This server never logs the URL itself.
  • Treat the URL like a password: don't paste it into shared docs or tickets.
  • To revoke, regenerate the SmartBill token — that invalidates every URL built from it.

Credentials in a header

Clients that can send headers should, since the token then stays out of access logs. Send both:

Authorization: Basic base64(username:token)
X-SmartBill-Cif: RO12345678

against the bare /mcp path. Query parameters for series defaults still apply. When both a header and a URL segment are present, the header wins.

Deploying

The Dockerfile builds for the runtime Prionman expects — linux/arm64, listening on port 80, no secrets baked into the image:

docker build --platform linux/arm64 -t smartbill-mcp .
docker run -p 8080:80 smartbill-mcp

Tools

Invoices

Tool What it does
create_invoice Issue an invoice, optionally recording a payment and emailing it.
create_invoice_from_estimate Issue an invoice that copies its details from a proforma.
create_reverse_invoice Issue a storno invoice reversing an existing one.
get_invoice_pdf Download the invoice PDF.
get_invoice_payment_status Total, paid and unpaid amounts for an invoice.
cancel_invoice / restore_invoice Cancel an invoice, or undo the cancellation.
delete_invoice Permanently delete an invoice.

Estimates (proforme)

Tool What it does
create_estimate Issue a proforma.
get_estimate_pdf Download the proforma PDF.
get_estimate_invoices List invoices already issued from a proforma.
cancel_estimate / restore_estimate Cancel a proforma, or undo the cancellation.
delete_estimate Permanently delete a proforma.

Payments

Tool What it does
create_payment Record a collection, optionally settling specific invoices.
delete_receipt Delete a receipt (chitanta) by series and number.
delete_payment Delete a non-receipt payment (card, transfer, ...).
get_fiscal_receipt_text Printable text of a fiscal receipt, base64-decoded for you.

Account and catalogue

Tool What it does
list_series Document series configured on the account, with their next number.
list_taxes VAT rates, for the taxName / taxPercentage fields on invoice lines.
list_stocks Stock levels on a date, optionally per warehouse or product.
send_document_email Email an already-issued invoice or proforma.

How the tools are documented

The descriptions are written for a model choosing between them, not just for a human reading the list. Every tool states what it does, when to reach for it, what it returns, and which sibling tool to use instead when it is the wrong choice — delete_invoice points at cancel_invoice, create_invoice points at create_invoice_from_estimate, and so on. Irreversible tools say so and ask for confirmation; read-only ones are marked readOnlyHint and destructive ones destructiveHint, so clients can gate them.

The server also ships instructions, which explain the domain a model has to get right up front: how series and numbers work, the difference between an invoice, a proforma and a payment, and the asymmetry between deleting, cancelling and reversing.

test/documentation.test.ts enforces this — it fails the build if a tool loses its title, gets a thin description, stops saying when to use it, drops one of the cross-references, or grows an undocumented parameter.

Behaviour worth knowing

Errors. SmartBill reports business failures with HTTP 200 and a non-empty errorText (and the email endpoint uses a status.code instead). Both are turned into tool errors, so a failed call never looks like a success.

Rate limiting. SmartBill allows 3 calls per second. The client serialises requests and spaces them out, so a burst of tool calls queues instead of failing.

PDFs. Over stdio, get_invoice_pdf and get_estimate_pdf write the file to SMARTBILL_DOWNLOAD_DIR and return the path. Over HTTP the server has no filesystem the client can read, so they return base64 bytes instead. Either default can be overridden per call with as: "file" | "base64".

Email fields. SmartBill expects the email subject and body base64-encoded. Pass plain text; the encoding is handled for you.

Irreversibility. Only the last document in a series can be deleted. Older documents can be cancelled (cancel_invoice) or reversed with a storno invoice (create_reverse_invoice). The server tells the model this in its instructions and marks the destructive tools accordingly, but the client still decides whether to prompt — treat write tools as needing confirmation.

Development

npm test          # vitest, no network access needed
npm run typecheck
npm run dev       # stdio, from source

Tests drive the real MCP server — over an in-memory transport for the tool layer and over a real socket for the HTTP layer — with a stubbed fetch, so they cover the tool schemas, the request bodies sent to SmartBill, the error mapping and per-tenant credential isolation.

Notes on the API surface

SmartBill's reference lives at https://api.smartbill.ro/, which serves a Swagger spec at https://api.smartbill.ro/data/swagger.json. Every endpoint, field name and query parameter used here has been checked against it. Three details are worth flagging:

  • delete_payment calls DELETE /payment/v2. The plain /payment path accepts only POST; the delete operation for non-receipt collections lives on /v2, and takes the same query parameters this tool already sent.
  • Receipts have no cancel operation. Invoices and proformas can be voided while keeping their number (/invoice/cancel, /estimate/cancel), but there is no /payment/cancel — a receipt can only be deleted, and only if it is the last one in its series.
  • create_payment sends the internal note as observation, singular. Invoices and proformas spell the same field observations.

Amounts carry no currency

GET /invoice/paymentstatus returns invoiceTotalAmount, paidAmount and unpaidAmount as bare doubles. The documented response schema has no currency field, and the figures are in whatever currency the invoice was issued in — an EUR invoice returns the EUR amount, indistinguishable from a RON one.

Read unqualified next to a Romanian invoicing service, that reads as RON. It happened: a 1250 EUR invoice was reported as "1250 RON", understating it more than fivefold. get_invoice_payment_status therefore annotates its result with currency: "unknown" and a note, so the caveat sits beside the number rather than only in the tool description. get_invoice_pdf is the way to establish the actual currency.

What the API cannot do

There is no way to enumerate anything. The published API is 20 paths, and every document read — /invoice/paymentstatus, /invoice/pdf, /estimate/pdf, /estimate/invoices — is keyed by cif + seriesName + number. There is no search, no date range, no pagination, and no customer resource of any kind: clients are only ever written, as a nested block on a document, with saveToDb persisting them into the nomenclator with no read path back out.

So "all invoices for client ABC", "everything issued last month" and "list my customers" are not answerable through this API. /tax, /series and /stocks are the only endpoints that return a list. The server instructions tell the model this, so it reports the limitation instead of probing series numbers one at a time — which would also hit SmartBill's request rate limit.

create_invoice_from_estimate sends useEstimateDetails: true with an estimate reference and no client block, letting SmartBill copy the client and line items from the proforma — this matches the documented exempluFacturaDinProforma shape.

License

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured