mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-09 07:09:11 +02:00
feat: Add telemetry interop mode for FastMCP (#4046)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Jeremiah Lowin <153965+jlowin@users.noreply.github.com>
This commit is contained in:
parent
c3cbe8b9a3
commit
75b9f92504
10 changed files with 494 additions and 45 deletions
|
|
@ -77,7 +77,7 @@ These control how the server listens when running with an HTTP transport.
|
|||
|
||||
| Environment Variable | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `FASTMCP_ENABLE_TELEMETRY` | `bool` | `true` | Whether FastMCP's native [OpenTelemetry instrumentation](/servers/telemetry) is active. Enabled by default; FastMCP uses only the OpenTelemetry API, so span creation is a no-op with negligible overhead unless an OpenTelemetry SDK and exporter are configured. Set to `false` to turn instrumentation off entirely, in which case no FastMCP spans are created even when an SDK is configured. |
|
||||
| `FASTMCP_TELEMETRY_MODE` | `Literal["native", "propagation_only", "off"]` | `native` | Controls FastMCP's native [OpenTelemetry instrumentation](/servers/telemetry). `native` emits FastMCP's MCP spans and propagates trace context; because FastMCP uses only the OpenTelemetry API, this costs almost nothing unless an SDK and exporter are configured. `propagation_only` keeps `_meta` trace propagation and still parents downstream spans from the incoming context, but emits none of FastMCP's own spans, so another instrumentation layer can own the MCP span hierarchy. `off` is a full pass-through: no spans, and no trace context extracted or attached. |
|
||||
|
||||
## Tasks (Docket)
|
||||
|
||||
|
|
|
|||
|
|
@ -21,11 +21,21 @@ FastMCP uses the OpenTelemetry API for instrumentation. This means:
|
|||
|
||||
Because FastMCP only depends on the OpenTelemetry API, span creation is a no-op until you configure an SDK and exporter — so being on by default costs nothing until you opt into collection.
|
||||
|
||||
### Turning Telemetry Off
|
||||
### Telemetry Modes
|
||||
|
||||
<VersionBadge version="4.0.0" />
|
||||
|
||||
To disable FastMCP's instrumentation entirely, set `FASTMCP_ENABLE_TELEMETRY=false` (or `fastmcp.settings.enable_telemetry = False`). When disabled, FastMCP creates no spans even if an SDK is configured.
|
||||
`FASTMCP_TELEMETRY_MODE` (or `fastmcp.settings.telemetry_mode`) controls how much of the instrumentation is active:
|
||||
|
||||
| Mode | FastMCP spans | Trace context |
|
||||
|---|---|---|
|
||||
| `native` (default) | Emitted | Propagated |
|
||||
| `propagation_only` | Suppressed | Propagated |
|
||||
| `off` | Suppressed | Untouched |
|
||||
|
||||
Use `off` to disable FastMCP's instrumentation entirely. No spans are created even if an SDK is configured, and FastMCP leaves the surrounding OpenTelemetry context exactly as it found it.
|
||||
|
||||
Use `propagation_only` when another instrumentation layer already owns the MCP span hierarchy — see [Interoperability](#interoperability) below.
|
||||
|
||||
## Enabling Telemetry
|
||||
|
||||
|
|
@ -148,6 +158,37 @@ trace.set_tracer_provider(provider)
|
|||
|
||||
The name check must happen before `ParentBased` delegates. If the name-based sampler is nested inside `ParentBased`, it is not consulted for child spans whose parent was already sampled.
|
||||
|
||||
## Interoperability
|
||||
|
||||
<VersionBadge version="4.0.0" />
|
||||
|
||||
FastMCP assumes it owns the MCP span hierarchy. When something else already owns it — an MCP-aware OpenTelemetry instrumentation library, or a service mesh that understands the protocol — FastMCP's spans duplicate what that layer already emits, and the same request shows up twice in your traces.
|
||||
|
||||
Setting `propagation_only` resolves the duplication in FastMCP's favor of the other layer:
|
||||
|
||||
```bash
|
||||
export FASTMCP_TELEMETRY_MODE=propagation_only
|
||||
```
|
||||
|
||||
The distinction from `off` matters here. Both emit no FastMCP spans, but `off` is fully transparent, while `propagation_only` still extracts the trace context arriving in `_meta` and attaches it for the duration of the request. Spans created downstream — by your tool handlers, or by the instrumentation layer that owns the hierarchy — are parented to the calling trace rather than starting a new one. Outbound requests still carry `traceparent` and `tracestate` in `_meta`.
|
||||
|
||||
### Suppressing spans for a single block
|
||||
|
||||
Library authors embedding FastMCP inside their own instrumented stack often want to own the hierarchy for one specific operation rather than process-wide. `suppress_fastmcp_telemetry()` applies `propagation_only` semantics to a block:
|
||||
|
||||
```python
|
||||
from fastmcp import Client
|
||||
from fastmcp.telemetry import suppress_fastmcp_telemetry
|
||||
|
||||
async def search(client: Client, query: str):
|
||||
with suppress_fastmcp_telemetry():
|
||||
return await client.call_tool("search", {"query": query})
|
||||
```
|
||||
|
||||
This is narrower than OpenTelemetry's global instrumentation suppression: only FastMCP's spans are skipped, so nested instrumentation for HTTP clients, databases, and everything else keeps emitting normally.
|
||||
|
||||
The context manager has no effect when `telemetry_mode` is already `off`. A request to skip FastMCP's spans cannot re-enable the context propagation that `off` deliberately omits.
|
||||
|
||||
## Programmatic Configuration
|
||||
|
||||
For more control, configure the SDK in your Python code before importing FastMCP:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue