Evo MCP server
Point any MCP client — Claude Code, Claude, Cursor, Codex — at Evo and it reads your creative-performance warehouse directly: spend and KPIs, creative tags, competitor intelligence, previews. Read-only, and scoped to the accounts each user already has.
https://evo.alison.ai/mcp
streamable-http
read-only
Overview
What you get, and what you can't do with it.
The MCP surface is the same analytics engine that Evo's own analyst agent runs on, exposed directly to your client. Every tool is a read: nothing on this surface writes, mutates, or spends model budget. There is no SQL to author — the tools take structured arguments (metrics, dimensions, filters) and compile them server-side against the KPI registry.
The server speaks streamable-http at /mcp. Both /mcp and
/mcp/ work, so a client that doesn't follow redirects still connects.
Quickstart
Add the server with no credential. Everything else happens in your browser, once.
- Add the serverOne command, just the URL — no token, no config file to edit.
- It comes back unauthorizedNot connected yet. The
401says where to authenticate, so the client opens that page (or hands you the link). - Sign in and approveEvo's page: email or SSO, pick which product this client may read, Approve.
- The token is stored for youMinted server-side, kept by the client, reused on every later call. Nobody has to handle it.
claude mcp add --transport http evo https://evo.alison.ai/mcp
Then run /mcp, pick evo, and authenticate — your browser opens on the Evo
sign-in page, you choose a product and approve, and Claude Code keeps the token it's handed.
In Settings → Connectors → Add custom connector, paste the server URL:
https://evo.alison.ai/mcp
Connecting opens the Evo sign-in page. Approve, and the connector is live in new chats.
Add to ~/.cursor/mcp.json (or .cursor/mcp.json to scope it to one
project), then use the Login action on the server in Cursor's MCP settings:
{
"mcpServers": {
"evo": { "url": "https://evo.alison.ai/mcp" }
}
}
codex mcp add evo --url https://evo.alison.ai/mcp
codex mcp login evo
login opens the same sign-in and consent pages and stores the token Codex is
handed. codex mcp logout evo disconnects it.
Any MCP client that speaks streamable-http and OAuth connects the same way — give it the URL and let it authenticate. To sanity-check the connection from a terminal, using the token your client obtained:
curl -s https://evo.alison.ai/mcp \
-H "Authorization: Bearer $EVO_TOKEN" \
-H 'Accept: application/json, text/event-stream' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | jq '.result.tools | length'
# 14
Authentication
Your users sign in with the identity they already have. Nothing to provision, no credential to distribute.
The browser flow
The client's first call comes back 401 with a WWW-Authenticate header
pointing at /.well-known/oauth-protected-resource/mcp. From there the client
registers itself and opens Evo, where the user sees two screens:
- Sign in to Evo — email and password, or Google, Microsoft or LinkedIn SSO.
- Connect “<client name>” — choose which product this client may read, then Approve or Deny.
On approval the server mints a token and hands it back through the redirect. The client stores it and reconnects on its own; it is never displayed, and the product chosen on that screen is the ceiling on what the client can ever see. Some clients open the browser for you, others print the URL — same pages either way.
Under the hood
OAuth 2.1 with PKCE and dynamic client registration, so no client is pre-provisioned on either side:
| Step | Endpoint |
|---|---|
| Challenge | 401 + WWW-Authenticate naming the resource metadata |
| Discovery | /.well-known/oauth-protected-resource/mcp → /.well-known/oauth-authorization-server |
| Registration | POST /oauth/register — RFC 7591, public client, no secret issued |
| Authorization | GET /oauth/authorize — the screens above; PKCE S256 required, an unregistered redirect_uri is rejected without a redirect |
| Token | POST /oauth/token — single-use code, PKCE-verified, 60s TTL |
Revoking access
GET /api/keys lists every client connected under your user — name, product, when it
was created, when it was last used. DELETE /api/keys/{id} disconnects one.
Revocation is immediate, not eventually consistent: authorization is re-resolved from the store on
every single request and never cached, so the next call from that client fails closed.
Scope & permissions
The client cannot widen its own reach. Only the server decides what's visible.
Every request resolves the token to the accounts its user holds within the product it was
approved for, and that set becomes the query grant. An integration_ids outside
the grant comes back as a validation_error — never as data, never as a silent empty
result. A token whose accounts carry no servable data is refused outright rather than handed
zeroes.
The same rule governs the product tools, so get_creative cannot reach an asset the
analytics tools would refuse.
draft_content — the agent loop's copy-writing tool — is deliberately not mounted
here: it spends a model call per invocation, so exposing it would widen what a connected client
can do rather than replace anything the read tools already offer.
Every call is logged with the calling user, the client's token and the product it was approved for, so traffic on this surface is attributable to a person rather than to a shared service account.
Tool reference
The map, not the API reference — your client reads every argument schema over the
protocol and fills them in itself. scope_overview is where a session starts: one call
that reports what these accounts can be measured and grouped by.
| Tool | What it does |
|---|---|
| Orientation | |
scope_overview | One call to orient: what you can measure, what you can group by, and the rules that apply. Start here. |
list_integrations | Ad-network connections in scope, with metadata. |
describe_integrations | Per-integration coverage, freshness, MMP and custom metrics. |
list_kpis | Available KPIs, raw metrics and dimensions, tiered to keep context small. |
list_features | Annotation features and their tag values for given integrations. |
discover_filters | Real filter values available for a scope and date range. |
describe_marketing_entity | Campaign / ad-group / ad metadata from the marketing catalog. |
get_asset_labels | Annotation labels for specific assets. |
| Analysis | |
run_report | Compose and execute a report: metrics × dimensions × filters over a date range. |
run_recipe | Run a pre-composed analysis from the recipe catalog in one call (tag pairs, KPI trend, top performers, annotation coverage, banded uplift). |
| Competitive intelligence | |
list_competition_metrics | SensorTower / Pathmatics measures and dimensions. |
discover_competition_filters | Distinct competition values: country, OS, ad type, competitor name. |
run_competition_report | Query competitor creatives and share of voice. |
| Media | |
get_creative | Public thumbnail / preview URLs for creative ids you already hold. Batch up to 25 per call. |
run_report, run_recipe or
run_competition_report into one batched get_creative call.Errors & limits
| Status | Meaning | Fix |
|---|---|---|
| 401 | Missing, invalid, revoked or expired token. | Reconnect — the client runs the sign-in flow again. |
| 403 | The token is valid, but its user has no servable accounts in the product it was approved for. | Reconnect and choose a different product, or have account access granted. |
| 429 | 20 rejected auth attempts from one IP inside 15 minutes. | Fix the credential and wait out the window. Authenticated traffic is never rate-limited. |
| 503 | The credential store is unreachable — our dependency, not your request. | Retry with backoff. |
validation_error | Arguments outside the grant, or a report the KPI registry can't compile. | Caller-fixable — the message names what to change. |
HTTP status codes cover transport and authentication only. A tool that refuses its arguments
answers 200 with an error_kind in the result, so your client can correct
itself and retry rather than treating it as an outage.
Per-call blast radius is bounded at 150 integration ids in one list, and
get_creative takes at most 25 ids per call. Neither is the authorization boundary —
the grant is.