MCP Server

Connect AI agents to your analytics data using the Model Context Protocol (MCP). This guide covers authentication, available tools, client configuration, and integration examples.

Overview

The Model Context Protocol (MCP) is an open standard for connecting AI agents and LLMs to external data sources and tools. Agent Analytics implements an MCP server that provides AI assistants like Claude with programmatic access to web analytics data and site management capabilities.

Why MCP Matters for AI Agents

MCP enables AI assistants to:

  • Query real-time analytics data across sites
  • Create and manage tracked websites
  • Record custom server-side events
  • Access visitor metrics, pageviews, referrers, and more
  • Invite team members to access analytics

Protocol Details

Agent Analytics implements MCP using:

  • Protocol: JSON-RPC 2.0
  • Transport: Streamable HTTP (stateless mode)
  • Protocol Version: 2025-03-26
  • Server Name: Agent Analytics
  • Server Version: 0.0.1

The server uses MCP's Streamable HTTP transport in stateless mode, which means each request is independent with no persistent connection required. Server-Sent Events (SSE) are not supported, but batch requests are fully supported.

Authentication

Every MCP request must be authenticated. There are two supported methods:

  • OAuth 2.1 (recommended for remote clients) — used automatically by Claude web and desktop when you add Agent Analytics as a custom connector. No API key handling required.
  • API key — a legacy aa_live_ Bearer token, useful for scripts and custom agents.
http
Authorization: Bearer <oauth_token_or_api_key>

Unauthenticated requests receive 401 Unauthorized with a WWW-Authenticate challenge pointing at the OAuth metadata, which lets compatible clients start the OAuth flow automatically.

Connecting with OAuth (no local config)

Add https://api.statscontext.com/mcp as a custom/remote MCP connector in Claude. The client discovers and registers itself automatically, then sends you to sign in with your Agent Analytics dashboard account and approve access. The connection flow:

  1. Client reads /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server (discovery).
  2. Client registers via POST /oauth/register (RFC 7591 dynamic registration).
  3. You are redirected to /oauth/authorize, sign in with your dashboard account, and approve the consent screen (PKCE / S256 enforced).
  4. Client exchanges the authorization code at POST /oauth/token for an access token (and a refresh token) and uses it as the Bearer token for all MCP calls.

Connecting with an API key

Generate an API key from your dashboard and pass it as a Bearer token. The token identifies your account and scopes all operations to your account's sites and data.

Getting Started with MCP

Endpoint

URL: POST https://api.statscontext.com/mcp

Content-Type: application/json

Supported HTTP Methods:

  • POST - Send JSON-RPC 2.0 requests
  • DELETE - Close session (returns 204 No Content)
  • GET - Not supported (SSE disabled, returns 405)

Initialization Handshake

Before using tools, clients must send an initialize request to establish protocol compatibility.

Request:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-03-26",
    "capabilities": {},
    "clientInfo": {
      "name": "my-client",
      "version": "1.0.0"
    }
  }
}

Response:

json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-03-26",
    "capabilities": {
      "tools": {}
    },
    "serverInfo": {
      "name": "Agent Analytics",
      "version": "0.0.1"
    }
  }
}

Tool Discovery

After initialization, list available tools using the tools/list method.

Request:

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list"
}

Response:

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "tools": [
      {
        "name": "query_stats",
        "description": "Query analytics data for a site...",
        "inputSchema": { ... }
      },
      {
        "name": "list_sites",
        "description": "List all sites...",
        "inputSchema": { ... }
      },
      {
        "name": "create_site",
        "description": "Register a new site...",
        "inputSchema": { ... }
      },
      {
        "name": "get_site",
        "description": "Get details for a specific site...",
        "inputSchema": { ... }
      },
      {
        "name": "track_event",
        "description": "Record a custom analytics event...",
        "inputSchema": { ... }
      },
      {
        "name": "invite_user",
        "description": "Send an invitation to a user...",
        "inputSchema": { ... }
      }
    ]
  }
}

