Skip to content

5.3 MCP 服务器接入 ​

本节你会学到

  • 什么场景适合用 MCP,什么场景应当写自定义 Tool
  • Agentao 支持的三种 transport(stdio、Streamable HTTP、SSE)
  • 多租户模式:会话级 extra_mcp_servers、环境变量展开、trust: 的边界

MCP(Model Context Protocol) 是"工具互操作的事实标准"。Agentao 作为 MCP Client,可以接入任何 MCP 兼容服务器——GitHub、Filesystem、Postgres、Slack、Jira、你自己写的……所有这些工具都会自动以 mcp_{server}_{tool} 的形式出现在 Agent 可用工具列表里。

MCP 能做什么 ​

场景推荐的 MCP Server
读写文件 / 代码仓库@modelcontextprotocol/server-filesystem
GitHub issues/PR@modelcontextprotocol/server-github
数据库查询@modelcontextprotocol/server-postgres
Slack / Linear / Jira官方或社区 MCP
内部工具自建 MCP Server(见末尾)

优势:不用自己写 Tool 子类——社区已经写好并维护。

配置的两种方式 ​

方式 A · JSON 配置文件 ​

文件位置(同名冲突时用户级胜;项目级仅可新增名字):

~/.agentao/mcp.json         ← 用户级(所有项目共享;任何在该文件声明的 server name 都以此为准)
<working_dir>/.agentao/mcp.json ← 项目级(仅可声明新 name,不能覆盖用户级同名条目)

为何仅可新增?

项目级 mcp.json 一旦 checked-in 进 git,就可能被恶意(或意外)地把已知 server name(如 github)静默重定向到不同 transport 或 endpoint。冲突时打 warning + 跳过项目项。要解决,请改名项目项或移除用户级同名条目。

格式:

json
{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/code"],
      "env": {},
      "trust": false,
      "timeout": 60
    },
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
      }
    },
    "analytics-sse": {
      "url": "https://mcp.your-company.com/sse",
      "type": "sse",
      "headers": {
        "Authorization": "Bearer ${ANALYTICS_TOKEN}"
      },
      "timeout": 30
    }
  }
}

方式 B · 程序式(嵌入首选) ​

构造 Agent 时通过 extra_mcp_servers 参数注入——完全跳过 JSON 文件,按会话/租户动态生成:

python
from agentao import Agentao

agent = Agentao(
    working_directory=Path(f"/tmp/tenant-{tenant.id}"),
    extra_mcp_servers={
        "github-per-tenant": {
            "command": "npx",
            "args": ["-y", "@modelcontextprotocol/server-github"],
            "env": {"GITHUB_PERSONAL_ACCESS_TOKEN": tenant.github_token},
        },
    },
)

合并规则:同名覆盖 .agentao/mcp.json 的同名条目。

配置字段详解 ​

stdio 传输(子进程) ​

字段必填说明
command✅可执行文件(npx, python, 绝对路径)
args❌命令行参数列表
env❌额外环境变量;支持 $VAR / ${VAR} 从进程环境展开
cwd❌子进程工作目录
timeout❌写数字:连接和握手的时限(秒),默认 60,不限制工具调用。要同时限制每次调用,写 {"startup": 60, "request": 30};request 没有默认值(配置参考)
trust❌为 true 时跳过工具确认

Streamable HTTP 传输(远程服务,裸 url 的默认) ​

字段必填说明
url✅Streamable HTTP endpoint URL
type❌"http"(别名:"streamable-http" / "streamable_http");给裸 url 时这是默认值
headers❌HTTP 头;支持 ${VAR} 展开
timeout❌同 stdio
trust❌同上

SSE 传输(远程服务,旧版) ​

字段必填说明
url✅SSE endpoint URL
type✅必须为 "sse"——SSE 是旧版传输(在 MCP 规范 2025-03-26 中已弃用)。裸 url 现在默认走 Streamable HTTP,所以 SSE 必须显式选择。
headers❌HTTP 头;支持 ${VAR} 展开
timeout❌同 stdio
trust❌同上

三种 transport 还都接受两个键,对应下文的两个功能:

字段默认值作用
resourcestrue设为其他任何值,该 server 都会从资源工具和 /mcp resources 中隐去,按名字指定它也会被拒绝
skillsfalse只有 true 才为该 server 开启 MCP Skills 扩展

