swiss-qr-bill-mcp

swiss-qr-bill-mcp

Enables AI agents to generate, validate, decode, and verify Swiss QR payment slips, with all file writes confined to a specified output directory.

Category
访问服务器

README

swiss-qr-payment

Generator for the Swiss QR-bill — the payment part and receipt that every Swiss invoice carries since the end of the orange and red payment slips.

The package covers the full standard:

  • the Swiss QR Code payload ("Swiss Payments Code") with all 34 elements,
  • validation of every rule of the guidelines, each check citing the chapter it enforces,
  • the printed slip in PDF, SVG or PNG, in German, French, Italian, English or Romansh.

Normative sources, both published by SIX Interbank Clearing:

Document Used for
Swiss Implementation Guidelines QR-bill, v2.3 / v2.4 data structure, validation rules, QR code parameters
Style Guide QR-bill, v1.1 print layout, typography, headings

A generated payment part and receipt

The 210 x 105 mm slip as this package renders it, here in German. It is the specimen printed on page 19 of the Style Guide, and the test suite compares the two run by run.

Every millimetre in swiss_qr_payment/layout.py was measured from the 1:1 vector artwork of the Style Guide with tools/measure_styleguide.py, and the test suite compares the rendered slip against that artwork element by element.


Installation

python3 -m venv .venv
.venv/bin/pip install -e .

Runtime dependencies are segno (QR encoding) and reportlab (PDF and font metrics). PNG output additionally needs pymupdf.

The four typefaces the guidelines permit are Arial, Frutiger, Helvetica and Liberation Sans. Liberation Sans is the default: it is metrically compatible with Arial, freely redistributable, and gets embedded into the PDF, so the printout does not depend on the fonts of whoever opens the file.


Library

from swiss_qr_payment import Address, QRBill, PrintOptions, generate

bill = QRBill(
    account="CH4431999123000889012",                    # IBAN or QR-IBAN
    creditor=Address(
        name="Max Muster & Söhne",
        street="Musterstrasse",
        building_number="123",
        postal_code="8000",
        town="Seldwyla",
        country="CH",
    ),
    amount="1949.75",                                    # None prints a blank field
    currency="CHF",                                      # CHF or EUR
    debtor=Address(                                      # omit to print a blank field
        name="Simon Muster",
        street="Musterstrasse",
        building_number="1",
        postal_code="8000",
        town="Seldwyla",
        country="CH",
    ),
    reference="210000000003139471430009017",             # QRR, SCOR or nothing
    unstructured_message="Auftrag vom 15.06.2020",
    billing_information="//S1/10/10201409/11/170309/20/14000000/30/106017086",
    alternative_procedures=["eBill/B/simon.muster@example.com"],
)

generate(bill, "bill.pdf", PrintOptions(language="de"))

generate validates the bill first and raises ValidationError if it violates the standard. To inspect the findings instead:

from swiss_qr_payment import validate

for message in validate(bill):
    print(message)      # ERROR reference: ... [IG 2.12.1]

Every settable element

Attribute of QRBill Payload line Notes
account 4 IBAN or QR-IBAN, CH or LI
creditor 5–11 structured address, mandatory
amount 19 Decimal, str, int or None
currency 20 CHF or EUR
debtor 21–27 structured address, may be empty
reference_type 28 QRR, SCOR, NON; inferred when omitted
reference 29 check digits are verified
unstructured_message 30
billing_information 32 Swico syntax, //S1/...
alternative_procedures 33–34 at most two

Lines 12–18 (ultimate creditor) are reserved by the standard for future use and are always emitted empty, which is why they are not exposed.

Print options

Attribute of PrintOptions Values Default
language de, fr, it, en, rm en
font_family Liberation Sans, Arial, Helvetica, Frutiger Liberation Sans
page a4 (slip at the bottom of the page), slip (210 × 105 mm alone) a4
separator none, solid, dashed, dotted solid
scissors draw the scissors symbol on the separation lines True
separator_text print "Separate before paying in" instead of the scissors False
force_country_prefix print CH- in front of Swiss postal codes too False

Command line

swiss-qr-bill \
    --account CH4431999123000889012 \
    --creditor-name "Max Muster & Söhne" \
    --creditor-street Musterstrasse --creditor-building-number 123 \
    --creditor-postal-code 8000 --creditor-town Seldwyla --creditor-country CH \
    --amount 1949.75 --reference 210000000003139471430009017 \
    --language de --output bill.pdf

