 为 FastMCP 工具构建稳定指纹以检测 schema 变更?)
如何用 tool.key 和 to_mcp_tool() 为 FastMCP 工具构建稳定指纹以检测 schema 变更【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp下游系统路由、网关、审计日志经常需要判断一次部署前后某个 MCP 工具的 schema 是否发生了变化。如果每个系统都自己实现一套 JSON 归一化和哈希逻辑结果很容易不一致。FastMCP 提供的做法是直接用tool.key和tool.to_mcp_tool()这两个 API 组装指纹 payload再做确定性序列化和哈希文档标注的版本要求为 3.0.0 及以上见 Tool Fingerprinting。两个构建块的职责tool.key— FastMCP 的规范组件标识把组件类型、标识符和版本编码进一个字符串格式为{key_prefix}:{identifier}{version}tool:greet1.0 # 带版本的工具 tool:greet # 未设置版本的工具用key而不是单纯的name可以保证同一工具的两个版本产生不同指纹且同名工具与资源如都叫foo不会互相冲突。这个定义见 组件基类 中的make_key与key属性。tool.to_mcp_tool()— 返回协议层面向的MCPTool对象即 MCP 客户端实际收到的形状含inputSchema、description、outputSchema、annotations等字段。实现见 Tool.to_mcp_tool。因为路由和网关通常工作在协议层指纹应基于这个对象而不是 FastMCP 内部结构。FastMCP 本身没有内置一个契约哈希因为包含哪些字段属于各应用自己的策略有的系统只关心 input schema有的还要包含 description、tags 或 version。你需要自己决定 payload 包含什么。为单个工具生成指纹最短可运行的主路径如下直接来自文档tool_name替换为你要指纹化的工具名import hashlib import json from fastmcp import FastMCP mcp FastMCP(demo) mcp.tool() def greet(name: str) - str: Say hello. return fHello {name} async def fingerprint_tool(server: FastMCP, tool_name: str) - str: tool await server.get_tool(tool_name) if tool is None: raise ValueError(fTool {tool_name!r} not found) mcp_tool tool.to_mcp_tool() dumped mcp_tool.model_dump(modejson, by_aliasTrue, exclude_noneTrue) payload { key: tool.key, inputSchema: dumped[inputSchema], } canonical json.dumps(payload, sort_keysTrue, separators(,, :)) return hashlib.sha256(canonical.encode(utf-8)).hexdigest()几个关键细节server.get_tool(tool_name)是 FastMCP server 的异步方法见 server.get_tool工具不存在时返回None所以要先判空。model_dump(modejson, by_aliasTrue, exclude_noneTrue)用 MCP 协议字段名产出一个干净、可序列化的字典并去掉None字段。json.dumps(..., sort_keysTrue, separators(,, :))做确定性序列化键排序 紧凑分隔符保证同一份内容每次得到同样的字符串。指纹在工具的名称、版本和 input schema 不变时跨进程重启保持稳定——这是文档明确给出的稳定性边界不是任意字段都保证稳定。自定义 payload决定什么算契约包含策略由你拥有。例如要检测文档漂移description 变化比如 LLM 路由决策依赖描述时把description加进 payloadasync def custom_fingerprint(server: FastMCP, tool_name: str) - str: tool await server.get_tool(tool_name) if tool is None: raise ValueError(fTool {tool_name!r} not found) mcp_tool tool.to_mcp_tool() dumped mcp_tool.model_dump(modejson, by_aliasTrue, exclude_noneTrue) # Include description to detect documentation drift payload { key: tool.key, inputSchema: dumped[inputSchema], description: dumped.get(description), } canonical json.dumps(payload, sort_keysTrue, separators(,, :)) return hashlib.sha256(canonical.encode(utf-8)).hexdigest()文档给出的字段取舍参考哪些字段、什么场景下加入字段何时加入inputSchema始终加入——这是核心契约description当文档漂移有业务影响时如 LLM 路由依赖描述outputSchema当下游会校验响应形状时annotations当行为提示read-only、destructive影响路由时_meta当自定义元数据驱动策略决策时注意payload 里加了哪个字段指纹就会对哪个字段敏感。加description后改一句 docstring 也会触发变更。在 CI 中检测 schema 漂移完整的检测流程是为所有工具生成指纹 manifest存为产物与上一次运行对比。文档给出的两个函数import json import hashlib from pathlib import Path from fastmcp import FastMCP async def generate_manifest(server: FastMCP) - dict[str, str]: Generate a fingerprint manifest for all tools. manifest {} for tool in await server.list_tools(): mcp_tool tool.to_mcp_tool() dumped mcp_tool.model_dump(modejson, by_aliasTrue, exclude_noneTrue) payload { key: tool.key, inputSchema: dumped[inputSchema], } canonical json.dumps(payload, sort_keysTrue, separators(,, :)) manifest[tool.key] hashlib.sha256(canonical.encode(utf-8)).hexdigest() return manifest async def check_drift(server: FastMCP, baseline_path: Path) - list[str]: Compare current fingerprints against a stored baseline. current await generate_manifest(server) baseline json.loads(baseline_path.read_text()) changed [] for key, fingerprint in current.items(): if baseline.get(key) ! fingerprint: changed.append(key) for key in baseline: if key not in current: changed.append(key) return changed使用方式每次构建后在 CI 中运行generate_manifest把返回的{key: sha256}字典存成 JSON 产物baseline_path即指向该文件的路径由你的 CI 环境决定。下一次构建时调用check_drift(server, baseline_path)它会比较当前 manifest 与基线。check_drift的判定逻辑覆盖了三种情况返回的changed列表即需要关注的tool.key指纹值不同schema 变了基线中有、当前没有的 key工具被删除或改名/改版本注意它只对当前存在且指纹不同的 key 逐个比对新增工具同样会因baseline.get(key) ! fingerprint进入changed。返回非空列表即表示存在下游需要知晓的 schema 变更。限制与适用条件指纹只在名称、版本、input schema 不变时保证跨进程稳定如果你自定义 payload 加入了description等字段对应字段变化同样会改变指纹——这是策略选择不是缺陷。tool.key的版本段来自组件的version字段未设置版本时 key 以结尾如tool:greet此时升级版本号会直接产生新指纹即使 schema 完全没变。本方案面向进程内同环境生成的指纹做对比文档没有覆盖跨机器时钟、环境差异等场景也不需要覆盖。FastMCP 不内置契约哈希以上 payload 组装、序列化与比对逻辑都需要在你的构建脚本中维护。完整配方可直接查阅 docs/servers/tool-fingerprinting.mdx。【免费下载链接】fastmcp The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考