Python MCP Analytics installation
Contents
Python MCP Analytics is in beta, so its API may change. Pin your posthog version during the beta.
MCP Analytics for Python ships inside the posthog package as posthog.mcp. It instruments:
FastMCPand the low-levelServerfrom the officialmcppackage, 1.x (mcp>=1.26)MCPServer, FastMCP's new name onmcp2.x, and the 2.x low-levelServer- jlowin's standalone FastMCP (the
fastmcppackage)
The SDK handles both the 2025-11-25 and 2026-07-28 protocol revisions. You need Python 3.10+ and a PostHog project token. Using a dispatcher with no server object? See Custom dispatchers.
The wizard installs the package and adds instrument() for you. To install by hand, follow the steps below.
- 1
Install the package
RequiredTerminalinstrument()needsmcporfastmcpat runtime. You already have one, because you built your server with it. - 2
Wrap your server
RequiredCall
instrument(server, posthog)once, before the server accepts requests:Pythoninstrument()returns an analytics handle for custom events. A second call on the same server reuses the first one. - 3
Send queued events on shutdown
Requiredinstrument()captures events in the background, and theposthogclient sends them in batches. When the process stops, wait for pending captures, then stop the client:PythonSee the complete example.
- 4
Check your first events
RequiredConnect an agent to your server and call a tool. Then open the activity feed and filter for
event = $mcp_tool_call. See the event reference for every property.
Read request headers
The two mcp majors pass request context in different shapes. Use get_request_headers(extra) in identify, intent_fallback, and event_properties. It returns a dictionary with lowercase keys on HTTP transports, returns None on stdio, and never raises:
Stateless and multi-pod servers
A stateless server creates a new instance for each request, often on a different pod. Without correlation, each request gets its own $session_id, and events after initialize lose the client name and version.
On 2025-11-25 traffic, the SDK fixes this without a shared store or sticky routing. At initialize, it puts a token with the session ID and client metadata in the Mcp-Session-Id response header. Clients send the header back, so any pod reads the same values. This works with JSON and SSE responses. The 2026-07-28 revision has no initialize, so use conversation IDs there.
On FastMCP and jlowin's fastmcp, instrument() wraps streamable_http_app() and sse_app(), which run() also uses. Set the server to stateless mode:
When you build the ASGI app yourself, such as for a low-level Server or a custom dispatcher, add the middleware once:
Configuration
Pass options as MCPAnalyticsOptions:
| Option | Default | What it does |
|---|---|---|
context | True | Add a context argument to capture agent intent. Pass MCPAnalyticsContextOptions(description=...) to change its prompt. |
intent_fallback | – | (request, extra) -> str \| None. Supplies intent when the agent sends no context. |
capture_model | True | Capture the calling model from client metadata or an llm_model argument. |
enable_conversation_id | True | Add a conversation_id argument to group calls across requests. |
identify | – | (request, extra) -> UserIdentity \| None, sync or async. Maps a request to one of your users. |
report_missing | False | Add the get_more_tools tool for missing capabilities. |
missing_capability_tool_name | "get_more_tools" | Renames the report_missing tool. |
enable_exception_autocapture | True | Emit a $exception event for each failed tool call. |
before_send | – | (event) -> event \| None. Change or drop each event before it's sent. See Privacy. |
event_properties |