Skip to content

feat(client): override SDK surface to "mcp" for usage telemetry#15

Merged
caballeto merged 1 commit into
mainfrom
feat/surface-telemetry
May 2, 2026
Merged

feat(client): override SDK surface to "mcp" for usage telemetry#15
caballeto merged 1 commit into
mainfrom
feat/surface-telemetry

Conversation

@caballeto
Copy link
Copy Markdown
Member

Summary

Tells the API that traffic from this server is MCP traffic, not bare-SDK traffic. Surgical 10-line override at the single SDK construction site (get_client); zero callsite changes.

Wire effect

Header Before After
X-DevHelm-Surface sdk-py (defaulted by SDK) mcp
X-DevHelm-Surface-Version devhelm SDK version devhelm-mcp-server version
X-DevHelm-Sdk-Name sdk-py sdk-py (preserved — lets the API debug client-version skew between MCP + SDK)

Dependencies

This PR pins devhelm>=0.6.0 because the surface= / surface_version= constructor kwargs land in devhelmhq/sdk-python#19. Order:

  1. Merge + release devhelmhq/sdk-python#19 (cuts devhelm 0.6.0)
  2. Merge this PR
  3. Cut a devhelm-mcp-server release

Until the SDK release ships, this PR's CI will fail dependency resolution. That's expected and the failure message is the dependency itself.

Future work (deliberately not in this PR)

Detecting the host MCP client (Cursor vs Claude Desktop vs Claude Code vs Zed vs ...) so the API gets X-DevHelm-Mcp-Client: <name> for finer-grained adoption analytics. The fastmcp Context.session.client_params.clientInfo carries that info, but threading Context through every tool would be exactly the per-callsite surgery this PR's goal forbids. The wire contract already supports X-DevHelm-Mcp-Client via surface_metadata so we can layer it in as a small follow-up.

Tests

  • 1 new test in test_client.py asserts get_client() builds the SDK with surface=mcp + version + sdk-name preserved
  • Full suite (58) green, ruff + mypy clean

Test plan

  • pytest -q — 58 / 58 green (against local devhelm 0.6.0 install)
  • ruff check + format --check + mypy --strict — clean
  • Post-release: smoke an MCP tool call via Cursor against stage; verify org_surface_usage row carries surface=mcp + the mcp_server_first_call PostHog event fires

Wire contract: https://devhelm.io/telemetry
API-side handler: devhelmhq/mono#332
SDK-side change: devhelmhq/sdk-python#19

Made with Cursor

Constructs the underlying devhelm.Devhelm SDK with surface="mcp" so
the API attributes traffic to the MCP server rather than to bare-SDK
use. The SDK's X-DevHelm-Sdk-Name header is preserved alongside, so
the API can still see which devhelm SDK version this MCP build is
on for client-version skew debugging.

Surface version comes from importlib.metadata; reports the installed
devhelm-mcp-server release. Falls back to "unknown" for source-tree
installs.

Detecting the host MCP client (Cursor vs Claude Desktop vs ...) is
deferred — fastmcp's Context.session.client_params.clientInfo carries
that info, but threading Context through every tool would be a wide
surgery against this PR's "no callsite changes" goal. The wire
contract already supports X-DevHelm-Mcp-Client via surface_metadata
so we can layer it in later without an API change.

Depends on devhelm>=0.6.0 (which ships the surface=/surface_version=/
surface_metadata= constructor kwargs). pyproject pin bumped accordingly.

Tests: 1 new test in test_client.py asserting the SDK is built with
the mcp surface override. Full suite (58) green; ruff + mypy clean.

Wire contract docs: https://devhelm.io/telemetry
API-side handler: devhelmhq/mono#332
SDK-side change:  devhelmhq/sdk-python#19

Co-authored-by: Cursor <cursoragent@cursor.com>
@caballeto caballeto merged commit 8f35b1b into main May 2, 2026
4 of 8 checks passed
@caballeto caballeto deleted the feat/surface-telemetry branch May 2, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant