MCP Server

unulu

ai.unulu/unulu
Design & Creative Developer Tools Public & reachable MCP 2026-07-28

What this MCP does

Creates, publishes, inspects, and updates link-in-bio websites, including handle availability checks.

unulu_check_handle
Check handle availability
Check whether a handle is available on unu.lu (not other platforms like Instagram, TikTok, etc.). For example, checking 'joe' tells you if joe.unu.lu is available for claiming. Use this to help users choose a handle before they visit the claim page.
Read only
Input schema
{'type': 'object', 'required': ['handle'], 'properties': {'handle': {'type': 'string', 'description': 'The handle to check (3-30 chars, alphanumeric + hyphens)'}}}
Output schema
{'type': 'object', 'required': ['available', 'handle'], 'properties': {'handle': {'type': 'string'}, 'available': {'type': 'boolean'}}}
unulu_create_site
Create a link-in-bio site
Create a link-in-bio site with a name, bio, and links. Returns a live URL on unu.lu that expires in 1 hour unless claimed. Do NOT call until you have at least a name and one link from the user — gather real content first, never create with placeholder or empty content. If the user provides a handle (e.g. '_guy.a' for Instagram), construct the full URL yourself — do not ask them to paste it. When feasible, offer to find all public links for the person. Before calling: tell the user they can choose a skin on the preview page for the overall aesthetic; ask permission before adding emoji leading_icons to decorate links (for not-well-known social URLs). The response includes an assistant_message with the site URL and claim details — present these to the user. Persist claim_token and claim_code_short for subsequent updates — never ask the user for them back in the same conversation. Never create a duplicate site; always update the existing one. After creation: tell the user they can pick a custom handle when they claim; share the preview URL in a copy-paste block; offer to refine bio, links, or ordering. Keep iteration fast — apply changes immediately, don't re-confirm minor edits.
Open world
Input schema
{'type': 'object', 'required': ['name'], 'properties': {'bio': {'type': 'string', 'maxLength': 5000, 'description': "Short bio in first person, 1â\x80\x933 sentences, capturing what's distinctive â\x80\x94 no filler phrases like 'passionate about' or 'dedicated to'. If you lack context, write something short and honest rather than generic. Supports markdown."}, 'name': {'type': 'string', 'maxLength': 200, 'description': 'Display name / heading for the site'}, 'links': {'type': 'array', 'items': {'type': 'object', 'required': ['title', 'url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Link URL (http or https; https recommended)'}, 'title': {'type': 'string', 'maxLength': 200, 'description': 'Link label'}, 'subtitle': {'type': 'string', 'maxLength': 200, 'description': 'Optional subtitle (supports markdown)'}, 'leading_icon': {'type': 'object', 'required': ['kind', 'value'], 'properties': {'url': {'type': 'string', 'description': 'Alias for value â\x80\x94 accepted for convenience when round-tripping get_state output. Prefer value for new input.'}, 'kind': {'enum': ['emoji', 'image'], 'type': 'string', 'description': 'Icon type'}, 'value': {'type': 'string', 'description': 'Emoji character(s) or image URL'}}, 'description': 'Optional leading icon for links the renderer does not auto-detect. Well-known social links (GitHub, Twitter/X, LinkedIn, etc.) always display branded icons regardless of this field. Provide kind + value.'}, 'trailing_badge': {'type': 'string', 'maxLength': 50, 'description': "Optional trailing badge text for emphasis or highlights (e.g. 'New', 'Preferred', 'DMs open'). Use to draw attention to important links or flag contact preferences."}}}, 'maxItems': 20, 'description': 'Link-in-bio links. Order matters â\x80\x94 place the most important link first. Well-known social links display branded icons automatically â\x80\x94 omit leading_icon for these.'}, 'skin_id': {'enum': ['editorial-minimal', 'midnight-gradient', 'soft-pastel-craft', 'neo-brutal-poster', 'liquid-glass', 'carbon-folio', 'concrete-grid', 'signal-noir', 'studio-warm', 'sunset-decks', 'electric-sermon', 'hot-press', 'chrome-vogue', 'tidal-shelf', 'dune-light', 'moss-veil', 'golden-hour'], 'type': 'string', 'description': 'Optional theme skin. When omitted, the live site includes a visual theme chooser â\x80\x94 prefer omitting so the user can pick their own.'}}}
Output schema
{'type': 'object', 'required': ['site_id', 'url', 'expires_at', 'assistant_message'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Live URL on unu.lu â\x80\x94 always present as a clickable markdown link when showing to the user'}, 'site_id': {'type': 'string', 'description': 'Unique site identifier'}, 'expires_at': {'type': 'string', 'format': 'date-time', 'description': 'ISO 8601 expiry timestamp (~1 hour from creation)'}, 'claim_token': {'type': 'string', 'description': 'HMAC token for updating ephemeral (pre-claim) sites via updateSite. Present when the site is unclaimed.'}, 'claim_code_short': {'type': 'string', 'description': 'Backup code (XXXX-XXXX) for claiming the site via the short-code flow'}, 'assistant_message': {'type': 'string', 'description': 'Pre-formatted message with the live site link, expiry notice, and short-code claim instructions. Share with the user as-is â\x80\x94 do not paraphrase or restructure.'}}}
unulu_get_state
Get site state
Returns the current state of a site including its name, bio, links, and URL. No authentication required. If expires_at is absent, the site has been claimed and is permanent. Accepts a site ID, a full URL (e.g. https://abc123.unu.lu), or a bare hostname (e.g. abc123.unu.lu).
Read only
Input schema
{'type': 'object', 'required': ['site_id'], 'properties': {'site_id': {'type': 'string', 'description': 'Site ID, full URL (e.g. https://abc123.unu.lu), or bare hostname (e.g. abc123.unu.lu)'}}}
Output schema
{'type': 'object', 'required': ['site_id', 'url', 'name', 'links'], 'properties': {'bio': {'type': 'string'}, 'url': {'type': 'string', 'format': 'uri'}, 'name': {'type': 'string'}, 'links': {'type': 'array', 'items': {'type': 'object', 'properties': {'id': {'type': 'string'}, 'url': {'type': 'string', 'format': 'uri'}, 'title': {'type': 'string'}, 'subtitle': {'type': 'string'}, 'leading_icon': {'oneOf': [{'type': 'object', 'required': ['kind', 'value'], 'properties': {'kind': {'const': 'emoji'}, 'value': {'type': 'string'}}}, {'type': 'object', 'required': ['kind', 'value'], 'properties': {'kind': {'const': 'image'}, 'value': {'type': 'string', 'format': 'uri'}}}]}, 'trailing_badge': {'type': 'string'}}}}, 'site_id': {'type': 'string'}, 'skin_id': {'enum': ['editorial-minimal', 'midnight-gradient', 'soft-pastel-craft', 'neo-brutal-poster', 'liquid-glass', 'carbon-folio', 'concrete-grid', 'signal-noir', 'studio-warm', 'sunset-decks', 'electric-sermon', 'hot-press', 'chrome-vogue', 'tidal-shelf', 'dune-light', 'moss-veil', 'golden-hour'], 'type': 'string'}, 'expires_at': {'type': 'string', 'format': 'date-time'}}}
unulu_update_site
Update a site
Update an existing site. All fields are optional — only provided fields are changed. Links replace the entire array (omit to keep existing). Before updating, always call unulu_get_state first to read the current links and their ids — do not guess link ids. Authorization depends on site lifecycle: X-Claim-Token header for ephemeral (pre-claim) sites, X-Edit-Token header for claimed (post-claim) sites. If neither token is available and the site is claimed, use requestEditAccess to obtain an edit_token. If you created the site in this conversation, you already have the claim_token — use it directly. Returns the full updated site state. Keep iteration fast — apply changes immediately without re-confirming minor edits unless ambiguous. Accepts a site ID, a full URL (e.g. https://abc123.unu.lu), or a bare hostname (e.g. abc123.unu.lu).
Destructive Open world
Input schema
{'type': 'object', 'required': ['site_id'], 'properties': {'bio': {'type': 'string', 'maxLength': 5000, 'description': "Short bio in first person, 1â\x80\x933 sentences, capturing what's distinctive â\x80\x94 no filler phrases like 'passionate about' or 'dedicated to'. If you lack context, write something short and honest rather than generic. Supports markdown."}, 'name': {'type': 'string', 'maxLength': 200, 'description': 'Display name / heading for the site'}, 'links': {'type': 'array', 'items': {'type': 'object', 'required': ['title', 'url'], 'properties': {'url': {'type': 'string', 'format': 'uri', 'description': 'Link URL (http or https; https recommended)'}, 'title': {'type': 'string', 'maxLength': 200, 'description': 'Link label'}, 'subtitle': {'type': 'string', 'maxLength': 200, 'description': 'Optional subtitle (supports markdown)'}, 'leading_icon': {'type': 'object', 'required': ['kind', 'value'], 'properties': {'url': {'type': 'string', 'description': 'Alias for value â\x80\x94 accepted for convenience when round-tripping get_state output. Prefer value for new input.'}, 'kind': {'enum': ['emoji', 'image'], 'type': 'string', 'description': 'Icon type'}, 'value': {'type': 'string', 'description': 'Emoji character(s) or image URL'}}, 'description': 'Optional leading icon for links the renderer does not auto-detect. Well-known social links (GitHub, Twitter/X, LinkedIn, etc.) always display branded icons regardless of this field. Provide kind + value.'}, 'trailing_badge': {'type': 'string', 'maxLength': 50, 'description': "Optional trailing badge text for emphasis or highlights (e.g. 'New', 'Preferred', 'DMs open'). Use to draw attention to important links or flag contact preferences."}}}, 'maxItems': 20, 'description': 'Replaces all links â\x80\x94 omit to keep existing links unchanged. Order matters â\x80\x94 place the most important link first. Well-known social links display branded icons automatically â\x80\x94 omit leading_icon for these.'}, 'site_id': {'type': 'string', 'description': 'Site ID, full URL (e.g. https://abc123.unu.lu), or bare hostname (e.g. abc123.unu.lu)'}, 'skin_id': {'enum': ['editorial-minimal', 'midnight-gradient', 'soft-pastel-craft', 'neo-brutal-poster', 'liquid-glass', 'carbon-folio', 'concrete-grid', 'signal-noir', 'studio-warm', 'sunset-decks', 'electric-sermon', 'hot-press', 'chrome-vogue', 'tidal-shelf', 'dune-light', 'moss-veil', 'golden-hour'], 'type': 'string', 'description': 'Optional theme skin. When omitted, the live site includes a visual theme chooser â\x80\x94 prefer omitting so the user can pick their own.'}, 'claim_url': {'type': 'string', 'description': 'Legacy parameter â\x80\x94 accepts a full Claim URL from older create-site responses. The server extracts the claim_token automatically. Prefer claim_token directly.'}, 'edit_token': {'type': 'string', 'description': 'JWT token for claimed sites (alternative to X-Edit-Token header)'}, 'claim_token': {'type': 'string', 'description': 'HMAC token for ephemeral sites (alternative to X-Claim-Token header)'}}}
Output schema
{'type': 'object', 'required': ['site_id', 'url', 'name', 'links'], 'properties': {'bio': {'type': 'string'}, 'url': {'type': 'string', 'format': 'uri'}, 'name': {'type': 'string'}, 'links': {'type': 'array', 'items': {'type': 'object', 'properties': {'id': {'type': 'string'}, 'url': {'type': 'string', 'format': 'uri'}, 'title': {'type': 'string'}, 'subtitle': {'type': 'string'}, 'leading_icon': {'oneOf': [{'type': 'object', 'required': ['kind', 'value'], 'properties': {'kind': {'const': 'emoji'}, 'value': {'type': 'string'}}}, {'type': 'object', 'required': ['kind', 'value'], 'properties': {'kind': {'const': 'image'}, 'value': {'type': 'string', 'format': 'uri'}}}]}, 'trailing_badge': {'type': 'string'}}}}, 'site_id': {'type': 'string'}, 'skin_id': {'enum': ['editorial-minimal', 'midnight-gradient', 'soft-pastel-craft', 'neo-brutal-poster', 'liquid-glass', 'carbon-folio', 'concrete-grid', 'signal-noir', 'studio-warm', 'sunset-decks', 'electric-sermon', 'hot-press', 'chrome-vogue', 'tidal-shelf', 'dune-light', 'moss-veil', 'golden-hour'], 'type': 'string'}, 'expires_at': {'type': 'string', 'format': 'date-time'}}}
Added
unulu_check_handle
Sept. 17, 2026, 7:57 a.m.
Added
unulu_update_site
Sept. 17, 2026, 7:57 a.m.
Added
unulu_get_state
Sept. 17, 2026, 7:57 a.m.
Added
unulu_create_site
Sept. 17, 2026, 7:57 a.m.