MCP SDK v2
Contents
The MCP TypeScript SDK has two majors. @posthog/mcp detects and supports both at runtime. Neither MCP SDK major is bundled with the package. The Python SDK also supports both majors.
| Your imports | Major | Protocol revisions it serves |
|---|---|---|
@modelcontextprotocol/sdk | v1 | 2025-11-25 and earlier |
@modelcontextprotocol/core, /server, /client | v2 | 2025-11-25 and 2026-07-28 |
Setup
Use the same instrument() call as v1. Import McpServer from the v2 package. Use registerTool() because v2 removes server.tool():
Model capture and conversation IDs are enabled by default. The low-level Server uses the same instrument(server, posthog) call.
If your callbacks read headers, change them
v1 stores headers at extra.requestInfo.headers. v2 stores a WHATWG Request at extra.http.req. Read its headers with .get(). Using the v1 location on v2 returns undefined. This can make identify() return null and send anonymous events without an error.
Use the exported helper in identify, intentFallback, and eventProperties. It handles both majors and returns an object with lowercase keys. beforeSend receives the built event without request context:
Python
The Python SDK supports both official mcp majors (mcp>=1.26,<3) and detects them at runtime. On 2.x, FastMCP was renamed – the instrument() call stays the same:
The low-level Server works on both majors. jlowin's standalone fastmcp package pins mcp<2, so it stays on the 1.x adapter path.
Python also provides different request context shapes on each major. Use the exported helper in identify, intent_fallback, and event_properties. It returns a dictionary with lowercase keys on HTTP transports. It returns None on stdio and never raises:
Python uses the same defaults. Both SDKs derive the same session ID from the same echoed conversation handle.
Ruby
The official Ruby MCP SDK has a single major that serves both revisions: the initialize handshake for 2025-11-25 and earlier, and the per-request _meta envelope for 2026-07-28. The experimental, unsupported Ruby SDK handles both with the same call:
On 2026-07-28 requests it reads the client name, version, and protocol version off the envelope for every event and never answers with an Mcp-Session-Id, so conversation IDs or identify are what correlate calls there.
Callbacks (identify, intent_fallback, event_properties) receive extra["headers"], a lowercase-keyed Hash on HTTP transports and an empty Hash on stdio, so header reads look the same on either revision:
Sessions on 2026-07-28
That revision removed the initialize handshake and the Mcp-Session-Id header, so the stateless session token doesn't apply. Use a conversation handle to correlate requests:
- Conversation IDs – enabled by default in both SDKs. The SDK injects a
conversation_idparameter and returns a handle in eligible tool results. Calls share a$session_idwhen the agent echoes that handle. Clients that ignore it can still produce a separate session per request. identify– attributes calls to a person viadistinct_id. Use it for user-level grouping. It does not provide a$session_id.
The protocol revision belongs to each request. A v2 server also serves 2025-11-25 traffic, which most clients still negotiate.
On 2025-11-25, the client sends its name and version only at initialize. For servers that create an instance per request, the SDK uses a session token. The transport must write response headers after the handler runs to send this token.
@rekog/mcp-nest supports this with enableJsonResponse: true. The legacy path in createMcpHandler does not. On that path, expect $mcp_client_name and $mcp_client_version to be absent. $mcp_protocol_version remains available.
Capture model identity on both revisions
Both SDKs capture model identity on 2025-11-25 and 2026-07-28. Recognized client metadata takes priority over the agent's self-reported llm_model argument. $mcp_llm_model_source records "client_metadata" or "self_reported".
Both sources are unverified. See model capture for defaults, opt-outs, and the Python standalone FastMCP limitation.
MCP Apps
@posthog/mcp preserves MCP App tool metadata, ui:// resources, structured tool output, result metadata, HTML, and content security policy metadata on both supported revisions. On the tested high-level McpServer path, injected context and llm_model arguments don't reach the App handler.
The SDK captures App tool calls with intent, self-reported model, and protocol version. Resource listing and read requests emit $mcp_resources_list and $mcp_resource_read on both server paths. Resource payloads pass through unchanged. The SDK never captures a resource read body.
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. |
Multi round-trip (resultType: "input_required") | Each round counts as its own $mcp_tool_call, inflating call counts and durations. |
server/discover | Not captured – no session-start event on this revision. |
Mcp-Method / Mcp-Name headers | Not read. |
clientCapabilities in _meta | Not captured. clientInfo and protocol version are. |
Task-based tools and multi-round-trip tools can produce misleading counts and durations. Check these limitations before using their dashboard metrics.