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
-
Obtain a personal API key using the MCP Server preset.
-
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().
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
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,
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' })
await client.connect(transport)
const toolsResp = await client.request({ method: 'tools/list' }, ListToolsResultSchema)
const tools = toolsResp?.tools ?? []
console.log('Tools:', tools.length)
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:
- The
create-feature-flag tool creates the flag with a 20% rollout
- 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:
- The
query-trends tool executes a trends query filtering for $signup events
- 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:
- The
experiment-create tool creates an experiment with control/test variants
- Sets up a funnel metric: pricing page view → checkout page view
- 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:
- The
query-error-tracking-issues-list tool fetches error groups sorted by occurrence count
- 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.
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.
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:
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:
Developing with local resources
To develop with warm loading for MCP resources (workflows, prompts, examples):
- Start the context-mill dev server:
cd ../context-mill && npm run dev
- 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
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:
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.