MCP server for shrtnr, a free, open-source, self-hosted URL shortener built on Cloudflare Workers + D1. Provides tools for managing short links, custom slugs, bundles, analytics, and related operations.
The shrtnr MCP server provides 34 well-defined tools with excellent naming conventions (all start with action verbs like get_, create_, list_, update_, delete_), comprehensive descriptions ranging 150-300+ characters, and complete input schemas with typed parameters. Nearly all tools include enum constraints for range parameters and clear descriptions of what each parameter controls. Output schemas are documented in descriptions. Error handling is present but could be richer with recovery guidance. Security patterns appear sound (no credentials exposed in params). The main gap is that output schemas are not explicitly formalized in the source code provided, they are described in tool descriptions rather than returned as structured JSON Schema definitions. This is adequate but not A+ grade.
Add a custom slug to an existing short link. A link can have multiple slugs. Slugs must be globally unique. Returns the full link with all its slugs. Only the link owner can add slugs.
Add a short link to a bundle. The link and bundle must have the same owner. A link can belong to multiple bundles.
Archive a bundle so it no longer appears in the user's active bundle list. Archived bundles can be unarchived. Only the bundle owner can archive it.
Compare statistics for two links side-by-side. Useful for understanding how similar links perform relative to each other. Returns totals, deltas, and percentage differences. Only the link owner can compare links they own.
Create a bundle: a named, optionally archived collection of short links. Bundles have their own analytics and can be shared as a bundle page or QR code. Only the creator can edit the bundle.
Output schemas are documented in natural language descriptions rather than formalized as JSON Schema definitions. Tools like list_links and get_link_analytics describe their responses inline but don't expose explicit response schema structures that LLMs can parse programmatically.
Error handling descriptions are minimal. Tools document happy paths but don't specify what errors may occur, how to detect them, or how the agent should recover. For example, add_custom_slug mentions 'collisions are reported' but doesn't specify the error response structure or how the agent should interpret slug_rejections.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | C | 69 | 2026-07-28+ | v2 |
Shorten a URL and create a short link. Idempotent, like an UPSERT: a repeat call with the same destination returns the existing link with `duplicate: true` instead of failing. It normalizes the trailing slash first, so `https://example.com/a/` and `https://example.com/a` resolve to one link. There is no need to look up or search for a URL before calling this. Optional custom slugs are attached after creation; slugs already in use are reported in `slug_rejections` rather than failing the call.
Permanently delete a bundle and all its member associations. The links themselves are not deleted, only removed from the bundle. This is irreversible. Only the bundle owner can delete it.
Permanently delete a short link and all its analytics. This is irreversible. Only the link owner can delete it. The slugs are also deleted.
Disable a short link so it no longer redirects (404s are issued instead). The link remains in the catalog and analytics continue to record misses. Only the link owner can disable it.
Disable a slug so it no longer redirects (404s are issued instead). The slug remains attached to the link and can be re-enabled. Only the link owner can disable slugs.
Re-enable a disabled short link so it redirects again. Only the link owner can enable it.
Re-enable a disabled slug so it redirects again. Only the link owner can enable slugs.
Get full details for a bundle by its numeric ID. Returns the bundle metadata, member links, and analytics.
Get click analytics for a bundle over a time range. Returns referrer hosts, user agents, countries, and UTM params. All breakdowns are limited to the top rows; the response includes exact distinct counts for each dimension. Only the bundle owner can read its analytics.
Get a QR code for a bundle as an SVG data URL. The code embeds the bundle's short id and is deterministic and not cached, so reuse it across follow-ups without re-fetching.
Get aggregate dashboard statistics: total link count, total clicks, trending links, and reference breakdowns. Results are scoped to the requested range.
Get full details for a short link by its numeric ID. `total_clicks` and `delta_pct` are scoped to the requested range. Defaults to the user's `default_range` setting (or 30d). Response includes `range_used`.
Get click analytics for a short link over a time range. Returns referrer hosts, user agents, countries, and UTM params. All breakdowns are limited to the top rows; the response includes exact distinct counts for each dimension. Only the link owner can read its analytics.
Get a QR code for a short link as an SVG data URL. Use the short slug directly in the QR; the LLM never needs to speak the full destination URL. The code is deterministic and not cached, so reuse it across follow-ups without re-fetching.
Get click timelines for a short link. Returns counts by hour (24h), day (7d, 30d, 90d, 1y), or week (all). Only the link owner can read its timeline.
Get the user's trending short links, ranked by recent click growth. Returns the top links that saw the most net growth in clicks over the selected range vs. the previous equal-length period.
Check that the shrtnr server is reachable and read its version and database schema state. Takes no arguments. Use it to diagnose a failing connection, not to look up links.
List all links in a bundle. Results are paginated. Includes link metadata and click counts. Counts are scoped to the requested range.
List all bundles owned by the calling user. Results are paginated. Includes bundle metadata and top-link summaries. Counts are scoped to the requested range.
List all bundles that contain a specific link. Only the link owner can call this.
List all short links with their slugs and click counts. Counts and `delta_pct` are scoped to the requested range; reuse the same range across follow-ups to keep numbers comparable. Defaults to the user's `default_range` setting (or 30d). Response includes `range_used`.
List short links owned by a specific user. Results are paginated. Counts are scoped to the requested range.
Remove a short link from a bundle. Only the bundle owner can remove links.
Permanently remove a slug from a link. This is irreversible. The slug becomes available for others to use. Only the link owner can remove slugs.
Search for short links by destination URL, label, or slug. Results are paginated. Counts are scoped to the requested range.
Set a custom slug as the primary slug for a link. All other slugs are secondary. Only the link owner can set the primary slug.
Unarchive a bundle so it appears in the user's active bundle list again. Only the bundle owner can unarchive it.
Update a bundle's name, description, or accent color. Only the bundle owner can update it.
Change the destination URL, label or expiry of an existing short link. Slugs are separate: use add_custom_slug, disable_slug or remove_slug for those. Only the link owner can update it.
Destructive operations (delete_link, delete_bundle, remove_slug) lack confirmation or dry-run capability. Agents cannot preview what will be deleted or request confirmation before permanent removal, increasing risk of accidental data loss.
The custom_slug parameter in create_link uses a union type description ('e.g. my-blog-post or [slug-a, slug-b]') rather than a properly constrained schema. Union types are not formally specified and could confuse LLMs about whether to pass a string or array.
Pagination parameters (limit, offset) appear in several tools (search_links, list_links_by_owner, list_bundles) with a default limit of 25, but no mention of maximum limit or guidance on when to use larger/smaller values. This risks LLMs requesting unbounded result sets.
Tools like get_link and get_bundle accept optional range parameters that default to user's default_range or 30d, but this behavior is not deterministic from the LLM's perspective. Tools should either always require explicit range or always document what fallback is used when omitted.