Add OpenTelemetry instrumentation to authserver
Nobody has claimed this yet.
- Dominant language
- Go
- Stars
- 2.2k
- Forks
- 300
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 184
Description
Context
The authserver has zero OpenTelemetry instrumentation — no traces, no metrics. The MCP proxy (pkg/telemetry/middleware.go) and vMCP server (pkg/vmcp/server/telemetry.go) both have comprehensive OTel coverage. The authserver needs parity.
Since the authserver is embedded in the proxy runner, it will receive TracerProvider and MeterProvider from the runner (plumbed through NewEmbeddedAuthServer / NewHandler).
Scope
Standard HTTP metrics layer
Add the stable OTel HTTP server metric using otelhttp middleware or adapted telemetry middleware:
http.server.request.duration(Histogram, units) with stable semconv attributes:http.request.method,url.scheme,http.response.status_code,http.route,error.type- Routes:
/oauth/authorize,/oauth/callback,/oauth/token,/oauth/register
This gives RED metrics on every endpoint for free, usable with any standard dashboard.
Custom auth-specific metrics
Follow the decorator pattern from pkg/vmcp/server/telemetry.go. Use dot-separated names (the Prometheus bridge handles conversion).
Counters:
toolhive.authserver.token.issued— labels:grant_type(authorization_code, refresh_token),error.type(omit on success; set toinvalid_grant,invalid_client, etc. on failure). This replaces both the "issued" and "failures" counters — failures are token issuance attempts witherror.typeset, following the OTel error pattern.toolhive.authserver.client.registrations— labels:error.typetoolhive.authserver.authorize.requests— labels:upstream(IDP type),error.typetoolhive.authserver.upstream.callbacks— labels:upstream,outcome(success, error, upstream_error)
Histograms:
toolhive.authserver.upstream.exchange.duration(units) — upstream IDP code exchange latencytoolhive.authserver.upstream.refresh.duration(units) — upstream IDP token refresh latency
Note: no token_request_duration_seconds — this is already captured by http.server.request.duration with http.route=/oauth/token.
UpDownCounters (not Gauges — sessions are countable increments/decrements):
toolhive.authserver.active_sessions— increment on session creation, decrement on revocationtoolhive.authserver.registered_clients— increment on DCR, decrement on deletion
Do NOT manually append _total to counter names — the OTLP→Prometheus bridge does this automatically.
Tracing
Top-level spans follow HTTP server semconv naming — {METHOD} {route}:
GET /oauth/authorizeGET /oauth/callbackPOST /oauth/tokenPOST /oauth/register
Set http.route as a span attribute. Do NOT use custom names like authserver.authorize at the top level.
Child spans for domain operations:
authserver.upstream.exchange_code— wrapsExchangeCode()callauthserver.upstream.resolve_identity— wrapsResolveIdentity()callauthserver.upstream.refresh_tokens— wraps upstream token refresh
Skip in-memory storage spans — nanosecond operations produce noise without value. Add storage-level tracing later if/when a persistent backend (PostgreSQL) is introduced.
Authorize → Callback correlation via span links:
The authorize and callback are two separate HTTP requests correlated by the state parameter. Use an OTel span link (not parent-child):
- In
AuthorizeHandler: serialize the span context (trace ID + span ID) into thePendingAuthorizationstruct stored to storage - In
CallbackHandler: reconstruct the span context and attach as atrace.Linkon the callback span
This is the canonical OTel pattern for causally-related spans across separate traces.
Upstream HTTP client instrumentation:
Wrap the HTTP client in upstream.OAuth2Provider with otelhttp.NewTransport:
- Automatically creates client spans for upstream IDP calls
- Propagates
traceparent/tracestateheaders into outgoing requests - Pairs with the
upstream.exchange.durationandupstream.refresh.durationhistograms
Span attributes:
oauth.client_id,oauth.grant_type,oauth.upstream_provider- Standard HTTP attributes using stable semconv names (
http.request.method,http.response.status_code— note: the existingpkg/telemetry/middleware.gouses pre-stable names) - Sensitive data (tokens, codes, PKCE) NEVER included
Provider plumbing
- Extend
NewEmbeddedAuthServer/NewHandlerto accepttrace.TracerProviderandmetric.MeterProvider - Runner passes its existing providers when constructing the embedded authserver
Acceptance criteria
- Standard
http.server.request.durationmetric emitted with stable semconv attributes - All custom counters, histograms, and UpDownCounters implemented as described
- Top-level HTTP spans follow
{METHOD} {route}naming convention - Child spans created for upstream IDP calls
- Authorize→callback correlation implemented via span links
- Upstream HTTP client wrapped with
otelhttp.NewTransport - No in-memory storage spans
- Errors recorded on spans via
span.RecordError()andspan.SetStatus(codes.Error, ...) -
TracerProviderandMeterProviderinjected as dependencies (not global access) - Runner plumbs its providers to the embedded authserver
- Sensitive data never appears in span attributes or metric labels
- Unit tests verify metrics recorded and spans created for success/failure paths
Dependencies
- Depends on #3918 (HTTP middleware — request IDs useful for trace correlation)
- Can be developed in parallel with the audit logging issue (#3919)
References
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with pkg/telemetry/middleware.go and pkg/vmcp/server/telemetry.go, then trace NewEmbeddedAuthServer/NewHandler through the runner and inspect upstream.OAuth2Provider. Implement the specified metrics, spans, provider injection, span-link correlation, and otelhttp transport without sensitive attributes or storage spans. Add unit tests covering success and failure paths for metrics and spans.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go
- Domain
- backend-api-design, observability-sre
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100