Streamable HTTP 已支持

Agentao 的 MCP 客户端导入了 stdio_client、sse_client,以及 streamable_http_client(外加 create_mcp_http_client),三种 transport 都能接入。type 选择 transport("stdio" / "http" / "sse");省略时自动推断——command → stdio,裸 url → Streamable HTTP。旧版 SSE 需要显式写 "type": "sse"。未知的 type 或缺少必填键会 fail closed(McpTransportConfigError)。ACP 握手会通告 mcpCapabilities.http: true。

环境变量展开 ​

json
"env": {
  "TOKEN": "${MY_TOKEN}",     // ${...} 形式
  "REGION": "$AWS_REGION"     // $... 形式
}

展开时机:加载配置时——也就是 Agent 构造时。展开后的字面量值进入子进程 env。

未定义的变量展开成空字符串(不抛错)。

MCP 工具的命名 ​

一个 MCP Server 发现的每个工具都被包装为 Agentao Tool,名字加前缀:

Server: "github"
MCP 工具: "create_issue"
Agentao 里的名字: "mcp_github_create_issue"

名字里的非 [a-zA-Z0-9_] 字符会被替换成 _。

这意味着:

  • 你写自己的 Tool 时,不要以 mcp_ 打头(避免看起来像 MCP 工具)
  • 权限规则可以按前缀匹配:{"tool": "mcp_github_*", ...}

MCP 资源(resources) ​

声明了 resources capability 的 server 还会暴露可供模型读取的数据。只要有一个已连接的 server 声明了它,就会注册三个只读工具(名字和形状沿用 codex 与 pi,模型已经认识):

工具参数返回
list_mcp_resourcesserver?、cursor?JSON {server?, resources: [{server, uri, name, …}], nextCursor?, errors?}
list_mcp_resource_templatesserver?、cursor?JSON {server?, resourceTemplates: [{server, uriTemplate, name, …}], nextCursor?, errors?}
read_mcp_resourceserver、uri(均必填)资源内容
  • 不带 server 时遍历所有 server;单个 server 的失败放进 errors。cursor 必须配 server。
  • 文本按文本返回。二进制资源保存到 <working_directory>/.agentao/tool-outputs/(0600)并返回路径;超过 10 MiB 的 blob 只报告、不解码。
  • 三者都是只读的:只读模式和 plan 模式下都允许,从不弹确认。点名它们的权限规则仍然生效,disable_tools / enabled_tools 也接受这三个名字。
  • server 配置 "resources": false 会把它从三个工具中隐去(点名也会被拒)。
  • 工具结果里的 resource_link 会保留 URI,并在模型能跟进时指明用 read_mcp_resource 及对应 server 读取。

同样的操作在 manager 上是公开的,宿主可以据此做自己的选择器:

python
page = agent.mcp_manager.list_resources("docs")            # ResourcePage
templates = agent.mcp_manager.list_resource_templates("docs")
read = agent.mcp_manager.read_resource("docs", "report://q3")  # ResourceRead(文本或 base64 blob)

失败时抛出 agentao.mcp.resources.McpResourceError(带 kind),不会返回残缺数据。CLI 里 /mcp resources [server] 可直接列出,不消耗模型轮次。

MCP skills ​

