Skip to content

feat: trace FastMCP HTTP requests with OpenTelemetry - #280

Merged
lesnik512 merged 2 commits into
mainfrom
feat/fastmcp-opentelemetry
Oct 9, 2026
Merged

lesnik512 merged 2 commits into
mainfrom
feat/fastmcp-opentelemetry

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Why

FastMcpBootstrapper has logging, Sentry, Pyroscope, health checks and Prometheus, but no tracing: FastMcpConfig has no OpenTelemetryConfig and no instrument creates server spans. Ported from community-of-python/microbootstrap#152.

Design

FastMCP builds its ASGI application lazily, in http_app(). The user calls it after bootstrap, or run(transport="http") calls self.http_app(...) (in fastmcp 3.0 through 3.4). So nothing exists to instrument at bootstrap time. microbootstrap solves this by subclassing FastMCP, but lite-bootstrap receives the user's own instance. Instead:

  • _postprocess_http_apps() overrides http_app on that instance only, passes every application it builds through a postprocessor, and returns an undo that teardown calls. Overrides chain, and teardown runs in reverse order, so the metrics PR stacked on this one can hook the same method.
  • FastMcpOpenTelemetryInstrument adds OpenTelemetryMiddleware to each application. The span name and http.route come from the first Route/Mount that fully matches. Unmatched paths get a method-only span with no http.route.
  • Exclusions use the same full-URL anchoring Litestar used before feat!: trace Litestar through its own OpenTelemetryPlugin #279: ^\w+://[^/]*/health(?:/|$), parsed with parse_excluded_urls, because asgi 0.49b0 cannot parse a string itself.
  • Applications already carrying _is_instrumented_by_opentelemetry, the flag StarletteInstrumentor sets, are skipped, so a request is never traced twice.
  • The anchoring helper is duplicated from the Litestar instrument on purpose. feat!: trace Litestar through its own OpenTelemetryPlugin #279 moves Litestar to path-based patterns, so there is no shared format left to hoist.

Changes

  • FastMcpConfig includes OpenTelemetryConfig. No new required fields.
  • New is_fastmcp_opentelemetry_installed probe, and a fastmcp-otl extra (otl plus opentelemetry-instrumentation-asgi>=0.49b0), included in fastmcp-all and in the install-isolation CI list.
  • The floor smoke for fastmcp turns on OpenTelemetry and builds an http_app().
  • Docs: FastMCP tracing section, installation table, metrics note.

Tests

  • Exported spans for GET /health/ and POST /mcp carry http.route. An unmatched path gets a method-only span.
  • The metrics path, the health path (with health spans off), configured URLs and both trailing-slash forms are excluded; the lookalike /custom-healthy is not.
  • Every http_app() gets exactly one middleware, and instrumenting twice is a no-op. Teardown removes the override. The instrument is skipped without asgi instrumentation.
  • just lint and just test pass (348 passed).
  • Floor smoke passes (fastmcp==3.0.0, opentelemetry-instrumentation-asgi==0.49b0, Python 3.11). At those floors, real requests give GET /health/, POST /mcp and a bare GET for a 404, and /metrics is excluded.

FastMcpConfig now includes OpenTelemetryConfig. FastMcpOpenTelemetryInstrument
wraps the application's http_app() so every ASGI application it builds, including
the one run(transport="http") builds, gets OpenTelemetry's ASGI middleware with
spans named by route. Teardown restores http_app. New fastmcp-otl extra, part of
fastmcp-all.
@lesnik512
lesnik512 merged commit cc33819 into main Oct 9, 2026
30 checks passed
@lesnik512
lesnik512 deleted the feat/fastmcp-opentelemetry branch October 9, 2026 19:21
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