接入 MCP

对应源码

扩展环节源码
MCP 配置模型nanobot/config/schema.py::MCPServerConfig
连接、关闭和运行时控制nanobot/agent/loop.py::_connect_mcp、close_mcp、nanobot/agent/tools/mcp.py
MCP Tool/Resource/Prompt 包装nanobot/agent/tools/mcp.py::MCPToolWrapper、MCPResourceWrapper、MCPPromptWrapper
验证与重连tests/agent/test_mcp_transient_retry.py、tests/tools/test_mcp_probe.py、tests/tools/test_mcp_tool.py
WebUI preset 接入nanobot/webui/mcp_presets_api.py、tests/webui/test_mcp_presets_api.py

学习目标

读完后,你应当能够:

  • 判断何时应配置 MCP,而不是自己写 Tool;
  • 解释三种 transport 的连接和关闭边界;
  • 预测 MCP tool/resource/prompt 的 nanobot 名称;
  • 配置最小 allowlist,并排查 Windows、SSRF 和重连问题。

前置知识

  • 知道 MCP server 向 client 暴露 tools、resources 和 prompts;
  • 知道 stdio 是本机子进程通信,HTTP/SSE 是网络通信;
  • 理解 ToolRegistry 和 AsyncExitStack 的用途。

若需要 MCP 协议的完整规范,请阅读官方 MCP 文档;本篇只解释 nanobot client 如何接入。

1. MCP 在 nanobot 中的位置

MCP 不改变 AgentRunner 的模型—工具循环。连接成功后,MCP capability 被包装成普通 Tool,注册到同一个 ToolRegistry。

flowchart LR
    A["MCP Server"] --> B["ClientSession"]
    B --> C["list_tools / resources / prompts"]
    C --> D["MCP*Wrapper"]
    D --> E["ToolRegistry"]
    E --> F["AgentRunner"]

因此,Runner 不需要知道某个 Tool 是 Python 内置还是 MCP。

2. 配置模型

nanobot/config/schema.py::MCPServerConfig 当前字段:

字段用途
typestdio、sse、streamableHttp,可省略
command / args / env / cwdstdio server
url / headersHTTP/SSE server
toolTimeout单次 capability 调用超时,默认 30 秒
enabledTools允许注册的名称;默认 ["*"]

配置位于 tools.mcpServers.<server-name>。

自动判断规则:

  • 有 command → stdio;
  • 有 url 且路径以 /sse 结尾 → SSE;
  • 其他 URL → streamable HTTP。

最好显式写 type,避免端点路径变化后语义漂移。

3. 名称包装

当前命名大致是:

MCP capabilitynanobot Tool 名称
tool read_filemcp_<server>_read_file
resource schemamcp_<server>_resource_schema
prompt reviewmcp_<server>_prompt_review

server 和 capability 名会被 _sanitize_name() 处理,仅保留模型 Provider 普遍接受的字母、数字、下划线和连字符,并折叠重复下划线。

enabledTools 可以写:

  • 原始 MCP tool name;
  • 包装后的 nanobot name;
  • "*";
  • 空列表表示一个也不注册。

未知 allowlist 项会记录可用 raw/wrapped names,便于定位拼写问题。

4. 每个 server 独立生命周期

connect_mcp_servers() 为每个 server 创建独立 AsyncExitStack。原因是 MCP/AnyIO transport 有自己的 cancel scope;多个 server 共用一个 stack 容易产生关闭冲突。

连接成功后:

  1. 建立 transport;
  2. 创建 ClientSession;
  3. initialize();
  4. 列出 capabilities;
  5. 包装并注册;
  6. 保存 stack。

AgentLoop.close_mcp() 在拥有连接的 task 中关闭这些 stack。

一个 server 失败不会阻止其他 server 继续连接。

5. stdio 的信任边界

stdio 配置本质上授权 nanobot 启动本机命令:

{
  "tools": {
    "mcpServers": {
      "example": {
        "type": "stdio",
        "command": "python",
        "args": ["-m", "my_mcp_server"],
        "enabledTools": ["read_data"]
      }
    }
  }
}

安全检查:

  • command/package 是否可信;
  • env 中是否传入了超出需要的 secret;
  • cwd 是否会让 server 读到不应访问的文件;
  • server 暴露的工具是否最小化;
  • 安装脚本是否会执行任意 postinstall。

SSRF guard 不适用于 stdio,因为它不是 HTTP 请求。这不代表 stdio 更安全;它拥有本机进程权限。

Windows

Windows 上 npx、npm、pnpm、yarn、bunx 和 .cmd/.bat launcher 会被归一为:

cmd.exe /d /c <command> ...

已有 exe/com 或显式 cmd/PowerShell 不再重复包装。相关函数:

  • _normalize_windows_stdio_command()

6. HTTP/SSE 的安全边界

连接前,nanobot 使用 validate_url_target() 阻止:

  • localhost/loopback;
  • 私网;
  • CGNAT;
  • link-local;
  • metadata 地址。

每个 outgoing HTTP request 还有 event hook,再次检查 redirect target。确实需要可信内网 MCP 时,用 tools.ssrfWhitelist 放行尽可能窄的 CIDR,例如单主机 /32 或 /128。

不要为方便放行整个私网

MCP capability 通常能读取或修改数据。广泛 allowlist 会把 prompt injection 转化为内网访问能力。

7. 暂时性错误与重连

MCP wrapper 对已识别的断流、broken pipe、connection reset/refused 等暂时性连接错误重试一次。

若重试暴露出 session terminated:

  1. 注销该 server 的旧 Tool;
  2. 关闭旧 stack;
  3. 重连 server;
  4. 重新注册 capability;
  5. 再交回可用 wrapper。

它不是任意异常的无限重试。ValueError、普通业务错误等不会被当成瞬时 transport 故障。

8. 选择 MCP 还是原生 Tool

优先 MCP:

  • 已有稳定 MCP server;
  • 多个 Agent/client 需要复用;
  • capability 与 nanobot 发布周期独立;
  • server 自己管理复杂 SDK 和认证。

优先原生 Tool:

  • 逻辑极小、只为 nanobot 服务;
  • 需要直接复用 ToolContext 或 workspace policy;
  • 不希望引入额外进程/网络生命周期;
  • 需要与 core checkpoint/event 做紧密集成。

9. 最小验证

  1. 配置能通过 Pydantic;
  2. server 可连接并列出 capabilities;
  3. enabledTools 只注册预期名称;
  4. 参数 schema 被规范化;
  5. 调用结果正确;
  6. timeout 生效;
  7. HTTP 私网和重定向被拦截;
  8. 断开后按预期重试/重连;
  9. 正常关闭没有残留进程。

源码:

  • nanobot/agent/tools/mcp.py
  • nanobot/config/schema.py::MCPServerConfig
  • nanobot/agent/loop.py::_connect_mcp
  • nanobot/agent/loop.py::close_mcp

测试:

  • tests/tools/test_mcp_tool.py
  • tests/tools/test_mcp_probe.py
  • tests/agent/test_mcp_transient_retry.py
  • tests/webui/test_mcp_presets_api.py

官方配置参考:

自检

  1. MCP Tool 为什么不需要 AgentRunner 的特殊分支?
  2. enabledTools=[] 与 ["*"] 有何差别?
  3. stdio server 为什么不受 SSRF guard 保护?
  4. Windows 下 npx 为什么需要 cmd.exe /d /c 包装?

下一篇:新增 Provider