server 还可以通过 MCP Skills 扩展(io.modelcontextprotocol/skills)发布 Agent Skills。它默认关闭:在 mcp.json 里给该 server 加上 "skills": true。需要 mcp 2.x,且 server 的协议版本不低于 2026-07-28。

  • 目录。 连接时列出该 server 的 skill(不取任何文件)。每个 skill 的名字是 mcp:<server>:<SKILL.md URI>,即身份本身,所以两个同名 skill 不会冲突;它们在系统提示词里单独成节,并标明由该 server 编写。
  • 激活。 activate_skill("mcp:docs:skill://pdf-processing/SKILL.md") 每个会话问用户一次,然后取回 SKILL.md,按 server 的条目核对大小、摘要和 frontmatter。正文放在 <mcp-skill server="…" uri="…"> 里交给模型。列表里没有的 skill 也能用这个名字加载。
  • 文件。 read_skill_file(skill, path) 从该 skill 自己的 server 读取已激活 MCP skill 的文件并校验,不在清单里的路径一律拒绝。在已加载 skill 目录内的 read_mcp_resource 也按同样方式校验。
  • 闸门。 只要有 MCP skill 已加载(直到 /clear,停用不算),run_shell_command、派生能执行 shell 的子代理、以及对其他 server 的 read_mcp_resource 都会询问,任何权限模式下都是如此。它们只会收紧:deny 规则和只读模式仍然拒绝。
  • 子代理共享会话里已加载的 skill,所以闸门在子代理里同样生效。
  • 没有人可以询问的宿主。 激活和每个闸门确认都需要人来回答。NullTransport、没有 confirm_tool 的 SdkTransport、没有 confirmation_callback 的 build_compat_transport 会一律拒绝。会记住"总是允许"的宿主必须读取 agentao.transport.gate_note()(见 4.5)。
  • 会话恢复。 restore_agent_skills 从不重新激活 MCP skill,会在恢复的消息里隐去 MCP skill 的内容,并重新打开闸门(见 2.4)。
  • 限制。 每个 skill 最多 512 个文件、16 MiB。SKILL.md 超过 100,000 字节的 skill 会被列为不可用,因为已激活 skill 的 SKILL.md 每次请求都会发送。read_skill_file 每次最多返回 30,000 个字符,用 offset 和 limit 分页读取。
  • 内容保存在哪里。 skill 内容从不写入 .agentao/tool-outputs/。replay 文件和 agentao.log 原样保留工具结果,包括 MCP skill 内容;之后用 read_file 读取它们不受闸门限制。

宿主可以从 manager 读到同样的数据:

python
agent.mcp_manager.skill_servers()                 # 通过闸门的 server
entries, unavailable, problem = agent.mcp_manager.skill_listing("docs")
entry = agent.mcp_manager.get_skill("docs", "skill://pdf-processing/SKILL.md")  # skills/get

/skills 按 server 列出 MCP skill,并说明哪些无法加载以及原因;/mcp list 显示每个开启了 skills 的 server 有多少个 skill,或者为什么一个都没有。

调试 MCP 接入 ​

python
# 查看所有发现的工具
for t in agent.tools.list_tools():
    if t.name.startswith("mcp_"):
        print(t.name, "—", t.description[:60])

# 查看 MCP manager 状态
if agent.mcp_manager:
    print(f"{len(agent.mcp_manager.clients)} server(s) connected")

日志文件 agentao.log 会记录:

  • MCP Server 启动成功/失败
  • 每个工具发现
  • 工具调用参数和结果

自己写一个 MCP Server(3 分钟上手) ​

最小的 MCP Server 用 Python 写:

python
# my_mcp_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("my-internal-tools")

@mcp.tool()
def get_user_info(user_id: str) -> str:
    """Query internal user info by ID."""
    return my_backend.get_user(user_id).to_json()

@mcp.tool()
def send_notification(user_id: str, message: str) -> str:
    """Send an in-app notification to a user."""
    my_backend.notify(user_id, message)
    return "ok"

if __name__ == "__main__":
    mcp.run()   # 默认 stdio

然后在 .agentao/mcp.json 里:

json
{
  "mcpServers": {
    "internal": {
      "command": "python",
      "args": ["/path/to/my_mcp_server.py"]
    }
  }
}

Agent 重启后自动发现 mcp_internal_get_user_info 和 mcp_internal_send_notification。

多租户策略 ​

生产 SaaS 里,典型 MCP 使用模式:

Server谁写范围
官方/开源(github、filesystem、postgres)用户/运维配全局或项目级 JSON 文件
你自己的业务 MCP你写 Python/Node每租户一个实例,经 extra_mcp_servers= 按会话启
租户自带的 MCP租户配置(SaaS 控制台)存 DB,构造 Agent 时翻译成 extra_mcp_servers=

安全要点:

  • 租户 token/密钥永远不要写进 JSON 文件——通过环境变量或 extra_mcp_servers 的 env 动态注入
  • MCP 子进程继承父进程环境变量——确保没有泄漏其他租户的凭据
  • 每会话独立子进程,避免跨租户状态污染

与权限引擎的配合 ​

