// 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.

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:

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:

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 args

Returns 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 army

Visitors, pageviews, sessions, pageviews-per-visitor — grouped by any dimension, filtered by anything, over any preset window.

query_realtime

live

Active visitors in the last five minutes plus a stream of recent events (pageviews, custom events, scroll depth).

query_vitals

p75

Core Web Vitals at the 75th percentile: LCP, FID, CLS, FCP, TTFB, INP.

query_funnel

stateless

An ordered, per-session conversion funnel over inline steps — nothing is stored server-side; pass the steps you want each time.

delete_site

destructive

Permanently 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.