// docs / mcp
Connect an LLM to your analytics.
Abner exposes five read tools and one destructive site-admin tool over the Model Context Protocol. Once authorized, your agent picks the right tool for the job.
// on this page
01Install in Claude Code
One command, browser opens for consent, you're done:
claude mcp add --transport http --scope user abner https://mcp.abner.app/mcp
Use --scope user so the server is available from any directory. The first time you call a tool, Claude Code pops your browser to log in to Abner and approve the consent screen.
In Claude Desktop
Settings → Developer → Edit Config, add:
{
"mcpServers": {
"abner": {
"transport": "http",
"url": "https://mcp.abner.app/mcp"
}
}
}
In Cursor
Cursor follows the same Streamable HTTP spec. Add an HTTP MCP entry pointing at https://mcp.abner.app/mcp.
02Authentication (OAuth 2.1)
Abner is a fully spec-compliant OAuth 2.1 server with:
- Authorization Code grant + PKCE (S256 only)
- Refresh tokens that rotate on every use
- Dynamic Client Registration (RFC 7591) — your client self-registers; no manual client_id setup
- Resource indicators (RFC 8707) — tokens are audience-bound to the MCP server URL
Discovery happens via two well-known endpoints:
# issued by the resource server
GET https://mcp.abner.app/.well-known/oauth-protected-resource
# issued by the authorization server
GET https://www.abner.app/.well-known/oauth-authorization-server
Token lifetimes:
- Authorization code: 60 seconds, single-use
- Access token: 1 hour
- Refresh token: 30 days, rotated on every use
03Tools reference
Six tools under the single scope analytics:read: five query tools plus delete_site. Site deletion requires an owner/admin role and exact domain confirmation.
list_sites
no argsReturns every site the authenticated user can read, across every workspace. Always call this first if you don't already have a site_id.
query_metrics
swiss armyVisitors, pageviews, sessions, pageviews-per-visitor — grouped by any dimension, filtered by anything, over any preset window.
query_realtime
liveActive visitors in the last five minutes plus a stream of recent events (pageviews, custom events, scroll depth).
query_vitals
p75Core Web Vitals at the 75th percentile: LCP, FID, CLS, FCP, TTFB, INP.
query_funnel
statelessAn ordered, per-session conversion funnel over inline steps — nothing is stored server-side; pass the steps you want each time.
delete_site
destructivePermanently deletes a site and its Postgres plus ClickHouse data. Requires owner/admin membership and confirm_domain.
query_metrics — schema
type Input = {
site_id: "<uuid>", // from list_sites
metrics: ["visitors" | "pageviews" | "sessions" | "pageviews_per_visitor" | "events"],
dimensions: ["pathname" | "referrer_host" | "country" | "region" | "city" |
"browser" | "device_type" |
"utm_source" | "utm_medium" | "utm_campaign" |
"time_day" | "time_hour" | "event_name"],
filters?: { [dimension: string]: string },
period?: "24h" | "7d" | "14d" | "30d" | "month" | "year" | "all", // default 7d
limit?: number // default 50, max 1000
}
With no dimensions, returns a single overview row. Pass time_day or time_hour for a time series. Pass pathname for top pages. Pass filters: { country: "US" } to scope, or filters: { event_name: "signup" } to isolate a custom event, e.g. metrics: ["visitors"] to count signup conversions.
query_funnel — schema
type Input = {
site_id: "<uuid>", // from list_sites
steps: [string], // 2-8 ordered specs: "path:<pathname>" or "event:<name>"
period?: "24h" | "7d" | "14d" | "30d" | "month" | "year" | "all", // default 7d
window_seconds?: number // max seconds between first & last step in a session; default 86400, max 604800
}
Steps must occur in order within one session. Example — signup funnel over the last 30 days:
result = await session.call_tool("query_funnel", {
"site_id": site_id,
"period": "30d",
"steps": ["path:/pricing", "path:/signup", "event:signed_up"],
})
// → { site_id, period: "30d", steps: [
// { step: 1, label: "path:/pricing", sessions: 820, conversion_rate: 100, dropoff_rate: 0 },
// { step: 2, label: "path:/signup", sessions: 240, conversion_rate: 29.3, dropoff_rate: 70.7 },
// { step: 3, label: "event:signed_up", sessions: 164, conversion_rate: 20, dropoff_rate: 31.7 },
// ] }
delete_site — schema
type Input = {
site_id: "<uuid>", // from list_sites
confirm_domain: "example.com" // must exactly match the site's domain
}
This tool permanently deletes the site row, related Postgres data, and ClickHouse rows in analytics, vitals, search-console, forecast, conversion, and anomaly tables. The authenticated user must be an owner or admin of the site's workspace.
04Calling from code
The MCP server speaks plain Streamable HTTP — any MCP SDK works.
Python
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
async with streamablehttp_client(
"https://mcp.abner.app/mcp",
headers={"Authorization": f"Bearer {access_token}"},
) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
sites = await session.call_tool("list_sites", {})
result = await session.call_tool("query_metrics", {
"site_id": sites.structuredContent["sites"][0]["site_id"],
"metrics": ["visitors", "pageviews"],
"dimensions": ["time_day"],
"period": "30d",
})
TypeScript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
const transport = new StreamableHTTPClientTransport(
new URL("https://mcp.abner.app/mcp"),
{ requestInit: { headers: { Authorization: `Bearer ${token}` } } },
);
const client = new Client({ name: "my-app", version: "0.1.0" });
await client.connect(transport);
const result = await client.callTool({
name: "query_realtime",
arguments: { site_id },
});
Curl (for debugging)
curl -sS https://mcp.abner.app/mcp \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
05Token management
Every active access token issued for your account appears at /account/mcp-tokens/. You can revoke any token there — the client will need to re-authorize.
Tokens are stored hashed at rest (SHA-256). Plaintext is returned exactly once on issuance and never logged.
06Scopes & rate limits
v0.1 has a single scope, analytics:read. Query tools are read-only. delete_site is destructive and is additionally guarded by workspace role plus exact domain confirmation.
Rate limits will be applied per-token; documented values will land here once they're enforced. For now: be reasonable.