PostHog MCP

MCP

PostHog's official hosted MCP: lets agents query product analytics with HogQL, manage feature flags and A/B experiments, and investigate errors and session replays. Sign in to PostHog to authorize; tool calls are free, though a few LLM-powered tools count toward AI usage.

PostHog/posthog · services/mcp40.3k
GitHub에서 보기
README

PostHog MCP

The official MCP server for PostHog. PostHog makes your product self-driving — it reads your data and ships changes with you, never without you — and this server gives MCP clients (Claude, Cursor, VS Code, Zed, and more) that full surface: analytics and SQL, dashboards, experiments, feature flags, surveys, session replay, error tracking, and more.

Documentation: https://posthog.com/docs/model-context-protocol

Use the MCP Server

Quick install

You can install the MCP server automatically into Cursor, Claude, Claude Code, VS Code and Zed by running the following command:

npx @posthog/wizard@latest mcp add

Manual install

  1. Obtain a personal API key using the MCP Server preset.

  2. Add the MCP configuration to your desktop client (e.g. Cursor, Windsurf, Claude Desktop) and add your personal API key

{
  "mcpServers": {
    "posthog": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://mcp.posthog.com/mcp",
        "--header",
        "Authorization:${POSTHOG_AUTH_HEADER}"
      ],
      "env": {
        "POSTHOG_AUTH_HEADER": "Bearer {INSERT_YOUR_PERSONAL_API_KEY_HERE}"
      }
    }
  }
}

Minimal Node client (Streamable HTTP)

If you want to call MCP from Node (outside an IDE), use the Model Context Protocol SDK’s Streamable HTTP transport.

  • Auth: Use a personal PostHog API key and pass it as a Bearer token in Authorization.
  • Accept header: Clients must include Accept: application/json, text/event-stream.
  • Lifecycle: MCP requires initialize then a client notifications/initialized; the SDK performs this during connect().
// tools-list.mjs
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
import { ListToolsResultSchema } from '@modelcontextprotocol/sdk/types.js'
import { mkdirSync, writeFileSync } from 'node:fs'
import { join } from 'node:path'
import { URL } from 'node:url'

const AUTH = process.env.POSTHOG_AUTH_HEADER // "Bearer phx_…"
const MCP_URL = process.env.MCP_URL || 'https://mcp.posthog.com/mcp'

if (!AUTH?.startsWith('Bearer ')) {
  console.error('Set POSTHOG_AUTH_HEADER="Bearer phx_..."')
  process.exit(1)
}

const transport = new StreamableHTTPClientTransport(new URL(MCP_URL), {
  requestInit: {
    headers: {
      Authorization: AUTH,
      // Required for Streamable HTTP (JSON + SSE)
      Accept: 'application/json, text/event-stream',
    },
  },
  serverInfo: { name: 'example-node-client', version: '0.0.1' },
})

const client = new Client({ name: 'example-node-client', version: '0.0.1' })

// Handles initialize + notifications/initialized
await client.connect(transport)

const toolsResp = await client.request({ method: 'tools/list' }, ListToolsResultSchema) // { tools: [...] }
const tools = toolsResp?.tools ?? []
console.log('Tools:', tools.length)

// (Optional) Save the full JSON-RPC envelope to a file (run from repo root)
const envelope = { jsonrpc: '2.0', id: 'list-1', result: toolsResp }
mkdirSync('reports', { recursive: true })
writeFileSync(join('reports', 'tools-list-http.json'), JSON.stringify(envelope, null, 2))
console.log('Saved: reports/tools-list-http.json')

await client.close()

Why these headers & steps?

  • Streamable HTTP requires the Accept header to include both JSON and SSE.
  • After initialize, the client must send notifications/initialized; the SDK does this for you in connect().

See also the main PostHog MCP docs for available tools and setup flows: https://posthog.com/docs/model-context-protocol

Example Prompts

Below are detailed examples showing realistic prompts and expected outputs:

Example 1: Feature flag management

