Python SDK 服务端内置 OpenTelemetry 追踪:零配置的 MCP 可观测性
2026/9/21 2:28:11 网站建设 项目流程
  • 人工智能
  • MCP 服务
  • MCP Clients

【免费下载链接】python-sdk

The official Python SDK for Model Context Protocol servers and clients

项目地址:https://gitcode.com/gh_mirrors/pythonsd/python-sdk
点击查看免费下载

MCP Python SDK(Model Context Protocol 官方 Python SDK)在服务端开箱即用地内置了 OpenTelemetry 与 docs/run/opentelemetry.md 展开,结合仓库源码与测试,讲清楚这些 span 携带什么信息、如何做到"零成本默认开启"、如何打通客户端到服务端的全链路追踪,以及何时、怎样按需关闭它。

你的服务器已经被追踪了

这是本主题最反直觉、也最实用的一点:你不需要做任何事。从你调用MCPServer(...)的那一刻起,追踪就已经存在——它不是你在代码里写的,也不是你显式 import 进来的。

下面这份代码(见 docs_src/opentelemetry/tutorial001.py)就是一个完整的、已带追踪的服务器

from mcp.server import MCPServer mcp = MCPServer("Bookshop") @mcp.tool() def search_books(query: str) -> str: """Search the catalog by title or author.""" return f"Found 3 books matching {query!r}."

调用search_books,SDK 就会为这次调用创建一个 span。同样的行为也适用于底层Server——追踪同时存在于高层MCPServer与低层Server两层。从源码看,这一默认行为是通过低层Server初始化时向self.middleware列表头部写入的OpenTelemetryMiddleware()实现的(见 src/mcp/server/lowlevel/server.py 的注释与初始化代码),任何构建在其上的服务器都会继承这一行为。测试 tests/server/test_otel.py 也明确断言:新建的Servermiddleware中默认包含OpenTelemetryMiddleware实例。

你得到了什么

每个入站消息对应一个 SERVER span

每个入站消息都会变成名为"方法 + 目标"的SERVERspan:

  • search_books的一次tools/call会产生名为tools/call search_books的 span;
  • 一次不带目标的tools/list则是单纯的tools/list

命名逻辑在 src/mcp/server/_otel.py 中实现:name=f"{ctx.method}{f' {target}' if target else ''}",其中target取自消息参数中的name字段。

每个 span 携带的属性

每个 span 都带有若干固定属性:

属性出现位置说明
mcp.method.name每个 span被调用的 JSON-RPC 方法名,如tools/call
mcp.protocol.version每个 spanMCP 协议版本
jsonrpc.request.id请求上(通知没有)该 JSON-RPC 请求的 id,转为字符串写入

错误处理也会自动反映到 span 状态上:

  • 处理器抛出异常时,span 状态被标记为错误(StatusCode.ERROR);
  • 工具结果带is_error=True时同样如此。

具体实现在 src/mcp/server/_otel.py:处理器抛出MCPError时写入error.type(错误码)与rpc.response.status_code;参数校验失败(ValidationError)时按线上响应镜像写入INVALID_PARAMS对应属性;其他异常则记录异常并置为错误状态。对tools/call还做了一层"预序列化"的探测:只有真正会以错误形态到达线上的结果才算错误——即CallToolResult(is_error=True)模型形态或原始的{"isError": True}字面布尔形态,非布尔可强制的值(如1"true")因过于罕见而被有意忽略,注释与测试都说明了这一点。

tools/call 与 prompts/get 遵循 GenAI 语义约定

由于追踪工具调用是最常见的诉求,tools/call的 span 遵循 OpenTelemetry 的 GenAI 语义约定:

  • gen_ai.operation.name,固定为"execute_tool"
  • gen_ai.tool.name,值为被调用工具的名字。

prompts/get的 span 也以同样的精神携带gen_ai.prompt.name。列表类方法(tools/list等)不携带任何gen_ai.*键,因为没有可命名的目标。

[!tip] 正是这些 GenAI 属性,让追踪界面把你的工具调用与其他任何 Agent 的工具调用以同样的方式分组展示。这份分组是白送的,无需任何额外代码。

上述属性写入逻辑集中在 src/mcp/server/_otel.py,并由 tests/server/test_otel.py(tools/call mytool的 span 名、gen_ai.operation.name == "execute_tool"gen_ai.tool.name == "mytool"等断言)与 tests/docs_src/test_opentelemetry.py 加以验证。测试还覆盖了"非工具/非 prompt 方法不携带 gen_ai 属性"(见 tests/server/test_otel.py)。

它"零成本",直到你需要它

为什么"默认开启"是个舒服的默认?关键在于 SDK 只依赖 OpenTelemetry 的轻量半opentelemetry-api。这一点由 pyproject.toml 中的依赖声明(opentelemetry-api>=1.28.0)佐证;而opentelemetry-sdkopentelemetry-exporter-otlplogfire等则属于测试依赖(见 pyproject.toml 附近的开发依赖清单),运行时并不强制安装。

