Repository navigation
feat: trace FastMCP HTTP requests with OpenTelemetry - #280
Merged
Merged
Conversation
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.
…er with Generator
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.
Why
FastMcpBootstrapperhas logging, Sentry, Pyroscope, health checks and Prometheus, but no tracing:FastMcpConfighas noOpenTelemetryConfigand 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, orrun(transport="http")callsself.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()overrideshttp_appon 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.FastMcpOpenTelemetryInstrumentaddsOpenTelemetryMiddlewareto each application. The span name andhttp.routecome from the firstRoute/Mountthat fully matches. Unmatched paths get a method-only span with nohttp.route.^\w+://[^/]*/health(?:/|$), parsed withparse_excluded_urls, because asgi 0.49b0 cannot parse a string itself._is_instrumented_by_opentelemetry, the flagStarletteInstrumentorsets, are skipped, so a request is never traced twice.Changes
FastMcpConfigincludesOpenTelemetryConfig. No new required fields.is_fastmcp_opentelemetry_installedprobe, and afastmcp-otlextra (otlplusopentelemetry-instrumentation-asgi>=0.49b0), included infastmcp-alland in the install-isolation CI list.fastmcpturns on OpenTelemetry and builds anhttp_app().Tests
GET /health/andPOST /mcpcarryhttp.route. An unmatched path gets a method-only span./custom-healthyis not.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 lintandjust testpass (348 passed).fastmcp==3.0.0,opentelemetry-instrumentation-asgi==0.49b0, Python 3.11). At those floors, real requests giveGET /health/,POST /mcpand a bareGETfor a 404, and/metricsis excluded.