Prompt: "Create a feature flag called 'new-checkout-flow' that's enabled for 20% of users, and show me the configuration"

What happens:

  1. The create-feature-flag tool creates the flag with a 20% rollout
  2. Returns the flag configuration including the key, rollout percentage, and targeting rules

Expected output:

Created feature flag 'new-checkout-flow':
- Key: new-checkout-flow
- Active: true
- Rollout: 20% of all users
- URL: https://us.posthog.com/project/<project-id>/feature_flags/12345
Example 2: Analytics query

Prompt: "How many unique users signed up in the last 7 days, broken down by day?"

What happens:

  1. The query-trends tool executes a trends query filtering for $signup events
  2. Returns daily counts with unique user aggregation

Expected output:

Signups over the last 7 days:

| Date       | Unique users |
|------------|--------------|
| 2025-01-17 | 142          |
| 2025-01-18 | 156          |
| 2025-01-19 | 98           |
| ...        | ...          |

Total: 847 unique signups
Example 3: A/B test creation and monitoring

Prompt: "Create an A/B test for our pricing page that measures conversion to the checkout page"

What happens:

  1. The experiment-create tool creates an experiment with control/test variants
  2. Sets up a funnel metric: pricing page view → checkout page view
  3. Creates an associated feature flag for variant assignment

Expected output:

Created experiment 'Pricing page test':
- Feature flag: pricing-page-test
- Variants: control (50%), test (50%)
- Primary metric: Funnel conversion (pricing_page → checkout)
- Status: Draft (ready to launch)
- URL: https://us.posthog.com/project/<project-id>/experiments/789
Example 4: Error investigation

Prompt: "What are the top 5 errors in my project this week and how many users are affected?"

What happens:

  1. The query-error-tracking-issues-list tool fetches error groups sorted by occurrence count
  2. Returns error details including affected user counts

Expected output:

Top 5 errors this week:

1. TypeError: Cannot read property 'id' of undefined
   - Occurrences: 1,247
   - Users affected: 89
   - First seen: 2 days ago

2. NetworkError: Failed to fetch
   - Occurrences: 856
   - Users affected: 234
   - First seen: 5 days ago
...
Quick prompts

For simpler queries, you can use shorter prompts:

  • "What feature flags do I have active?"
  • "Show me my LLM costs this week"
  • "List my dashboards"
  • "What events are being tracked?"

Feature Filtering

You can limit which tools are available by adding query parameters to the MCP URL. If no features are specified, all tools are available. When features are specified, only tools matching those features are exposed.

https://mcp.posthog.com/mcp?features=flags,workspace,dashboards

Available features:

FeatureDescription
actionsActions
alertsAlerts
annotationsAnnotations
batch_exportsData pipelines
business_knowledgeBusiness knowledge
canvasCanvas
cohortsCohorts
conversationsConversations
coreCore utilities (project switching, docs search)
customer_analyticsCustomer analytics
dashboardsDashboards
data_catalogData catalog
data_schemaData schema exploration
data_warehouseData warehouse
debugDebug and diagnostic tools
docsPostHog documentation search
early_access_featuresEarly access features
endpointsEndpoints
engineering_analyticsEngineering analytics
error_trackingError tracking alerts
eventsEvent and property definitions
experimentsExperiments
feedbackSend feedback to the PostHog team
field_notesField notes
flagsFeature flags
health_issuesHealth
hog_function_templatesCDP function template browsing
hog_functionsFunctions
insightsInsights & analytics
integrationsIntegrations
linksPostHog app URL generation
llm_analyticsAI observability
logsLogs
managed_migrationsManaged migrations
marketing_analyticsMarketing analytics
messagingMessaging
mcp_analyticsMCP analytics
mcp_storeMCP Store
metricsMetrics
notebooksNotebooks
personsPersons
platform_featuresPlatform Features
product_analyticsProduct analytics
remindersReminders
replaySession replays
replay_visionReplay vision
reverse_proxyReverse proxy record management
review_hogReviewHog
signalsSignals
skillsSkills
sqlSQL query execution
stamphogStamphog
streamlit_appsStreamlit apps
subscriptionsSubscriptions
surveysSurveys
tasksTasks
tracingTracing
visual_reviewVisual review
warehouse_sourcesWarehouse sources
web_analyticsWeb analytics
workflowsWorkflows
workspaceOrganization and project management

