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
For the TypeScript SDK, I would start with instrumentation hooks rather than a vendor opinion.
Accept a tracer/meter provider or a small lifecycle subscriber, then emit boundaries for client request, server request, tools/call, resources/read, prompts, and notifications. Each event/span needs session id, request id, method, tool/resource name when present, and trace context from MCP metadata.
That gives OpenTelemetry users first-class spans, while users with their own audit layer can consume the same lifecycle without pulling an exporter into the SDK core.
I needed this for a tool server that sends blockchain transactions (its spans should be children of the agent's tool call span), so I tested trace context over _meta with stdio on 1.32.1 and 2.3.1. In case it helps whoever picks this up:
Inject on the client and extract on the server is all it takes. With v2: propagation.inject(context.active(), meta) into each callTool's _meta, and in the handler context.with(propagation.extract(context.active(), ctx.mcpReq._meta ?? {}), fn). With that, the server's spans join the agent's trace; without it, they start their own.
Two stdio details that cost me spans: StdioClientTransport passes only getDefaultEnvironment() plus env to the server, so OTEL_* settings have to go into env explicitly; and close() waits 2 s before SIGTERM, so the server has to flush its exporter when stdin ends or the last spans are lost.
#3115 covers the global-API half of this issue. The other half from the description, letting users bring their own tracer/meter provider, is still open. Here's a concrete case for it.
We run the New Relic Node agent. With its OpenTelemetry bridge on, successful POST /mcp requests lose their Web transaction name (they show up unnamed, or as Other/undefined/Custom/*), so we can't register a global OpenTelemetry provider. We'd still like the SDK's per-operation view (method, tool, outcome, duration, protocol version) sent to our own sinks.
Protocol already has the seam for this: ProtocolInstrumentation, which the OpenTelemetry implementation uses. A small change could make it public, along the lines @Necmttn suggested above: an optional instrumentation that runs alongside the built-in one, or replaces it when combined with openTelemetry: false, plus type-only exports of the operation and outcome types. Defaults wouldn't change. The other route is openTelemetry: { tracerProvider, meterProvider }, which would also work for us.
@KKonstantinov, is this something you'd take a PR for, and which shape do you prefer? I have a working draft of the instrumentation version, with integration tests and a docs section, and I'm happy to rework it into whichever shape you choose.
Activity
Fix OTel context propagation: parent tool spans to HTTP root span
Necmttn commentedon Jun 21, 2026
For the TypeScript SDK, I would start with instrumentation hooks rather than a vendor opinion.
Accept a tracer/meter provider or a small lifecycle subscriber, then emit boundaries for client request, server request, tools/call, resources/read, prompts, and notifications. Each event/span needs session id, request id, method, tool/resource name when present, and trace context from MCP metadata.
That gives OpenTelemetry users first-class spans, while users with their own audit layer can consume the same lifecycle without pulling an exporter into the SDK core.
Generated with ax.
selimaytac commentedon Oct 18, 2026
I needed this for a tool server that sends blockchain transactions (its spans should be children of the agent's tool call span), so I tested trace context over
_metawith stdio on 1.32.1 and 2.3.1. In case it helps whoever picks this up:propagation.inject(context.active(), meta)into eachcallTool's_meta, and in the handlercontext.with(propagation.extract(context.active(), ctx.mcpReq._meta ?? {}), fn). With that, the server's spans join the agent's trace; without it, they start their own.StdioClientTransportpasses onlygetDefaultEnvironment()plusenvto the server, soOTEL_*settings have to go intoenvexplicitly; andclose()waits 2 s before SIGTERM, so the server has to flush its exporter when stdin ends or the last spans are lost.The test that runs this end to end (agent and stdio server as separate processes, with and without propagation): https://github.com/selimaytac/hashspan/blob/9da1b25/integrations/test/mcp.int.test.ts
feat(otel): add @modelcontextprotocol/otel on top of the instrumentat…
feat: built-in OpenTelemetry, no option and no separate package
lwc commentedon Nov 11, 2026
#3115 covers the global-API half of this issue. The other half from the description, letting users bring their own tracer/meter provider, is still open. Here's a concrete case for it.
We run the New Relic Node agent. With its OpenTelemetry bridge on, successful
POST /mcp requestslose their Web transaction name (they show upunnamed, or asOther/undefined/Custom/*), so we can't register a global OpenTelemetry provider. We'd still like the SDK's per-operation view (method, tool, outcome, duration, protocol version) sent to our own sinks.Protocolalready has the seam for this:ProtocolInstrumentation, which the OpenTelemetry implementation uses. A small change could make it public, along the lines @Necmttn suggested above: an optional instrumentation that runs alongside the built-in one, or replaces it when combined withopenTelemetry: false, plus type-only exports of the operation and outcome types. Defaults wouldn't change. The other route isopenTelemetry: { tracerProvider, meterProvider }, which would also work for us.@KKonstantinov, is this something you'd take a PR for, and which shape do you prefer? I have a working draft of the instrumentation version, with integration tests and a docs section, and I'm happy to rework it into whichever shape you choose.