Skip to content

feat(sdk): emit X-DevHelm-Surface telemetry headers#19

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

feat(sdk): emit X-DevHelm-Surface telemetry headers#19
caballeto merged 2 commits into
mainfrom
feat/surface-telemetry

Conversation

@caballeto
Copy link
Copy Markdown
Contributor

Summary

Reports the SDK's identity to the DevHelm API on every authenticated request so the API can attribute usage to the right devtool. Wire contract documented at https://devhelm.io/telemetry; the matching API-side handler shipped in devhelmhq/mono#332.

What lands on the wire (defaults)

Header Value
`X-DevHelm-Surface` `sdk-py`
`X-DevHelm-Surface-Version` the installed `devhelm` package version
`X-DevHelm-Sdk-Name` `sdk-py`

Wrapper override (e.g. MCP server)

```python
Devhelm(
token=...,
surface="mcp",
surface_version="0.5.0",
surface_metadata={"Mcp-Client": "cursor"},
)
```

The SDK identity (`X-DevHelm-Sdk-Name: sdk-py`) is preserved alongside the wrapper surface so the API can distinguish "wrapper on top of v0.5 SDK" from "wrapper on top of v0.6 SDK" when debugging client-version skew.

Opt-out

`DEVHELM_TELEMETRY=0` drops every `X-DevHelm-Surface*` header at the client level. Single env var, not per-call. Auth and tenant headers are unaffected.

Zero callsite changes

Surface is set once when `Devhelm(...)` is constructed. Every existing tool/test using the SDK keeps working unchanged.

Tests

  • 3 new `test_http.py` tests: default headers, wrapper override, env opt-out
  • Full suite (716) green
  • `ruff` clean, `mypy --strict` clean

Test plan

  • `make test` — 716 / 716 green
  • `make lint` — clean
  • `make typecheck` — clean
  • Post-release: smoke an SDK call against stage with the new release; verify the `org_surface_usage` row + the `sdk_first_authenticated_request` PostHog event

Made with Cursor

caballeto and others added 2 commits May 1, 2026 18:14
Adds optional surface identification to every authenticated API call so
the API can attribute usage to the right devtool. Wire contract documented
at https://devhelm.io/telemetry; the matching API-side handler shipped in
mono#332.

Headers emitted by default (every request):
- X-DevHelm-Surface: sdk-py
- X-DevHelm-Surface-Version: <package version>
- X-DevHelm-Sdk-Name: sdk-py

Wrappers (e.g. devhelm-mcp-server) override `surface` / `surface_version`
at construction time so their traffic is attributed correctly:

    Devhelm(token=..., surface="mcp", surface_version="0.5.0",
            surface_metadata={"Mcp-Client": "cursor"})

The SDK identity (`X-DevHelm-Sdk-Name`) is preserved alongside the
wrapper surface so the API can still distinguish "wrapper on top of a
v0.5 SDK" from "wrapper on top of a v0.6 SDK" when debugging client-
version skew.

Opt-out is intentionally a single env var, not per-call:
`DEVHELM_TELEMETRY=0` drops every X-DevHelm-Surface* header at the
client level. Auth + tenant headers are unaffected.

Zero callsite changes — surface is set once when the Devhelm client is
constructed, every existing tool/test using `Devhelm(...)` keeps working
unchanged.

Tests: 3 new tests in test_http.py covering defaults, wrapper override,
and opt-out. Full suite (716) green; ruff + mypy clean.

Co-authored-by: Cursor <cursoragent@cursor.com>
The new `surface` / `surface_version` / `surface_metadata` constructor
kwargs are an additive feature; downstream wrappers (e.g. devhelm-mcp-server)
need to pin `devhelm>=0.6.0` to use them, so cutting a minor bump.

Co-authored-by: Cursor <cursoragent@cursor.com>
@caballeto caballeto merged commit dd1b2bf into main May 1, 2026
4 checks passed
@caballeto caballeto deleted the feat/surface-telemetry branch May 1, 2026 17:23
caballeto added a commit to devhelmhq/mcp-server that referenced this pull request May 2, 2026
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>
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