fastmcp.telemetry
OpenTelemetry instrumentation for FastMCP.
This module provides native OpenTelemetry integration for FastMCP servers and clients.
It uses only the opentelemetry-api package, so telemetry is a no-op unless the user
installs an OpenTelemetry SDK and configures exporters.
Example usage with SDK:
Functions
telemetry_mode
fastmcp.settings.telemetry_mode, except that an active
suppress_fastmcp_telemetry() block downgrades native to
propagation_only. Suppression never upgrades or overrides off: off
means FastMCP touches nothing, and a narrower request to skip FastMCP’s
spans cannot re-enable the context propagation off deliberately omits.
native_spans_enabled
suppress_fastmcp_telemetry
telemetry_mode="propagation_only", for callers that
embed FastMCP inside their own instrumented stack and want to own the MCP
span hierarchy for a specific block. Narrower than OpenTelemetry’s global
instrumentation suppression: only FastMCP’s spans are skipped, so nested
instrumentation (HTTP clients, databases) keeps emitting, and trace context
still flows through _meta so those spans are parented correctly.
Has no effect when telemetry_mode is already off.
get_tracer
fastmcp.settings.telemetry_mode is
propagation_only or off — or the caller is inside a
suppress_fastmcp_telemetry() block — this returns a pass-through tracer
that creates no spans and leaves the current OTel context untouched even
when an SDK is configured.
Args:
version: Optional version string for the instrumentation
- A tracer instance. Returns a non-attaching pass-through tracer when
- FastMCP’s own spans are disabled; span creation is otherwise a no-op
- unless an SDK is configured.
inject_trace_context
meta: Optional existing meta dict to merge with trace context
- A new dict containing the original meta (if any) plus trace context keys,
- or None if no trace context to inject and meta was None
record_span_error
restore_dropped_attributes
Tracer.start_span builds the span from SamplingResult.attributes, not
the attributes= kwarg it was given for creation — a custom Sampler
whose SamplingResult.attributes defaults to None silently discards
every attribute FastMCP passed at creation time. Call this immediately
after span creation to recover from that case.
The restore only fires when the span has no attributes at all AND the
SDK hasn’t evicted anything (dropped_attributes == 0):
- A bare, non-forwarding sampler (the regression this exists to fix) leaves the span with an empty attribute mapping, so everything is restored.
- A sampler that supplied any attributes of its own — whether by forwarding ours untouched, redacting or replacing some of our values, or substituting its own attributes entirely (e.g. to strip component names or resource URIs for privacy or cardinality control) — leaves the span non-empty, so it is left alone entirely. This is what makes the gate precise: a sampler that deliberately supplies only its own attributes must not have them clobbered by a restore that assumes “no FastMCP keys” means “sampler forwarding failed.”
- A sampler that forwards most of our attributes but deliberately drops one is still non-empty, so it’s covered by the same “leave alone” branch — a dropped key here is indistinguishable from the SDK’s bounded attribute map evicting it, and reinserting it would just push the map’s bound and evict a different retained key, churning which attributes survive without changing how many are lost. No attempt is made to restore individual missing keys; the gate is all-or-nothing.
- A low
OTEL_SPAN_ATTRIBUTE_COUNT_LIMITthat evicts every attribute a forwarding sampler passed through is indistinguishable, from the span’s attribute state alone, from a bare non-forwarding sampler — both leave an empty mapping.dropped_attributes == 0is what tells them apart: eviction always increments it, so that case is correctly excluded from the restore and the SDK’s bounded map is left as computed.
if span.is_recording():; it
does no work worth skipping for non-recording spans, but the check is
kept at call sites so it reads alongside the sibling is_recording()
guards already in those functions.
extract_trace_context
meta: The meta dict from an MCP request (ctx.request_context.meta)
- An OpenTelemetry Context with the extracted trace context,
- or the current context if no trace context found or already in a trace