Note: Hyphens and underscores are treated as equivalent in feature names (e.g., error-tracking and error_tracking both work).

To view which tools are available per feature, see our documentation or check schema/tool-definitions-all.json.

Learning and skills in cli mode

Claude web and desktop silently drop exec when its serialized inputSchema reaches 16,384 characters. In cli mode, the exec tool keeps the guidance needed for routine calls in its schema. The compact tool-domain index stays inline in the command schema so Claude can discover relevant tools before making a call. Optional, task-specific guidance is served through the same tool:

  • learn lists the available built-in guides and, when enabled, the PostHog/project skill discovery syntax.
  • learn analytics loads detailed analytics guidance and examples.
  • learn visualizations loads rendering guidance when visualizations are available.
  • learn urls loads the PostHog app link formatting rules (kept inline for other clients; served as a guide on Claude web and desktop to protect the schema budget).
  • learn feedback loads feedback guidance when feedback is available.
  • learn skills lists qualified names from the published PostHog bundle (posthog:) and the current project's Skills store (project:).
  • learn -s "<up to 8 keywords>" searches both sources in names, descriptions, Markdown bodies, and bundled file paths. Longer keywords take priority when a query contains more than eight. Results from both sources are merged into one relevance order.
  • learn -d <source>:<skill> [...] prints the one-line description of each named skill without reading its body (up to 20 per call). Unknown names are reported inline without failing the batch.
  • learn posthog:<skill> [path] or learn project:<skill> [path] reads a skill or one of its bundled files.
  • learn <source>:<skill> <path> [path...] reads several bundled files, and learn <source>:<skill> [<source>:<skill>...] reads several skills, in one call (up to 10 targets).
  • learn <source>:<skill> <path> -s "<up to 8 keywords>" searches within one Markdown file.
  • learn <source>:<skill> <path> --lines <start>:<end> reads an inclusive line range.

Built-in guides are specific to Claude web and desktop. Skill discovery is independently available to every cli-mode client when the mcp-exec-skills feature flag is enabled. Other clients, including Claude Code, receive only the skill commands and do not receive Claude's built-in guides. If the flag is missing, disabled, or cannot be evaluated, skill commands are omitted from the schema and rejected at runtime. When skill discovery is enabled, the inline prompt tells every non-plugin cli client, including Claude web and desktop, to search with learn -s "<task keywords>" before non-trivial PostHog work, load matches by exact qualified name, and follow the loaded SKILL.md before choosing tools. Trivial lookups and unrelated conversation skip this workflow. Skill use is advisory: a product call is never rejected for skipping learn, so every client behaves the same whether or not it holds an MCP session id.

The skill bundle is shared through Redis: each pod loads it once at startup, parses it into memory, and serves every learn command from that parsed catalog. A background timer polls a small version key in Redis; only when the version changes does a pod read the archive bytes again. One pod at a time refreshes the archive from its source when the shared copy is older than ten minutes, using a conditional request so an unchanged release costs a 304. The cached bytes are kept for thirty days and re-extended on every refresh, so a source outage serves the last good archive. No learn, initialize, or discover request reads the archive from Redis. By default it is loaded from https://github.com/PostHog/posthog/releases/download/agent-skills-latest/skills.zip. Set POSTHOG_MCP_SKILLS_URL to use another archive during local development. Custom archive URLs use separate Redis cache namespaces so a local bundle cannot read or overwrite the published bundle's cache entry. Project skills are read directly from the request-authenticated project and are not cached by the MCP server. Only latest, active, uncategorized skills are exposed through learn; category-specific skills such as scouts stay on their own surfaces. Project full-text search is bounded to 10 skills, two short snippets per skill, and a five-second database timeout. Individual learn responses stay below 44,000 characters; large references return a heading outline for follow-up search or line reads. The fixed command syntax stays in the tool schema, while skill names and bodies are loaded only when requested. consumer=plugin and consumer=posthog-code omit learn because both surfaces already supply their own bundled skill context, regardless of the feature flag.