A bill can also be described by a JSON document, which command line options then override:

swiss-qr-bill --from-json bill.json --amount 99.90 --output bill.svg

Other useful modes:

swiss-qr-bill --from-json bill.json --check-only      # validate, exit 1 on error
swiss-qr-bill --from-json bill.json --print-payload   # dump the QR code content
swiss-qr-bill --make-qr-reference 313947143000901     # append the check digit
swiss-qr-bill --make-creditor-reference 539007547034  # build an RF reference

swiss-qr-bill --help lists every option.


MCP server: letting an agent issue bills

swiss-qr-bill-mcp speaks the Model Context Protocol over stdio, so an agent can produce a payment slip itself:

{
  "mcpServers": {
    "swiss-qr-bill": {
      "command": "swiss-qr-bill-mcp",
      "env": { "SWISS_QR_BILL_OUTPUT_DIR": "/srv/invoices" }
    }
  }
}

Install it with pip install swiss-qr-payment[mcp]. SWISS_QR_BILL_OUTPUT_DIR confines every file the agent writes to one directory and refuses paths that try to leave it; without the variable, paths are resolved against the working directory.

Tool What it does
create_bill Writes a print-ready PDF, SVG or PNG and returns the path, what the slip shows, the QR payload and the symbol size
check_bill Same checks without writing anything, for validating a draft
make_reference Turns an invoice number into a QR reference or an ISO 11649 Creditor Reference with correct check digits
decode_bill Reads the content of a Swiss QR Code that was scanned elsewhere
read_bill Reads a file back: decodes the printed symbol with the same reader banking apps use and reports the page geometry and embedded fonts

create_bill refuses to write a bill that violates the guidelines and returns the offending rules instead, each quoting its chapter, so the agent can correct the data rather than guess. read_bill closes the loop: after issuing a bill the agent can confirm that a scanner really sees the intended payment data before the file is sent to anyone.


Examples

python3 examples/generate.py

writes the six reference bills of the Style Guide plus a euro bill with a foreign debtor and a "DO NOT USE FOR PAYMENT" notification into examples/out/, in German and English, together with scan-test.pdf, scan-test.svg and scan-test.png for a scanning test.


How the output is verified

Formal verification, not eyeballing. pytest tests/ runs 255 checks:

Against the official artwork. Each of the six example bills printed in the Style Guide is rendered, the resulting PDF is read back (swiss_qr_payment.probe), and every text run is compared with the corresponding run in SIX's own 1:1 artwork — position, baseline, font size and right edge. The line breaks of the structured billing information come out identical to the artwork, character for character.

Against the standard's own data. tools/extract_annex_examples.py parses the six example tables of Annex A of the Implementation Guidelines straight out of the PDF; the payload encoder is checked against those 34 element values. This also reproduces a documented erratum: the Creditor Reference of Annex A example 4 fails its own mod-97-10 check in versions 2.2, 2.3 and 2.4 of the guidelines, and the test asserts that this is detected.

Against a real reader. The slip is rasterised at 200, 300 and 600 dpi and decoded with ZXing, the library behind most mobile banking scanners. The decoded bytes must equal the generated payload exactly, including the largest possible bill and non-ASCII content.

Through a real MCP client. The server is driven over stdio by an actual client session rather than by calling its functions: the schema an agent sees, the transport and the structured results are what is asserted, including that a bill written through create_bill reads back through read_bill with the identical payload, and that the output directory cannot be escaped.

Across the backends. The SVG is rasterised by an independent renderer (rsvg-convert), its symbol is decoded, and the symbol area is compared with the PDF rendering pixel by pixel — it comes out identical, so the two backends cannot drift apart unnoticed.

Known limits of the standard, recorded rather than hidden. Two of them are checked by tests so that they cannot be forgotten:

  • The 997 of chapter 6.2 are bytes, not characters. A bill with every element filled to its character limit is 869 bytes in ASCII, 939 bytes with half the message accented, and 1009 bytes when all of it is — the last one does not fit and is rejected.
  • At 939 bytes the symbol reaches version 25 with 117 modules, which at the 46 mm mandated by chapter 6.4 gives 0.393 mm per module — below the 0.4 mm minimum of chapter 6.3. The guidelines contradict themselves here, so the validator issues a warning instead of pretending otherwise.

