关于 Otel
mcp-otel 是供 MCP 服务作者和客户端使用的轻量库,可将请求元数据中的 W3C 跟踪上下文连接到 OpenTelemetry span。它不负责初始化 OpenTelemetry 或选择导出器。
主要功能
- 01通过 _meta 传播 W3C 上下文
- 02为工具处理器包装服务端 span
- 03提供跟踪上下文提取与注入工具
- 04记录 span 状态和异常
- 05支持自定义 span 属性
适用场景
- 01串联 Host、工具与下游 span
- 02为指定 MCP 工具处理器添加插桩
- 03从 MCP 客户端转发跟踪上下文
MCP 相关
它在 MCP 的 _meta 中读取和写入 traceparent、tracestate 与 baggage。在服务端创建工具调用 span;客户端可将当前跟踪上下文注入请求。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
属于 StudioMeyer MCP Stack — 源自马略卡 🌴 · 如果你在使用,请点 ⭐
mcp-otel
适用于 Model Context Protocol 的 W3C Trace Context 桥接库。它通过 MCP 的 _meta 字段传播追踪上下文,并生成 OpenTelemetry Span,因此,沿着 Host → MCP 服务 → 工具 → 下游 HTTP 流转的请求会在 Jaeger、Tempo、Honeycomb 或 Datadog 中显示为一条连贯的追踪记录。
为什么要开发这个项目
MCP 2026-07-28 候选发布版明确了 MCP 的分布式追踪规范:
- SEP-414 将未加前缀的
_meta键traceparent、tracestate和baggage保留给 W3C Trace Context 和 W3C Baggage。这些键会随每个请求一同传入params._meta,MCP 传输层必须原样透传这些键。 - SEP-2577 弃用了 MCP 的日志记录功能,并指出今后应使用 OpenTelemetry 实现可观测性。
规范公告:https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate/
因此,协议现在规定了追踪上下文存放的位置以及应使用的遥测系统,但当时没有一个轻量库来完成实际的接线工作:从 _meta 中读取调用方的上下文,为工具调用创建具有正确父 Span 的 OpenTelemetry Span,并让下游调用成为该 Span 的子项。mcp-otel 就是用来完成这些工作的。
它刻意保持轻量。它不会替你配置 OpenTelemetry、提供导出器或隐藏追踪器。采样、资源和导出器仍由你完全掌控;mcp-otel 只负责桥接 _meta ↔ Span。
安装
npm install mcp-otel @opentelemetry/api
# @modelcontextprotocol/sdk is an optional peer — only needed if you use the
# tool-handler wrapper against the SDK (the low-level helpers don't import it).
对等依赖:
| Package | Range | Required? |
|---|---|---|
@opentelemetry/api | ^1.9.0 | 是 |
@modelcontextprotocol/sdk | >=1.10.0 <2 | 可选(仅 instrumentToolHandler 的 extra 格式需要) |
Node 20+。同时提供 ESM 和 CommonJS 版本。
快速入门 — 服务端
在启动时配置一次 OpenTelemetry(这是常规的 OTel 初始化步骤,mcp-otel 不会替你完成),然后包装每个工具处理程序。
import { 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:
- 从调用方的
_meta中读取traceparent/tracestate/baggage(会检查extra._meta——已发布的 SDK 1.x 格式——以及extra.mcpReq._meta), - 启动一个名为
tools/call <toolName>的SERVERSpan,作为调用方 Span 的子项, - 在该 Span 处于激活状态时运行你的处理程序(通过 OTel 上下文),
- 以
OK/ERROR状态结束 Span,并记录抛出的异常。
在 Span 上设置的属性:mcp.method("tools/call")、mcp.tool.name、mcp.request.id,以及可用时的 mcp.session.id。传入 attributes 可设置 gen_ai.system 等 GenAI 约定属性。
更倾向于使用显式的基础函数?如果能直接获取 _meta 对象,请使用 runInToolSpan:
import { 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);
},
);
快速入门 — 客户端
如果你在编写 MCP 客户端,请将当前激活的追踪上下文注入请求的 _meta,以便服务端继续你的追踪:
import { 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?) 会修改并返回 _meta 对象;默认使用当前激活的 OpenTelemetry 上下文。已有的 _meta 键(例如 progressToken)会保留。
发送到 Jaeger
mcp-otel 会发出标准的 OpenTelemetry span,因此任何 exporter 都能使用。最简 Jaeger 配置:
docker run -d --name jaeger \
-p 16686:16686 -p 4318:4318 \
jaegertracing/all-in-one
// otel-setup.ts — import this once, before anything else.
import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";
import { resourceFromAttributes } from "@opentelemetry/resources";
import { ATTR_SERVICE_NAME } from "@opentelemetry/semantic-conventions";
const sdk = new NodeSDK({
resource: resourceFromAttributes({ [ATTR_SERVICE_NAME]: "my-mcp-server" }),
traceExporter: new OTLPTraceExporter({ url: "http://localhost:4318/v1/traces" }),
});
sdk.start();
NodeSDK 会安装 AsyncLocalStorageContextManager,让 mcp-otel 将当前 span 传播到下游调用。打开 http://localhost:16686,你会看到 host → tools/call <name> → downstream 构成一条 trace。
**关于上下文传播的说明:**下游 span 的自动嵌套依赖于已安装上下文管理器,而这正是
NodeSDK/NodeTracerProvider.register()所做的。在没有上下文管理器的进程中(例如使用裸BasicTracerProvider),仍会创建 span,并将其正确设为调用方的子 span,但处理程序中的context.active()不会携带该 span。这是标准的 OpenTelemetry 契约,并非此库的特殊行为。
可运行示例
examples/connected-trace.mjs 中提供了一个零基础设施示例(使用内存 exporter 并打印 span 树):
npm run build
node examples/connected-trace.mjs
它会打印:
- host.chat-turn [INTERNAL]
- tools/call weather.lookup [SERVER]
- GET /weather [CLIENT]
All 3 spans share one traceId: yes
真实 SDK 的配置位于 examples/mcp-server.ts。
API 参考
插桩
instrumentToolHandler(toolName, handler, options?)→ 包装后的处理程序。可直接替换(args, extra) => result的包装器。选项:method(默认为"tools/call")、tracer、attributes,以及提取器覆盖项getMeta/getRequestId/getSessionId。runInToolSpan(meta, options, body)→Promise<T>。底层原语:在以meta的 trace 上下文为父级的SERVERspan 内运行body(span)。选项:toolName、method、requestId、sessionId、attributes、tracer。
传播辅助函数
extractTraceContext(meta, base?)→Context。根据_meta(traceparent+tracestate+baggage)构建 OTel 父上下文。base默认为ROOT_CONTEXT,以确保行为确定。injectTraceContext(meta, context?)→Meta。根据context(默认使用当前上下文)将 trace 键写入_meta。会修改并返回meta。parseTraceparent(s)→SpanContext | null。按 W3C 规范解析。版本00会严格检查长度(必须恰好包含四个字段);对于更高版本,则采用向前兼容的解析方式(读取前四个字段,忽略后续字段,符合规范),确保随着 Trace Context 的演进仍能正常工作。格式错误、版本为ff或值全为零(无效的 trace-id / parent-id)时返回null。结果为isRemote: true。formatTraceparent(spanContext)→string | null。序列化为版本00的traceparent;span 上下文无效时返回null。spanContextToContext(spanContext, base?)→Context。将已有的 span 上下文包装成用于注入的上下文。traceContextFields()→string[]。此库读取 / 写入的_meta键(在转发_meta前剔除这些键时很有用)。
常量
TRACEPARENT_META_KEY、TRACESTATE_META_KEY、BAGGAGE_META_KEY、TRACE_META_KEYS,以及属性键 ATTR_MCP_METHOD、ATTR_MCP_TOOL_NAME、ATTR_MCP_REQUEST_ID、ATTR_MCP_SESSION_ID。
为什么使用 _meta,而不是 HTTP 标头?
你可能会问:MCP 可以通过 Streamable HTTP 运行,那为什么不和普通 Web 服务一样,直接从 HTTP 请求标头中读取 traceparent?
- MCP 与传输方式无关。 同一套服务端逻辑可以通过 stdio、Streamable HTTP 等方式运行。stdio 完全没有标头。将跟踪上下文放在
_meta中,意味着它在所有传输方式上的传播方式都相同——这正是规范将这些键保留在_meta中,而不是依赖 HTTP 的原因。 - 一个 HTTP 连接承载多个逻辑请求。 使用 Streamable HTTP 时,单个长连接会复用多个 JSON-RPC 消息。传输层标头描述的是连接,而不是每次工具调用。跟踪上下文属于单个请求;在 MCP 中,请求就是 JSON-RPC 消息——其单条消息的元数据就是
_meta。 - 主机通常不是 HTTP 客户端。 拥有根 span 的对象(LLM 主机 / Agent)和打开套接字的对象可能是不同的进程。
_meta会随逻辑请求跨越这些跳转;逐跳传递的 HTTP 标头则不会。
因此,_meta 是唯一既针对单个请求、又与传输方式无关的位置——这就是 SEP-414 将其标准化的原因,也是这个库在此处读取和写入数据的原因。
它不会做什么
- 它不会初始化 OpenTelemetry、选择导出器或设置采样器——这些都由你的应用负责;将这些功能排除在外,也意味着 SDK 方面不会被特定版本锁定。
- 它不会自动检测传输层,也不会全局修补 SDK;你可以选择在每个工具处理程序中启用它(或自行调用这些辅助函数)。
- 它不会创建指标或日志——它只处理 span,遵循 SEP-2577 的 OTel 方向。
属于 StudioMeyer MCP 工具包
一组专注且达到生产级别的小型工具,可用于构建和运营 MCP 服务——按需组合:
- mcp-armor — 运行时防御 sidecar:扫描工具调用、验证签名清单、拦截已知存在漏洞的 CVE
- mcp-gauntlet — 部署前使用
mcp-fuzz(具备架构感知能力的模糊测试工具)+mcp-storm(负载测试工具) - mcp-otel (本项目) — W3C Trace Context → OpenTelemetry 桥接工具
- mcp-cache-kit — 安全防泄漏的 SEP-2549 缓存(
ttlMs+cacheScope) - skilldoctor — Agent 技能文件的代码检查工具 + 安全扫描器
许可证
MIT © StudioMeyer
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。