Most QR code generators are either ad-covered landing pages or SaaS products with a paywall behind "dynamic codes". I wanted the opposite: a generator that's free, private by default, and natively callable by AI agents. So I built FreeQrly — open source (BryceBin/freeqrly, MIT), running entirely on Cloudflare Pages + D1.
Here's the architecture, including the part I think is most interesting: one shared auth/quota core serving three surfaces — web UI, REST API, and MCP.
1. Static QR API — no key, no signup, CORS wide open
GET https://freeqrly.com/api/qr?data=https://example.com&size=1024&color=2F4A62&bgcolor=ffffff&margin=4&ecc=M
Returns an SVG. data is required (400 with a clear error otherwise), everything else has sane defaults, input is validated (data ≤ 2000 chars, color format checked). Static codes are generated 100% in the browser on the web UI — the API just mirrors that server-side for agents and scripts.
2. Dynamic codes — login-gated, editable after print
Dynamic codes resolve through short /q/xxxxxxx links, so you can change the destination after printing and see scan stats (date / country / device / city breakdowns). Free tier: 8 codes. Login via Google, GitHub, or email+password (Turnstile-protected). The /q/ hosted pages carry noindex + X-Robots-Tag so they never end up in search results.
3. MCP — Streamable HTTP, no auth for static, token for dynamic
POST https://freeqrly.com/mcp
generate_qr works with zero auth. Send an Authorization: Bearer <token> header and five more tools appear in tools/list: dynamic_qr_create, dynamic_qr_list, dynamic_qr_update, dynamic_qr_delete, dynamic_qr_stats.
The API-token flow reuses the UI's auth and quota logic instead of duplicating it:
fqly_, are shown exactly once, and only a SHA-256 hash is stored. Max 20 per account, revocable anytime.functions/_lib/codes_api.js) with the UI and the MCP tools — same validation, same 8-code quota, same ownership checks. A token can't create or revoke other tokens (that requires a login session), and deleting your account wipes your tokens.403 quota_exceeded; cross-user access returns 403; revoked/unknown tokens return 401.
Because it's one code path, the three surfaces can't drift apart: what the UI enforces, the API and MCP enforce identically. I verified this end to end with a real test account in a real browser — login, token creation, REST create→list→stats→update→scan-redirect→delete, MCP tool calls, then 401s after revocation — and cleaned up all test data afterward.
I added a server.json and published to the official MCP Registry as io.github.BryceBin/freeqrly (v1.1.0). Directories like Smithery and PulseMCP sync from the Registry, so one publish covers several. The repo also ships llms.txt and a skills/qr-generator/SKILL.md so agents can discover the API without reading docs.
qr_download, batch_generate, scan_decode) that I query with plain SQL.
If you run an agent that needs QR codes — event badges, WiFi cards, vCards, batch generation (CSV in, ZIP out) — the static API and MCP are free with no signup: https://freeqrly.com/developers/ has the full reference. Happy to answer questions about the token/quota design in the comments.