GitHub

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:

Feature Description
actions Actions
alerts Alerts
annotations Annotations
batch_exports Data pipelines
business_knowledge Business knowledge
canvas Canvas
cohorts Cohorts
conversations Conversations
core Core utilities (project switching, docs search)
customer_analytics Customer analytics
dashboards Dashboards
data_catalog Data catalog
data_schema Data schema exploration
data_warehouse Data warehouse
debug Debug and diagnostic tools
docs PostHog documentation search
early_access_features Early access features
endpoints Endpoints
engineering_analytics Engineering analytics
error_tracking Error tracking alerts
events Event and property definitions
experiments Experiments
feedback Send feedback to the PostHog team
field_notes Field notes
flags Feature flags
health_issues Health
hog_function_templates CDP function template browsing
hog_functions Functions
insights Insights & analytics
integrations Integrations
links PostHog app URL generation
llm_analytics AI observability
logs Logs
managed_migrations Managed migrations
marketing_analytics Marketing analytics
messaging Messaging
mcp_analytics MCP analytics
mcp_store MCP Store
metrics Metrics
notebooks Notebooks
persons Persons
platform_features Platform Features
product_analytics Product analytics
reminders Reminders
replay Session replays
replay_vision Replay vision
reverse_proxy Reverse proxy record management
review_hog ReviewHog
signals Signals
skills Skills
sql SQL query execution
stamphog Stamphog
streamlit_apps Streamlit apps
subscriptions Subscriptions
surveys Surveys
tasks Tasks
tracing Tracing
user_interviews User interview topics
visual_review Visual review
warehouse_sources Warehouse sources
web_analytics Web analytics
workflows Workflows
workspace Organization 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.

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 posthog 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) and ChatGPT (matched by its openai-mcp … (ChatGPT) User-Agent).

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
Value Behavior
tools Force tools mode (one MCP tool per PostHog tool).
cli Force cli mode (single posthog 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)
  • typescript/ - A small shim (typescript/src/tools/posthogAiTools/) consumed by posthog-ai

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

See the tools documentation for a guide on adding new tools to the MCP server.

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.

Read the original on github.com ↗