You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
MCP tool calls do not continue distributed traces from their parent HTTP requests within the same service. The MCP integration creates new transactions with new trace IDs instead of joining the existing trace from the incoming HTTP request.
Environment:
sentry-python version: 2.48.0
MCP server: FastMCP (HTTP/SSE transport) within FastAPI application
Service architecture:
Service A (Go + Sentry) → Service B (Python/FastAPI + Sentry + MCP Server)
HTTP trace propagates correctly to Service B's FastAPI handler
MCP tool execution in Service B breaks the trace chain
Current behavior:
When get_start_span_function() executes during MCP tool handler wrapping, the HTTP transaction isn't accessible due to async context isolation. The function checks the current scope, but only finds unrelated spans (e.g., Redis, database operations) with different trace IDs, not the HTTP transaction that has the correct trace context from the incoming request.
Expected behavior:
MCP tool transaction should continue the HTTP request's trace (same trace_id, becomes child of HTTP span).
Actual behavior:
New transaction created with new trace_id from unrelated span in scope (e.g., Redis connection span), breaking the trace chain.
Root cause:
Async context isolation prevents the HTTP transaction from being accessible in the MCP tool handler's execution context.
Solution Brainstorm
Current workaround
I patched sentry-sdk to get it working for my project:
"""Fix for MCP integration to properly continue distributed traces as transactions.The issue: The MCP integration's get_start_span_function() doesn't check forpropagated trace context from incoming sentry-trace headers, so it creates newtransactions with new trace_ids instead of continuing the distributed trace.The fix: When sentry-trace headers are present in the MCP request, usecontinue_trace() to create a transaction that continues the existing trace(same trace_id, but still a transaction for MCP Insights dashboard).This maintains both:1. Distributed tracing (same trace_id across services)2. MCP Insights dashboard functionality (transactions, not spans)"""importloggingfromtypingimportAny, Callableimportsentry_sdkfromsentry_sdk.tracingimportTransactionlogger=logging.getLogger(__name__)
defcreate_transaction_from_trace_headers(
op: str=None,
name: str=None,
origin: str="manual",
**kwargs: Any
) ->Transaction:
""" Creates a transaction that continues the distributed trace from MCP request headers. This uses continue_trace() which creates a Transaction with the trace_id and parent_span_id from the sentry-trace header, so the transaction becomes a child of the distributed trace while remaining a transaction (not a span) for MCP Insights compatibility. """frommcp.server.lowlevel.serverimportrequest_ctxtry:
ctx=request_ctx.get()
ifctxandhasattr(ctx, "request") andctx.requestisnotNone:
request=ctx.requestifhasattr(request, "headers"):
headers= {}
# Extract sentry-trace and baggage headerssentry_trace=request.headers.get("sentry-trace")
baggage=request.headers.get("baggage")
ifsentry_trace:
headers["sentry-trace"] =sentry_traceifbaggage:
headers["baggage"] =baggageifheaders:
# Use continue_trace to create a transaction that continues the trace# This maintains the same trace_id but creates a transaction (not a span)isolation_scope=sentry_sdk.get_isolation_scope()
transaction=isolation_scope.continue_trace(
environ_or_headers=headers,
op=op,
name=name,
origin=origin
)
# Start the transaction on the scope so it becomes the active transaction# This allows the MCP integration to set data on itreturnsentry_sdk.start_transaction(transaction=transaction)
exceptException:
# Could not extract headers - fall through to creating new transactionpass# Fallback to regular transaction if headers not availablereturnsentry_sdk.start_transaction(op=op, name=name, origin=origin, **kwargs)
defpatched_get_start_span_function() ->Callable:
""" Patched version that checks for propagated trace in MCP request headers. Returns a function that will create either: - A transaction continuing the distributed trace (if sentry-trace header present) - A span (if there's a transaction in scope) - A new transaction (if neither of the above) """# First, check if we have sentry-trace headers in MCP request contexthas_trace_header=Falsetry:
frommcp.server.lowlevel.serverimportrequest_ctxctx=request_ctx.get()
ifctxandhasattr(ctx, "request") andctx.requestisnotNone:
request=ctx.requestifhasattr(request, "headers"):
sentry_trace=request.headers.get("sentry-trace")
ifsentry_trace:
has_trace_header=TrueexceptException:
# MCP not installed or no request context - fall through to normal logicpass# If we have trace headers, return our custom function that creates# a transaction continuing the traceifhas_trace_header:
returncreate_transaction_from_trace_headers# Otherwise, use original logiccurrent_span=sentry_sdk.get_current_span()
scope=sentry_sdk.get_current_scope()
transaction=scope.transactionifscopeelseNoneiftransaction:
returnsentry_sdk.start_spanifcurrent_spanandcurrent_span.containing_transaction:
returnsentry_sdk.start_span# No transaction or propagated trace foundreturnsentry_sdk.start_transaction# Apply the patch to both the utils module AND the mcp integration moduleimportsentry_sdk.ai.utilssentry_sdk.ai.utils.get_start_span_function=patched_get_start_span_function# Also patch it in the mcp module where it's already importedimportsentry_sdk.integrations.mcpsentry_sdk.integrations.mcp.get_start_span_function=patched_get_start_span_functionlogger.info("MCP integration patched: get_start_span_function creates transactions from trace headers")
Before this change:
After this change:
Proper Solution:
Modifie get_start_span_function() in sentry_sdk/ai/utils.py to check for sentry-trace headers in MCP request context before falling back to scope checking.
Where _create_transaction_from_mcp_headers extracts headers and uses continue_trace() to create a transaction inheriting the trace context.
For instance: #5271
Transport compatibility: HTTP headers work for SSE/HTTP transports, while _meta would work for all transports (stdio, SSE, HTTP). What's preferred?
Transactions vs Spans: This creates transactions (not spans) to maintain MCP Insights dashboard compatibility while continuing the distributed trace. Is this the right trade-off?
Problem Statement
MCP tool calls do not continue distributed traces from their parent HTTP requests within the same service. The MCP integration creates new transactions with new trace IDs instead of joining the existing trace from the incoming HTTP request.
Environment:
Current behavior:
When
get_start_span_function()executes during MCP tool handler wrapping, the HTTP transaction isn't accessible due to async context isolation. The function checks the current scope, but only finds unrelated spans (e.g., Redis, database operations) with different trace IDs, not the HTTP transaction that has the correct trace context from the incoming request.Expected behavior:
MCP tool transaction should continue the HTTP request's trace (same trace_id, becomes child of HTTP span).
Actual behavior:
New transaction created with new trace_id from unrelated span in scope (e.g., Redis connection span), breaking the trace chain.
Root cause:
Async context isolation prevents the HTTP transaction from being accessible in the MCP tool handler's execution context.
Solution Brainstorm
Current workaround
I patched sentry-sdk to get it working for my project:
Before this change:

After this change:

Proper Solution:
Modifie
get_start_span_function()insentry_sdk/ai/utils.pyto check forsentry-traceheaders in MCP request context before falling back to scope checking.Where
_create_transaction_from_mcp_headersextracts headers and usescontinue_trace()to create a transaction inheriting the trace context.For instance: #5271
Open questions:
_metafield: This approach reads trace context from HTTP headers. feat(develop): Add distributed tracing for MCP sentry-docs#15752 documents using the _meta field inMCP protocol. Should both be supported?
_metawould work for all transports (stdio, SSE, HTTP). What's preferred?