FastMCP's native OpenTelemetry telemetry interferes with third-party instrumentations that need to control the span hierarchy. We encountered this while building opentelemetry-instrumentation-fastmcp, which follows the MCP semantic conventions.
The problem has three parts:
1. start_as_current_span overrides external context. FastMCP's client_span and server_span use tracer.start_as_current_span(...), which unconditionally sets FastMCP's spans as the active trace context. When a third-party instrumentation has already established a parent span (e.g., an invoke_agent or execute_tool span), FastMCP's span replaces it as the active context. Downstream propagate.inject() calls then propagate FastMCP's span ID instead of the instrumentation's, breaking the client → server trace link. The result: client and server spans end up on different traces.
2. No standard flag to disable native telemetry. When users bring their own instrumentation — either via zero-code (opentelemetry-instrument) or manually — there's no way to turn off FastMCP's built-in spans. This leads to duplicate, conflicting span hierarchies where both FastMCP and the external instrumentation create overlapping spans for the same operations.
3. Native spans don't follow MCP semantic conventions. The OTel GenAI MCP semantic conventions define standard attributes (gen_ai.operation.name, mcp.method.name, network.transport, etc.) and a specific span structure for MCP operations. FastMCP's native spans use custom naming and attributes that don't align with these conventions, making them less useful for observability tooling that expects the standard schema.
Minimal repro for issue 1:
from opentelemetry import trace, context, propagate
tracer = trace.get_tracer("my-instrumentation")
# External instrumentation creates and activates a parent span
parent = tracer.start_span("execute_tool get_weather")
token = context.attach(trace.set_span_in_context(parent))
# FastMCP's client_span replaces the active context
with client_span("tools/call", ...): # uses start_as_current_span internally
carrier = {}
propagate.inject(carrier)
# carrier now has FastMCP's span ID, not the instrumentation's parent
# → server extracts a different parent → traces break
context.detach(token)
Expected behavior: A way to disable native span creation so external instrumentations can own the span hierarchy while FastMCP's context propagation (inject_trace_context/extract_trace_context) continues to work.
FastMCP's native OpenTelemetry telemetry interferes with third-party instrumentations that need to control the span hierarchy. We encountered this while building
opentelemetry-instrumentation-fastmcp, which follows the MCP semantic conventions.The problem has three parts:
1.
start_as_current_spanoverrides external context. FastMCP'sclient_spanandserver_spanusetracer.start_as_current_span(...), which unconditionally sets FastMCP's spans as the active trace context. When a third-party instrumentation has already established a parent span (e.g., aninvoke_agentorexecute_toolspan), FastMCP's span replaces it as the active context. Downstreampropagate.inject()calls then propagate FastMCP's span ID instead of the instrumentation's, breaking the client → server trace link. The result: client and server spans end up on different traces.2. No standard flag to disable native telemetry. When users bring their own instrumentation — either via zero-code (
opentelemetry-instrument) or manually — there's no way to turn off FastMCP's built-in spans. This leads to duplicate, conflicting span hierarchies where both FastMCP and the external instrumentation create overlapping spans for the same operations.3. Native spans don't follow MCP semantic conventions. The OTel GenAI MCP semantic conventions define standard attributes (
gen_ai.operation.name,mcp.method.name,network.transport, etc.) and a specific span structure for MCP operations. FastMCP's native spans use custom naming and attributes that don't align with these conventions, making them less useful for observability tooling that expects the standard schema.Minimal repro for issue 1:
Expected behavior: A way to disable native span creation so external instrumentations can own the span hierarchy while FastMCP's context propagation (
inject_trace_context/extract_trace_context) continues to work.