- 人工智能
- MCP 服务
- MCP Clients
【免费下载链接】python-sdk
The official Python SDK for Model Context Protocol servers and clients
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 也明确断言:新建的Server的middleware中默认包含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 | 每个 span | MCP 协议版本 |
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-sdk、opentelemetry-exporter-otlp、logfire等则属于测试依赖(见 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 logfire、logfire.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.name与mcp.protocol.version;tools/call与prompts/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
相关推荐
MCP Python SDK 内置 OpenTelemetry 追踪:零配置的服务器可观测性指南
MCP Python SDK 内置 OpenTelemetry 追踪:零配置的服务器可观测性指南 本篇技术指南讲解 MCP Python SDK 中 默认开启、
人工智能MCP 服务MCP ClientsPython MCP SDK 服务端 OpenTelemetry 追踪:零代码接入的默认可观测性机制
Python MCP SDK 服务端 OpenTelemetry 追踪:零代码接入的默认可观测性机制 导读 :本文讲解 Model Context Protoc
人工智能MCP 服务MCP ClientsMCP Python SDK 服务器可观测性:开箱即用的 OpenTelemetry 追踪
MCP Python SDK 服务器可观测性:开箱即用的 OpenTelemetry 追踪 你写的每一个 MCP 服务器,无论用高层 MCPServer 还是底
人工智能MCP 服务MCP Clients
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考