Skip to content

MCP server

Connect Claude, Cursor or any MCP client to your Pug project - twelve read-only analytics tools behind a private API key.

Updated View as Markdown

Pug serves its read-only analytics API as a Model Context Protocol server at /mcp, so an agent can answer questions about your product’s data (run an insight, browse the raw event stream, look up one user) without you writing the query. Twelve tools, one project, no writes.

The endpoint is a thin adapter, not a second API: every tool call is replayed through the same Connect handlers an HTTP request hits, so validation, authentication and authorization behave identically. Anything the Insights, Profiles or Activity services refuse over HTTP, they refuse here.

Endpoint

Environment URL
Pug Cloud https://api.pugs.dev/mcp
Self-hosted / local http://localhost:3000/mcp

Transport is streamable HTTP in stateless mode - no stdio bridge, so your client needs remote-server support. Both /mcp and /mcp/ are served, so a trailing slash from an ingress or a pasted URL is fine.

Every reply comes back on the POST as JSON. The spec’s optional standalone SSE stream (GET /mcp) is refused with 405 - stateless means there is no session for it to attach to. Clients that treat that stream as optional, which is all of the ones above, are unaffected.

Authentication

/mcp accepts a private key only. Send it either way:

Authorization: Bearer prv_YOUR_PRIVATE_KEY
x-api-key: prv_YOUR_PRIVATE_KEY

Most MCP clients can only attach an Authorization header, so Pug normalises the Bearer prv_... form into the x-api-key the rest of the API expects. Reach for whichever your client makes easy.

The key resolves the project, so there is no project ID in the config and the agent sees exactly one project’s data. Public keys (pub_...) and dashboard JWTs are both rejected with 401 - a public key is extractable from client code, and a JWT would widen the endpoint from one project to every project its holder can read. See Authentication for where keys live.

Connect a client

claude mcp add --transport http pug https://api.pugs.dev/mcp \
  --header "Authorization: Bearer prv_YOUR_PRIVATE_KEY"

Run /mcp inside Claude Code to confirm the server connected and to list its tools.

This writes to your own local config. Adding --scope project instead writes .mcp.json in the repo, which would commit the key - use an environment variable there, or keep the default.

codex mcp add pug --url https://api.pugs.dev/mcp \
  --bearer-token-env-var PUG_PRIVATE_KEY

Or write it into ~/.codex/config.toml, or .codex/config.toml for a single trusted project:

[mcp_servers.pug]
url = "https://api.pugs.dev/mcp"
bearer_token_env_var = "PUG_PRIVATE_KEY"

Codex sends that variable’s value as Authorization: Bearer .... There is no inline token field (a literal key in the config is rejected), so export PUG_PRIVATE_KEY before launching Codex.

In ~/.cursor/mcp.json (global) or .cursor/mcp.json (this project only):

{
  "mcpServers": {
    "pug": {
      "url": "https://api.pugs.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${env:PUG_PRIVATE_KEY}"
      }
    }
  }
}

Cursor resolves ${env:...} from the environment it was launched with, so export PUG_PRIVATE_KEY in your shell profile rather than pasting the key here.

In .vscode/mcp.json. The inputs entry makes VS Code prompt for the key once and store it itself, so nothing secret lands in the file:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "pug-private-key",
      "description": "Pug private API key",
      "password": true
    }
  ],
  "servers": {
    "pug": {
      "type": "http",
      "url": "https://api.pugs.dev/mcp",
      "headers": {
        "Authorization": "Bearer ${input:pug-private-key}"
      }
    }
  }
}

Anything that speaks streamable HTTP and can set a request header works. Point it at https://api.pugs.dev/mcp and add Authorization: Bearer prv_....

To check the URL and key without a client, ask the server for its tool list. The endpoint is stateless, so this works with no prior handshake:

curl -X POST https://api.pugs.dev/mcp \
  -H "Authorization: Bearer prv_YOUR_PRIVATE_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

A healthy response lists all twelve tools. 401 means the credential was refused; check the prv_ prefix and that the key has not been revoked. 400 with a complaint about Accept means that header is missing a half: it must offer both application/json and text/event-stream, even though the reply comes back as JSON.

The server ships its own guidance to the agent at initialize, so a client that surfaces MCP instructions already knows to discover the schema before querying, and which tools are project-wide versus per-user.

Use with Claude connectors

