You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(mcp): capture $mcp_client_user_agent and $mcp_vendor_client (#883)
* feat(mcp): capture $mcp_client_user_agent and $mcp_vendor_client
clientInfo.name says which client *library* is calling, not which product.
Anthropic reports "claude-code" from the CLI, the Agent SDK, the VS Code
extension and the desktop app alike, so $mcp_client_name collapses every
surface into one bucket — which is why the harness breakdown reads 100%
"Other" for Python-backed servers, reported from the field.
The distinguishing detail lives in the User-Agent parenthetical
(claude-code/2.1.0 (cli) vs (sdk-ts) vs (claude-vscode)) and in vendor
headers like x-anthropic-client. Both are captured raw and classified
nowhere: friendly names resolve at query time, so labels improve and new
surfaces appear without waiting on an SDK release, and there is one
resolver rather than one per installed version.
Read through get_request_headers, so it works identically on both SDK
majors; HTTP transports only, so stdio and in-memory events stay
byte-identical. Values are bounded by the existing metadata cap, so a
hostile header cannot inflate an event, and the read is fully guarded —
surface attribution must never break a tool call. Custom dispatchers hold
their own request object, so every PostHogMCP.capture_* method takes
client_user_agent / vendor_client directly.
Also unifies how the v2 adapter reaches the request context: it read the
private _request_context while v1 reads the public property. Both work,
but the public read (guarded, since it raises outside a request) removes a
private-attribute dependency and the asymmetry.
Parity with @posthog/mcp's transport-identity module.
Generated-By: PostHog Code
Task-Id: ebafcb71-b03b-443d-b40c-d527ed4a04f4
* test(mcp): prove header capture over real HTTP transports, both majors
The unit tests drove a hand-built headers mapping, which proves the
function works but not the feature: the whole point is reading headers off
a real request, and "the User-Agent never showed up" is the production
symptom this closes. Both new tests send a real User-Agent and
X-Anthropic-Client through an actual streamable-HTTP app and assert the
properties land on the captured event:
- v2: through the existing dual-era httpx/ASGITransport harness
- v1: through a real FastMCP app via Starlette's TestClient, reusing the
pattern in test_session_token.py — v1 is where every MCP client today
still lives, so it is the lane that most needs the real-transport proof
Both exercise Starlette's own Headers object rather than a dict, which is
the shape get_request_headers actually meets in production.
Generated-By: PostHog Code
Task-Id: ebafcb71-b03b-443d-b40c-d527ed4a04f4
feat(mcp): capture `$mcp_client_user_agent` and `$mcp_vendor_client` so MCP usage can be attributed to a product surface. `clientInfo.name` only says which client *library* is calling — Anthropic reports `claude-code` from the CLI, the Agent SDK, the VS Code extension and the desktop app alike — so `$mcp_client_name` collapses every surface into one bucket and the harness breakdown reads 100% "Other" for Python-backed servers. The distinguishing detail lives in the User-Agent parenthetical (`claude-code/2.1.0 (cli)` vs `(sdk-ts)`) and in vendor headers like `x-anthropic-client`. Both are captured raw and classified at query time, so labels can improve without an SDK release. HTTP transports only: stdio and in-memory servers carry no headers and their events are unchanged. Custom dispatchers pass their own via new `client_user_agent` / `vendor_client` arguments on every `PostHogMCP.capture_*` method. Parity with `@posthog/mcp`.
0 commit comments