learn-claude-code s14:MCP 工具发现与动态工具池——把外部服务接入 Agent Loop

learn-claude-code s14:MCP 工具发现与动态工具池——把外部服务接入 Agent Loop learn-claude-code s14MCP 工具发现与动态工具池——把外部服务接入 Agent Loop【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code本篇基于 learn-claude-code 课程 s14 章节s14_mcp_plugin/README.ja.md及配套实现 s14_mcp_plugin/code.py讲解如何用 MCPModel Context Protocol思路解决每接入一个外部服务就要手写一套工具定义、参数 schema 和调用 handler的维护问题。读完后你将理解MCPClient、connect_mcp、assemble_tool_pool三个核心组件的职责划分掌握mcp__{server}__{tool}前缀命名、host 侧权限策略与工具边界错误处理的设计并能在本仓库中直接运行该章节代码验证整个动态工具池流程。一、背景问题硬编码工具无法扩展课程前面章节如 s04_hooks的基础工具——bash、read_file、write_file、edit_file、glob——都直接写在code.py中。若要把文档系统和部署平台接进来最直接的做法是手写search_docs、deploy_status、trigger_deploy三个工具但每增加一个服务就要在 harness 里新增一组工具定义、参数 schema 和调用 handler维护成本随服务数量线性增长。MCP 把这部分工作拆成两个角色server 端提供工具列表对应tools/list和调用入口对应tools/call并自带每个工具的 description 与 inputSchemaharness 端负责连接 server、给工具分配 model-facing 名称、做权限检查最后把发现的工具交给模型。s14_mcp_plugin/code.py完整实现了这套机制约 530 行其中docs和deploy两个 server 是进程内 mock用来演示tools/list、tools/call边界和动态工具池本章不实现真实的 MCP transport。二、方案总览三个新增组件s14 在 s04 的五个基础工具和 Hooks 之上新增三个部分组件职责源码位置MCPClient保存 server 返回的工具定义tools/list结果与调用 handlertools/call边界code.py#L160-L187connect_mcp连接一个 server 并取得其工具列表暴露给模型作为内置工具code.py#L279-L296assemble_tool_pool每轮 model call 前把基础工具与所有已连接 server 的 MCP 工具合并成一个工具池code.py#L313-L356两个 mock server 通过工厂函数注册在MOCK_SERVERS中code.py#L273-L276docsserversearch文档搜索、get_version查 API 版本两者均带readOnlyHint: true注解deployservertrigger触发部署带destructiveHint: true注解、status查部署状态。三、机制详解3.1 基础 Agent Loop 保持不变关键设计是Agent Loop 本身不因 MCP 而改变变化的只是每轮调用模型前组装当前工具池这一步def agent_loop(messages: list): while True: tools, handlers assemble_tool_pool() response client.messages.create( modelMODEL, systemassemble_system_prompt(), messagesmessages, toolstools, max_tokens8000, ) ...对应源码 code.py#L467-L506新 server 连接后下一次assemble_tool_pool()就会把它的工具加入 model input工具执行结果仍按惯例以tool_result块追加到 messages。此外assemble_system_prompt()code.py#L359-L362会在已连接 server 非空时向 system prompt 追加Connected MCP servers: ...提示帮助模型知道哪些服务可用。3.2 MCPClient发现结果与调用边界class MCPClient: def register(self, tool_defs, handlers): self.tools list(tool_defs) self._handlers dict(handlers) def call_tool(self, tool_name, args): handler self._handlers.get(tool_name) if not handler: return fMCP error: unknown tool {tool_name} try: return str(handler(**args)) except Exception as error: return fMCP error: {type(error).__name__}: {error}register()代表发现得到的工具列表call_tool()代表调用边界错误被捕获后返回给模型而不是终止 Agent Loop。从源码看实际的register()还做了三道校验code.py#L168-L178每个工具的name必须是非空字符串同一 server 内不允许重名工具Duplicate MCP tool name;每个工具都必须有对应 handlerMissing MCP handlers。这三道校验把server 提供不完整工具清单这类错误提前到注册阶段暴露。3.3 connect_mcp只做连接与发现def connect_mcp(name: str) - str: if name in mcp_clients: return fMCP server {name} already connected factory MOCK_SERVERS.get(name) if not factory: return fUnknown server {name} server factory() mcp_clients[name] server ...对应 code.py#L279-L292它对已连接 server 去重、对未知 server 返回可用的 server 列表成功后把客户端存入全局mcp_clients并打印发现结果[mcp] connected: docs - search, get_version。connect_mcp本身是一个内置工具CONNECT_TOOLcode.py#L299-L307其name参数用enum: [docs, deploy]限制了可选 server。也就是说模型启动时看到的只有 5 个基础工具加connect_mcp执行connect_mcp(namedocs)之后下一次 model call 的工具列表才会多出mcp__docs__search mcp__docs__get_version这正是动态工具池的含义——工具集合随会话演进而不是一次性固定。3.4 前缀命名区分不同 server 的同名工具多个 server 都可能提供search、status这类同名工具harness 统一使用如下模型可见名mcp__{server}__{tool}配套规则code.py#L192-L208normalize_mcp_name()用正则[^a-zA-Z0-9_-]把 model 工具名不允许的字符替换成下划线例如docs.one/get.version会变成docs_one/get_version组装工具池时检查归一化后的名称冲突和64 字符长度上限冲突直接抛ValueError终止组装prefixed fmcp__{safe_server}__{safe_tool} if prefixed in origins: raise ValueError(MCP tool name collision after normalization)这样docs.one/get.version与docs_one/get_version两个 server 不会在归一化后静默映射到同一个mcp__docs_one__get_version。仓库测试 tests/test_agent_teams_runtime.py 中的test_normalized_mcp_tool_name_collisions_are_rejected正是构造了docs.oneget.version与docs_oneget_version两个 client断言assemble_tool_pool()抛出含collision的ValueError。3.5 工具定义与 handler 成对入池tools.append({ name: prefixed, description: tool_def.get(description, ), input_schema: schema, }) handlers[prefixed] ( lambda *, clientserver, toolraw_name, **kwargs: client.call_tool(tool, kwargs) )对应 code.py#L342-L350。这里有两个易错点被显式处理模型看到的是带前缀的名字mcp__docs__search而 handler 内部用 server 的原始工具名search去调MCPClient.call_tool两边通过闭包绑定default argument 捕获循环变量clientserver, toolraw_name把当前 client 和原始工具名固定在每个 lambda 的默认参数里避免循环结束后所有 lambda 都指向最后一个工具的经典闭包陷阱。同时组装阶段还会校验 server 提供的inputSchema必须是 object 类型code.py#L338-L340并按 host 策略写入每个前缀工具的权限策略。3.6 权限由 host 决定而非 server 声明MCP server 可以返回readOnlyHint、destructiveHint注解本仓库 mock server 就带上了这些注解但这些只是 server 的自述不是授权依据。本章采用 host 侧策略MCP_HOST_POLICY { (docs, search): allow, (docs, get_version): allow, (deploy, status): allow, (deploy, trigger): confirm, }code.py#L194-L200permission_hook()中针对mcp__前缀工具的处理code.py#L402-L407if block.name.startswith(mcp__): policy mcp_tool_policies.get(block.name, confirm) if policy ! allow: print(f\n[permission] External tool {block.name}({block.input})) if input(Allow? [y/N] ).strip().lower() not in {y, yes}: return Permission denied by user两点值得注意未在策略中配置的外部工具默认confirm需用户确认即未声明即不信任description 里写readOnly也不会自动放行。测试 tests/test_agent_teams_runtime.py 的test_mcp_permission_uses_host_policy构造了一个名为mcp__third_party__erase、描述含(readOnly)的伪造工具断言其依然要求用户确认——这是对server 自述不可信原则的直接验证。同一测试还确认mcp__deploy__status策略allow可静默执行、mcp__deploy__trigger策略confirm被拒时返回Permission denied by user。权限 hook 通过register_hook(PreToolUse, permission_hook)挂载与 bash deny list、文件路径越界检查共同构成 s04 遗留的权限层。3.7 输入错误留在工具边界内模型可能漏传 required 参数如search缺query或发送 server 不接受的字段。execute_tool()code.py#L450-L462与MCPClient.call_tool()双层都会捕获异常并返回 error 形式的tool_resultMCP error: TypeError: lambda() missing 1 required argument: query课程脚本因此不会崩溃模型可以在下一轮自行修正参数重试。四、与 s04 的完整差异组件s04s14基础工具5 个固定工具不变工具来源code.py内的定义基础工具 发现的 MCP 工具工具池固定TOOLS每轮由assemble_tool_pool()组装外部工具名无mcp__{server}__{tool}权限Shell deny list 与 path check额外引入 host-side MCP policyMCP transport无用进程内 mock server 演示边界本章刻意不引入 Task、Background、Cron、Team、Worktree 等机制它们会在 s15 Integrated Harnesss15_integrated_harness中与 MCP 汇合到同一个 runtime。五、动手运行运行前提来自 code.py 头部注释与 requirements.txtpip install anthropic python-dotenv # .env 中配置 ANTHROPIC_API_KEY及 MODEL_ID 环境变量然后cd learn-claude-code python s14_mcp_plugin/code.py输入docs server に接続し、agent hooks を検索して、現在の documentation API version を教えてください。典型的工具调用 trace 为connect_mcp(namedocs) mcp__docs__search(queryagent hooks) mcp__docs__get_version()继续输入deploy server に接続して web service の status を確認してください。deployment は trigger しないでください。按 host 策略mcp__deploy__status直接执行若模型尝试mcp__deploy__trigger则会弹出Allow? [y/N]确认。仓库还有两条自动化验证路径可以参考tests/test_agent_teams_runtime.py 的test_mcp_lesson_builds_on_the_base_kernel断言连接前工具池恰为{bash, read_file, write_file, edit_file, glob, connect_mcp}连接docs后mcp__docs__search出现在工具池中且 handler 可调用并返回[docs] Found 3 results for hooksWeb 端场景数据 web/src/data/scenarios/s15.json 中编排了connect_mcp→mcp__deploy__status的调用序列用于站点上的交互演示。六、小结与适用边界s14 给出的是一套可移植到真实 MCP 集成的设计模式发现与调用分离MCPClient保存tools/list结果与tools/call边界server 侧改动不影响 loop动态工具池工具集合随connect_mcp逐轮增长Agent Loop 结构不变归一化 冲突检测 长度上限保证模型可见名无歧义host 侧权限策略默认 confirm、description 不可信保证外部工具默认安全错误留在工具边界以 errortool_result形式交还模型自纠。适用边界需要说明本章的docs/deploy是进程内 mockconnect_mcp的可选 server 由enum固定为两者未实现跨进程/跨网络的真实 MCP transport生产环境中这部分需要替换为真实的 MCP client transport但assemble_tool_pool的命名、策略与错误处理逻辑可以直接沿用。后续阅读可参见 s14_mcp_plugin/README.md 与 s14_mcp_plugin/README.zh.md 的英中版本以及下一章 s15 Integrated Harness 中 MCP 与其他 harness 层的合并方式。【免费下载链接】learn-claude-codeBash is all you need - A nano claude code–like 「agent harness」, built from 0 to 1项目地址: https://gitcode.com/GitHub_Trending/an/learn-claude-code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考