MCP 工具默认也需要确认(等同 requires_confirmation=True),除非配置 trust: true:

json
{
  "mcpServers": {
    "trusted-internal": {
      "command": "...",
      "trust": true     ← 这些工具直接允许执行,不走 confirm_tool
    }
  }
}

或者用权限规则细粒度控制:

json
{
  "rules": [
    {"tool": "mcp_github_get_*", "action": "allow"},
    {"tool": "mcp_github_delete_*", "action": "deny"},
    {"tool": "mcp_github_create_*", "action": "ask"}
  ]
}

权限详见 5.4。

MCP 工具注解:readOnlyHint / destructiveHint ​

如果 server 声明了标准 MCP ToolAnnotations,Agentao 会读取 —— 但仅当 trust: true 时(按 MCP 规范:客户端不应基于来自不受信任 server 的注解作出工具决策)。

trust注解效果
false(默认)任何注解被忽略 —— server 可能撒谎;总是要求确认
truereadOnlyHint: trueis_read_only=True(read-only 模式可放行该调用),不要确认
truedestructiveHint: true要求确认 —— 覆盖 trust 默认
true无 / 都没有当前 trusted 行为 —— 不要求确认

这是安全方向正向的接线:注解可以增加摩擦(一个 trusted server 把某调用标为 destructive 时触发确认),但永远不会在不受信任路径上降低摩擦。Host 可以通过 McpTool.mcp_annotations 读到原始 dict 用于更丰富的投影。

⚠️ 常见陷阱 ​

上线前先确认这几条

  • ❌ Server 启动失败但 Agent 静默继续 —— agent.chat() 不会暴露 MCP init 失败
  • ❌ 工具名超长 —— Agentao 会截断并加哈希,权限规则要写注册后的名字
  • ❌ trust: true 用得太宽 —— 绕过所有安全确认

下面每一条都附完整修法。

❌ Server 启动失败但 Agent 静默继续 ​

Agentao 的 MCP 初始化是容错的——单个 Server 失败只会 log warning,不会阻塞 Agent 构造。检查 agentao.log:

MCP: failed to start 'github': ...
MCP: 12 tools from 2 server(s)       ← 有些 Server 没起来

部署前务必确认期望数量的 Server 都在。

❌ 工具名超长 ​

有些 MCP Server 工具名很长。拼上前缀后可能超过 OpenAI function calling 的名字长度限制(64 字符)。Agentao 会截断这样的名字,并加上一个由原始服务器名和工具名算出的 8 位哈希(每次连接都相同),工具照常可用——但权限规则或子代理的 tools: 列表要写注册后的名字,agentao.log 里有记录(Registered MCP tool: …)。

❌ trust: true 用得太宽 ​

写过/删过东西的 Server 不要轻易设 trust: true——等于绕过所有安全确认。只给纯读或已经有自己权限层的 Server 用。

TL;DR ​

  • MCP 用来消费第三方工具生态(GitHub / 文件系统 / Postgres / Slack …);自定义 Tool 用来封装你自己的业务逻辑。
  • 三种 transport:stdio 子进程、Streamable HTTP URL(裸 url 的默认)、或旧版 SSE URL("type":"sse")。
  • 多租户 token:构造时传 extra_mcp_servers({name: {command, args, env}}),同名会覆盖 .agentao/mcp.json。项目级 .agentao/mcp.json 是仅可新增的 —— 不能覆盖用户级同名条目。
  • 工具命名:mcp_{server}_{tool} ——自动加前缀避免不同服务器的同名冲突。含非 ASCII 字符的服务器名或工具名,也会像超长名字一样加上 8 位哈希后缀(否则 查询 和 搜索 都会变成 __),所以升级后注册名会变。仍然映射到同一名字的两个工具(my-srv 与 my_srv)不会都注册:先列出的保留这个名字,另一个不注册,并在 agentao.log 记一条错误。
  • 写操作能力的 Server 绝对不要设 trust: true,会绕过所有确认。
  • server 的 resources 会变成三个只读工具("resources": false 可隐去)。它的 skills 默认关闭,要设 "skills": true;加载了 MCP skill 期间,shell 和跨 server 读取在任何模式下都会询问。

→ 下一节:5.4 权限引擎