news 2026/9/11 7:18:12

FastMCP CodeMode 实战:用 search + execute 两个元工具把整个工具目录折叠进沙箱

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FastMCP CodeMode 实战:用 search + execute 两个元工具把整个工具目录折叠进沙箱

FastMCP CodeMode 实战:用 search + execute 两个元工具把整个工具目录折叠进沙箱

【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp

CodeMode 是 FastMCP 提供的一种服务端 Transform:它把完整的工具目录折叠成search(关键词发现)与execute(沙箱内执行 Python 脚本、链式调用工具)两个元工具。LLM 不再为每个中间结果消耗上下文 token,而是编写一段在服务端沙箱中运行的脚本,只把最终答案传回。本文以仓库中的 examples/code_mode 示例为主线,结合源码与官方文档,讲清 CodeMode 的原理、运行方式、配置手段与安全边界。

CodeMode 要解决的两个扩展性问题

标准 MCP 的工具调用模式存在两个规模化瓶颈:

  1. 目录全量加载:整个工具目录会在开始时全部装入 LLM 上下文。当工具数量达到数百个时,LLM 在读取用户请求之前就已经消耗了数万 token。
  2. 逐次往返:每一次call_tool都是一次"LLM 调用工具 → 结果穿回上下文 → LLM 推理 → 再调用下一个工具"的完整往返,仅用于喂给下一步的中间结果也在反复烧 token。

CodeMode 同时解决这两个问题:LLM 看到的不是完整工具目录,而是"发现工具"和"编写并执行代码"两类元工具。它按需发现(on demand discovery),在沙箱里写一段链式调用工具的脚本,最终只拿到最终答案。该思路最初由 Cloudflare 在 Code Mode 中提出,后被 Anthropic 在 Code Execution with MCP 中进一步探索(详见 docs/servers/transforms/code-mode.mdx)。

快速运行:两个终端即可体验

仓库中的 examples/code_mode 目录包含三个文件:

  • README.md:示例说明与运行方式
  • server.py:定义 8 个普通工具并挂载 CodeMode Transform
  • client.py:演示searchexecute的完整调用链

启动方式(两个终端):

uv run python server.py # 终端一:启动服务 uv run python client.py # 终端二:运行客户端

也可以按 server.py 文件头注释中的方式运行:uv run python examples/code_mode/client.py

依赖前提:沙箱依赖pydantic-monty,需要安装code-modeextra:

pip install "fastmcp[code-mode]"

安装后uvpip均可解析依赖。如果未安装pydantic-montyexecute会抛出ImportError,提示"Install it withfastmcp[code-mode]or pass a custom SandboxProvider"(见 code_mode.py)。

服务端:一行 Transform 折叠 8 个工具

server.py 用@mcp.tool定义了 8 个普通工具:addmultiplyfibonaccireverse_stringword_countto_uppercaselist_filesread_file。这些函数与标准 FastMCP 服务没有任何区别,关键在最后一行:

from fastmcp import FastMCP from fastmcp.experimental.transforms.code_mode import CodeMode mcp = FastMCP("CodeMode Demo") # ... 8 个 @mcp.tool 定义 ... mcp.add_transform(CodeMode())

也可以采用官方文档推荐的构造方式:mcp = FastMCP("Server", transforms=[CodeMode()])(见 docs/servers/transforms/code-mode.mdx)。Transform 包裹已有工具,工具函数本身完全不用改动。

客户端:发现、检索、一次往返执行

client.py 完整演示了三步流程:

第一步:list_tools()只返回两个合成元工具

async with Client(Path("examples/code_mode/server.py")) as client: tools = await client.list_tools()

服务端明明有 8 个工具,但客户端此时只能看到两个:

┌────────────── list_tools() ──────────────┐ │ Tool Description │ │ search Search for available tools ... │ │ execute Chain `await call_tool(...)` ... │ └── 8 backend tools collapsed into 2 ──────┘

第二步:search按关键词发现工具

result = await client.call_tool("search", {"query": "add multiply numbers"})

返回按相关度排序的匹配结果(示例输出中命中 3 个):

┌──── search(query="math arithmetic") ─────┐ │ # Tool Description │ │ 1 add Add two numbers together. │ │ 2 multiply Multiply two numbers. │ │ 3 fibonacci Generate the first n ... │ └── 3 results ─────────────────────────────┘

第三步:execute一段脚本完成全部链式调用

code = """\ a = await call_tool("add", {"a": 3, "b": 4}) b = await call_tool("multiply", {"x": a["result"], "y": 2}) fib = await call_tool("fibonacci", {"n": b["result"]}) return {"sum": a["result"], "product": b["result"], "fibonacci": fib["result"]} """ result = await client.call_tool("execute", {"code": code})

