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.
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:
- Client reads
/.well-known/oauth-protected-resourceand/.well-known/oauth-authorization-server(discovery). - Client registers via
POST /oauth/register(RFC 7591 dynamic registration). - You are redirected to
/oauth/authorize, sign in with your dashboard account, and approve the consent screen (PKCE / S256 enforced). - Client exchanges the authorization code at
POST /oauth/tokenfor 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 requestsDELETE- 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:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {
"name": "my-client",
"version": "1.0.0"
}
}
}Response:
{
"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:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}Response:
{
"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:
{
"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:
{
"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:
{
"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:
{
"type": "object",
"properties": {}
}No parameters required.
Example Call:
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "list_sites",
"arguments": {}
}
}Example Response:
{
"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:
{
"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:
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {
"name": "create_site",
"arguments": {
"domain": "newsite.com",
"name": "New Site"
}
}
}Example Response:
{
"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):
{
"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:
{
"type": "object",
"properties": {
"site_id": {
"type": "string",
"description": "The site ID to look up"
}
},
"required": ["site_id"]
}Example Call:
{
"jsonrpc": "2.0",
"id": 6,
"method": "tools/call",
"params": {
"name": "get_site",
"arguments": {
"site_id": "site_abc123"
}
}
}Example Response:
{
"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:
{
"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:
{
"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:
{
"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:
{
"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:
{
"jsonrpc": "2.0",
"id": 8,
"method": "tools/call",
"params": {
"name": "invite_user",
"arguments": {
"email": "teammate@example.com",
"role": "member"
}
}
}Example Response:
{
"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
{
"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:
{
"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:
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:
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"}
}
}'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.
[
{
"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:
{
"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.
{
"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.