mcpField
O
Otel

Otel

本地服务
npmjs.com开发工具01 周前更新

关于 Otel

mcp-otel 是供 MCP 服务作者和客户端使用的轻量库,可将请求元数据中的 W3C 跟踪上下文连接到 OpenTelemetry span。它不负责初始化 OpenTelemetry 或选择导出器。

主要功能

  1. 01通过 _meta 传播 W3C 上下文
  2. 02为工具处理器包装服务端 span
  3. 03提供跟踪上下文提取与注入工具
  4. 04记录 span 状态和异常
  5. 05支持自定义 span 属性

适用场景

  1. 01串联 Host、工具与下游 span
  2. 02为指定 MCP 工具处理器添加插桩
  3. 03从 MCP 客户端转发跟踪上下文

MCP 相关

它在 MCP 的 _meta 中读取和写入 traceparent、tracestate 与 baggage。在服务端创建工具调用 span;客户端可将当前跟踪上下文注入请求。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"otel": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"\\"
]
}
}
}
启动命令docker run -i --rm '\'

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
oci\

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

属于 StudioMeyer MCP Stack — 源自马略卡 🌴 · 如果你在使用,请点 ⭐

mcp-otel

适用于 Model Context Protocol 的 W3C Trace Context 桥接库。它通过 MCP 的 _meta 字段传播追踪上下文,并生成 OpenTelemetry Span,因此,沿着 Host → MCP 服务 → 工具 → 下游 HTTP 流转的请求会在 Jaeger、Tempo、Honeycomb 或 Datadog 中显示为一条连贯的追踪记录。

npm CI OpenSSF Scorecard License: MIT


为什么要开发这个项目

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).

对等依赖:

PackageRangeRequired?
@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> 的 SERVER Span,作为调用方 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 上下文为父级的 SERVER span 内运行 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

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。