Other clients keep the full inline command reference.

Tool filtering

For finer-grained control you can allowlist specific tools by name using the tools query parameter. Only the exact tool names listed will be exposed, regardless of their feature category.

https://mcp.posthog.com/mcp?tools=dashboard-get,feature-flag-get-all,execute-sql

When features and tools are both provided they are combined as a union — a tool is included if it matches a feature category or is in the tools list. This lets you select a feature group and add a handful of individual tools on top:

https://mcp.posthog.com/mcp?features=flags&tools=dashboard-get

The example above exposes all flag tools plus dashboard-get.

Server mode (tools vs cli)

The MCP server can register either every PostHog tool individually (tools mode) or wrap them all behind a single exec CLI-like tool (cli mode). cli is the default for all clients. When the caller does not pin a mode, the server only auto-selects tools mode for a short allow-list of clients that are better served by the full per-tool roster — currently Cursor (matched by its self-reported client name or its Cursor/… User-Agent). Every OpenAI surface (ChatGPT, Codex, Agent Builder, Responses API) gets the cli default. OpenAI's openai-mcp client caches the roster it captures for a published plugin and serves that snapshot to every user of the plugin, so the mode a plugin listing should run in is pinned on the URL submitted to OpenAI rather than inferred from a User-Agent label.

You can pin the choice yourself with either a query parameter or a header. Only tools and cli are accepted:

https://mcp.posthog.com/mcp?mode=cli
https://mcp.posthog.com/mcp?mode=tools
x-posthog-mcp-mode: cli
x-posthog-mcp-mode: tools
ValueBehavior
toolsForce tools mode (one MCP tool per PostHog tool).
cliForce cli mode (single exec tool wraps all tools).

The header wins when both the header and the query parameter are set. An explicit value always wins over the client auto-detection; any other value is ignored and the auto-detection takes over.

The cli-mode command surface is documented publicly on posthog.com/docs/model-context-protocol/tools, which embeds schema/exec-command-reference.md at build time. That fragment is generated from the templates in src/templates/sections/ by scripts/generate-exec-docs.ts (part of hogli build:openapi); edit the templates, not the fragment.

Consumer attribution

Wrapping apps and AI-tool plugins that install or proxy the PostHog MCP can self-identify so usage can be attributed to the install path (e.g. plugin-installed vs. manually-pasted URL). The wrapped MCP client (Claude Code, Cursor, …) is already captured separately via the MCP clientInfo handshake — this signal is only for the wrapping context.

https://mcp.posthog.com/mcp?consumer=plugin
x-posthog-mcp-consumer: plugin

The header wins when both the header and the query parameter are set. Reserved values: plugin (AI-tool plugin installs), posthog-code (PostHog Desktop Tasks sandbox), slack (Slack integration).

Data processing

The MCP server runs in PostHog's US and EU Kubernetes clusters and stores session state in the region you connect to. A stateless Cloudflare Worker in front of it only authenticates requests and routes them to your cloud region; it does not store any sensitive data.

Using self-hosted instances

If you're using a self-hosted instance of PostHog, you can specify a custom base URL by setting the POSTHOG_API_BASE_URL environment variable when running the MCP server locally or on your own infrastructure, e.g. POSTHOG_API_BASE_URL=https://posthog.example.com

Development

To run the MCP server (Hono on Node) locally, run the following command:

pnpm run dev

Or use bin/start-mcp-server from the repo root, which also bootstraps .env and sets Redis/port defaults. Then replace https://mcp.posthog.com/mcp with http://localhost:8787/mcp in the MCP configuration.

