feat(mcp): capture $mcp_client_user_agent and $mcp_vendor_client - #883
Merged
Conversation
Contributor
|
Reviews (1): Last reviewed commit: "feat(mcp): capture $mcp_client_user_agen..." | Re-trigger Greptile |
Contributor
posthog-python Compliance ReportDate: 2026-08-21 12:51:58 UTC ✅ All Tests Passed!111/111 tests passed Capture_V1 Tests✅ 94/94 tests passed View Details
Feature_Flags Tests✅ 17/17 tests passed View Details
|
marandaneto
approved these changes
Aug 21, 2026
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
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
gesh
force-pushed
the
posthog-code/mcp-client-attribution
branch
from
August 21, 2026 12:46
63f8f93 to
9825917
Compare
This was referenced Aug 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
clientInfo.namesays which client library is calling, not which product. Anthropic reportsclaude-codefrom the CLI, the Agent SDK, the VS Code extension and the desktop app alike, so$mcp_client_namecollapses every surface into one bucket — which is why the harness breakdown reads 100% "Other" for Python-backed servers.Reported from the field by a team running a hand-rolled Python dispatcher:
The change
$mcp_client_user_agentuser-agentheader, e.g.claude-code/2.1.0 (cli)vs(sdk-ts)vs(claude-vscode)$mcp_vendor_clientx-anthropic-clientheader, captured as a second independent signalBoth are captured raw and classified nowhere — friendly names resolve at query time, so labels can improve and new surfaces appear without waiting on an SDK release, and there's one resolver instead of one per installed version. Emitted on every event type that carries client identity.
Read through
get_request_headers(added in #881), so it works identically on both SDK majors. HTTP transports only — stdio and in-memory servers carry no headers and their events stay byte-identical.Custom dispatchers hold their own request object, so every
PostHogMCP.capture_*method now takesclient_user_agent/vendor_clientdirectly — that's the path the reporting team is on.Safety
Also unifies how the v2 adapter reaches the request context — it read the private
_request_contextwhile v1 reads the public property. Both work; the guarded public read removes a private-attribute dependency and the asymmetry.Tests
9 new, both majors: both headers stamped, case-insensitivity, each header independent, stdio/no-ctx stamping nothing, a throwing header object, an end-to-end instrumented server, custom dispatchers passing their own, absent headers leaving events byte-identical, and a 10KB header being bounded.
Suite: 206 → 215 (v1) · 191 → 200 (v2). ruff, mypy, public-API snapshot clean.
Created with PostHog Code