Every time I get an invoice or a broker deposit instruction, I do the same dumb, error-prone thing: copy the IBAN, the amount, and the variable symbol into my banking app by hand. One transposed digit and the money goes to a stranger. Czech invoices increasingly carry a QR Platba code you just scan — but a lot of them still don't (anyone invoicing from Word/Excel, foreign senders, smaller shops). So I built a small tool that turns those payment details into a scannable QR, and it grew into something I think is worth sharing. Live: https://qr.cz-agents.dev · Code (MIT): https://github.com/martinhavel/cz-agents-mcp · npm: @czagents/payqr What it does Generates SPAYD (Czech/Slovak "QR Platba") and EPC / GiroCode (the SEPA standard, huge in Germany/Austria) payment QR codes from an IBAN + amount + reference. Also does plain-text, Wi-Fi and vCard QR, and reads a QR back (decode + classify). You can type the details or drop a payment screenshot — OCR runs in your browser. The lazy path (in an AI client / MCP): hand it a whole invoice — PDF or photo — and the model reads the IBAN, amount and reference and generates the payment QR for you. No retyping at all, with a verify step before anything is shown. It's 100% client-side: nothing is uploaded, no account, no tracking, no AI, MIT. The bits that were actually interesting to build 1. The payment formats are just strings. SPAYD is a delimited string: SPD*1.0*ACC:CZ6508000000192000145399*AM:1250.00*CC:CZK*X-VS:1234567890 EPC/GiroCode is 12 newline-separated lines (service tag, BIC, recipient, IBAN, amount, remittance…). EUR-only, recipient name required. Once you know the spec, generating them is deterministic — no API, no key, no rounding surprises. A lot of "QR code generator" tools just wrap a remote API; this one computes everything locally, which is what makes the privacy claim real. 2. IBAN validation is a tiny party trick. mod-97: move the first 4 chars to the end, turn letters into numbers, and the whole thing mod 97 must equal 1. ~10 lines, catches most typos before a QR is ever drawn. 3. Privacy is an architecture, not a checkbox. OCR uses tesseract.js and QR decoding uses jsQR, both lazy-loaded in the browser. The image never leaves the device. I could only honestly write "your image never leaves your device" because there is literally no backend. The MCP part (and a payment-safety gotcha) It's also an MCP server, so in an AI client you can hand it an invoice (PDF or photo) and the model reads the IBAN/amount/reference and calls qr_payment. The instructions force a read-back step: the model echoes the extracted fields and asks you to verify before showing the QR — because a misread digit is real money gone. Here's the non-obvious part I burned an afternoon on. The tool returns the QR as an MCP image content block: content: [ { type: 'image', data: base64png, mimeType: 'image/png' }, { type: 'text', text: JSON.stringify(result) }, ] In Claude Desktop, that image is given to the model as rendered pixels — which the model cannot re-export. So when I told it "show the user the QR as a file," it couldn't access the bytes… and helpfully regenerated the QR from the payload using its own library. For a payment QR that's dangerous: the model just became the source of truth, and a single mistyped character would silently corrupt the transfer. Two fixes: Never regenerate — and if the model has no choice, make it qr_read its own image and diff the payload character-for-character before showing it. Return the PNG as base64 text too (qr_png_base64), so the model can write the exact bytes to a file with zero re-encoding. Lesson for anyone building image-returning MCP tools: the rendered image block is for the human; if you also want the model to do something with the bytes, hand them over as text. Try it https://qr.cz-agents.dev — type an IBAN, or drop a screenshot. It's free and there's nothing to sign up for. I'd love feedback on EPC/GiroCode edge cases (BICs, structured vs unstructured remittance) — that's where the spec gets fiddly.

— Summary