The server defaults to port 8787, reads config from .env (see .env.example), and expects a local Redis on port 6379 for session state; production deployments must set REDIS_URL to a TLS-encrypted rediss:// endpoint.

Session cache

A session's client context lives in one mcp:s:<id>:c key with a 24-hour idle expiry, refreshed on every request in the session. Concurrent requests merge their fields through a Lua compare-and-merge, so a field first seen mid-session is never lost to an overlapping write.

Monitor mcp_session_cache_operations_total for read_error and write_error. Both are non-blocking: a failed read serves whatever context the current request carries, so attribution degrades rather than the call failing. Each also logs a warning prefixed [McpSessionRedisStore], so a Redis failure on this path is greppable in logs and not only visible on the metrics counter.

Edge-proxy worker (Cloudflare)

In production, a thin Cloudflare Worker sits in front of the Hono deployments as a stateless edge router: it serves the OAuth metadata endpoints, validates tokens, resolves the caller's cloud region, and proxies /mcp traffic to mcp.us.posthog.com / mcp.eu.posthog.com. It does not serve the MCP protocol itself - see ARCHITECTURE.md. To run just the worker locally:

pnpm run dev:proxy

Developing with local resources

To develop with warm loading for MCP resources (workflows, prompts, examples):

  1. Start the context-mill dev server: cd ../context-mill && npm run dev
  2. Start the MCP server with local resources: pnpm run dev:local-resources (runs bin/start-mcp-server with POSTHOG_MCP_LOCAL_SKILLS_URL pointed at context-mill)

Changes in the examples repo will be reflected on the next request.

Project Structure

  • src/ - The MCP server: Hono app (src/hono/), tool handlers (src/tools/), prompt templates (src/templates/)
  • definitions/ - Hand-authored YAML tool definitions (per-product YAML lives at products/<product>/mcp/ in the monorepo)
  • schema/ - Generated schema files, including tool-definitions-all.json (the full tool catalog)

Development Commands

  • pnpm run dev - Start the MCP development server
  • pnpm run dev:proxy - Start the edge-proxy worker (wrangler)
  • pnpm run lint / pnpm run format:check - Verify linting and formatting without changing files
  • pnpm run lint:fix - Apply safe lint fixes without suggestion fixes
  • pnpm run format - Format code with Oxfmt only
  • pnpm run fix - Apply safe lint fixes, always format code, and report failures from either tool

Adding New Tools

Most tools are generated from products/<product>/mcp/tools.yaml. Follow the implementing MCP tools skill and the handbook guide for that flow. Use src/tools/README.md only for hand-written tools.

Environment variables

Copy .env.example to .env in the root and adjust the values as needed.

Configuring the Model Context Protocol Inspector

During development you can directly inspect the MCP tool call results using the MCP Inspector.

You can run it using the following command:

npx @modelcontextprotocol/inspector npx -y mcp-remote@latest http://localhost:8787/mcp --header "\"Authorization: Bearer {INSERT_YOUR_PERSONAL_API_KEY_HERE}\""

Alternatively, you can use the following configuration in the MCP Inspector:

Use transport type STDIO.

Command:

npx

Arguments:

-y mcp-remote@latest http://localhost:8787/mcp --header "Authorization: Bearer {INSERT_YOUR_PERSONAL_API_KEY_HERE}"

Developing against Claude Desktop

Claude Desktop is one of the easiest ways to test MCP Apps - while PostHog Desktop doesn't support it. You can configure access Settings > Developer and then edit claude_desktop_config.json with the following:

{
  "mcpServers": {
    "posthog-local": {
      "command": "npx",
      "args": ["-y", "mcp-remote@latest", "http://localhost:8787/mcp"]
    }
  }
}

Privacy & Support

Data handling

The MCP server acts as a proxy to your PostHog instance. It does not store your analytics data - all queries are executed against your PostHog project and results are returned directly to your AI client. Session state (active project/organization) is cached temporarily, keyed by your API key hash.

For EU users, use the mcp-eu.posthog.com endpoint to ensure OAuth flows route to the EU PostHog instance.