posthog-v7.40.0
Minor changes
-
b0ab12c feat(mcp): support MCP Python SDK v2 and bring
posthog.mcpto parity with the TypeScript SDK (@posthog/mcp). Most of this reaches SDK 1.x servers too — the parity work is not v2-only.MCP SDK v2 / spec 2026-07-28.
instrument()now wrapsmcp.server.mcpserver.MCPServer(the renamed FastMCP) and the v2 low-levelServer(constructor-injected handlers, string-keyed registry, lateadd_request_handlerregistrations included), capturing tool calls, tools/list, errors, intent, client identity, and$mcp_protocol_versionon both protocol eras — the legacy handshake and the stateless 2026-07-28 envelope, decided per request. Previouslyinstrument()raisedImportErroronmcp>=2and took the host application down with it; it now degrades to a logged no-op on any unsupported or unrecognized SDK.Cross-SDK parity (SDK 1.x and 2.x alike). Conversation-anchored sessions land as the cross-pod correlation the stateless era needs: with
enable_conversation_id,$session_idderives deterministically from the agent-echoedconversation_id(new exportderive_session_id_from_conversation, byte-compatible with@posthog/mcp). Only a handle the SDK could have minted (a uuidv7) anchors a session, so two callers inventing the same id can no longer be merged. The handle is delivered over both channels a tool result has — acontenttext block carrying it as plain JSON data on the minting response (an imperative server sentence inside a tool result is prompt-injection-shaped, and a client that strips it silently breaks the feature), and an_mcp_instructionskey declared on the tool's output schema and mirrored intostructuredContenton every response. That second channel is what makes the feature work at all for tools with structured output: clients that readstructuredContentnever rendercontent, so the agent had no handle to echo (0% echo rate measured against Claude Code before the mirror). The prompt-back now rides errored results too, so a failure on a conversation's first call doesn't split the retry into a new session. The session is resolved only once the handle's fate is known, so the call that mints a handle joins the same session as the calls that echo it — while a handle that could not be delivered anchors nothing, rather than stranding events in a conversation nobody holds. Host callbacks (identify,intent_fallback,event_properties) receive the SDK's own per-request context asextra["ctx"]identically on both majors, with a new exportedget_request_headers(extra)to read HTTP headers off it — the underlying shape differs per major, and a hand-rolled read that works on one silently returns nothing on the other, sending every event out anonymous.Fixes affecting existing SDK 1.x users. Analytics could break a tool call in three ways, each now fixed and regression-tested: the SDK's tool cache is rebuilt from an internal listing pass we skipped injecting on, so after any call to an unlisted tool name a strict schema rejected either the analytics parameters we advertise (
Input validation error) or the conversation key we write (Output validation error); the conversation handle was written into the caller's result object in place, so a tool returning a shared or cached result served one conversation's handle to every later caller; and on jlowin's FastMCP the advertised schema markedcontextrequired while the adapter strips it before validation, failing every call understrict_input_validation=True. Two behavioural changes come with the parity work: an invented (non-uuidv7)conversation_idecho is replaced with a fresh handle rather than trusted, and minted prompt-backs are now appended to errored results. — Thanks @gesh!