How to propagate OpenTelemetry trace context through MCP Python SDK requests?
Using the modelcontextprotocol python-sdk with FastMCP over SSE (issue opened April 2025), I want OpenTelemetry traces with correct parent-child spans across an MCP client and server. HTTP-header-based propagation only seems to work for the initialize request, and later requests reuse the first request's tracing context. How should traceparent be passed for every MCP protocol request?
Originally asked on GitHub: https://github.com/modelcontextprotocol/python-sdk/issues/421
5 answers
Status: explained. This thread does not show a merged SDK-level OpenTelemetry implementation; it shows why HTTP-header and ASGI-middleware approaches fail, and it documents a custom wrapper that works.
HTTP-header trace propagation only works for the initialize request, because subsequent MCP protocol requests do not carry the original HTTP headers. One commenter got correct parent-child spans by wrapping the server-side
_handle_requestfunction inmcp/server/lowlevel/server.pyand the client-sidesend_requestfunction inmcp/shared/session.py, then passingtraceparentthroughparams.meta.traceparentonRequestParamsfromtypes.py. That produced correct span hierarchy between an agent application and MCP servers.Another commenter tried a
SofaTracerMiddleware(BaseHTTPMiddleware)added to the FastMCP SSE app, extracting OpenTracing context from request headers. It did not work for later MCP requests: all requests kept using the first request's ContextVarsScope, even though the middleware created a new span context each time.The thread also links to the MCP spec request for tracing (modelcontextprotocol/modelcontextprotocol#246), a spec PR proposing
params._meta.traceparent(PR #414), theopeninference-instrumentation-mcppackage as an existing implementation, and a Logfire issue about contextvars being lost in async streams (#1459). A contributor later proposed a minimal fix that attaches acontextvars.ContexttoSessionMessageand restores it in the worker tasks; that PR is not shown as merged in this thread.Did this work for you?What @fali007 tried on GitHub (Worked)
Wrapped the server
_handle_requestfunction inmcp/server/lowlevel/server.pyand the clientsend_requestfunction inmcp/shared/session.py, propagatingtraceparentthroughparams.meta.traceparentonRequestParams. This produced correct parent-child span IDs between the agent application and the MCP server.Did this work for you?What @wenxuwan tried on GitHub (Didn't work)
Added a
SofaTracerMiddleware(BaseHTTPMiddleware)to the FastMCP SSE app to extract OpenTracing context from HTTP headers and start a server span. Subsequent MCP requests reused the first request's ContextVarsScope instead of the new per-request context.Did this work for you?What a GitHub user tried on GitHub (Didn't work)
Tried implementing distributed tracing via traceloop, but could not get the correct parent-child relationship between spans.
Source: https://github.com/modelcontextprotocol/python-sdk/issues/421 (opens external site)
Did this work for you?What @aabmass tried on GitHub (Outcome unclear)
A contributor proposed a minimal fix to propagate contextvars through anyio streams by attaching a
contextvars.ContexttoSessionMessageand restoring it in the worker tasks. It was offered as a possible PR, but no merge is shown in this thread.Did this work for you?
Related questions
- MCP python-sdk simple-chatbot: 'Attempted to exit cancel scope in a different task than it was entered in'
- Why is my MCP resource template (greeting://{name}) not available in Claude Desktop?
- How to disable TLS certificate verification (verify=False) in MCP streamablehttp_client?
- anyio.ClosedResourceError in MCP StreamableHTTP message router on fast-failing requests?
- Python MCP SDK streamable HTTP: 'Error in message router' ClosedResourceError after 307 redirect