在没有安装 OpenTelemetry SDK 与任何 exporter 的情况下,opentelemetry-api的默认追踪器是 No-Op 的——创建 span 是一个空操作。所以你的服务器此刻生成的 span 几乎不花任何成本,也没有人在收集它们。

当你想真正"看见"这些 span 的那一天,装上另一半并指向某个后端即可:

uv add opentelemetry-sdk opentelemetry-exporter-otlp

然后按 OpenTelemetry 常规方式配置 exporter——SDK 一直悄悄创建的那些 span 就会全部亮起来。服务器代码一行都不用改。

[!info] Pydantic Logfire 就是这样一个后端,它把配置也替你做了:pip install logfirelogfire.configure(),你的 MCP span 就会出现在实时视图中。它构建在 OpenTelemetry 之上,因此下文的一切对它同样适用。

跨网络的追踪:从客户端到服务端

一条 trace 最有价值的时候,是它能跟随请求从客户端一路贯穿到服务端、形成一幅连贯图景的时候。

客户端和服务端都跑在 SDK 上时,这种关联是自动发生的:

  • 客户端向请求注入 W3C trace context(traceparent/tracestate);
  • 服务端把上下文读出来,于是服务端 span 嵌套在客户端 span 之下,属于同一条 trace。

这是 SEP-414(spec 层面的协议增强),SDK 无需你申请就实现了它。

从源码看,注入发生在客户端出站路径:mcp/shared/jsonrpc_dispatcher.py在发送请求时打开一个CLIENT类型的 span(MCP send {method} {target}),并调用inject_trace_context(out_meta)把 W3C 上下文写进_meta(见 src/mcp/shared/jsonrpc_dispatcher.py)。而inject_trace_context/extract_trace_context这对助手位于 src/mcp/shared/_otel.py,分别对应opentelemetry.propagate.inject/extract

如果入站消息没有携带 trace context——例如请求来自一个非 SDK 客户端——服务端 span 并不会开启一条全新的孤儿 trace,而是直接挂到服务端当前已存在的 span 之下。这一点在extract_trace_context的实现里体现得很明确(src/mcp/shared/_otel.py):当载体缺失、格式非法(extract抛出ValueError/TypeError)或traceparent无效时返回None,调用方据此退回到"环境父级"嵌套,而不是用一个显式的空Context把 span 变成孤儿。对应的测试包括 tests/shared/test_otel.py(畸形traceparent降级为无父级)以及 tests/server/test_otel.py(无traceparent时嵌套到环境 span 之下)。

如何关闭它

追踪本质上是一个 middleware——而且是你服务器 middleware 列表里的第一个。如果你确实需要一个不产生任何 span 的服务器,把它摘掉即可:

from mcp.server._otel import OpenTelemetryMiddleware mcp._lowlevel_server.middleware[:] = [ m for m in mcp._lowlevel_server.middleware if not isinstance(m, OpenTelemetryMiddleware) ]

[!warning] 上面的 import 带有下划线前缀,这是故意的。OpenTelemetryMiddleware类目前是临时性的(provisional),与Server.middleware同样是临时 API,因此导入路径在未来版本中可能变化。你几乎永远不需要这招:在没有安装 exporter 的情况下 span 是免费的,所以通常的答案就是保持开启、不装 exporter

从源码结构看,这一"摘除"方案可行,正是因为OpenTelemetryMiddleware是上下文层的ServerMiddleware,被默认注入到Server.middleware列表头部(src/mcp/server/lowlevel/server.py);过滤掉它即可彻底停止 span 生成。

总结

  • 每个MCPServer和每个底层Server默认都会为每条入站消息生成一个SERVERspan——你什么都不用写;
  • span 携带mcp.method.namemcp.protocol.versiontools/callprompts/get还额外携带 GenAI 属性,让你的工具调用像任何其他 Agent 一样被分组展示;
  • 在安装 OpenTelemetry SDK 与 exporter 之前它零成本;装好之后,无需改动服务器代码,一切即刻可见;
  • 当客户端与服务端都运行在 SDK 上时,客户端到服务端的 trace context 自动传播,形成完整链路。

追踪解决的是"看清请求如何执行";而决定"请求是否会被执行"的,是 授权(Authorization)。

  • 人工智能
  • MCP 服务
  • MCP Clients

【免费下载链接】python-sdk

The official Python SDK for Model Context Protocol servers and clients

项目地址:https://gitcode.com/gh_mirrors/pythonsd/python-sdk
点击查看免费下载
上一篇:从图像到代码:Qwen3-VL-30B-A3B-Instruct视觉编码功能实现Draw.io/HTML生成教程
下一篇:Isaac Lab 3步装好,10分钟跑通机器人强化学习仿真

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询