Skip to content

[FEATURE]: MCP — inject/extract distributed trace context via _meta (cross-language parity with dd-trace-py) #9090

Description

@tomasAlabes

What

dd-trace-js instruments MCP (@modelcontextprotocol/sdk) with client and server spans, but does not propagate distributed trace context across the MCP boundary. A JS MCP client calling an out-of-process server (notably a non-JS server) produces a server-side trace that is detached from the client's trace.

dd-trace-py already implements this over a defined wire format. Please add the symmetric support to dd-trace-js so a mixed fleet stitches end-to-end.

Why / impact

MCP servers are frequently a different runtime than the agent calling them. Today:

The existing wire format (dd-trace-py)

ddtrace/contrib/internal/mcp/patch.py:

  • Inject_set_distributed_headers_into_mcp_request wraps BaseSession.send_request: HTTPPropagator.inject(span.context, headers) stored at request.params._meta._dd_trace_context.
  • Extract_extract_distributed_headers_from_mcp_request on RequestResponder.__enter__: reads params._meta._dd_trace_contextactivate_distributed_headers(...).
  • Gated by DD_MCP_DISTRIBUTED_TRACING (default true).

_meta is the MCP-spec-sanctioned place for this (https://modelcontextprotocol.io/specification/2025-06-18/basic#meta).

Proposal for dd-trace-js

  1. Client inject at Protocol.request (the single chokepoint every client request flows through; the symmetric point to py's BaseSession.send_request): set request.params._meta._dd_trace_context from a TEXT_MAP injection of the active span. No-op without an active span; best-effort.
  2. Server extract at Protocol._onrequest: prefer params._meta._dd_trace_context as the parent when present, falling back to the current same-process ALS context.
  3. Reuse the DD_MCP_DISTRIBUTED_TRACING env name for parity with dd-trace-py.

The original instrumentation (#7845) explicitly reserved this:

// NOTE: Protocol.request (...) is intentionally not instrumented here.
// It will be used for distributed tracing header injection when server-side coverage is added.

Repro

  • Client: Node, dd-trace@5.106.0, @langchain/mcp-adapters, @modelcontextprotocol/sdk@1.29.0.
  • Server: Python, ddtrace@4.10.5 (default DD_MCP_DISTRIBUTED_TRACING).
  • Observed: server mcp.tools_call span is parent_id:0 (root); downstream gRPC/Bedrock/Postgres spans nest under that detached root, not the client trace. The Python server's extract works — there's simply no _meta._dd_trace_context to read because the JS client never injects it.

Willing to contribute

Happy to open the PR (client inject + server extract). See related work in #8916 / #8921.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions