From 88c9f86ba6fc033242a23be01e144655845de6af Mon Sep 17 00:00:00 2001
From: Georgis Andonis <6430745+gesh@users.noreply.github.com>
Date: Fri, 21 Aug 2026 17:27:00 +0300
Subject: [PATCH] docs(mcp-analytics): update Python docs for MCP SDK v2 and TS
parity
posthog-python's posthog.mcp now supports MCP Python SDK v2 (mcp>=1.26,<3, MCPServer), emits $mcp_error_type/$mcp_error_message, and captures $mcp_client_user_agent/$mcp_vendor_client (PostHog/posthog-python#881, #882, #883). Remove the stale "TypeScript only" / "Python soon" claims and document the new surface.
Generated-By: PostHog Desktop
Task-Id: a5edeffa-82df-4f0e-a565-01611e261806
---
.../docs/mcp-analytics/custom-servers.mdx | 2 ++
contents/docs/mcp-analytics/events.mdx | 4 +--
contents/docs/mcp-analytics/installation.mdx | 15 +++++---
contents/docs/mcp-analytics/sdk-v2.mdx | 36 +++++++++++++++----
4 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/contents/docs/mcp-analytics/custom-servers.mdx b/contents/docs/mcp-analytics/custom-servers.mdx
index eb3b5dfd0af4..436b3d844541 100644
--- a/contents/docs/mcp-analytics/custom-servers.mdx
+++ b/contents/docs/mcp-analytics/custom-servers.mdx
@@ -148,6 +148,8 @@ posthog.flush() # PostHogMCP is a posthog client — flush/shutdown it yourself
`PostHogMCP(api_key, missing_capability_tool_name="get_more_tools", mcp_exception_autocapture=True, **posthog_kwargs)` accepts the standard `posthog` client kwargs (e.g. `host`). Set `mcp_exception_autocapture=False` to stop a failed tool call from emitting a `$exception` sibling. As in TypeScript, the wrapping-path hooks (`identify`, `context`, `intent_fallback`, `event_properties`) don't apply here — pass identity and properties on each `capture_*` call.
+The `capture_*` methods also take `error_type` (a low-cardinality failure label for `$mcp_error_type`), `client_user_agent`, and `vendor_client` — the same fields documented in the [event reference](/docs/mcp-analytics/events#core-properties).
+
### Stateless / multi-pod dispatchers
On a stateless deployment (a fresh server per request, often across pods) there's no connection to carry a session, so `$session_id` fragments and the client name/version — sent only at `initialize` — go missing from later requests. Add the mint middleware to your ASGI app once. It mints a self-encoded token onto the `Mcp-Session-Id` response header at `initialize` and decodes the client's replay on every later request, so every pod recovers the same values with no shared store:
diff --git a/contents/docs/mcp-analytics/events.mdx b/contents/docs/mcp-analytics/events.mdx
index f16fb72d3390..717ff8522475 100644
--- a/contents/docs/mcp-analytics/events.mdx
+++ b/contents/docs/mcp-analytics/events.mdx
@@ -37,8 +37,8 @@ Present on most `mcp_*` events.
| `$mcp_listed_tool_names` | string[] | Names of tools advertised in a `tools/list` response. Only on `$mcp_tools_list`. Useful for joining against `$mcp_tool_call` via `$session_id` to find tools advertised but never called. |
| `$mcp_duration_ms` | number (ms) | Wall-clock duration of the tool call |
| `$mcp_is_error` | boolean | True if the tool threw or returned `isError: true` |
-| `$mcp_error_type` | string | Low-cardinality failure category, so you can break errors down by cause without joining to the `$exception` sibling. Defaults to the thrown error's type; pass an explicit label to categorize failures yourself (e.g. `validation`, `permission`, `timeout`, `rate_limited`). Only set when `$mcp_is_error` is true, and TypeScript only for now — the [Python SDK](/docs/mcp-analytics/installation#python) doesn't emit it yet. |
-| `$mcp_error_message` | string | The failed call's error message, truncated and passed through the same redaction as `$mcp_parameters` and `$mcp_response`. Only set when `$mcp_is_error` is true, and TypeScript only for now. |
+| `$mcp_error_type` | string | Low-cardinality failure category, so you can break errors down by cause without joining to the `$exception` sibling. Defaults to the thrown error's type; pass an explicit label to categorize failures yourself (e.g. `validation`, `permission`, `timeout`, `rate_limited`). Only set when `$mcp_is_error` is true. |
+| `$mcp_error_message` | string | The failed call's error message, truncated and passed through the same redaction as `$mcp_parameters` and `$mcp_response`. Only set when `$mcp_is_error` is true. |
| `$mcp_server_name` | string | `server._serverInfo.name` |
| `$mcp_server_version` | string | `server._serverInfo.version` |
| `$mcp_client_name` | string | The calling client as it reports itself. Resolved per request, field by field, through the MCP SDK v2 request envelope, then `params._meta`, then the server's own `getClientVersion()`. |
diff --git a/contents/docs/mcp-analytics/installation.mdx b/contents/docs/mcp-analytics/installation.mdx
index c6ab83858687..c5f385db3a28 100644
--- a/contents/docs/mcp-analytics/installation.mdx
+++ b/contents/docs/mcp-analytics/installation.mdx
@@ -14,7 +14,7 @@ import WizardCommand from 'components/WizardCommand'
## Requirements
- Node.js 20.20+ or 22.22+ (TypeScript/JavaScript), or Python 3.10+ — see [Python](#python) below
-- An MCP server built on the MCP TypeScript SDK — either major, `@modelcontextprotocol/sdk` (v1) or `@modelcontextprotocol/{core,server,client}` (v2), see [MCP SDK v2](/docs/mcp-analytics/sdk-v2) — or the `mcp` package (Python). (Running a custom dispatcher with no server object to wrap? See [Custom servers](/docs/mcp-analytics/custom-servers).)
+- An MCP server built on the MCP TypeScript SDK — either major, `@modelcontextprotocol/sdk` (v1) or `@modelcontextprotocol/{core,server,client}` (v2) — or the Python MCP SDK, also either major (`mcp>=1.26,<3`). See [MCP SDK v2](/docs/mcp-analytics/sdk-v2). (Running a custom dispatcher with no server object to wrap? See [Custom servers](/docs/mcp-analytics/custom-servers).)
- A PostHog [project API key](/docs/getting-started/project-token) (`phc_…`)
## AI wizard
@@ -257,11 +257,12 @@ A Python SDK ships inside the [`posthog`](/docs/libraries/python) package (the s
pip install posthog
```
-`instrument()` needs the MCP SDK at runtime, but you already have it — you built your server with `mcp` or `fastmcp`, so it's treated as a peer dependency rather than bundled. (`PostHogMCP` for custom dispatchers needs nothing beyond `posthog`.)
+`instrument()` needs the MCP SDK at runtime, but you already have it — you built your server with `mcp` or `fastmcp`, so it's treated as a peer dependency rather than bundled. Both `mcp` majors are supported (`mcp>=1.26,<3`), detected at runtime. (`PostHogMCP` for custom dispatchers needs nothing beyond `posthog`.)
`instrument(server, posthog_client, options?)` works with every common Python MCP server:
-- `FastMCP` and the low-level `Server` from the official [`modelcontextprotocol/python-sdk`](https://github.com/modelcontextprotocol/python-sdk) (the `mcp` package)
+- `FastMCP` and the low-level `Server` from the official [`modelcontextprotocol/python-sdk`](https://github.com/modelcontextprotocol/python-sdk) (the `mcp` package, 1.x)
+- `MCPServer` — FastMCP's new name on `mcp` 2.x — and the v2 low-level `Server`, see [MCP SDK v2](/docs/mcp-analytics/sdk-v2#python)
- [jlowin's standalone **FastMCP 2.0**](https://github.com/jlowin/fastmcp) (the separate `fastmcp` package)
- `PostHogMCP` for custom dispatchers with no server object (see below)
@@ -276,6 +277,10 @@ posthog = Posthog(
)
server = FastMCP("my-server")
+# On MCP SDK 2.x, FastMCP was renamed — instrument() works the same:
+# from mcp.server.mcpserver import MCPServer
+# server = MCPServer("my-server")
+
# register your tools as usual...
analytics = instrument(server, posthog)
@@ -341,13 +346,13 @@ await analytics.flush() # drain in-flight auto-capture events
posthog.shutdown() # flush + stop the posthog client
```
-No server object to wrap (a custom HTTP/edge dispatcher)? Use `PostHogMCP`, a `posthog` client subclass (needs nothing beyond `posthog` — no MCP SDK) with `capture_tool_call()`, `capture_initialize()`, `prepare_tool_list()`, and `prepare_tool_call()` — the Python equivalent of [Custom servers](/docs/mcp-analytics/custom-servers).
+No server object to wrap (a custom HTTP/edge dispatcher)? Use `PostHogMCP`, a `posthog` client subclass (needs nothing beyond `posthog` — no MCP SDK) with `capture_tool_call()`, `capture_initialize()`, `capture_tools_list()`, `capture_missing_capability()`, `prepare_tool_list()`, and `prepare_tool_call()` — the Python equivalent of [Custom servers](/docs/mcp-analytics/custom-servers).
The Python SDK is in beta (pre-1.0); the API may still change before `v1`, and some TypeScript-only features may land first. It emits the identical `$mcp_*` events documented on the [events](/docs/mcp-analytics/events) page.
-It doesn't support the `2026-07-28` protocol revision yet — that's in progress. See [MCP SDK v2](/docs/mcp-analytics/sdk-v2) for what that revision changes.
+It supports both `mcp` majors and the `2026-07-28` protocol revision. See [MCP SDK v2](/docs/mcp-analytics/sdk-v2) for what that revision changes.
diff --git a/contents/docs/mcp-analytics/sdk-v2.mdx b/contents/docs/mcp-analytics/sdk-v2.mdx
index 0ca11ceba8d2..e5a49286449e 100644
--- a/contents/docs/mcp-analytics/sdk-v2.mdx
+++ b/contents/docs/mcp-analytics/sdk-v2.mdx
@@ -4,7 +4,7 @@ title: MCP SDK v2
import CalloutBox from 'components/Docs/CalloutBox'
-The MCP TypeScript SDK has two majors. `@posthog/mcp` supports both, and detects which one you're on at runtime — neither is a dependency of the package.
+The MCP TypeScript SDK has two majors. `@posthog/mcp` supports both, and detects which one you're on at runtime — neither is a dependency of the package. The Python MCP SDK made the same split; see [Python](#python) below.
| Your imports | Major | Protocol revisions it serves |
| --- | --- | --- |
@@ -13,12 +13,6 @@ The MCP TypeScript SDK has two majors. `@posthog/mcp` supports both, and detects
Use `@posthog/mcp` 0.11.2 or later on v2. Earlier versions captured nothing, silently.
-
-
-This page covers the MCP **TypeScript** SDK. The [Python SDK](/docs/mcp-analytics/installation#python) is still in active development: it emits the same `$mcp_*` events, but doesn't support the `2026-07-28` revision yet. That support is on the way.
-
-
-
## Setup
Same call as v1. The only difference is where `McpServer` comes from, and that v2 registers tools with `registerTool()` instead of the removed `server.tool()`:
@@ -59,6 +53,32 @@ instrument(server, posthog, {
})
```
+## Python
+
+The [Python SDK](/docs/mcp-analytics/installation#python) supports both `mcp` majors too (`mcp>=1.26,<3`), detected at runtime. On 2.x, `FastMCP` was renamed — same `instrument()` call:
+
+```python
+from mcp.server.mcpserver import MCPServer
+from posthog.mcp import instrument
+
+server = MCPServer("my-server")
+instrument(server, posthog)
+```
+
+The low-level `Server` works on both majors. jlowin's standalone `fastmcp` package pins `mcp<2`, so it stays on the 1.x path — nothing to change there.
+
+The header gotcha above applies in Python too: the request context reaches your callbacks in a different shape on each major. Use the exported helper in `identify`, `intent_fallback`, and `event_properties` — it returns a lowercase-keyed dict on HTTP transports, `None` on stdio, and never raises:
+
+```python
+from posthog.mcp import get_request_headers
+
+def identify(request, extra):
+ headers = get_request_headers(extra) or {}
+ return resolve_user(headers.get("authorization"))
+```
+
+[Sessions on `2026-07-28`](#sessions-on-2026-07-28) work as described below: `enable_conversation_id=True` is the only shared `$session_id` on that revision, and both SDKs derive the same session id from the same `conversation_id`, so a mixed TypeScript-and-Python fleet agrees on sessions.
+
## Sessions on `2026-07-28`
That revision removed the `initialize` handshake and the `Mcp-Session-Id` header, so the [stateless session token](/docs/mcp-analytics/installation#stateless-and-multi-pod-servers) doesn't apply to it — and left alone, **every request becomes its own `$session_id`**:
@@ -76,6 +96,8 @@ On that revision the client sends its name and version only at `initialize`. If
## Not instrumented yet
+These gaps apply to the TypeScript and Python SDKs alike.
+
| `2026-07-28` feature | What you get today |
| --- | --- |
| **Tasks** (`io.modelcontextprotocol/tasks`) | A tool returning a task handle records an instant success, so task-based tools look fast and always-succeeding. |