Note on zbar: it cannot read a Swiss QR Code at all. The recognition symbol sits on top of the central alignment pattern; ZXing reconstructs the geometry and repairs the covered modules through the error correction, zbar gives up. tests/test_qr.py::test_zbar_cannot_read_the_recognition_symbol records this and proves the cause by decoding the same symbol without the cross.

Running the tests

The layout tests need the specification PDFs, which are not redistributed here:

python3 tools/fetch_spec.py          # downloads them into spec/
.venv/bin/pip install -e ".[test]"
.venv/bin/python3 -m pytest tests/ -q

Tests that need a document or a library that is missing are skipped, not failed.

Only the PDF embeds the typeface. The SVG names the font family and relies on the viewer having it, so line breaks and ellipses — computed from the real metrics — can look different where the font is missing. Use the PDF whenever the printout matters.

tools/measure_styleguide.py works on any QR-bill PDF, including the output of this package, and prints its geometry in slip-local millimetres — useful for checking a printout against the standard by hand:

python3 tools/measure_styleguide.py bill.pdf 0 --kind text

How this compares to the other Python packages

Two packages already generate Swiss QR-bills in Python. Both are honest work, and both reproduce the same deviations, because both descend from the same Java reference implementation, SwissQRBill by Manuel Bleichenbacher, whose layout constants differ from the Style Guide artwork in places.

Measured with tools/measure_styleguide.py against page 19 of the Style Guide, rendering the specimen printed there:

qrbill 1.2.0 swiss-qr-bill 0.0.31 this package
Output formats SVG SVG (PDF and PNG raise NotImplementedError) PDF, SVG, PNG
Typeface embedded no no yes, in the PDF
"Amount" heading, payment part 7.00 mm too far left on the artwork
"Amount" heading, receipt 10.00 mm too far left on the artwork
Receipt information baselines 1.31 mm too high on the artwork
Blank amount field 27 x 11 mm 30 x 10 mm, as specified
Blank debtor field, receipt 52 x 25 mm 52 x 20 mm, as specified
Swiss QR Code 45.8 mm 46 mm 46 mm
Country code on a Swiss address printed as CH 8000 Seldwyla omitted, as the guidelines ask
Checked against the artwork no no every specimen, every run

The point of the table is not that the others are careless: the deviations are inherited, and nothing in those projects would have caught them, because neither compares its output with the source of truth. That comparison is what this package is built around.


Layout reference

Coordinates in millimetres, origin at the top left of the 210 × 105 mm slip.

                    receipt                 payment part
        0        5              57  62    67          113  118           205  210
      0 +--------------------------+-------------------------------------------+
      5 |        Receipt           |    Payment part   |  Account / Payable to |
     12 |  Account / Payable to    |                   |  ...                  |
        |  Reference               | 17 +-----------+  |  Reference            |
        |  Payable by              |    | Swiss QR  |  |  Additional info      |
        |                          |    |   Code    |  |  Payable by           |
        |                          | 63 +-----------+  |                       |
     68 |  Currency   Amount       |    Currency  Amount                       |
     82 |          Acceptance point| 90 ----------------------------------------
        |                          |    Name AV1: ...                          |
    105 +--------------------------+-------------------------------------------+
Element Value
Slip 210 × 105 mm, receipt 62 mm, payment part 148 mm
Margins 5 mm on all four sides
Swiss QR Code 46 × 46 mm at (67, 17), quiet zone 5 mm
Recognition symbol 7 × 7 mm white square, 6 × 6 mm black square, bars 3.896 × 1.171 mm
Receipt title 11 pt bold, headings 6 pt bold, values 8 pt, leading 9 pt
Payment part title 11 pt bold, headings 8 pt bold, values 10 pt, leading 11 pt
Blank amount field receipt 30 × 10 mm at (27, 70), payment part 40 × 15 mm at (77.7, 73.8)
Blank debtor field receipt 52 × 20 mm, payment part 65 × 25 mm
Corner marks 3 mm legs, 0.75 pt

Licence

MIT. The scissors outline in swiss_qr_payment/drawing.py is taken from the MIT licensed reference implementation SwissQRBill by Manuel Bleichenbacher.

推荐服务器

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 模型以安全和受控的方式获取实时的网络信息。

官方
精选