esyURL
by esyurl · remote · no sign-in to list tools · v0.2.0 · published by GitHub user esyurl
Short links and trackable QR codes: editable after printing, device routing, scan stats.
21 tools: 4 can delete or cancel things, 10 change things and 7 only read.
Scanned Oct 21, 2026
- Deletes or cancels4Claude Code: deny: see these tools
- Can't be undone0Claude Code: ask
- Changes things10Claude Code: ask: see these tools
- Only reads7Claude Code: allow: see these tools
1 check matched something in the tool definitions. No finding is not a safety guarantee. How the checks work.
claude mcp add --transport http esyurl https://esyurl.fyi/mcp --header 'Authorization: <your-authorization>'Findings
Matches of fixed checks, with the text they matched.
The server marks "get_custom_domain" as not read-only (readOnlyHint: false), and its words say it reads.
What this check looks forMatched text
annotations: {"readOnlyHint":false,"idempotentHint":true}; The name "get_custom_domain" says "get": a word for reading.
Tools
Open a tool for its reasons and full description. Descriptions are the publisher's own words, shown as they are.
21 tools
Deletes or cancels 4Claude Code: deny
delete_groupDelete a group.No findings
Why it is in this group
- The name "delete_group" says "delete": a word for removing or ending something.
- The server marks it destructive (destructiveHint: true).
Description, as the server wrote it Delete a group. Its links are NOT deleted; they just stop being members.
Annotations the server declares
- destructiveHint: true
delete_short_linkPermanently delete a short link and remove it from its groups.No findings
Why it is in this group
- The name "delete_short_link" says "delete": a word for removing or ending something.
- The description says "Permanently delete".
- The server marks it destructive (destructiveHint: true).
Description, as the server wrote it Permanently delete a short link and remove it from its groups. Its short URL and any printed QR codes stop working (404) and the slug becomes free for anyone. Prefer update_short_link status "paused" if the link may come back.
Annotations the server declares
- destructiveHint: true
remove_custom_domainRemove this tenant's custom domain.No findings
Why it is in this group
- The name "remove_custom_domain" says "remove": a word for removing or ending something.
- The server marks it destructive (destructiveHint: true).
Description, as the server wrote it Remove this tenant's custom domain. Short links immediately stop resolving on it (QR codes printed with it break) and new short URLs use the platform domain. The DNS records can be deleted afterwards.
Annotations the server declares
- destructiveHint: true
remove_link_from_groupTake one link out of a group.No findings
Why it is in this group
- The name "remove_link_from_group" says "remove": a word for removing or ending something.
Description, as the server wrote it Take one link out of a group. The link itself is untouched.
Changes things 10Claude Code: ask
add_links_to_groupAdd up to 25 links to a group in one call.No findings
Why it is in this group
- The name "add_links_to_group" says "add": a word for a change.
Description, as the server wrote it Add up to 25 links to a group in one call. Idempotent: links already in the group are reported in alreadyMembers rather than failing. Fails with not_found if any linkId is unknown (nothing is added).
Annotations the server declares
- idempotentHint: true
create_groupCreate a group to organise short links (e.g.No findings
Why it is in this group
- The name "create_group" says "create": a word for a change.
Description, as the server wrote it Create a group to organise short links (e.g. a campaign or a venue). Groups are many-to-many: a link can be in up to 20 groups. Add links with add_links_to_group, or pass groupIds when creating/updating a link.
create_short_linkCreate a short link that redirects to targetUrl, or shows a link page (pass page instead of targetUrl).No findings
Why it is in this group
- The name "create_short_link" says "create": a word for a change.
Description, as the server wrote it Create a short link that redirects to targetUrl, or shows a link page (pass page instead of targetUrl). Returns the link, including linkId (use it with the other tools) and shortUrl (share this). Give a slug for a memorable URL, otherwise a random 8-character one is generated. Slugs are unique across all tenants and cannot be changed later (409 if taken; route names like "api", "admin", "health" are reserved).
get_custom_domainGet this tenant's custom (alias) domain and advance its setup as far as DNS allows.1 finding
Why it is in this group
- The name "get_custom_domain" says "get": a word for reading.
- The server marks it as not read-only (readOnlyHint: false).
Findings
- Warn
The server marks "get_custom_domain" as not read-only (readOnlyHint: false), and its words say it reads.
What this check looks forMatched text
annotations: {"readOnlyHint":false,"idempotentHint":true}; The name "get_custom_domain" says "get": a word for reading.
Description, as the server wrote it Get this tenant's custom (alias) domain and advance its setup as far as DNS allows. Use it to check progress after creating a DNS record. Lifecycle: pending_validation (create the certificate_validation record, and the optional ownership_verification TXT if listed) → provisioning (automatic, seconds) → pending_dns (create the routing CNAME from the domain to `target`) → active. Nothing advances on its own: call get_custom_domain after each DNS change to move it along. The response always carries `dnsRecords` (exact name/type/value to create, with `done` once seen) and `instructions` saying what to do next.
Annotations the server declares
- readOnlyHint: false
- idempotentHint: true
render_qr_codeRender a QR code as a PNG image.No findings
Why it is in this group
- Nothing in its name or description says what it does, so it is counted as a write.
- The server marks it read-only (readOnlyHint: true), but an annotation never lowers a class.
Description, as the server wrote it Render a QR code as a PNG image. Pass linkId to render a short link: the image encodes {shortUrl}?s=qr so scans are counted as qr visits, and the link's qrStyle is the default look. Or pass data to render any text (URL, Wi-Fi config, vCard...) as a static QR code that is not tracked. Returns the image plus a text line naming exactly what was encoded. Styles are free: module shapes, corner markers, a logo or a halftone photo. For SVG, use GET /v1/links/{linkId}/qr?format=svg.
Annotations the server declares
- readOnlyHint: true
set_custom_domainServe all of this tenant's short links on a domain it controls, e.g.No findings
Why it is in this group
- The name "set_custom_domain" says "set": a word for a change.
Description, as the server wrote it Serve all of this tenant's short links on a domain it controls, e.g. "go.acme.com" → https://go.acme.com/{slug}. Once active, new short URLs and QR codes use it; links keep working on the platform domain too. One domain per tenant: setting a different one replaces (and tears down) the current one. Setting the same one again is harmless, and restarts it if it failed. Two DNS records are needed at the domain's DNS provider, returned one at a time: first a record (TXT or CNAME) that proves control (for the TLS certificate), then a CNAME pointing the domain at the platform. Lifecycle: pending_validation (create the certificate_validation record, and the optional ownership_verification TXT if listed) → provisioning (automatic, seconds) → pending_dns (create the routing CNAME from the domain to `target`) → active. Nothing advances on its own: call get_custom_domain after each DNS change to move it along. The response always carries `dnsRecords` (exact name/type/value to create, with `done` once seen) and `instructions` saying what to do next. Self-signed-up tenants (see /auth.md) pay once per new domain with x402: without a payment the tool returns an error whose structuredContent is an x402 PaymentRequired (x402Version 2, exact scheme, USDC). Retry with the signed PaymentPayload in the request's _meta["x402/payment"] (the x402 MCP transport), or, if your client cannot set _meta, in the `payment` argument. The settlement comes back in _meta["x402/payment-response"]. Re-setting the current domain, reading and removing are free; tenants given a key by an operator never pay.
Annotations the server declares
- idempotentHint: true
set_domain_home_pageChoose the link that the root (/) of this tenant's custom domain shows: a link page becomes the domain's home page, a redirect link sends / there.No findings
Why it is in this group
- The name "set_domain_home_page" says "set": a word for a change.
Description, as the server wrote it Choose the link that the root (/) of this tenant's custom domain shows: a link page becomes the domain's home page, a redirect link sends / there. Pass linkId null to clear it (/ answers 404 again). Has no effect on the platform domain.
Annotations the server declares
- idempotentHint: true
test_redirect_rulesDry run: which destination would a visit with these properties get from this link's rules?No findings
Why it is in this group
- Nothing in its name or description says what it does, so it is counted as a write.
- The server marks it read-only (readOnlyHint: true), but an annotation never lowers a class.
Description, as the server wrote it Dry run: which destination would a visit with these properties get from this link's rules? For a link page with no matching rule it returns page.itemIds (the buttons that visit would see) instead of targetUrl. Records no visit. Pass a real userAgent to test device routing, e.g. iPhone: "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1", Android: "Mozilla/5.0 (Linux; Android 14; Pixel 8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Mobile Safari/537.36". Returns targetUrl, matchedRule (absent when the link's targetUrl is used) and the context the rules saw (os, device, browser, language, country, referrer, source, query, at).
Annotations the server declares
- readOnlyHint: true
update_groupRename a group or change its description or color.No findings
Why it is in this group
- The name "update_group" says "update": a word for a change.
Description, as the server wrote it Rename a group or change its description or color. Membership doesn't change here.
Annotations the server declares
- idempotentHint: true
update_short_linkChange a short link.No findings
Why it is in this group
- The name "update_short_link" says "update": a word for a change.
Description, as the server wrote it Change a short link. Only the fields you pass change; tags, metadata, qrStyle and groupIds are replaced wholesale (pass the full new list; [] clears). Set targetUrl to send visitors somewhere else: the short URL and printed QR codes keep working. Set status "paused" to make the short URL answer 410, "active" to resume. The slug cannot be changed. page replaces the whole link page (a redirect becomes a page); page: null with a targetUrl turns a page back into a redirect.
Annotations the server declares
- idempotentHint: true
Only reads 7Claude Code: allow
get_groupFetch one group.No findings
Why it is in this group
- The name "get_group" says "get": a word for reading.
Description, as the server wrote it Fetch one group. To see its links use list_short_links with group = groupId.
Annotations the server declares
- readOnlyHint: true
get_group_visitsCombined visit analytics for a group's current member links: totals {visits, qr, direct, bot} and a zero-filled per-day breakdown (UTC).No findings
Why it is in this group
- The name "get_group_visits" says "get": a word for reading.
Description, as the server wrote it Combined visit analytics for a group's current member links: totals {visits, qr, direct, bot} and a zero-filled per-day breakdown (UTC). Links removed from the group no longer count.
Annotations the server declares
- readOnlyHint: true
get_short_linkFetch one short link by linkId, including its groupIds and lifetime visitCount.No findings
Why it is in this group
- The name "get_short_link" says "get": a word for reading.
Description, as the server wrote it Fetch one short link by linkId, including its groupIds and lifetime visitCount.
Annotations the server declares
- readOnlyHint: true
get_short_link_visitsVisit analytics for one link: totals over the window ({visits, qr, direct, bot}; visits = qr + direct, bots such as link-preview crawlers are counted separately and excluded from v…No findings
Why it is in this group
- The name "get_short_link_visits" says "get": a word for reading.
Description, as the server wrote it Visit analytics for one link: totals over the window ({visits, qr, direct, bot}; visits = qr + direct, bots such as link-preview crawlers are counted separately and excluded from visits), a zero-filled per-day breakdown (UTC, oldest first), and the most recent individual visits (newest first, with source, referrerHost, userAgent, country when known, and ruleId when a redirect rule decided it; kept 90 days). byRule splits the window's visits by rule (fallback = went to the link's targetUrl); daily entries carry byRule on days a rule matched. For link pages, byItem counts button clicks per item (daily entries carry clicks by item id).
Annotations the server declares
- readOnlyHint: true
list_groupsList your groups (with linkCount), ordered by groupId.No findings
Why it is in this group
- The name "list_groups" says "list": a word for reading.
Description, as the server wrote it List your groups (with linkCount), ordered by groupId. Page with cursor = nextCursor.
Annotations the server declares
- readOnlyHint: true
list_short_linksList your short links, newest first.No findings
Why it is in this group
- The name "list_short_links" says "list": a word for reading.
Description, as the server wrote it List your short links, newest first. Filter by tag and/or groupId. A page can hold fewer than limit items even when more exist: keep calling with cursor = nextCursor until nextCursor is absent.
Annotations the server declares
- readOnlyHint: true
preview_link_pageRender a link's page as HTML for a simulated visit, without recording anything: the saved page, or a draft (page) to try before saving (pictures given as data URLs are drawn inline…No findings
Why it is in this group
- The name "preview_link_page" says "preview": a word for reading.
Description, as the server wrote it Render a link's page as HTML for a simulated visit, without recording anything: the saved page, or a draft (page) to try before saving (pictures given as data URLs are drawn inline; nothing is stored or charged). Returns the visible item ids (items with "when" conditions depend on the visit) and the HTML.
Annotations the server declares
- readOnlyHint: true
This is not a safety rating, and no finding is not a safety guarantee. The groups and findings come from fixed checks of the tool definitions the server returned on the date shown, not from its code or what it does when called. How the checks work.
Install
Pinned to version 0.2.0. Values in angle brackets are yours to fill in.
claude mcp add --transport http esyurl https://esyurl.fyi/mcp --header 'Authorization: <your-authorization>'Run in a terminal.
Claude Code permissions
Allow the reads, ask before a change, deny the deletes. Paste into .claude/settings.json.
{
"permissions": {
"allow": [
"mcp__esyurl__list_short_links",
"mcp__esyurl__get_short_link",
"mcp__esyurl__preview_link_page",
"mcp__esyurl__get_short_link_visits",
"mcp__esyurl__list_groups",
"mcp__esyurl__get_group",
"mcp__esyurl__get_group_visits"
],
"ask": [
"mcp__esyurl__create_short_link",
"mcp__esyurl__update_short_link",
"mcp__esyurl__set_domain_home_page",
"mcp__esyurl__test_redirect_rules",
"mcp__esyurl__render_qr_code",
"mcp__esyurl__create_group",
"mcp__esyurl__update_group",
"mcp__esyurl__add_links_to_group",
"mcp__esyurl__get_custom_domain",
"mcp__esyurl__set_custom_domain"
],
"deny": [
"mcp__esyurl__delete_short_link",
"mcp__esyurl__delete_group",
"mcp__esyurl__remove_link_from_group",
"mcp__esyurl__remove_custom_domain"
]
}
}Tools that only read are allowed, tools that change things ask first, tools that delete or cancel are denied. It assumes the server is installed as esyurl. Review it before you use it.
