@icubeswire/unified-mcp-server
v0.2.1
Published
MCP server exposing read-only APIs of the Unified Backend (influencer discovery, campaigns, plans).
Maintainers
Readme
unified-mcp-server
A Model Context Protocol server that exposes the read-only APIs of the Unified Backend (influencer-marketing platform) as MCP tools, so an MCP client (Claude Desktop, Claude Code, etc.) can query influencers, campaigns, and plans in natural language.
- Transport: stdio
- Access: read-only (every tool is an HTTP
GET) - Auth: per-user Laravel Sanctum bearer token — the backend applies that user's own permissions/policies to each request. There is no elevated service account.
Tools (v1)
Influencer discovery
| Tool | Endpoint |
| --------------------------------- | ------------------------------------------ |
| discovery_search | GET /discovery-search |
| get_influencer_by_handle | GET /discovery-search/influencer/details |
| discovery_list | GET /get-discovery |
| get_influencer_growth | GET /discovery/growth/{id} |
| get_influencer_audience | GET /discovery/audience/{id} |
| get_influencer_engagement | GET /discovery/engagement/{id} |
| get_influencer_content | GET /discovery/content/{id} |
| get_influencer_posts | GET /discovery/posts/{id} |
| get_influencer_related_profiles | GET /discovery/related-profiles/{id} |
| get_influencer_brand_mentions | GET /discovery/brand-mentions/{id} |
Campaigns
| Tool | Endpoint |
| -------------------------- | -------------------------------------- |
| list_campaigns | GET /campaign/all-new |
| get_campaign_analytics | GET /campaign/analytics/{offer_id} |
| get_campaign_influencers | GET /campaign/influencers/{offer_id} |
Plans
| Tool | Endpoint |
| ------------------------------- | ------------------------------------------------------- |
| get_plan_platform_statistics | GET /plan/platform/statistics/{plan_id} |
| get_plan_platform_influencers | GET /plan/platform/influencer/{plan_id}/{platform_id} |
| get_plan_audience_geo | GET /plan/audience/geo/{plan_id}/{platform_id} |
The per-influencer detail tools take an InfluencerDetail id (_id). Resolve a
handle/name to an id first with discovery_search or get_influencer_by_handle.
All tools are annotated readOnlyHint + openWorldHint, so clients like Claude
can treat them as safe reads.
Prompts
MCP Prompts appear in Claude as slash-commands. Each one steers the model through the correct tool chain:
| Prompt | Args | What it does |
| --------------------- | ---------- | --------------------------------------------------------- |
| influencer-brief | handle | Resolve handle → growth/audience/engagement/content brief |
| compare-influencers | handles | Comma-separated handles → side-by-side comparison table |
| campaign-recap | offer_id | Campaign analytics + influencers → performance recap |
Quick start
npx -y @icubeswire/unified-mcp-server loginOpens unified.icubeswire.co/mcp/authorize in your browser. You're already
signed in to the dashboard, so you just click Approve — the CLI receives the
token, registers itself with Claude Code, and bounces you back to the editor.
No token to mint, nothing to paste.
Restart Claude Code and run /mcp to verify (16 tools + 3 prompts).
- Binds a one-shot listener on
127.0.0.1:<ephemeral port>(loopback only). - Opens
/mcp/authorize?port=…&state=…—stateis a CSRF nonce. - On approval the dashboard redirects back with a short-lived one-time code — never the token itself, since redirect URLs land in browser history, referrer headers, and proxy logs.
- The code is exchanged over
POST /mcp/tokenfor the Sanctum token. claude mcp add … -s useris run for you; if theclaudebinary isn't onPATH, the equivalent command is printed instead.
| Var | Purpose |
| ------------------------ | ------------------------------------------------------------- |
| UNIFIED_WEB_BASE_URL | Dashboard origin (default https://unified.icubeswire.co) |
| UNIFIED_API_BASE_URL | API base used for the code exchange |
| UNIFIED_MCP_NO_BROWSER | Set to 1 over SSH/CI — prints the URL instead of opening it |
Manual setup
If you'd rather configure it by hand, or login isn't available to you.
The package is public on npm, so npx fetches it with no registry auth. You
still need your own Sanctum token — the package ships no credentials, and every
request is made as you.
Claude Code
claude mcp add unified-backend -s user \
--env UNIFIED_API_BASE_URL=https://apis.icubeswire.co/api/v1 \
--env UNIFIED_API_TOKEN=<your-sanctum-token> \
-- npx -y @icubeswire/unified-mcp-serverClaude Desktop / Cursor / other clients
Add to the client's MCP config (Claude Desktop:
claude_desktop_config.json; Cursor: .cursor/mcp.json):
{
"mcpServers": {
"unified-backend": {
"command": "npx",
"args": ["-y", "@icubeswire/unified-mcp-server"],
"env": {
"UNIFIED_API_BASE_URL": "https://apis.icubeswire.co/api/v1",
"UNIFIED_API_TOKEN": "<your-sanctum-token>"
}
}
}
}Restart the client, then verify (Claude Code: /mcp → unified-backend, 16
tools + 3 prompts).
Getting a Sanctum token
Issued by the Laravel app for a real user (the server acts as that user). From
the backend (tinker):
$user = App\Models\User::find(<id>);
$user->createToken('mcp')->plainTextToken; // paste into UNIFIED_API_TOKENConfiguration
Configure via environment variables (see .env.example):
| Var | Required | Default | Description |
| -------------------------------- | -------- | ---------------------- | ------------------------------------------------------------------ |
| UNIFIED_API_BASE_URL | yes | — | API base incl. /api/v1, e.g. https://apis.icubeswire.co/api/v1 |
| UNIFIED_API_TOKEN | yes | — | A Sanctum personal access token for the acting user |
| UNIFIED_API_TIMEOUT_MS | no | 30000 | Per-request timeout |
| UNIFIED_API_MAX_RETRIES | no | 2 | Retries on 429 / 5xx / network (honours Retry-After) |
| UNIFIED_API_RETRY_BASE_MS | no | 500 | Base for exponential backoff + jitter |
| UNIFIED_API_MAX_CONCURRENCY | no | 4 | Max simultaneous in-flight requests |
| UNIFIED_API_MAX_RESPONSE_BYTES | no | 5000000 | Reject responses larger than this (0 disables) |
| UNIFIED_API_USER_AGENT | no | unified-mcp-server/… | User-Agent sent on every request |
| LOG_LEVEL | no | info | error | warn | info | debug (JSON logs to stderr) |
Getting a Sanctum token
The token is issued by the Laravel app for a real user. From the backend
(tinker):
$user = App\Models\User::find(<id>);
$user->createToken('mcp')->plainTextToken; // paste into UNIFIED_API_TOKENRun from source (contributors)
git clone https://bitbucket.org/prawezicw/unified-mcp-server.git
cd unified-mcp-server
npm install # builds automatically via "prepare"
cp .env.example .env # fill in UNIFIED_API_BASE_URL + UNIFIED_API_TOKEN
npm run dev # watch mode (loads .env)To point an MCP client at a local checkout instead of npm, use
"command": "node", "args": ["<abs-path>/dist/index.js"].
Publishing (maintainers)
The package is public on npmjs.org under the @icubeswire scope
(publishConfig.access: "public"). You must be an org member to publish.
It contains no credentials — every request is authenticated with the token the end user supplies, so a public package grants no access to anything.
npm login # once, per machine
npm version patch|minor|major # bump package.json + create the git tag
npm publish # runs `npm run check` + build first
git push --follow-tags # the version commit + tag are local until thisprepublishOnly runs the full check (typecheck + lint + format + tests) and
prepare builds dist/, so a broken build can't be published. With 2FA on the
account, add --otp=<code> to npm publish.
When bumping, update the version in three places — package.json (via
npm version), SERVER_VERSION in src/index.ts, and the userAgent default
in src/config.ts. test/version.test.ts fails the build if they disagree, so
a drifted release can't be published rather than silently reporting the wrong
version to Claude and in request logs.
Production behaviours
- Config validation —
src/config.tsvalidates the whole environment up front (zod) and fails with a single, human-readable list of problems. - Resilience — bounded concurrency, per-request timeout, and retries with
exponential backoff + jitter on transient failures (429 / 5xx / network),
honouring
Retry-After. A response-size guard prevents a huge payload from blowing up the model context. - Structured logging — JSON log lines on stderr only (stdout is the
JSON-RPC transport). Bearer tokens and
token=/api_key=query params are redacted everywhere. - Graceful shutdown —
SIGINT/SIGTERM/ stdin-close close the server cleanly;uncaughtException/unhandledRejectionare logged and exit non-zero.
Development
npm run dev # watch mode (loads .env)
npm test # unit tests (node:test, no network)
npm run check # typecheck + lint + format:check + test
npm run smoke # boot the built server and list tools (loads .env)
npm run smoke -- discovery_search '{"q":"virat"}' # call one tool end-to-endCI (bitbucket-pipelines.yml) runs typecheck → lint → format:check → test →
build on every PR and on main.
Design notes
- The server is a thin HTTP proxy: it forwards each tool call to the existing API and returns the raw JSON. It does not touch Mongo/Postgres/OpenSearch directly, so all existing auth, validation, and business logic still apply.
- Array params are sent Laravel-style (
key[]=), and nested objects (e.g.discovery_list'sfilter) are JSON-encoded. - Errors (non-2xx, timeouts, network) are returned as
isErrortool results with the backend's message/body, never as a transport crash.
Extending
Add a tool by calling server.registerTool(...) in the relevant
src/tools/*.ts module (or a new one wired up in src/index.ts). Keep this
server read-only — writes belong behind a separate, explicitly-scoped surface.
