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.
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 posthog 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.
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).
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
| 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)
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.