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
访问服务器

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

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选