Available Tools

1. query_stats

Query analytics data for a site. Returns comprehensive metrics including pageviews, visitors, sessions, bounce rate, duration, top pages, referrers, countries, devices, browsers, custom events, and timeseries data.

Input Schema:

json
{
  "type": "object",
  "properties": {
    "site_id": {
      "type": "string",
      "description": "The site ID to query stats for"
    },
    "period": {
      "type": "string",
      "enum": ["today", "7d", "30d", "90d", "custom"],
      "default": "7d",
      "description": "Time period"
    },
    "metric": {
      "type": "string",
      "enum": [
        "all",
        "pageviews",
        "visitors",
        "sessions",
        "bounce_rate",
        "avg_duration",
        "top_pages",
        "referrers",
        "countries",
        "devices",
        "browsers",
        "custom_events",
        "timeseries"
      ],
      "default": "all",
      "description": "Which metric to return"
    },
    "from": {
      "type": "string",
      "description": "Start date (YYYY-MM-DD) for custom period"
    },
    "to": {
      "type": "string",
      "description": "End date (YYYY-MM-DD) for custom period"
    },
    "limit": {
      "type": "number",
      "default": 10,
      "description": "Max results for list metrics (1-100)"
    }
  },
  "required": ["site_id"]
}

Example Call:

json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "query_stats",
    "arguments": {
      "site_id": "site_abc123",
      "period": "30d",
      "metric": "all",
      "limit": 20
    }
  }
}

Example Response:

json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"pageviews\": 15420,\n  \"visitors\": 3241,\n  \"sessions\": 4892,\n  \"bounce_rate\": 0.42,\n  \"avg_duration\": 185.3,\n  \"top_pages\": [\n    {\"path\": \"/\", \"views\": 5234},\n    {\"path\": \"/blog\", \"views\": 2341}\n  ],\n  \"referrers\": [...],\n  \"countries\": [...],\n  \"devices\": {...},\n  \"browsers\": {...},\n  \"custom_events\": [...],\n  \"timeseries\": [...]\n}"
      }
    ]
  }
}

2. list_sites

List all sites registered under the authenticated account.

Input Schema:

json
{
  "type": "object",
  "properties": {}
}

No parameters required.

Example Call:

json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tools/call",
  "params": {
    "name": "list_sites",
    "arguments": {}
  }
}

Example Response:

json
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"sites\": [\n    {\n      \"id\": \"site_abc123\",\n      \"account_id\": \"acc_xyz789\",\n      \"domain\": \"example.com\",\n      \"name\": \"My Example Site\",\n      \"created_at\": \"2026-01-15T10:30:00Z\"\n    },\n    {\n      \"id\": \"site_def456\",\n      \"account_id\": \"acc_xyz789\",\n      \"domain\": \"blog.example.com\",\n      \"name\": \"Example Blog\",\n      \"created_at\": \"2026-02-01T14:20:00Z\"\n    }\n  ]\n}"
      }
    ]
  }
}

3. create_site

Register a new site for tracking. Returns the site details and an HTML tracking snippet to embed in your website.

Input Schema:

json
{
  "type": "object",
  "properties": {
    "domain": {
      "type": "string",
      "description": "The domain of the site (e.g., example.com)"
    },
    "name": {
      "type": "string",
      "description": "A friendly name for the site"
    }
  },
  "required": ["domain", "name"]
}

Example Call:

json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "create_site",
    "arguments": {
      "domain": "newsite.com",
      "name": "New Site"
    }
  }
}

Example Response:

json
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"site\": {\n    \"id\": \"site_new789\",\n    \"account_id\": \"acc_xyz789\",\n    \"domain\": \"newsite.com\",\n    \"name\": \"New Site\",\n    \"created_at\": \"2026-02-10T09:15:00Z\"\n  },\n  \"snippet\": \"<script defer data-site-id=\\\"site_new789\\\" src=\\\"https://newsite.com/tracker.js\\\"></script>\"\n}"
      }
    ]
  }
}

