# opn-mcp [![opn-mcp MCP server](https://glama.ai/mcp/servers/ysalitrynskyi/opn-mcp/badges/score.svg)](https://glama.ai/mcp/servers/ysalitrynskyi/opn-mcp) An [MCP](https://modelcontextprotocol.io) server for **[opn.onl](https://opn.onl)** — the open-source, self-hostable URL shortener. It lets AI assistants (Claude Desktop, Cursor, etc.) shorten links, read analytics, generate QR codes, and manage links in natural language. Works against the hosted service **or your own self-hosted instance**. ## Setup ### 1. Get an API key On your opn.onl instance, go to **Settings → API Keys**, create a key, and copy it (it starts with `opn_` and is shown once). ### 2. Add the server to your MCP client **Claude Desktop** — edit `claude_desktop_config.json` (`~/Library/Application Support/Claude/` on macOS, `%APPDATA%\Claude\` on Windows): ```json { "mcpServers": { "opn": { "command": "npx", "args": ["-y", "opn-mcp"], "env": { "OPN_API_KEY": "opn_your_key_here" } } } } ``` Restart your client. That's it — it talks to the hosted API (`https://l.opn.onl`) by default. > Prefer to run from source? Swap the `args` for the GitHub build — same config: > `"args": ["-y", "github:ysalitrynskyi/opn-mcp"]` (it builds on install). ### Self-hosted instance Point `OPN_BASE_URL` at your own instance's API host: ```json { "mcpServers": { "opn": { "command": "npx", "args": ["-y", "opn-mcp"], "env": { "OPN_API_KEY": "opn_your_key_here", "OPN_BASE_URL": "https://l.your-domain.com" } } } } ``` ## Configuration | Env var | Required | Default | Description | |---------|----------|---------|-------------| | `OPN_API_KEY` | ✅ | — | Your API key (`opn_…`), from Settings → API Keys | | `OPN_BASE_URL` | — | `https://l.opn.onl` | API base URL — set this for a self-hosted instance | ## Tools **Links** | Tool | Description | |------|-------------| | `shorten_url` | Create a short link — optional alias, title, notes, scheduling (`starts_at`/`expires_at`), `max_clicks`, password, burn-after-reading, folder and tags | | `shorten_urls_bulk` | Shorten many URLs at once, optionally into a folder | | `list_links` | List your links (limit, offset, search, folder or tag filter) | | `update_link` | Update destination, title, notes, scheduling, click limit, folder or protections; clear fields with `remove_*` flags | | `delete_link` | Delete a link | | `clone_link` | Duplicate a link under a fresh short code | | `toggle_link_pin` | Pin or unpin a link | | `check_alias_available` | Check whether a custom alias is free before using it | **Analytics** | Tool | Description | |------|-------------| | `get_link_stats` | Per-link analytics (clicks, unique visitors, geo, cities, devices, browsers, OS, referrers); optional `days` window | | `get_dashboard_stats` | Account-wide analytics across all your links | **QR & URL helpers** | Tool | Description | |------|-------------| | `get_qr_code` | Get a link's QR image — optional brand colour, centre logo, PNG/SVG | | `check_url_health` | Check a destination URL is reachable before shortening | | `build_utm_url` | Append UTM campaign parameters to a URL | | `preview_url_metadata` | Fetch Open Graph metadata (title, description, image) for a URL | **Tags & folders** | Tool | Description | |------|-------------| | `list_tags` / `create_tag` | List or create tags | | `add_tags_to_link` / `remove_tags_from_link` | Attach or detach tags on a link | | `list_folders` / `create_folder` | List or create folders | | `move_links_to_folder` | Move links into a folder | ## Example prompts - "Shorten https://example.com/very/long/url and call it launch-2026" - "Shorten these five URLs into a new folder called Q3 Campaign" - "How many clicks did link 42 get in the last 30 days, and from which countries?" - "Give me a branded SVG QR code for link 42" - "Tag my last 10 links as 'newsletter' and show my dashboard stats" - "Build a UTM link for https://example.com — source newsletter, medium email" ## Development ```bash npm install npm run build # tsc → dist/ npm test # vitest OPN_API_KEY=opn_… npm run dev # run from source (stdio) ``` ## Releasing All three registries are owned by **`ysalitrynskyi`** (npm user, GitHub user, and the `io.github.ysalitrynskyi` MCP-registry namespace), so publishing must be done while signed in as that account. 1. Bump the version in **three** places and keep them identical: `package.json`, `server.json` (top-level **and** the `packages[0].version`), and `SERVER_VERSION` in `src/server.ts`. 2. Land it: commit to `main` and push. CI (`.github/workflows/ci.yml`) runs `build` + `test` — it does **not** publish. 3. Publish to npm (as npm user `ysalitrynskyi`): ```bash npm login npm publish # prepublishOnly runs the build ``` 4. Publish to the MCP registry (as GitHub user `ysalitrynskyi`): ```bash mcp-publisher validate # optional, offline check mcp-publisher login github # interactive browser OAuth mcp-publisher publish ``` [Smithery](https://smithery.ai) (`smithery.yaml`) and [Glama](https://glama.ai) (`glama.json`) track the npm/registry release automatically — no separate step. ## License MIT © ysalitrynskyi. Part of the [opn.onl](https://github.com/ysalitrynskyi/opn.onl) project.