
rdyrct
io.github.baronunreadPublished by GitHub account baronunreadv1.1.0Updated Nov 2, 2026
Create and manage short links and QR codes, and read click analytics, on your own domain.
Overview
Remote MCP server for rdyrct, a self-hosted link shortener, letting an assistant create short links and QR codes and read click analytics on your own domain.
- What you can do
- Create and manage short links with custom or random slugs, UTM parameters and extra aliases
- Generate QR codes for links, with optional center logo and PNG/SVG download
- Read privacy-friendly click analytics: clicks per day, top links, countries, referrers and devices
- Work within organizations, where roles decide who manages links, domains and billing
- When to use it
- Useful when you run a rdyrct instance and want an assistant to shorten links, produce QR codes or pull click statistics without opening the web app. Less relevant if you do not already host rdyrct.
- Requirements
- A remote endpoint at rdyrct.com reached over streamable HTTP, with OAuth sign-in required. The instance itself is a Cloudflare Workers deployment with D1, KV, R2 and queues; optional features need Resend, Polar, Google, Cloudflare and Turnstile credentials.
Installation
In SourceWeft
- Open rdyrct in the dashboard and add it to a workspace.
- Connect the required credentials. They are stored privately and never shown on public pages.
- Enable the server for the chats that should use its tools.
Web executable via Streamable HTTP. Remote servers run from the web runtime once configured in a workspace.
Use with an agent
Paste this prompt into Claude Code, Codex, Cursor or another agent — or into SourceWeft chat. The agent reads this server's install guide, shows you its publisher, endpoint and sign-in, and adds it to your client once you agree. Keys never go into a command.
Read https://sourceweft.com/mcp/io.github.baronunread%2Frdyrct/install.md and connect the MCP server it describes to my client. Before changing any config, show me its publisher, endpoint and sign-in method and wait for my OK. Never put an API key in a command or in a file I would commit; ask me where it should come from.Other MCP clients
Add this to your client's mcpServers config. The client completes OAuth sign-in on first connect.
{
"mcpServers": {
"rdyrct": {
"type": "http",
"url": "https://rdyrct.com/api/mcp"
}
}
}README
rdyrct
Organization-based link shortener + QR codes, running entirely on Cloudflare's edge.
[License: MIT] [Built on Cloudflare Workers] [Bun] [React 19]
Deploy in one click
Cloudflare copies this repository to your GitHub account, creates the database, storage and queues, and asks for one thing: the email of the account that becomes the platform admin. Leave every other value as it is. The first build serves your copy on its workers.dev address, generates the session key and applies the migrations; each push to your copy deploys it again. When you want your own domain, see Move to your own domain.
Prefer a terminal? bun run setup does the same from a clone: see Deploy to Cloudflare Workers.
Features
- Short links with custom or random slugs, optional UTM parameters (source, medium, campaign, term, content) applied at redirect time.
- Grouped link addresses: renaming a link keeps the old slug redirecting for 48 hours before it's freed, and a link can carry extra aliases that all redirect to the same destination.
- QR codes per link (paid plans), with an optional logo in the center, live preview, and PNG/SVG download.
- Custom domains (Pro): connect
links.yourbrand.comvia Cloudflare for SaaS, with slugs namespaced per domain. - Organizations with
owner/admin/memberroles: admins manage the team, domains, and billing; members manage links. - Magic-link invites: single-use, valid for 7 days, sent by email.
- Privacy-friendly analytics: clicks per day, top links, countries, referrers, devices. No IP addresses are ever stored.
- Per-user Free/Hobby/Pro billing through Polar, which acts as Merchant of Record and handles tax/VAT.
- Secret-pinned superadmin console: the account matching
SUPERADMIN_EMAILgets a cloud-console panel for usage, org drill-down, and plan management. It 404s for everyone else.
Stack
rdyrct is built Cloudflare-first: the entire product runs in a single Worker with no separate backend to host:
- Cloudflare Workers: one Worker serves the API, the redirect hot path, and the static React app.
- D1: SQLite at the edge, source of truth for auth, orgs, links, clicks, and domains.
- KV: slug → destination lookups on the redirect hot path, so a click never waits on D1.
- R2: QR logo images, keyed by org, served immutable and cached.
- Cloudflare for SaaS: custom hostnames for Pro orgs' own short-link domains.
- Workers Rate Limiting: separate abuse controls for auth, email, writes, uploads, domains, billing, and click recording.
Application layer:
- Hono for routing
- BetterAuth for email+password auth, with email-OTP verification and PBKDF2/WebCrypto hashing
- Drizzle ORM over D1
- React 19 + Vite +
@cloudflare/vite-plugin - Tailwind v4 + Base UI
- TanStack Query + TanStack Router
- Polar for billing, Resend for transactional email
Local development
What you need
- Bun 1.4 or newer.
- Node 24 or newer. Bun runs the scripts, but portless and wrangler run on Node.
- macOS or Linux. Dev servers run behind portless (installed with the project), which gives each one a stable HTTPS name instead of a port.
- Optional: the Polar CLI, only to receive sandbox billing webhooks.
First run
That is the whole setup. On the first run bun run dev:
- creates
.dev.varsfrom.dev.vars.local.example, with an auth secret of its own (every other value works locally as it is); - applies the local database migrations (it does this on every run, so a pull never leaves you a migration behind);
- starts the app, a local email inbox and a local Google sign-in, each behind portless (portless may ask for your password once, to listen on port 443 and trust its own certificate);
- prints the addresses once the app answers:
Then, in a second terminal, fill the database with fake organizations, links and clicks:
and open the scenarios page. It signs you in as a seeded account in each state the app has: pro-owner, hobby-owner, free-owner, fresh (a brand-new account), member, viewer, over-plan, grace-ended, locked-org, click-cap and admin. /__dev/scenarios/<name>?to=/links opens a page directly.
Ctrl+C stops everything. If one of the three crashes, the other two stop with it.
Day to day
-
One name per checkout. The main checkout is
rdyrct,rdyrct-mail,rdyrct-google; a git worktree gets the branch in its names (rdyrct-<branch>,rdyrct-<branch>-mail…), so any number of checkouts run side by side.bun scripts/dev-hosts.tsprints this checkout's addresses. -
Email. Sign-up codes and every other mail land in the local inbox, never a real one:
-
Google sign-in goes through the local emulator with two accounts,
[email protected]and[email protected]. -
The admin console. Open the
adminscenario. (Or setSUPERADMIN_EMAILin.dev.varsand sign up with that address.) -
Start the database over:
bun run db:reset:local, thenbun run devandbun run db:seed:localagain. -
rdyrct.com's own pages. The dev server runs the self-hosted app. Add
VITE_RDYRCT_SITE=1to.envto work on the landing and marketing pages.
From another computer or a phone
Switch portless to LAN mode once:
The addresses then end in .local (https://rdyrct.local) and any device on the same network opens them; bun run dev says so when it starts. The first visit from another device shows a certificate warning, because that device does not trust portless's certificate: continue past it. Every name is a single word before .local on purpose: Windows finds only those on a network. On Linux, LAN mode needs avahi-utils.
Integrations
When something is off
- "
.dev.varssets this checkout's addresses":bun run devworks outAPP_URL,APP_HOST,SHARED_LINK_HOST,RESEND_BASE_URLandGOOGLE_EMULATOR_URLitself. Delete the lines it lists from.dev.vars(an old copy has them). - "Invalid origin" when saving or signing out: open the address
bun run devprinted, notlocalhost:<port>. - A page will not load at all:
bunx portless doctorchecks the proxy, andbunx portless listshows what is running. - The seed stops with a 500: the local database is busy (a browser test run, say). Run it again when it is quiet.
Tests
Playwright owns the local server lifecycle and never resets or seeds local
Cloudflare state, so the browser tests run with or without bun run dev.
The browser and Worker tests bring their own settings, so your .dev.vars
and .env never change what they see. Failed browser tests keep a
screenshot and trace in test-results/.
Deploy to Cloudflare Workers
Setup asks three things: a name for the worker (Enter keeps rdyrct), a domain on your Cloudflare account (or Enter, for a free *.workers.dev address) and the email of the account that becomes the platform admin. It stops if your account already runs a worker by that name, so it never deploys over one. Then it:
- rewrites your copy of
wrangler.jsonc: your domain inAPP_URLandAPP_HOST, and rdyrct.com's own ids and settings (mail sender, Polar products, Cloudflare zone, Sentry) taken out; - has Wrangler create the D1 database, KV namespace, R2 bucket and queues on your account, each named after the worker, since a binding with no id gets a new resource on the first deploy;
- generates
BETTER_AUTH_SECRET, sets it andSUPERADMIN_EMAILas secrets, builds, deploys, applies the migrations and attaches your domain.
Commit the rewritten wrangler.jsonc to your fork. After that, bun run deploy ships each update: it applies new migrations, then deploys. Every build is the self-hosted app, which opens on sign-in and carries none of rdyrct.com's marketing pages, analytics or search tags. Only VITE_RDYRCT_SITE=1 builds rdyrct.com itself, and bun run deploy refuses it for any host but rdyrct.com.
Everything else is optional. Each piece says what it adds:
With no email provider and no Google sign-in, accounts are not verified and passwords cannot be reset. That is fine for a private instance and not recommended for a public one. The admin console says so while it is true.
Vars go in wrangler.jsonc. Set secrets one by one with bunx wrangler secret put <NAME>, or in bulk with cp prod.secrets.env.example prod.secrets.env, fill it in, then:
Move to your own domain
Started on workers.dev, from the button or from setup? Clone your copy, run bun run setup and give it the domain (it has to be on your Cloudflare account), then commit wrangler.jsonc and push. Setup attaches the domain and keeps your data, your secrets and the session key. By hand instead: Cloudflare dashboard → Workers → your worker → Settings → Domains & Routes → Add → Custom domain, then set APP_URL and APP_HOST in wrangler.jsonc, remove the vars that are rdyrct.com's (MAIL_FROM, WELCOME_FROM, POLAR_*, CF_ZONE_ID, SENTRY_DSN) and bun run deploy. Short links live at the root (https://links.example.com/<slug>); the app is served on every other path. Keep routes out of wrangler.jsonc: a routes list replaces whatever is attached in the dashboard, and committing yours would hand it to every fork. The */* route is only for customers' own domains (below).
Email (optional)
Set RESEND_API_KEY and MAIL_FROM (a sender at a domain you verified in Resend). Without them no mail goes out: sign-up skips the verification code and signs the user in, and the password-reset form says it needs an email provider. Sending lives in one function, sendEmail() in src/worker/email.ts, so another provider is a change there.
Selling plans (optional)
Skip this section to run with no caps and no billing. To sell plans, set POLAR_SERVER=sandbox (or production when live), POLAR_PRO_PRODUCT_ID and POLAR_HOBBY_PRODUCT_ID (recurring products you create in Polar), and the secrets POLAR_ACCESS_TOKEN and POLAR_WEBHOOK_SECRET.
Polar setup: create an Organization Access Token with scopes checkouts:write and customer_sessions:write. Add a webhook endpoint at https://rdyrct.com/api/webhooks/polar and subscribe to all five events the Worker handles. Miss one and billing state drifts:
Which subscription statuses keep access is one decision, in src/worker/entitlement.ts. active, trialing and past_due keep it, because Polar retries a failed payment for days and sends subscription.revoked when it gives up. unpaid, canceled, incomplete and incomplete_expired do not, and a status with no rule keeps nothing.
Paid access an admin grants by hand is a comp, stored apart from the subscription and never counted as a subscriber. A comp outranks the subscription underneath it, so revoking a subscription cannot take away access an admin gave.
The admin console reports counts, never money: how many people hold a paid plan, how many pay for it, and how many are comped. Who is cancelling shows on their own row in the user list, where it names someone. Revenue is read in the Polar dashboard, which knows what it charged net of discounts, tax and refunds.
Second shared-domain host (optional). Attach a shorter domain the same way, check that https://<it>/ answers with a redirect, and only then turn it on with bunx wrangler secret put SHARED_LINK_HOST. It takes effect without a deploy, and bunx wrangler secret delete SHARED_LINK_HOST turns it off. In that order because every new link starts using it at once: set before the domain resolves, it hands out links that go nowhere. Old links keep working on both hosts, since a shared-domain slug's KV key has no host in it. Unset, shared links use APP_HOST.
Review the rate-limiting policies, monitoring, WAF rule, and rollback steps before the first production deploy.
Review the architecture, data flows, and improvement priorities before changing link, domain, logo, or organization delete flows.
Customer custom domains (Pro): to let orgs use links.theirbrand.com, enable Cloudflare for SaaS on the rdyrct.com zone, create an originless proxied fallback origin (e.g. fallback.rdyrct.com AAAA 100::), and add a Worker route */* pointing at the rdyrct worker. The CF_API_TOKEN needs permission Zone → SSL and Certificates → Edit scoped to the zone. Also verify your sending domain (e.g. mail.rdyrct.com) in Resend so transactional email isn't blocked.
Configuration
Secrets are set with wrangler secret put NAME or wrangler secret bulk prod.secrets.env in production; locally they all come from .dev.vars (see .dev.vars.local.example).
Project layout
License
MIT © Andrea Bruno. See LICENSE.
Source: README.md at commit 269c039
Tools
0Version history
1- v1.1.0LatestOct 26, 2026
Score
85% of the applicable checks met
How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.
- Reached in the last 30 daysRequired25 pts
- Auth declaredRequired10 pts
- Tools listed15 pts
Answer tools/list without a credential, or ship a package that starts in a sandbox without one.
- DescriptionRequired15 pts
- Logo10 pts
- Documentation15 pts
- License10 pts