Error Response (Duplicate Domain):

json
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Error: A site with this domain already exists in your account."
      }
    ],
    "isError": true
  }
}

4. get_site

Get details for a specific site.

Input Schema:

json
{
  "type": "object",
  "properties": {
    "site_id": {
      "type": "string",
      "description": "The site ID to look up"
    }
  },
  "required": ["site_id"]
}

Example Call:

json
{
  "jsonrpc": "2.0",
  "id": 6,
  "method": "tools/call",
  "params": {
    "name": "get_site",
    "arguments": {
      "site_id": "site_abc123"
    }
  }
}

Example Response:

json
{
  "jsonrpc": "2.0",
  "id": 6,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"site\": {\n    \"id\": \"site_abc123\",\n    \"account_id\": \"acc_xyz789\",\n    \"domain\": \"example.com\",\n    \"name\": \"My Example Site\",\n    \"created_at\": \"2026-01-15T10:30:00Z\"\n  }\n}"
      }
    ]
  }
}

5. track_event

Record a custom analytics event for a site. Use this for server-side event tracking when you can't use the client-side tracker.

Input Schema:

json
{
  "type": "object",
  "properties": {
    "site_id": {
      "type": "string",
      "description": "The site ID to track the event for"
    },
    "name": {
      "type": "string",
      "description": "Event name (e.g., 'signup', 'purchase')"
    },
    "properties": {
      "type": "object",
      "additionalProperties": true,
      "description": "Optional key-value properties for the event"
    }
  },
  "required": ["site_id", "name"]
}

Example Call:

json
{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/call",
  "params": {
    "name": "track_event",
    "arguments": {
      "site_id": "site_abc123",
      "name": "purchase",
      "properties": {
        "amount": 49.99,
        "currency": "USD",
        "product_id": "prod_123"
      }
    }
  }
}

Example Response:

json
{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"event_id\": \"evt_xyz789\",\n  \"success\": true\n}"
      }
    ]
  }
}

6. invite_user

Send an invitation to a user's email to join your account. The invited user will receive access to the account's sites and analytics.

Input Schema:

json
{
  "type": "object",
  "properties": {
    "email": {
      "type": "string",
      "description": "Email address to invite"
    },
    "role": {
      "type": "string",
      "enum": ["owner", "member"],
      "default": "member",
      "description": "Role for the invited user"
    }
  },
  "required": ["email"]
}

Example Call:

json
{
  "jsonrpc": "2.0",
  "id": 8,
  "method": "tools/call",
  "params": {
    "name": "invite_user",
    "arguments": {
      "email": "teammate@example.com",
      "role": "member"
    }
  }
}

Example Response:

json
{
  "jsonrpc": "2.0",
  "id": 8,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\n  \"invitation_id\": \"inv_xyz789\",\n  \"email\": \"teammate@example.com\",\n  \"role\": \"member\",\n  \"token\": \"a1b2c3d4e5f6...\",\n  \"expires_at\": \"2026-02-18 10:30:00\",\n  \"accept_url\": \"/invite/a1b2c3d4e5f6...\"\n}"
      }
    ]
  }
}

Client Configuration

Claude (web & desktop) — remote connector via OAuth

The simplest option: in Claude, add a custom connector with the URL https://api.statscontext.com/mcp. Claude handles OAuth automatically — you only need to sign in with your dashboard account and approve access. No JSON config or API key required.

Claude Desktop — manual API key config

Alternatively, add Agent Analytics to your Claude Desktop configuration file with an API key:

macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

Windows: %APPDATA%\Claude\claude_desktop_config.json

claude_desktop_config.json
{
  "mcpServers": {
    "agent-analytics": {
      "url": "https://api.statscontext.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY_HERE"
      },
      "transport": "http"
    }
  }
}

After adding the configuration, restart Claude Desktop. The Agent Analytics tools will be available in all conversations.

Claude Code

For Claude Code CLI, add to your MCP configuration:

