Otel
About
W3C Trace Context bridge for the Model Context Protocol — propagate traces through MCP _meta (SEP-414) and emit OpenTelemetry spans so Host → MCP server → tool → downstream shows up as one trace.
Details
- License
- MIT
Explore
- reads traceparent / tracestate / baggage from the caller's _meta
- starts a SERVER span named tools/call <toolName> as a child of the caller's span
- runs your handler with that span active (via the OTel context)
- ends the span with OK / ERROR status and records thrown exceptions
- host.chat-turn [INTERNAL]
Setting up with Highlight
This MCP is not yet compatible with Highlight’s one-click setup. However, you can still use it with Highlight by following these steps:
- Download and install Highlight from highlightai.com/download
- Navigate to the plugins tab and select "Add Custom Plugin"
-
Configure the plugin with the settings below
Plugin Name
OtelCommand (node, npx, python, etc.)Please refer to the README for specific instructions on how to obtain API keys or other required environment variables.
- Enable "Start Automatically" if you want the plugin to start when Highlight launches
From the repository
npm install mcp-otel @opentelemetry/api
Set OpenTelemetry up once at startup (this is your normal OTel bootstrap — mcp-otel doesn't do it for you), then wrap each tool handler.
tsimport { instrumentToolHandler } from "mcp-otel";
import { z } from "zod";
server.registerTool(
"weather.lookup",
{ description: "Current weather", inputSchema: { city: z.string() } },
// The only change: wrap the handler. Same (args, extra) => result signature.
instrumentToolHandler("weather.lookup", async ({ city }) => {
// trace.getActiveSpan() here is the MCP server span.
// Any span you start, or any downstream fetch that injects the active
// context, nests under it automatically.
return { content: [{ type: "text", text: Weather for ${city} }] };
}),
);
instrumentToolHandler:
- reads traceparent / tracestate / baggage from the caller's _meta
(it checks extra._meta — the published SDK 1.x shape — and extra.mcpReq._meta),
- starts a SERVER span named tools/call <toolName> as a child of the caller's span,
- runs your handler with that span active (via the OTel context),
- ends the span with OK / ERROR status and records thrown exceptions.
Attributes set on the span: mcp.method ("tools/call"), mcp.tool.name, mcp.request.id, and mcp.session.id when available. Pass attributes for GenAI conventions like gen_ai.system.
Prefer the explicit primitive? Use runInToolSpan when you have the _meta object directly:
tsimport { runInToolSpan } from "mcp-otel";
const result = await runInToolSpan(
meta, // request.params._meta
{ toolName: "weather.lookup", requestId },
async (span) => {
span.setAttribute("weather.city", city);
return doWork(city);
},
);
If you write an MCP client, inject your active trace context into the request _meta so the server can continue your trace:
tsimport { injectTraceContext } from "mcp-otel";
const result = await client.callTool({
name: "weather.lookup",
arguments: { city: "Palma" },
_meta: injectTraceContext({}), // writes traceparent/tracestate/baggage from the active context
});
``
injectTraceContext(meta, context?) mutates and returns the _meta object, defaulting to the current active OpenTelemetry context. Existing _meta keys (like progressToken`) are preserved.
Package
Range
``
Peer dependencies:
| Package | Range | Required? |
| --- | --- | --- |
| @opentelemetry/api | ^1.9.0 | yes |@modelcontextprotocol/sdk
| | >=1.10.0 <2 | optional (only for instrumentToolHandler's extra shape) |
Node 20+. ESM and CommonJS are both shipped.
A small family of focused, production-grade tools for building and operating MCP servers — mix and match:
- mcp-armor — runtime defense sidecar: scans tool calls, verifies signed manifests, blocks known-bad CVEs
- mcp-gauntlet — pre-deploy mcp-fuzz (schema-aware fuzzer) + mcp-storm (load tester)ttlMs
- mcp-otel (this one) — W3C Trace Context → OpenTelemetry bridge
- mcp-cache-kit — leak-safe SEP-2549 caching ( + cacheScope`)
- skilldoctor — linter + security scanner for agent skill files
Claude Desktop / Cursor
Paste into your MCP client config file to install this server.
{
"mcpServers": {
"otel": {
"mcp-otel": {
"command": "docker",
"args": [
"run",
"-d",
"--name",
"jaeger",
"\\"
]
}
}
}
}
McpServers
{
"mcp-otel": {
"command": "docker",
"args": [
"run",
"-d",
"--name",
"jaeger",
"\\"
]
}
}
mcp-otel
W3C Trace Context bridge for the Model Context Protocol. It propagates trace context through MCP's _meta field and emits OpenTelemetry spans, so a request flowing Host → MCP server → tool → downstream HTTP shows up as one connected trace in Jaeger, Tempo, Honeycomb, or Datadog.
---
Why this exists
The MCP release candidate of 2026-07-28 nailed down distributed tracing for MCP:
- SEP-414 reserves the unprefixed _meta keys traceparent, tracestate, and baggage for W3C Trace Context and W3C Baggage. These keys ride along in params._meta of every request, and MCP transports must pass them through untouched.
- SEP-2577 deprecated MCP's logging capability and pointed at OpenTelemetry as the observability path going forward.
Spec post: <https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/>
So the protocol now says where the trace context lives and which telemetry system to use — but there was no small library that does the actual plumbing: read the caller's context out of _meta, start a correctly-parented OpenTelemetry span for the tool call, and let your downstream calls hang off it. That's all mcp-otel is.
It is deliberately thin. It does not configure OpenTelemetry for you, ship an exporter, or hide your tracer. You keep full control of sampling, resources, and exporters; mcp-otel only bridges _meta ↔ spans.
Install
```bash
npm install mcp-otel @opentelemetry/api
Sign in to leave a review
Use Google, GitHub, or an email account so ratings stay tied to real people.
No reviews posted yet.



