docs/python-sdk/fastmcp-telemetry.mdx
fastmcp.telemetryOpenTelemetry 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: ```python from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor
# Configure the SDK (user responsibility)
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter()))
trace.set_tracer_provider(provider)
# Now FastMCP will emit traces
from fastmcp import FastMCP
mcp = FastMCP("my-server")
```
telemetry_mode <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L86" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>telemetry_mode() -> 'TelemetryMode'
Resolve the effective telemetry mode for the current context.
This is 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 <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L103" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>native_spans_enabled() -> bool
Whether FastMCP should create its own spans right now.
suppress_fastmcp_telemetry <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L109" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>suppress_fastmcp_telemetry() -> Iterator[None]
Suppress FastMCP's own spans without disabling trace propagation.
Scoped equivalent of 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 <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L128" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>get_tracer(version: str | None = None) -> Tracer
Get the FastMCP tracer for creating spans.
Instrumentation is on 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. When 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 instrumentationReturns:
inject_trace_context <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L152" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>inject_trace_context(meta: dict[str, Any] | None = None) -> dict[str, Any] | None
Inject current trace context into a meta dict for MCP request propagation.
Args:
meta: Optional existing meta dict to merge with trace contextReturns:
record_span_error <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L183" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>record_span_error(span: Span, exception: BaseException) -> None
Record an exception on a span and set error status.
restore_dropped_attributes <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L209" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>restore_dropped_attributes(span: Span, attrs: Mapping[str, otel_types.AttributeValue]) -> None
Restore FastMCP attributes a non-forwarding sampler dropped entirely.
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):
OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT that 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 == 0 is 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.Callers are expected to guard this with 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 <sup><a href="https://github.com/PrefectHQ/fastmcp/blob/main/fastmcp_slim/fastmcp/telemetry.py#L263" target="_blank"><Icon icon="github" style="width: 14px; height: 14px;" /></a></sup>extract_trace_context(meta: dict[str, Any] | None) -> Context
Extract trace context from an MCP request meta dict.
If already in a valid trace (e.g., from HTTP propagation), the existing trace context is preserved and meta is not used.
Args:
meta: The meta dict from an MCP request (ctx.request_context.meta)Returns: