UsherStats for AI agents (MCP)
UsherStats is a remote Model Context Protocol server. Point any MCP client -- Claude Code, Claude Desktop, Cursor, or your own agent -- at
https://api.usherstats.com/mcpwith an API token, and the agent can do what the token can: list and create sites, add hostnames, read the install snippet and status, manage keys, settings, tokens and team members. Every tool is an API route, called as your token, so scopes, site restrictions and plan limits apply exactly as they do over HTTP. Analytics tools (stats, top pages and sources, crawlers, journeys, experiments, bot rules, search data) appear here as soon as those API routes ship, with no change on your side.
- Endpoint:
POST /mcp(Streamable HTTP, JSON-RPC 2.0, one JSON answer per request, no sessions). - Protocol versions:
2026-07-28(stateless: noinitialize; version in each request's_metaand theMCP-Protocol-Versionheader) and2025-03-26,2025-06-18,2025-11-25(opening withinitialize). - Authentication:
Authorization: Bearer us_live_...only. Sign-in cookies are not accepted on/mcp. - The HTTP API behind the tools is described at https://api.usherstats.com/v1/openapi.json (OpenAPI 3.1).
1. Make a token for the agent
Give the agent only what it needs. A token can be limited to some sites (siteIds) and given an expiry; it is shown
once. For an agent that reports and helps with setup but changes nothing:
curl https://api.usherstats.com/v1/tokens \
-H "Authorization: Bearer $US_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "claude (read only)", "scopes": ["sites:read", "analytics:read", "members:read"], "expiresAt": "2027-06-30T00:00:00Z"}'For an agent that may also set sites up (create sites, add hostnames, change settings), add sites:write:
curl https://api.usherstats.com/v1/tokens \
-H "Authorization: Bearer $US_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "claude (setup)", "scopes": ["sites:read", "sites:write", "analytics:read"], "expiresAt": "2027-06-30T00:00:00Z"}'You can also make tokens in the dashboard under Settings > API tokens. Revoke one with DELETE /v1/tokens/{id};
it stops working at once.
2. Connect your client
Keep the token out of files you commit: each snippet below reads it from the environment variable US_TOKEN.
Claude Code
claude mcp add --transport http usherstats https://api.usherstats.com/mcp --header "Authorization: Bearer $US_TOKEN"Or share it with a project in .mcp.json (Claude Code expands ${US_TOKEN} from the environment):
{
"mcpServers": {
"usherstats": {
"type": "http",
"url": "https://api.usherstats.com/mcp",
"headers": { "Authorization": "Bearer ${US_TOKEN}" }
}
}
}Claude Desktop
Claude Desktop starts local servers from claude_desktop_config.json; the mcp-remote bridge connects it to a remote
server with a header. Put the token in env, not in the arguments:
{
"mcpServers": {
"usherstats": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.usherstats.com/mcp", "--header", "Authorization:${US_AUTH}"],
"env": { "US_AUTH": "Bearer us_live_..." }
}
}
}Cursor
In ~/.cursor/mcp.json (every project) or .cursor/mcp.json (one project):
{
"mcpServers": {
"usherstats": {
"url": "https://api.usherstats.com/mcp",
"headers": { "Authorization": "Bearer ${env:US_TOKEN}" }
}
}
}Any other MCP client
Configure a Streamable HTTP (sometimes "HTTP" or "remote") server with the URL https://api.usherstats.com/mcp
and the request header Authorization: Bearer <your token>. The server does not use OAuth; a client that only
supports OAuth for remote servers can use the mcp-remote bridge as in the Claude Desktop example.
3. What to ask
- "Which of my sites have the UsherStats snippet installed, and when did each last send data?"
- "Create a site called Docs for docs.example.com in Europe/Bucharest and give me the snippet to paste."
- "Add shop.example.com as a hostname of my Shop site."
- "Mark 203.0.113.0/24 as our office network on every site so our own visits are not counted."
- "Add a goal named Signup on the path /welcome for the Shop site."
- "Who is on the team, and which invitations are still pending?"
- "List the API tokens nobody has used in the last 30 days."
The agent starts with get_me (what the token may do) and list_sites; every id it needs comes from a list_ or
get_ tool. When a call is refused, the tool error's first line says why and what to do -- for example
This needs the "sites:write" scope, which this API token does not have -- so the agent can tell you, rather than
guess.
Retries. Every tool that changes something takes an optional idempotencyKey. An agent that repeats a call with
the same key within 24 hours (after a timeout, say) gets the first result back instead of creating a second site or
token. The same key works over HTTP as the Idempotency-Key header.
Limits. A token makes up to 600 calls a minute, and every tool call counts, including each one inside a JSON-RPC batch (protocol version 2025-03-26 only). A batch carries at most 20 messages.
Tools
One tool per API route a token can call. The list is generated from the routes, so it is always exactly what the API
serves; tools/list returns each tool's input schema (JSON Schema) and, for tools that return data, its output schema.
| Tool | Scope |
|---|---|
get_me | none |
get_workspace | members:read |
update_workspace | members:write |
list_members | members:read |
create_member_invite | members:write |
delete_member_invite | members:write |
update_member | members:write |
delete_member | members:write |
list_tokens | tokens:read |
create_token | tokens:write |
delete_token | tokens:write |
list_sites | sites:read |
create_site | sites:write |
get_site | sites:read |
update_site | sites:write |
delete_site | sites:write |
create_site_hostname | sites:write |
get_site_hostname | sites:read |
verify_site_hostname | sites:write |
delete_site_hostname | sites:write |
list_site_keys | sites:read |
create_site_key | sites:write |
rotate_site_key | sites:write |
delete_site_key | sites:write |
get_site_snippet | sites:read |
get_site_install_status | sites:read |
update_site_settings | sites:write |
get_stats_catalogue | analytics:read |
get_site_stats | analytics:read |
query_site_stats | analytics:read |
batch_site_stats | analytics:read |
get_site_summary | analytics:read |
get_site_top | analytics:read |
list_site_goals | sites:read |
create_site_goal | sites:write |
delete_site_goal | sites:write |
get_site_goal_results | analytics:read |
list_site_experiments | sites:read |
create_site_experiment | sites:write |
update_site_experiment | sites:write |
delete_site_experiment | sites:write |
get_site_experiment_results | analytics:read |
get_site_journeys | analytics:read |
get_site_bot | bot:read |
update_site_bot | bot:write |
get_site_bot_decisions | bot:read |
get_site_search | search:read |
update_site_search_google | search:write |
update_site_search_bing | search:write |
delete_site_search_google | search:write |
delete_site_search_bing | search:write |
get_site_search_weeks | search:read |
internal_link_site | sites:write |
rotate_site_internal_link | sites:write |
export_site | export:read |
get_billing | billing:read |
checkout_billing | billing:write |
portal_billing | billing:write |
plan_billing | billing:write |
Not tools: sign-up, sign-in, passwords, sessions and two-factor setup (a person does these; an agent uses a token), accepting an invitation (it joins a signed-in person to a workspace), and the OpenAPI document (fetch it over HTTP).
The protocol, by hand
What a client sends, for anyone writing their own. Each request is its own POST /mcp. With 2026-07-28, the
version and client capabilities travel in params._meta, and the method (and, for tools/call, the tool name) are
mirrored in headers; a header that disagrees with the body is refused with 400 and error -32020.
Discover the server: supported versions, capabilities and instructions for the model.
curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: server/discover" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}'List the tools:
curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: $VERSION" \
-H "Mcp-Method: tools/list" \
-d '{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}'Call one. The result has the route's answer as JSON text in content and as structuredContent:
curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: create_site" \
-d '{"jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": {"name": "create_site", "arguments": {"name": "Docs", "hostnames": ["docs.example.com"], "idempotencyKey": "create-docs-1"}, "_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}'curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: get_site_snippet" \
--data @- <<EOF
{"jsonrpc": "2.0", "id": 4, "method": "tools/call",
"params": {"name": "get_site_snippet", "arguments": {"siteId": "$SITE_ID"},
"_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}
EOFA refusal is a tool result with "isError": true whose text starts with what to do. The read-only token cannot
create sites:
curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $READ_TOKEN" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: 2026-07-28" \
-H "Mcp-Method: tools/call" \
-H "Mcp-Name: create_site" \
-d '{"jsonrpc": "2.0", "id": 5, "method": "tools/call", "params": {"name": "create_site", "arguments": {"name": "Nope"}, "_meta": {"io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientCapabilities": {}}}}'Without a token, /mcp answers 401 (as application/problem+json, with WWW-Authenticate: Bearer):
curl https://api.usherstats.com/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 6, "method": "tools/list"}'A client on an earlier protocol version opens with initialize; no session id is issued, so every later request
stands alone (send MCP-Protocol-Version with the negotiated version):
curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc": "2.0", "id": 7, "method": "initialize", "params": {"protocolVersion": "2025-06-18", "capabilities": {}, "clientInfo": {"name": "my-agent", "version": "1.0"}}}'curl https://api.usherstats.com/mcp \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H "Content-Type: application/json" \
-H "MCP-Protocol-Version: $NEGOTIATED" \
-d '{"jsonrpc": "2.0", "id": 8, "method": "tools/call", "params": {"name": "list_sites", "arguments": {}}}'GET /mcp and DELETE /mcp answer 405: there is no standalone event stream and no session to end.