Claude’s hosted surfaces (claude.ai, Desktop and mobile) have no config file to edit. They connect through a custom connector instead: add https://api.pugs.dev/mcp under Add custom connector, which lives in Organization settings > Connectors for a Team or Enterprise owner, and in Customize > Connectors otherwise.

The credential goes in that dialog’s Request headers section. Pug’s key is a fixed credential rather than an OAuth identity, so this is the path - pick x-api-key from the header list and paste the key on its own:

Header Value
x-api-key prv_YOUR_PRIVATE_KEY

A request header is stored on the connector, not on a person, so everyone who can use that connector shares one key and reads the same project - and revoking the key disconnects all of them at once. For a team looking at its own analytics that is usually the intent; it is not a way to give each person their own scope.

Pug is not in the Claude connectors directory. A directory listing requires OAuth 2.0, and /mcp authenticates with a static private key.

Tools

Discovery: call these first

Filter schemas tell the agent which event kinds and property keys actually exist in your project, which is what keeps a query from being invented out of thin air.

Tool Returns
get_insights_filter_schema Event kinds and the property keys/types available to filter and break down by
get_insights_property_values The observed values for one property key (e.g. every country seen for $country)
get_activity_filter_schema Alias of get_insights_filter_schema
get_activity_property_values Alias of get_insights_property_values

The activity pair is not a differently-scoped schema: both services hand the request to the same underlying call with the same arguments, so the two return identical data. They exist as four tools because each proto package declares the concept. Call whichever the agent reaches for.

Project-wide questions

Tool Returns
query_insights The main analysis tool: trends, funnel, retention, segmentation, user flow (Sankey), top-K and map, chosen by insight_type. Carries its own time range and granularity. See Insights API for the full spec
explore_events A paginated, filterable page of raw events across all users. Does not resolve aliases, so a merged anonymous id appears under the id that sent the event

One user at a time

Start with a lookup:

Tool Returns
get_profile One profile by its Pug profile id
get_profile_by_external_id One profile by the external_id your application assigned

The three per-user reads then take that profile’s distinct_id. Each describes a single user and cannot answer an aggregate question - query_insights is for those.

Tool Returns
get_activity_feed That user’s events, newest first, filterable by session, time range and properties. Resolves aliases
get_activity_heatmap Per-day event counts for that user (last 60 days when no time range is given)
get_profile_stats First/last seen, total events, device, browser and location from the latest event, plus profile properties. Resolves aliases, so events from before the user identified are included

Compliance

Tool Returns
get_deletion_request The status of a previously submitted erasure request: the DSAR audit trail

What is deliberately not exposed

The tool surface is a curated subset, not everything the API can do:

  • Profile erasure (Delete, DeleteDataSubject): GDPR/DPDP erasure is irreversible and reaches the events table and every derived rollup. It stays a deliberate human action through the dashboard or the Profiles API, never an LLM-callable tool. get_deletion_request is read-only and remains available so an agent can report on a request someone else submitted.
  • SegmentUsers: the drill-down insight is still in progress; it is served as a Connect RPC but held off the tool surface until it lands.
  • List (profiles): server-streaming, which MCP tools do not model. Use explore_events or a profile lookup instead.
  • Everything under sdk.* and dashboard.*: ingest, identity and management are out of scope. /mcp exposes the shared.* read surface only, which is why it is safe to hand an agent.

The exposed set is pinned in the server: a generated tool with no policy entry, or a policy entry with no matching tool, fails startup rather than shipping a surprise tool. Renaming an RPC upstream breaks the build, not your agent.

Limits and errors

  • Two minutes per tool call. A wide query_insights over a long window is the one that will hit it; narrow the time range or the breakdown.
  • Time-range caps by granularity apply exactly as they do on the Insights API - the same handler enforces them.
  • Errors are Connect errors. A rejected argument comes back as invalid_argument naming the field, an unknown profile as not_found. See the error model.
  • A failed tool returns the whole error as JSON: code, message and the details array. The correlation id is in there, as a google.rpc.RequestInfo; quote it in a support request.

Self-hosting

/mcp is mounted on every Pug deployment with no configuration to enable - if your server is up, the endpoint is up, on the same host and port as the rest of the API (3000 by default, see Configuration).

Behind a reverse proxy, make sure /mcp is passed through and that the Authorization header survives the hop. Stripping it is the usual cause of a server that connects from localhost but 401s in production.

Further reading

Navigation

Type to search...

↑↓ navigate↵ selectEsc close