如何把外部服务经 MCP 接入 Agent 工具池:learn-claude-code s14 的工具发现、mcp__ 前缀与名称冲突检查
【免费下载链接】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
如果你的 Agent harness 已有自己的基础工具(文件读写、shell 等),现在想接入一个外部服务(比如文档系统、部署平台),又不想为每个服务手写一套工具定义、参数 schema 和调用处理代码,learn-claude-code 的 s14 章节(s14_mcp_plugin/)演示了一条 MCP 风格的接入路径:server 提供工具列表和调用入口,harness 负责连接、命名、权限检查,再把发现的工具合并进同一个工具池交给模型。运行python s14_mcp_plugin/code.py后,你在交互提示符里输入一句话,就能观察模型调用connect_mcp完成发现、随后以mcp__docs__search这类带前缀的名字调用外部工具的全过程。
需要先明确一条边界:s14 中的docs和deploy两个 server 是进程内模拟实现,用来展示tools/list、tools/call与动态工具池的边界,本章没有实现真实的 MCP transport(见 s14_mcp_plugin/README.md 的 “The Solution” 一节)。因此本文覆盖的是接入模式、命名规则、冲突检查与验证方式;把这套模式换成真实网络传输不在 s14 范围内。
准备条件:依赖、环境变量与运行入口
在仓库根目录下完成以下三步(requirements.txt 与 .env.example 即当前仓库自带文件):
- 安装依赖:
pip install -r requirements.txtrequirements.txt声明的依赖为anthropic>=0.25.0、python-dotenv>=1.0.0、pyyaml>=6.0。
- 创建环境变量文件(会在仓库根目录生成
.env,这是运行脚本的必要前置配置,不是对仓库源码的修改):
cp .env.example .env然后编辑.env。.env.example 中标注了两项必填:
ANTHROPIC_API_KEY:模型 API 密钥;MODEL_ID:模型 ID,文件给出的默认示例值为claude-sonnet-4-6。
ANTHROPIC_BASE_URL可选项用于接入 Anthropic 兼容的提供商。注意 s14_mcp_plugin/code.py 通过MODEL = os.environ["MODEL_ID"]直接读取该变量,未设置时脚本会在启动阶段报错,不会自行降级。
- 启动脚本(在仓库根目录执行):
python s14_mcp_plugin/code.py脚本进入交互循环,提示符为s14 >>,输入q退出。此时模型可见的工具是 s04 继承来的五个基础工具(bash、read_file、write_file、edit_file、glob)加上connect_mcp,尚无任何mcp__前缀工具。其中bash会真实执行 shell 命令(超时 120 秒),请在自己可控的目录中运行该脚本。
执行接入:触发工具发现并核对输出
启动后,输入 s14_mcp_plugin/README.md “Try It Out” 一节给出的示例指令:
Connect to the docs server, search for agent hooks, and tell me the current documentation API version.判断接入是否生效,按顺序核对以下三类输出:
连接与发现:控制台会打印(对应 code.py 中
connect_mcp的print):[mcp] connected: docs -> search, get_version模型侧收到的工具结果是
Connected to MCP server 'docs'. Discovered 2 tools: search, get_version。工具轨迹:文档给出的典型轨迹如下(文档示例,实际调用顺序可能因模型而异):
connect_mcp(name="docs") mcp__docs__search(query="agent hooks") mcp__docs__get_version()调用结果:模拟 server 的 handler 返回固定字符串,s14_mcp_plugin/README.md 将其标注为典型输出(以下均为文档示例输出,不是你需要逐字比对的固定结果):
[docs] Found 3 results for 'agent hooks' [docs] API v2.1.0
连接完成后,下一轮assemble_tool_pool()会把mcp__docs__search、mcp__docs__get_version加入模型输入,工具执行结果仍作为tool_result追加到消息中——基础 Agent Loop 本身没有改动。
再输入第二段示例指令,可以验证权限分支:
Connect to the deploy server and check the web service status. Do not trigger a deployment.deployserver 暴露status与trigger两个工具:status按宿主策略直接执行(示例输出[deploy] web: running (v1.4.2),文档示例);trigger需要用户确认,下一节说明规则。
命名规则:mcp__ 前缀、归一化与 64 字符冲突检查
多个 server 都可能提供search、status这样的同名工具,s14 用固定格式避免混淆:
mcp__{server}__{tool}名称进入工具池前经过三道检查,全部在 assemble_tool_pool() 中执行,且每轮模型调用前都会重跑:
归一化:normalize_mcp_name() 用正则
[^a-zA-Z0-9_-]把字母、数字、下划线、连字符之外的字符替换为_;归一化结果为空串时直接抛ValueError。长度限制:带前缀后的名字超过 64 个字符时抛
ValueError("MCP tool name is longer than 64 characters: ...")。冲突检查:若两个来源归一化后落到同一个带前缀名字(比如 README 中举的
docs.one/get.version与docs_one/get_version两个例子),抛出:ValueError: MCP tool name collision after normalization: '...' maps both ... and ...异常信息中会同时给出冲突双方的原始来源(
origins字典),方便定位是哪个 server 的哪个工具撞名。文档强调这保证不同来源“不会悄悄映射到同一个名字”。
加入工具池时,模型看到的是带前缀的名字,而 handler 仍用 server 的原始工具名回调MCPClient.call_tool()。code.py 中 handler 写成带默认参数的 lambda(lambda *, client=server, tool=raw_name, **kwargs: ...),README 解释了原因:默认参数把当前 client 和 tool 捕获进闭包,避免循环里创建的 lambda 全部指向最后一个工具。
权限:宿主侧策略,而不是 server 自述
MCP server 的工具定义里可以带readOnlyHint、destructiveHint(两个模拟 server 都带),但文档明确:这些提示来自 server,不能直接作为授权依据;即使 description 写着readOnly,工具也不会自动放行。s14 采用宿主侧策略表 MCP_HOST_POLICY:
MCP_HOST_POLICY = { ("docs", "search"): "allow", ("docs", "get_version"): "allow", ("deploy", "status"): "allow", ("deploy", "trigger"): "confirm", }permission_hook()在PreToolUse阶段按规范化后的工具名查这张表(code.py):策略为allow的工具直接执行;策略为confirm或未配置的外部工具默认弹出确认:
[permission] External tool mcp__deploy__trigger({'service': 'web'}) Allow? [y/N]输入y/yes放行,其余回答返回Permission denied by user作为工具结果。也就是说,接入一个新 server 时,如果你想让某个外部工具免确认,需要在宿主侧策略里显式登记(server, tool): "allow",而不是依赖 server 的自述。
运行中的报错如何判断
以下现象均来自 s14 文档与代码,可作为对照排查:
| 现象 | 来源与含义 |
|---|---|
Unknown server 'xxx'. Available: docs, deploy | 请求的 server 不在MOCK_SERVERS注册表里(code.py);s14 只内置docs和deploy两个模拟 server |
MCP server 'docs' already connected | 重复连接同一 server,返回提示后不再重复发现(code.py) |
MCP error: TypeError: <lambda>() missing 1 required argument: 'query' | 模型漏传了必填参数。execute_tool()与MCPClient.call_tool()都会捕获异常并把它作为错误tool_result返回,Agent Loop 不会因此退出,模型可在下一轮自行修正参数 |
MCP error: unknown tool '{tool_name}' | 调用方请求了该 client 上没有注册的工具名 |
控制台[hook] Large output from ...: N chars | PostToolUse的large_output_hook对超过 100000 字符的输出做提示 |
工具输入错误“留在工具边界内”是 s14 有意的设计:错误回到模型侧修正,而不是中断课程脚本。
限制与后续
- s14 的
docs/deploy是进程内 stand-in,不含真实 MCP transport;工具来源、发现、命名、冲突检查与宿主权限这套边界是本章实际演示的内容。 - s14 是独立的课程分支,不带入 Task、Background、Cron、Team 或 Worktree。后续 s15_integrated_harness/README.md 的 Integrated Harness 会把基础工具、Hooks、Skills、Context、Memory、Task、Background、Cron、Teams 与 MCP 放进同一个运行时,且沿用 s14 的动态 MCP 路径与宿主侧策略;如需在完整 harness 中接入外部服务,以 s15 为入口继续读。
- 完整章节说明见 s14_mcp_plugin/README.md(中文对照 s14_mcp_plugin/README.zh.md),可运行实现见 s14_mcp_plugin/code.py。
【免费下载链接】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),仅供参考