~/.config/claude/mcp_config.json
{
  "mcpServers": {
    "agent-analytics": {
      "url": "https://api.statscontext.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_API_KEY_HERE"
      },
      "transport": "http"
    }
  }
}

Python Client Example

Here's a complete Python client implementation for connecting to the Agent Analytics MCP server:

agent_analytics_client.py
import requests
import json

class AgentAnalyticsMCP:
    def __init__(self, base_url: str, token: str):
        self.base_url = base_url
        self.headers = {
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        }
        self.request_id = 0

    def call(self, method: str, params: dict = None) -> dict:
        self.request_id += 1
        payload = {
            "jsonrpc": "2.0",
            "id": self.request_id,
            "method": method,
            "params": params or {}
        }
        response = requests.post(self.base_url, json=payload, headers=self.headers)
        return response.json()

    def initialize(self):
        return self.call("initialize", {
            "protocolVersion": "2025-03-26",
            "capabilities": {},
            "clientInfo": {"name": "python-client", "version": "1.0.0"}
        })

    def list_tools(self):
        return self.call("tools/list")

    def call_tool(self, name: str, arguments: dict):
        return self.call("tools/call", {"name": name, "arguments": arguments})

# Usage
client = AgentAnalyticsMCP("https://api.statscontext.com/mcp", "your_api_key_here")
client.initialize()
sites = client.call_tool("list_sites", {})
print(sites)

curl Examples

Quick test with curl:

Initialize
curl -X POST https://api.statscontext.com/mcp \  -H "Authorization: Bearer YOUR_API_KEY" \  -H "Content-Type: application/json" \  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "initialize",
    "params": {
      "protocolVersion": "2025-03-26",
      "capabilities": {},
      "clientInfo": {"name": "curl", "version": "1.0"}
    }
  }'
List Sites
curl -X POST https://api.statscontext.com/mcp \  -H "Authorization: Bearer YOUR_API_KEY" \  -H "Content-Type: application/json" \  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "list_sites",
      "arguments": {}
    }
  }'

Batch Requests

Multiple JSON-RPC requests can be batched in a single HTTP request by sending an array of request objects. The server responds with an array of responses in the same order.

json
[
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  },
  {
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "list_sites",
      "arguments": {}
    }
  },
  {
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "query_stats",
      "arguments": {
        "site_id": "site_abc123",
        "period": "7d"
      }
    }
  }
]

Batch requests are useful for reducing network round-trips when you need to perform multiple operations.

Error Handling

JSON-RPC Error Codes

Standard JSON-RPC 2.0 errors are returned with these codes:

  • -32700 - Parse error (invalid JSON)
  • -32601 - Method not found
  • -32602 - Invalid params (unknown tool)

Example error response:

json
{
  "jsonrpc": "2.0",
  "id": 10,
  "error": {
    "code": -32601,
    "message": "Method not found: unknown_method"
  }
}

Tool-Level Errors

Tools return errors via the isError flag in the result object. These are returned as successful JSON-RPC responses (status 200) but with the isError: true flag set.

json
{
  "jsonrpc": "2.0",
  "id": 10,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "Error: Site not found or not owned by this account."
      }
    ],
    "isError": true
  }
}

Common tool errors include:

  • Site not found or not owned by account
  • Duplicate domain when creating a site
  • Pending invitation already exists
  • Invalid date format for custom periods

Security

Token Security

Account tokens provide full access to all sites and data for that account. Treat them as sensitive credentials:

  • Never commit tokens to version control
  • Use environment variables or secure configuration management
  • Rotate tokens regularly for long-lived integrations
  • Revoke tokens immediately if compromised

HTTPS Required

Always use HTTPS in production to protect tokens and data in transit. The production API endpoint enforces HTTPS.

Scope Isolation

All operations are automatically scoped to the authenticated account. Sites and data from other accounts are never accessible, even with a valid token.

Rate Limiting

Consider implementing rate limits on the client side to avoid overwhelming the server. Batch requests can help reduce the number of HTTP connections.

Additional Resources