整段脚本在服务端沙箱中运行,只有最终return的值穿回上下文:

┌────────────── execute ───────────────────┐ │ a = await call_tool("add", {"a": 3 ... │ │ b = await call_tool("multiply", ... │ │ return b │ └── result: 14.0 ──────────────────────────┘

核心洞察(README 原话):标准 MCP 中每次call_tool都是经过 LLM 的一次往返;而 CodeMode 下 LLM 只写一段脚本,所有工具调用在服务端完成,中间数据永远不会进入上下文窗口

源码级原理:CodeMode 是如何折叠目录的

code_mode.py 是完整实现,核心类CodeMode继承自CatalogTransform。从源码结构看,它通过两个钩子改造工具暴露面:

  • transform_tools(tools):把完整目录替换为发现工具列表 + execute,即[search, get_schema, execute](默认配置),见 code_mode.py;
  • get_tool(name, call_next):拦截对合成工具的查找,其余名字继续走下游 Transform 链,见 code_mode.py。

execute工具的内部实现值得细看(code_mode.py):

  • 沙箱内只注入一个异步函数call_tool(tool_name, params),它先通过transform.get_tool_catalog(ctx)拿到(经过鉴权过滤的)工具目录,找不到工具时抛NotFoundError("Unknown tool: ...")
  • 每次调用先检查max_tool_calls计数(默认 50),超限抛ToolError,防止一段 LLM 生成的代码里的循环扇出成大量后端操作;
  • 工具返回值经_unwrap_tool_result规范化:有输出 schema 时返回structured_content,否则把文本内容拼接成字符串返回,见 code_mode.py。

沙箱:默认 MontySandboxProvider 与资源限制

默认沙箱MontySandboxProviderpydantic-monty驱动。源码中的_DEFAULT_LIMITS给出了开箱即用的保守基线(code_mode.py):

_DEFAULT_LIMITS = { "max_duration_secs": 30.0, "max_memory": 100_000_000, # 100 MB }

即:不传limits时默认 30 秒超时、100 MB 内存上限,确保开箱配置不会无界运行。三个构造形态:

MontySandboxProvider() # 基线:30s、100 MB MontySandboxProvider(limits={...}) # 自定义 MontySandboxProvider(limits=None) # 显式不限

limits支持的键全部可选,省略即表示该维度不设上限(见 docs/servers/transforms/code-mode.mdx):

Key类型说明
max_duration_secsfloat最大墙钟执行时间
max_memoryint内存上限(字节)
max_allocationsint对象分配总数上限
max_recursion_depthint最大递归深度
gc_intervalint垃圾回收频率

安全警告SandboxProvider协议注释明确强调,run收到的code不可信的 LLM 生成代码,实现必须将其放入隔离沙箱执行,绝不能直接exec();生产环境应使用MontySandboxProvider(见 code_mode.py)。仓库测试中的_UnsafeTestSandboxProvider也自注释为 "UNSAFE: Uses exec() for testing only"(见 tests/experimental/transforms/test_code_mode.py)。

工具调用上限同样可调:

CodeMode() # 默认:每次 execute 最多 50 次 call_tool() CodeMode(max_tool_calls=200) # 调高上限 CodeMode(max_tool_calls=None) # 不设上限

发现工具:Search / GetSchemas / GetTags / ListTools

CodeMode 内置 4 个发现工具工厂,默认只启用SearchGetSchemas(code_mode.py)。每个工具都支持default_detail,LLM 也可在单次调用中覆盖。

SearchGetSchemas共享同一套三种详细度,输出格式一致:

级别输出Token 成本
"brief"工具名 + 一行描述最低,适合扫描
"detailed"紧凑 Markdown,含参数名、类型、必填标记中等,通常足够写代码
"full"完整 JSON schema最高
  • Search:基于 BM25 排序做自然语言检索(默认最大 50 条)。命中数少于目录总数时,结果带"N of M tools:"前缀提示 LLM 还有更多可发现;支持default_limit限制结果数、tags参数先按标签过滤再检索。示例:Search(default_limit=5)
  • GetSchemas:按工具名返回参数细节,detail="full"时给出完整 JSON schema,适合参数深度嵌套的场景。
  • GetTags:按标签浏览目录,brief 输出- math (3 tools)这类计数,full 输出各标签下的工具列表。默认不启用,适合大目录让 LLM 先按类别定位。
  • ListTools:整体倾倒目录,默认"brief"。默认不启用——对小型目录(约 20 个工具以内)"一次性看全"比多次 search 往返更快,大目录则 search 更省 token:
from fastmcp.experimental.transforms.code_mode import CodeMode, ListTools, GetSchemas code_mode = CodeMode( discovery_tools=[ListTools(), GetSchemas()], )

三种发现模式:按目录规模取舍

三阶段(默认):search → get_schema → execute。适合大型/复杂工具集,LLM 只为真正用到的 schema 付费:

mcp = FastMCP("Server", transforms=[CodeMode()])

如果工具带标签,可加GetTags形成四阶段渐进式披露:

code_mode = CodeMode( discovery_tools=[GetTags(), Search(), GetSchemas()], ) mcp = FastMCP("Server", transforms=[code_mode])

两阶段:让 Search 直接返回参数 schema,少一次往返,适合中小目录:

code_mode = CodeMode( discovery_tools=[Search(default_detail="detailed"), GetSchemas()], ) mcp = FastMCP("Server", transforms=[code_mode])

单阶段:跳过发现,把工具说明写进 execute 的描述里,适合工具很少、LLM 已知晓目录的场景:

code_mode = CodeMode( discovery_tools=[], execute_description=( "Available tools:\n" "- add(x: int, y: int) -> int: Add two numbers\n" "- multiply(x: int, y: int) -> int: Multiply two numbers\n\n" "Write Python using `await call_tool(name, params)` and `return` the result." ), ) mcp = FastMCP("Server", transforms=[code_mode])

自定义发现工具与命名

发现工具是"工厂可组合"的:每个工厂接收GetToolCatalog(目录访问函数,而非目录本身——因为目录是请求级作用域的,不同用户基于鉴权可能看到不同工具),返回一个Tool

from fastmcp.experimental.transforms.code_mode import CodeMode, GetToolCatalog, GetSchemas from fastmcp.server.context import Context from fastmcp.tools import Tool def list_all_tools(get_catalog: GetToolCatalog) -> Tool: async def list_tools(ctx: Context) -> str: """List all available tool names.""" tools = await get_catalog(ctx) return ", ".join(t.name for t in tools) return Tool.from_function(fn=list_tools, name="list_tools") code_mode = CodeMode(discovery_tools=[list_all_tools, GetSchemas()])

注意:LLM 看到的工具描述来自发现工具内部函数的 docstring,因此 docstring 应写清"返回什么、何时该调用"。工具名也可自定义:

code_mode = CodeMode( discovery_tools=[ Search(name="find_tools"), GetSchemas(name="describe"), ], execute_tool_name="run_workflow", )

源码中_build_discovery_tools会校验名称唯一性,并禁止发现工具与execute_tool_name重名(见 code_mode.py)。

何时使用 CodeMode

结合官方文档的指引,CodeMode 特别适合工具数量多、LLM 需要编排多步工具调用的服务:上下文占用从"目录全量 + 每步中间结果"降为"按需发现 + 最终答案",往返次数从"每步一次"降为"一段脚本一次"。官方文档同时给出经验之谈:对于真正复杂的服务器,分阶段发现往往效果更好——把用不上的工具详细 schema 一次性灌给 LLM,其代价可能超过多一次往返的成本。

需要注意的是,CodeMode 目前属于实验性功能(官方文档标注版本 3.1.0),核心接口稳定,但具体发现工具及其参数可能随实践演进。生产使用务必先落实code-modeextra 安装与沙箱资源限制,并针对自己的工具目录跑通端到端调用链(可参考 tests/experimental/transforms/test_code_mode.py 中的测试模式与 docs/servers/transforms/code-mode.mdx 的完整配置说明)。

【免费下载链接】fastmcp🚀 The fast, Pythonic way to build MCP servers and clients.项目地址: https://gitcode.com/GitHub_Trending/fa/fastmcp

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 7:17:03

如何为 Midscene.js 搭建容器化服务:Docker 部署完整指南

如何为 Midscene.js 搭建容器化服务:Docker 部署完整指南 🔥【免费下载链接】midscene GUI Agent for E2E Testing 项目地址: https://gitcode.com/GitHub_Trending/mid/midscene Midscene.js 是基于视觉语言模型的 GUI 自动化工具,可…

作者头像 李华
网站建设 2026/9/11 7:15:35

ML-KWS-for-MCU源码审计:MCU关键词识别工程的得与失

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 7:08:53

AutoHedge:面向Docker Swarm集群的自动化健康守护系统

1. 项目概述:AutoHedge 是什么,它解决的到底是什么问题?AutoHedge 这个名字乍一听像金融风控里的“自动对冲”,但结合热搜词里反复出现的Swarm、Docker、API、Python、MIT,再叠加“docker swarm集群巡检”“failed to …

作者头像 李华
网站建设 2026/9/11 7:07:11

WorkBuddy开放平台接入实战:从零构建Agent应用完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华