Use when users need URL shortening, branded links, QR codes, click analytics, or link management. No login required for anonymous URL shortening. Supports PicSee, 縮短網址, 短鏈接. OAuth unlocks all tools.
---
name: picsee-short-link
description: PicSee URL shortener via MCP — shorten URLs, generate QR codes, view rich click analytics (daily / platform / referrer / region / audience), and manage links. Use when the user asks to shorten a URL, see link analytics, list/search short links, edit/delete links, or mentions PicSee. Anonymous mode supports `create_short_link` only; OAuth 2.1 unlocks the full 14-tool authenticated surface.
license: MIT
compatibility: Requires an MCP-compatible client with Streamable HTTP transport (or a stdio bridge such as `mcp-remote`). Internet access to `api.picsee.io` and `public-api-oauth.picsee.io`.
metadata:
author: picsee
version: "3.0.2"
emoji: "🔗"
repository: https://github.com/PicSeeInc/picsee-short-link
mcp_url: https://api.picsee.io/mcp
mcp_command: npx mcp-remote https://api.picsee.io/mcp
oauth_authorization_server: https://public-api-oauth.picsee.io
scopes: "user:read user:write"
---
# PicSee Short Link
URL shortener with **rich click analytics** and full **link management** — exposed to AI agents through the **PicSee MCP server** at `https://api.picsee.io/mcp`.
The skill ships no code: it just teaches your agent how to call the MCP server. Authentication is handled by **OAuth 2.1 with PKCE** (Dynamic Client Registration), so no API tokens are ever stored on disk by the skill.
---
## Installation
Add the PicSee MCP server to your AI client's MCP config. Pick the transport your client supports — most modern clients speak Streamable HTTP directly.
### Streamable HTTP (recommended)
```json
{
"mcpServers": {
"picsee-short-link": { "url": "https://api.picsee.io/mcp" }
}
}
```
### Stdio bridge (for clients without remote-MCP support)
```json
{
"mcpServers": {
"picsee-short-link": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.picsee.io/mcp"]
}
}
}
```
See [README.md](README.md) for the exact config file path on each platform.
---
## Authentication
### Anonymous mode (no setup)
`create_short_link` is callable with no credentials. Anonymous calls are pinned to `pse.is` and have no access to `externalId` filtering / attribution dashboards.
### Authenticated mode (OAuth 2.1)
On the first authenticated tool call, the MCP client triggers an OAuth flow:
1. Dynamic Client Registration → `https://public-api-oauth.picsee.io/oauth/register`
2. Browser → `https://public-api-oauth.picsee.io/oauth/authorize`
3. User signs in to PicSee and grants `user:read` + `user:write`
4. Client receives access + refresh tokens via PKCE/S256
5. Token is stored by the MCP client — **never** by this skill
All 14 authenticated tools become available after this flow.
---
## Tools
15 tools total. `create_short_link` is callable anonymously; the rest require OAuth.
### `create_short_link` *(anonymous OK)*
Create a new short link.
| Field | Required | Notes |
|-------|----------|-------|
| `url` | ✅ | Destination URL, ≤2048 chars |
| `encodeId` | | Custom slug, 3–90 chars (letters / digits / `_` / `-` / Chinese). Must be globally unique — conflicts return `PUB00503` |
| `domain` | | `pse.is` or a BSD from `get_my_domains`. **Ignored when called anonymously** |
| `externalId` | | 1–100 chars. Agents SHOULD default this to their own product name — see [Attribution](#attribution) |
| `utm` | | `{ source, medium, campaign, term, content }` |
| `title` | | OG preview title, 3–300 chars |
| `description` | | OG preview description, 3–300 chars |
| `imageUrl` | | OG preview image URL |
| `tags` | | Array of up to 3 tag names — use `get_my_tags` to offer a picker |
| `targets` | | Device-specific redirects, see below |
| `fbPixel` | | Meta Pixel ID — must already be saved in PicSee (`get_my_tracking_tools`) |
| `gTag` | | GTM container ID — must already be saved in PicSee (`get_my_tracking_tools`) |
| `pathFormat` | | `{ key: "<param-name>" }` — Path Parameterization add-on (paid Advanced) |
`targets[].target` enum: `ios_android`, `ios`, `ios_store`, `android`, `android_store`, `ios_line`, `ios_safari`, `android_fb`, `pc_mac`, `pc`, `mac`, `facebook`, `twitter`. `ios_android` = all mobile; `pc_mac` = all desktop. App-store buckets only fire for users with the app installed.
Returns `picseeUrl` (the shortened link).
### Account / discovery tools
#### `get_api_status`
No params. Returns the account's API plan, lifetime quota, current period usage, and plan expiration. **Call this before bulk operations** to confirm remaining quota.
#### `get_api_usage_by_external_id`
| Field | Required | Notes |
|-------|----------|-------|
| `startTime` | | Taipei time `YYYY-MM-DDTHH:mm:ss`. Defaults to 30 days before `endTime` |
| `endTime` | | Taipei time `YYYY-MM-DDTHH:mm:ss`. Defaults to current hour. Max 31-day range |
Returns API-link counts grouped by `externalId` — useful for attributing usage to agents / campaigns.
#### `get_my_domains`
No params. Lists every short-link domain on the account: brand short domains (BSDs), PicSee subdomains, shared root. Each entry flags HTTPS support and default status. **Call before `create_short_link`** if the user wants a non-default domain.
#### `get_my_tags`
No params. Returns `{ id, value }` pairs, where `value` is the tag name accepted by `tags` on `create_short_link` / `edit_short_link`.
#### `get_my_tracking_tools`
No params. Returns previously-used UTM sources / mediums, saved Meta Pixels, and saved GTM containers. Use to populate pickers instead of asking the user to retype IDs.
### `list_short_links`
| Field | Notes |
|-------|-------|
| `limit` | 1–50, default 20 |
| `startTime` | Taipei time `YYYY-MM-DDTHH:mm:ss`. **Returns links created _at or before_ this timestamp** — i.e. queries backward. Default = now |
| `prevMapId` | Cursor: return links with `mapId` older than this. Combine with `startTime` for AND filtering |
| `isAPI` | `true` (default) = only API-created links; `false` = only website-created |
| `isStar` | `true` = starred only. Default `false` |
| `externalId` | Exact-match filter |
| `search.encodeId` | Exact slug — priority 1, overrides all other search fields |
| `search.authorId` | Filter by author's PicSee user ID — priority 2 |
| `search.tag` | Tag name, 3–30 chars — priority 3 |
| `search.keyword` | Substring, 3–30 chars — priority 4 |
**Tip**: when the user says "links from March 2026", pass `startTime: "2026-03-31T23:59:59"` (the end of the period) — the server queries backward from there.
### `edit_short_link`
| Field | Required | Notes |
|-------|----------|-------|
| `encodeId` | ✅ | Slug of the link to edit |
| `url` | | New destination. May be rejected with `PUB00510` if the new origin is on a different brand |
| `domain` | | |
| `title` / `description` / `imageUrl` | | 3–300 chars for text fields |
| `tags` | | Up to 3 |
| `targets` | | Same enum as `create_short_link` |
| `fbPixel` / `gTag` | | Pass `null` to clear |
| `utm` | | Pass `null` to clear all UTM params |
| `expireTime` | | Future Taipei time `YYYY-MM-DDTHH:mm:ss`, or `null` to remove. Requires the expiration add-on |
### `delete_short_link`
| Field | Required | Notes |
|-------|----------|-------|
| `encodeId` | ✅ | |
| `value` | | `delete` (default) = move to trash; `recover` = restore from trash |
- Starred links can't be deleted (`PUB00706`) — unstar via web first.
- Links trashed for >30 days can't be recovered (`PUB00704`).
### Per-link analytics
All five tools share the same shape: required `encodeId`, optional `startTime` / `endTime` (Taipei `YYYY-MM-DDTHH:mm:ss`). Default window is the last 30 days; Advanced plan can look back up to 365 days.
| Tool | Returns |
|------|---------|
| `get_link_overview` | Total clicks, unique clicks, destination URL, domain, HTTPS flag, creation time. **Use for at-a-glance summaries** |
| `get_link_daily_clicks` | Time-series of total + unique clicks aggregated by day. Use as raw data for chart rendering |
| `get_link_platforms` | Unique-click breakdown by device (`iphone`, `android`, `windows`, `macintosh`, …). Aggregate to mobile/desktop client-side if needed |
| `get_link_referrers` | Unique-click breakdown by referrer (search engines, social, AI agents, long-tail). Clicks without referrer info → `direct` |
| `get_link_regions` | Unique-click breakdown by country (no city-level data). Unknown countries → `Others` (`code: "others"`) |
| `get_link_audience_labels` | Interest + brand labels. **Privacy guard**: only returns data when the link has >100 lifetime unique clicks; otherwise both arrays come back empty. No `startTime` / `endTime` — covers link lifetime |
---
## Attribution
The `create_short_link` schema asks the calling agent to set `externalId` to its own product name when the user hasn't specified one. PicSee account owners use this to attribute API usage in their dashboard.
| Agent | Recommended `externalId` |
|-------|--------------------------|
| Claude Code | `Claude Code` |
| Cursor | `Cursor` |
| Codex / Codex CLI | `Codex` |
| Antigravity | `Antigravity` |
| OpenClaw / ClawHub | `OpenClaw` |
| Gemini CLI | `Gemini CLI` |
| GitHub Copilot | `Copilot` |
| ChatGPT | `ChatGPT` |
Rules:
- Canonical product name only — no version numbers, session IDs, or user names.
- If the user explicitly provides an `externalId`, **always honor their value** instead.
---
## Agent Recipes
The MCP server intentionally doesn't ship QR or chart-rendering tools — those are easier to compose client-side from public services.
### Generate a QR code from a short link
Construct a URL against [api.qrserver.com](https://goqr.me/api/):
```
https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=<URL-encoded-short-link>
```
Surface the URL inline if your client renders images; otherwise return it as a link. For a larger code, change `size=500x500`.
### Render a daily-clicks chart
1. Call `get_link_daily_clicks` to get the time series.
2. Build a [QuickChart](https://quickchart.io) URL from the data:
```
https://quickchart.io/chart?c={type:'line',data:{labels:['2026-03-01',...],datasets:[{label:'Clicks',data:[12,38,...]}]}}
```
URL-encode the `c` parameter. Display inline if possible; otherwise return the link.
---
## Common Workflows
### Shorten a URL with full attribution
1. Call `create_short_link` with `url` and `externalId` = your agent name.
2. Return `picseeUrl` to the user.
3. If the user wanted a custom slug and got `PUB00503`, suggest an alternative.
### Pick a branded short domain
1. `get_my_domains` to enumerate BSDs + PicSee subdomains.
2. Offer the user a picker (note which is `isDefault`).
3. Pass the chosen `domain` to `create_short_link`.
### Show analytics for a link
1. `get_link_overview` for the headline numbers.
2. If the user wants breakdowns, call one or more of `get_link_daily_clicks` / `get_link_platforms` / `get_link_referrers` / `get_link_regions` / `get_link_audience_labels`.
3. For audience labels: warn the user if the link has ≤100 unique clicks (data will be empty due to the privacy guard).
### List the user's recent links
1. Call `list_short_links` with the user's filters.
2. If they say "March 2026", pass `startTime: "2026-03-31T23:59:59"`.
3. Paginate with `prevMapId` from the last result if they ask for more.
### Migrate a link to a new destination
1. `edit_short_link` with `encodeId` + new `url`.
2. If you get `PUB00510`, the brand of the new URL doesn't match. Tell the user.
### Track API usage by agent
`get_api_usage_by_external_id` with no params returns the last 30 days, grouped by attribution tag. Useful for "which AI created most of my links?"
---
## Migration from v2.x
v2.x was a Node.js CLI that stored an encrypted API token under `~/.openclaw/`. v3.0.0 removes that entirely — no CLI, no token files, no Node.js requirement on the agent side.
```bash
# Clean up v2 artifacts
rm -f ~/.openclaw/.picsee_token ~/.openclaw/.picsee_salt
rm -rf ~/.claude/skills/picsee-short-link/cli
rm -rf ~/.openclaw/workspace/skills/picsee-short-link/cli
```
Behavioral changes:
- **Auth**: long-lived API token → OAuth 2.1 + PKCE (browser sign-in on first authenticated call)
- **QR / chart**: were CLI subcommands → now agent recipes against `api.qrserver.com` / `quickchart.io`
- **`delete` vs `recover`**: were two CLI commands → one tool `delete_short_link` with `value: "delete" | "recover"`
- **Analytics**: was a single `analytics` command → split into 6 granular tools (`get_link_overview` + 5 dimensional breakdowns)
- **Per-account discovery**: new tools `get_my_domains`, `get_my_tags`, `get_my_tracking_tools`, `get_api_status`, `get_api_usage_by_external_id` have no v2 equivalent
---
## Error codes
| Code | Meaning |
|------|---------|
| `PUB00503` | `encodeId` (custom slug) already taken |
| `PUB00510` | New `url` rejected on edit — different brand than the original |
| `PUB00704` | Cannot recover a link that has been in trash >30 days |
| `PUB00706` | Cannot delete a starred link — unstar it via the PicSee web app first |
---
## References
- PicSee website: https://picsee.io
- Developer docs: https://picsee.io/developers
- MCP spec: https://modelcontextprotocol.io
- OAuth 2.1 (MCP profile): https://modelcontextprotocol.io/specification/draft/basic/authorization
don't have the plugin yet? install it then click "run inline in claude" again.
Use this skill when a user needs to shorten a URL, create a branded short link, view click analytics (daily, platform, referrer, region, audience), or manage existing links. Anonymous mode works for basic URL shortening without login. OAuth 2.1 unlocks link management, rich analytics, custom domains, UTM tracking, QR code generation, and pixel/GTM integration. The skill exposes the PicSee MCP server at https://api.picsee.io/mcp to your agent with no local code or token storage.
https://api.picsee.io/mcp, or stdio bridge via npx mcp-remote https://api.picsee.io/mcpapi.picsee.io and public-api-oauth.picsee.iocreate_short_link works immediately. Results pinned to pse.is domain; no externalId attribution or analytics dashboards.create_short_link: destination URL (required), optional custom slug, domain, externalId, utm params, OG preview fields, device targets, tracking pixels/containers, tagsedit_short_link: encodeId (required), any field from create_short_link plus expireTimedelete_short_link: encodeId (required), optional recovery flaglist_short_links: filters by date range (startTime/endTime), externalId, slug, tag, keyword; pagination via prevMapIdget_link_*): encodeId (required), optional date range; all default to last 30 daysget_my_tracking_tools; pass pixel ID to fbPixel parameterget_my_tracking_tools; pass to gTag parameterget_my_domainsget_my_tracking_tools and get_my_tags for picker population~/.config/cline/cline_mcp_settings.json for Cline, or equivalent for Claude Desktop, Cursor, etc.)mcpServers:{
"picsee-short-link": { "url": "https://api.picsee.io/mcp" }
}
create_short_link is immediately callablenpx is available on your systemmcpServers:{
"picsee-short-link": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.picsee.io/mcp"]
}
}
create_short_link is immediately callablehttps://public-api-oauth.picsee.io/oauth/authorizeuser:read + user:write scopescreate_short_link with:url: destination (required, ≤2048 chars)encodeId: custom slug if provided (3-90 chars, globally unique, alphanumeric/dash/underscore/Chinese only)domain: if user chose a branded domain via get_my_domains, pass it here; else omit (anonymous calls ignore this)externalId: set to agent name (e.g. "Claude Code") if user did not specify; always honor explicit user valuetags: pass array of tag names if user specified; call get_my_tags first to offer a pickerutm: object with source/medium/campaign/term/content if user provided tracking paramstitle, description, imageUrl: OG preview fields if user providedtargets: device/platform-specific redirects if user provided (ios/android/mac/desktop/social/etc)fbPixel, gTag: if user specified tracking, verify via get_my_tracking_tools first; pass ID or omitpicseeUrl (the shortened link) to userPUB00503): offer alternative slug and retryget_link_overview with encodeId:encodeId and optional startTime/endTime:get_link_daily_clicks: time series of total + unique clicks per dayget_link_platforms: breakdown by device (iphone, android, windows, macintosh, etc)get_link_referrers: breakdown by referrer (search engines, social, direct, AI agents, etc)get_link_regions: breakdown by country code (no city-level data)get_link_audience_labels: interest + brand labels (empty if <100 unique lifetime clicks; privacy guard)list_short_links with:startTime: if user specified a month/date, use last moment of that period (e.g. "2026-03-31T23:59:59") because server queries backwardexternalId: if user asking for links from a specific agent/campaign, pass the exact externalIdsearch.encodeId: if user gave a slug, pass it here (highest priority, overrides other filters)search.tag: if user mentioned a tag, pass tag namesearch.keyword: if user gave a substring to find in title/description, pass itlimit: default 20; increase if user asks for moreprevMapId from last result and call list_short_links again with same filtersedit_short_link with encodeId and fields to change (url, domain, title, description, tags, targets, utm, expireTime, fbPixel, gTag)delete_short_link with encodeId and value: "delete" (move to trash) or value: "recover" (restore from trash)get_api_status with no paramsget_my_domains with no paramsdomain to create_short_linkget_my_tags with no params{ id, value } pairs where value is the tag namecreate_short_link or edit_short_linkget_my_tracking_tools with no paramsget_api_usage_by_external_id with no params (or startTime/endTime)If user only asks to shorten a URL and does not specify a custom domain, tracking, externalId, or device targets, then call create_short_link immediately in anonymous mode; OAuth is not needed.
Else if user requests any authenticated feature (custom domain, analytics, link management, pixel tracking, custom expireTime, or explicit externalId), then proceed to next call which triggers OAuth flow if not already authenticated.
If create_short_link returns error code PUB00503, then the requested slug is already taken; suggest an alternative slug to user and retry create_short_link with new slug.
Else if error is PUB00510, then the new destination URL (on edit) is on a different brand than the original; tell user they cannot change the brand via edit, only via delete + create.
If user calls get_link_audience_labels and the link has ≤100 unique lifetime clicks, then both interest and brand label arrays will be empty; warn user that data is unavailable due to privacy threshold.
Else (>100 clicks), labels are returned normally.
If user tries to delete a starred link via delete_short_link and gets error PUB00706, then tell user to unstar the link via the PicSee web app first, then retry delete.
If user tries to recover a deleted link via delete_short_link with value: "recover" and gets error PUB00704, then the link has been in trash >30 days and is no longer recoverable; advise user to recreate the link if needed.
If user says "show me links from March 2026", then set startTime: "2026-03-31T23:59:59" (the last moment of the month), not the first; the server queries backward from the start time.
Else (user specifies "in the last 7 days" or "after 2026-03-15"), calculate the appropriate startTime based on backward semantics.
If user requests expireTime on create_short_link or edit_short_link, then verify via get_api_status that the account has the expiration add-on; if not, tell user this feature requires an upgrade.
Else if user requests pathFormat (path parameterization), then verify the account has the Advanced plan; if not, inform user.
Else if user queries analytics for >30 days back, then verify the account is on Advanced plan (up to 365-day lookback); if not, restrict to last 30 days.
create_short_linkpicseeUrl (string, the shortened link){ "picseeUrl": "https://pse.is/abc123" } or { "picseeUrl": "https://mybrand.io/custom-slug" }get_link_overview: keys = totalClicks, uniqueClicks, destinationUrl, domain, httpsSupported, createdAtget_link_daily_clicks: array of { date, totalClicks, uniqueClicks }get_link_platforms, get_link_referrers, get_link_regions: array of { label, uniqueClicks } where label is device/referrer/countryget_link_audience_labels: arrays of { label, uniqueClicks } for interests and brands (empty if <100 lifetime clicks)list_short_linkslinks (array) and prevMapId (string for pagination)get_my_domains: array of { domain, isDefault, httpsSupported, type }get_my_tags: array of { id, value }get_my_tracking_tools: arrays of previousUtmSources, previousUtmMediums, savedMetaPixels, savedGtmContainersget_api_status: keys = plan, lifetimeQuota, currentPeriodUsage, expirationDate, remainingQuotaget_api_usage_by_external_id: array of { externalId, linkCount }success: true and optional message fielderror: { code, message }User receives a working short URL (e.g. https://pse.is/abc123) that redirects to the destination. If a custom domain was used, URL is from that domain (e.g. https://mybrand.io/custom-slug). Confirm by testing the link in a browser or curl.
User sees numeric click counts (total, unique) and optional breakdowns by day, device, referrer, region, or audience interests. If a chart was rendered, it displays a time series graph or a link to the chart image. Numbers match the PicSee web dashboard for the same link and date range.
User sees a paginated list of their short links with creation date, slug, destination URL, click counts, and tags. If multiple pages exist, pagination cursor is available and user can load more.
User receives confirmation that the link was modified or moved to trash. If recovering a deleted link, confirmation shows it is now active again. Link metadata is updated across all analytics dashboards.
User receives a picker or list of their custom domains, tags, tracking tools, or API usage. This data is used to auto-populate form fields or answer "how much quota do I have left?" in subsequent operations.
On first authenticated call, browser opened to PicSee login. After user signed in and granted scopes, the agent confirmed success and became able to call all 14 authenticated tools. No further login prompts appear (tokens auto-refresh).
If a call fails, user sees the error code (e.g. PUB00503, PUB00510) and a human-readable explanation. For conflicts (slug taken, wrong brand), the agent suggests a recovery action (new slug, recreate link,