@humanlabs-kr/link-mcp-server
v0.4.0
Published
Model Context Protocol (MCP) server for HumanlabsLink — connect Claude, Cursor, and other AI tools to your deep links and analytics
Downloads
963
Maintainers
Readme
Universal Link MCP Server
Connect Claude, Cursor, Claude Code, and any MCP-compatible AI tool to the Human Labs deep-link platform.
Quick start · Available tools · Configuration
The Model Context Protocol server for the Human Labs self-hosted universal-link platform (link.humanlabs.world). It exposes 15 tools that let an AI assistant analyze data with free-form read-only SQL (schema + query), register apps, manage deep links, configure templates/webhooks, and generate ready-to-paste SDK integration code — all in natural language.
Data reads go through raw SQL rather than fixed endpoints: call schema to discover the tables/columns, then query to run any read-only SELECT. The rest of the tools are write/management actions. There are intentionally no list_*/get_* read tools — an analyst queries the database directly.
This is the operator's control plane for the universal-link API, which sits behind Cloudflare Access. The server authenticates non-interactively with a Cloudflare Access service token.
The server supports two transport modes:
- stdio (default) — runs locally as a subprocess of your AI client, via
npx - HTTP — a stateless multi-tenant server you host yourself
What you can do with it
Once connected, you can ask things like:
- "Register a new app called Dollar with bundle id world.humanlabs.dollar"
- "Create a deep link for the spring sale with UTM source=instagram, campaign=spring-sale"
- "Which links drove the most clicks last week?" → the AI reads
schema, then writes and runs aSELECT - "Break down installs by attribution method and country for the last 30 days"
- "Add a webhook that fires on every install event"
- "Help me add the SDK to my React Native app"
For analytical questions the AI first calls schema, then composes a read-only SELECT and runs it via query, so it can answer anything the data supports instead of being limited to canned reports. Writes go through the dedicated management tools.
Quick start
1. Get a Cloudflare Access service token
The universal-link API is protected by Cloudflare Access. Ask an admin for a service token scoped to the API — you'll receive a Client ID (ends in .access) and a Client Secret.
2. Add to your MCP client (stdio, recommended)
No clone or build needed — npx pulls the published package. Requires Node.js 18+.
Claude Code:
claude mcp add universal-link \
-e HUMANLABSLINK_BASE_URL=https://link.humanlabs.world/api \
-e CF_ACCESS_CLIENT_ID=<your-client-id>.access \
-e CF_ACCESS_CLIENT_SECRET=<your-client-secret> \
-- npx -y @humanlabs-kr/link-mcp-serverClaude Desktop — edit your claude_desktop_config.json:
{
"mcpServers": {
"universal-link": {
"command": "npx",
"args": ["-y", "@humanlabs-kr/link-mcp-server"],
"env": {
"HUMANLABSLINK_BASE_URL": "https://link.humanlabs.world/api",
"CF_ACCESS_CLIENT_ID": "<your-client-id>.access",
"CF_ACCESS_CLIENT_SECRET": "<your-client-secret>"
}
}
}
}Cursor — add to Cursor → Settings → MCP Servers:
{
"universal-link": {
"command": "npx",
"args": ["-y", "@humanlabs-kr/link-mcp-server"],
"env": {
"HUMANLABSLINK_BASE_URL": "https://link.humanlabs.world/api",
"CF_ACCESS_CLIENT_ID": "<your-client-id>.access",
"CF_ACCESS_CLIENT_SECRET": "<your-client-secret>"
}
}
}The base URL defaults to
https://link.humanlabs.world/api, soHUMANLABSLINK_BASE_URLis optional — keep it for clarity or to point at another instance.
3. (Optional) HTTP transport
Host the stateless HTTP server yourself (see Dockerfile) and connect via URL. In HTTP mode the per-request Authorization: Bearer header carries the API key, so a single deployment can serve multiple users.
{
"mcpServers": {
"universal-link": {
"type": "http",
"url": "https://<your-mcp-host>/mcp",
"headers": { "Authorization": "Bearer dl_your_api_key_here" }
}
}
}Available tools
Data analysis (2)
| Tool | Description |
|----------|----------------------------------------------------------------------------------------------------------|
| schema | Return every universal_link table with columns, types, approximate row counts, and analyst notes |
| query | Run a single read-only SELECT/WITH statement and return the rows. Writes/DDL are rejected at the DB level |
Raw SQL runs server-side inside a
READ ONLYtransaction, de-escalated to a role that can only read theuniversal_linkschema (never other apps' data on the shared DB). Tables must be schema-qualified, e.g.universal_link.click_events. Timestamps are stored in UTC.
Apps (3)
| Tool | Description |
|--------------|---------------------------------------------------------------------|
| create_app | Register a first-party app (slug, iOS team/bundle, Android pkg/SHA) |
| update_app | Update any field on an app |
| delete_app | Delete a registered app |
Links (3)
| Tool | Description |
|---------------|----------------------------------------------------------------------------|
| create_link | Create a deep link with iOS/Android/web URLs, UTM, targeting, and more |
| update_link | Update any field on an existing link |
| delete_link | Permanently delete a link |
Templates (2)
| Tool | Description |
|------------------------|---------------------------------------------------------|
| create_template | Create a new template with default destinations and UTM |
| set_default_template | Mark a template as the workspace default |
Webhooks (4)
| Tool | Description |
|------------------|----------------------------------------------|
| create_webhook | Create a webhook for link/install events |
| update_webhook | Update a webhook's URL, events, or secret |
| delete_webhook | Delete a webhook |
| test_webhook | Send a test payload to a webhook |
SDK helper (1)
| Tool | Description |
|---------------------------|----------------------------------------------------------------------------------------------------|
| get_sdk_install_snippet | Generate ready-to-paste SDK init code for any platform (react-native, expo, ios, android, flutter) |
Configuration
stdio transport
| Environment variable | Required | Default | Description |
|---------------------------|----------|------------------------------------|-----------------------------------------------------------------|
| CF_ACCESS_CLIENT_ID | Yes* | — | Cloudflare Access service token Client ID (ends in .access) |
| CF_ACCESS_CLIENT_SECRET | Yes* | — | Cloudflare Access service token Client Secret |
| HUMANLABSLINK_API_KEY | No | — | Legacy bearer API key (dl_…); alternative to the CF token |
| HUMANLABSLINK_BASE_URL | No | https://link.humanlabs.world/api | Override for another self-hosted instance |
* Either the CF Access service token (both vars) or HUMANLABSLINK_API_KEY must be set. Without auth, control-plane calls return 401.
HTTP transport
| Environment variable | Required | Default | Description |
|---------------------------|----------|------------------------------------|----------------------------------------------|
| PORT | No | 3001 | HTTP server port |
| HUMANLABSLINK_BASE_URL | No | https://link.humanlabs.world/api | Override for another self-hosted instance |
| CF_ACCESS_CLIENT_ID | No | — | CF Access service token (server → API) |
| CF_ACCESS_CLIENT_SECRET | No | — | CF Access service token (server → API) |
In HTTP mode the workspace API key is provided per-request via the Authorization: Bearer header, not as an environment variable — so one deployment can serve multiple users.
Security and privacy
- No data is stored or logged by the MCP server. It is a stateless translator between MCP requests and the universal-link REST API. Every tool call is a fresh HTTP request.
- The CF Access service token gates the control plane. The server can only reach the API through Cloudflare Access; the token is read from your local MCP client config and sent only to
link.humanlabs.world. - Raw SQL is read-only and schema-scoped. The
querytool's SQL never touches the database directly — it is executed server-side by the API inside aREAD ONLYtransaction, de-escalated (SET LOCAL ROLE) to a role withSELECTon theuniversal_linkschema only. Writes/DDL and reads of other schemas on the shared cluster are rejected by Postgres. - stdio mode: the server runs locally as a subprocess; credentials live in your local client config.
Development
Part of the universal-link monorepo (pnpm + turbo).
# from the repo root
pnpm install
pnpm --filter @humanlabs-kr/link-mcp-server build
# watch mode
pnpm --filter @humanlabs-kr/link-mcp-server dev
# test stdio transport
CF_ACCESS_CLIENT_ID=... CF_ACCESS_CLIENT_SECRET=... node apps/mcp-server/dist/index.js
# test HTTP transport
node apps/mcp-server/dist/http.js # starts on port 3001
curl http://localhost:3001/health # health checkReleasing
Publishing is automated. Bump version in apps/mcp-server/package.json, merge to main, and promote the production release — the _mcp-publish workflow publishes the new version to npm (a version already on npm is skipped).
License
MIT
