MCP 与 AI Agent 工程化:从工具调用到多智能体协作

开发者/技术MCPAI Agent多智能体工具调用上下文工程权限控制2026-09-28

MCP 与 AI Agent 工程化:从工具调用到多智能体协作

一个团队把内部订单系统接进 Claude,写了一份 function schema。两周后要接 Cursor,schema 再写一遍。一个月后接自研 Agent 框架,字段名又对不上。三个月后维护着四份语义相同、命名不同的工具定义,改一个接口要改四个文件,漏改一个就线上报错。

MCP(Model Context Protocol)就是冲这个场景来的。

1. MCP 协议解决什么问题

从 M×N 到 M+N

模型侧的调用格式各写各的:OpenAI 用 tools 数组,Anthropic 用 tool_use 内容块,Google 用 functionDeclarations。工具侧有 GitHub、Postgres、内部 CMDB、工单系统。M 个模型乘 N 个工具,就是 M×N 套适配代码。

MCP 把中间这层抽出来做成协议:工具方实现一次 server,模型侧实现一次 client,两边都按协议说话。工具方从维护 M 份适配变成维护 1 份。

协议由 Anthropic 在 2024 年 11 月 25 日公开,规范按日期发版:2024-11-05 是首个版本,2025-03-26 用 Streamable HTTP 替换了旧的 HTTP+SSE,2025-06-18 增加了 elicitation、结构化工具输出和 resource links,同时移除了 JSON-RPC 的 batch 支持。跨版本混用是接入阶段最常见的坑,接之前先对齐 SDK 版本和目标 server 声明的协议版本。规范原文在 https://modelcontextprotocol.io/specification 。

三类角色,各管一段

角色负责什么现实中的例子
Host跑模型、管会话、决定要不要调工具、向用户要确认Claude Desktop、Claude Code、Cursor、VS Code 里的 Copilot
Client跟某个 server 保持 1:1 连接,转发请求和结果SDK 里的 ClientSession
Server暴露具体能力,执行实际操作你写的订单 server、GitHub 官方 server
一个 Host 里可以挂多个 Client,每个 Client 对应一个 Server。这个一对一是协议规定的,别在一个 Client 里塞多个 server 的连接。

服务端能暴露三种东西

原语谁决定用不用典型用途是否改状态
tools模型决定调用查订单、发消息、建工单可读可写
resources应用层决定何时注入日志片段、配置文件、数据库 schema只读
prompts用户显式触发"/review-pr" 这类预置模板无副作用
新手容易把什么都做成 tool。只读的、按需注入的上下文(比如一份表结构说明)更适合放 resources,让 Host 自己决定什么时候塞进上下文,比每次调用都跑一遍工具省 token。

客户端侧还有三个能力值得知道:sampling 让 server 反过来请求 Host 跑一次模型;roots 让 Host 告诉 server 能访问哪些目录;elicitation 让 server 在执行中途向用户追问缺失参数(2025-06-18 引入)。第三个在写操作里很有用——退款金额没给全时,先问人,别让模型猜。

它和 function calling 什么关系

function calling 管的是「模型这一次输出长什么样」。MCP 管的是「这个工具在哪、参数怎么定义、结果怎么回来、谁有权调」。两者叠加使用:Host 把 MCP server 的 inputSchema 转成自家模型要的 function 定义,模型输出调用后,Host 通过 Client 发给 Server,再把结果塞回上下文。

所以在 MCP 里,工具的服务发现和权限控制是协议层的事,不依赖某个模型厂商的接口形态。这是它跟「写个 function 塞进 tools 参数」最大的差别。

2. Agent 架构:规划、记忆、工具、执行

规划:能不做就不做

单步工具调用不需要规划器,加了只是多一次模型往返,还多一份出错的可能。什么时候该上:任务需要 3 步以上、步骤之间有依赖、或者要按中间结果分不同分支。

模式怎么跑适合代价
ReAct 循环想一步做一步,边观察边调整步骤少、环境反馈快的任务token 随步数线性涨,容易在原地绕圈
Plan-and-Execute先出完整计划再逐步执行步骤固定、可预先拆解的批处理计划第一步就错的话整段跟着错
我自己的判断:如果任务跑十次有八次步骤一致,写成固定工作流就够了,别上规划器。剩下两次的不确定性,用一个「要不要重试」的判断分支就能覆盖。

记忆:分三层放,别都堆在消息数组里

工作记忆是当前上下文窗口。会话记忆是摘要加关键事实,跨轮次保留。长期记忆放在外部存储——向量库、关系库或者普通文件。

Anthropic 的工程博客把上下文管理归纳成 write / select / compress / isolate 四类动作(见 Building effective agents 与同站 Engineering 栏目的上下文工程一文)。搬到自己的项目里就是四件事:

  • write:中间结果落盘或入库,别全留在消息数组里。一个查 500 行的结果占的 token 能顶掉后面所有推理空间。
  • select:按需检索,只把命中的片段注入。检索命中率低的时候,先改分块策略,别急着换向量模型。
  • compress:超过阈值就把早期轮次摘要化。摘要必须保留决策和异常,只写「已完成查询」等于没写。
  • isolate:子 agent 用独立上下文,只往主流程回传结论,不回传完整轨迹。
上下文爆掉的表现通常是:模型开始重复已做过的调用,或者忘记前面说过的约束。看到这两种症状,先查上下文长度,再怀疑模型能力。

工具:描述写得好不好,直接决定调用成功率

写 MCP tool 的几个实际做法:

  • 工具名用动词开头,get_order_status 比 order 好,refund_order 比 order_refund_v2 好。
  • 描述里写清三件事:什么时候用、什么时候别用、返回什么结构。尤其是「别用」——比如「订单不存在时返回 found=false,不要重试」,这一句能省掉大量无效循环。
  • 参数用 enum 和默认值收敛取值范围。字符串类型的 status 参数不加约束,模型会给你造出 processing、in_progress、pending 三种近义词。
  • 单个 server 的工具数控制在 20 个以内(经验值,不是协议要求)。工具越多,选择错误率越高,而且光是把所有 schema 塞进上下文就吃掉几千 token。超过就按业务域拆 server。

执行:三件事不做会出事

  • 超时。每个工具调用设 timeout,默认 30 秒。没超时的话,一个卡死的下游接口能把整个 Agent 会话拖住。
  • 幂等。所有写操作接受一个 request_id,重复调用返回同一个结果。Agent 重试是常态,没有幂等键就会重复退款、重复建单。
  • 沙箱。执行类工具(跑命令、写文件)限制在固定目录和命令白名单里。用 roots 声明允许访问的路径,server 侧再校验一次,别只靠 Host 那一层。

3. 多智能体协作与权限边界

三种常见拓扑

拓扑控制流代表实现什么时候用
Supervisor主 agent 派活,子 agent 无权直接对话LangGraph 的 supervisor 模式任务可拆成独立子任务,需要统一收口
Handoff控制权整体转移给下一个 agentOpenAI Agents SDK 的 handoffs客服分流这类「换个人接着聊」的场景
Group chat多角色共享一条消息总线AutoGen 的 GroupChat需要多视角互相挑错,比如代码评审
做多智能体之前先问一句:单 agent 加几个工具能不能解决。多数「多智能体」需求,实际是 prompt 里角色没分清。真需要拆的信号是:不同步骤要用不同的权限集,或者不同步骤的上下文会长到互相干扰。

权限跟着工具走,不跟着提示词走

在 system prompt 里写「你不能删除数据」,模型大部分时候会照做,但这是个概率问题。在网关层把 delete_* 工具从列表里摘掉,它就没得选。

flowchart LR A[Supervisor Agent] -->|派活| G[权限网关] G -->|白名单校验| P{该 Agent 允许 调用此工具?} P -->|否| R[拒绝并记录审计] P -->|是| C{是否高危写操作} C -->|是| H[人工确认] C -->|否| S[MCP Server] H -->|批准| S S --> A

落到配置上,是给每个 agent 分配一份独立的白名单:

Agent允许直接调用需人工确认明确禁止
分诊 agent查询订单、查询物流无所有写操作
运维 agent查询订单、查询物流、重启服务退款、改配置删除数据
财务 agent查询订单、查询流水退款、调额修改用户信息
远程 MCP server 走 OAuth 2.1(`2025-03-26` 版本引入),token 按终端用户隔离。用一个服务账号给所有人共用,审计日志里就只剩一个身份,出事查不出来。

审计日志至少记五样:agent 标识、凭据主体、工具名、入参、返回状态。参数里的手机号、身份证、token 要脱敏后再落盘。

三个会出事的细节

共享记忆写冲突。 两个子 agent 同时往同一份状态里写,后写的覆盖先写的。要么给每个子 agent 独立的命名空间,要么走单写入者的串行队列。

互相调用死循环。 A 调 B,B 又调回 A。加 hop 上限(建议 6 跳),同时算「调用指纹」——工具名加参数哈希——同一个指纹出现两次就中断。

上下文污染。 子 agent 把完整思考过程回传给主流程,主流程的上下文被灌满。约定子 agent 只返回结构化结论:结论、证据引用、置信度、用掉的工具。

4. 企业落地:先从只读环节切

公开可查的 MCP 支持情况:Claude Desktop 和 Claude Code 本身是 MCP Host,Cursor、VS Code 里的 GitHub Copilot 都支持挂 MCP server,Block 开源的 goose 也是 MCP 客户端。这意味着「内部工具接进 AI 编辑器」这件事,Host 侧基本不用自己写。

落地顺序建议按风险排,不按价值排:

  • 第一步:挑一个只读接口(查订单、查库存、查日志)包成 MCP server,接进团队现有的 AI 编辑器
  • 第二步:观察两周,收集模型调用时的参数错误和高频无效调用,回来改工具描述和参数约束
  • 第三步:加第二个只读 server,验证多 server 并存时的工具选择准确率
  • 第四步:引入一个低危写操作(建草稿单、发内部通知),配人工确认
  • 第五步:写操作上量之前,审计日志、幂等键、回滚方案先就位
跳过前三步直接上写操作,基本会在第四周撞上重复执行的问题。

几个反复出现的坑:

  • 把整个内部 API 一次性包成 tool。 两百个工具塞进上下文,模型选错的概率直线上升,钱也白花。按业务域拆 server,按需挂载。
  • 工具描述写成一句话。 「查询订单」这种描述,模型不知道能按什么查、返回什么、查不到怎么办。描述长度值得投入,它比换模型便宜。
  • token 写进日志。 调试方便,出事就是事故。日志里只留 token 的哈希前缀。
  • 只测 happy path。 至少补三类用例:下游超时、返回空、参数格式不对。Agent 在这些路径上的表现比正常路径更能说明工程质量。
  • 忽略协议版本。 server 用旧版 SDK,client 是新版 Host,握手阶段的报错信息往往很含糊。把版本写进 README。

5. 代码示例

一个带幂等和参数约束的 MCP Server

官方 Python SDK 提供 FastMCP,用类型注解和 docstring 自动生成 schema:

# server.py
from mcp.server.fastmcp import FastMCP
from pydantic import Field

mcp = FastMCP('order-tools')

@mcp.tool() def get_order_status( order_id: str = Field(description='订单号,SO- 开头,例如 SO-20260101-001'), ) -> dict: '''查询订单当前状态。只读操作。

订单不存在时返回 found=False,不要重试。 返回字段:found、status、updated_at。 ''' row = query_order(order_id) if not row: return {'found': False} return {'found': True, 'status': row['status'], 'updated_at': row['updated_at']}

@mcp.tool() def refund_order( order_id: str, amount_cents: int = Field(gt=0, description='退款金额,单位分'), request_id: str = Field(description='幂等键,同一业务动作复用同一个值'), ) -> dict: '''对订单发起退款。写操作。

幂等:同一 request_id 重复调用不会重复退款。 订单已退款时返回 skipped=True,不要再次调用。 ''' existing = lookup_request(request_id) if existing: return {'skipped': True, 'result': existing} result = do_refund(order_id, amount_cents, request_id) save_request(request_id, result) return {'skipped': False, 'result': result}

if __name__ == '__main__': # 本地调试用 stdio;部署成共享服务时换 streamable-http mcp.run(transport='streamable-http')

mcp.run() 不带参数时默认走 stdio,适合挂在桌面 Host 上。改成 transport='streamable-http' 之后 server 会起一个 HTTP 端点,适合部署成团队共享服务。

挂到 Claude Desktop

配置文件在 macOS 上是 ~/Library/Application Support/Claude/claude_desktop_config.json,Windows 上是 %APPDATA%\Claude\claude_desktop_config.json:

{
  "mcpServers": {
    "order-tools": {
      "command": "python",
      "args": ["/abs/path/server.py"]
    }
  }
}

Claude Code 用项目级 .mcp.json,结构相同,也可以用 claude mcp add 命令写。远程 server 把 command/args 换成 "url": "https://..." 加 headers。改完配置要重启 Host,热加载不可靠。

权限网关的最小实现

多智能体场景下,别让每个 agent 直接持有全部 server 连接。中间加一层,按 agent 身份过滤工具列表:

POLICY = {
    'triage': {
        'allow': {'get_order_status'},
        'confirm': set(),
    },
    'ops': {
        'allow': {'get_order_status', 'list_shipments', 'restart_service'},
        'confirm': {'refund_order'},
    },
}

def visible_tools(agent_id: str, all_tools: list[dict]) -> list[dict]: rule = POLICY.get(agent_id) if rule is None: return [] return [t for t in all_tools if t['name'] in rule['allow'] | rule['confirm']]

def dispatch(agent_id: str, tool_name: str, args: dict): rule = POLICY.get(agent_id) if rule is None or tool_name not in rule['allow'] | rule['confirm']: audit(agent_id, tool_name, args, 'denied') raise PermissionError(f'{agent_id} 无权调用 {tool_name}') if tool_name in rule['confirm'] and not ask_human(agent_id, tool_name, args): audit(agent_id, tool_name, args, 'rejected_by_human') return {'status': 'rejected'} result = call_mcp(tool_name, args) audit(agent_id, tool_name, args, 'ok') return result

关键点在于 visible_tools 和 dispatch 各校验一次。只过滤可见工具不够——模型可能从历史消息里翻出旧的工具名,所以实际调用时还要再拦一道。

6. 选型清单

你的情况建议理由
只让本机 AI 编辑器访问内部工具,单用户MCP server + stdio不用管鉴权,配置三行
多人共享同一批内部工具Streamable HTTP + OAuth 2.1token 按用户隔离,能审计
只需要固定流程编排直接写工作流,不上框架确定性任务用代码表达更省事
需要状态机、断点续跑、人工介入LangGraph图结构显式,检查点机制成熟
团队小,想快速跑通多 agentOpenAI Agents SDKhandoff 和 tracing 开箱可用
需要多角色讨论式协作AutoGenGroupChat 模式现成
需要快速搭业务型 agent 团队CrewAI角色和任务声明式,上手快
SDK 层面的选择:Python 用 [python-sdk](https://github.com/modelcontextprotocol/python-sdk),TypeScript 用 [typescript-sdk](https://github.com/modelcontextprotocol/typescript-sdk)。两个仓库里的 `examples` 目录比多数第三方教程更可信,卡住的时候先回去读它。

下一步可以做什么

翻一遍你团队现有的内部接口列表,挑一个纯只读、调用量不大、返回结构简单的,包成 MCP server,挂在 AI 编辑器里用一周。重点不是让它干活,是收集模型在真实调用中犯的错——参数格式、工具选错、无效重试。这份错误清单决定了你后面加写操作时要补哪些约束。


说明:本文涉及的协议版本信息以 https://modelcontextprotocol.io/specification 上的公开规范为准,SDK API 以对应仓库当前文档为准。MCP 规范仍在按日期迭代,接入生产环境前请核对实际依赖版本,不要直接照搬本文示例中的版本假设。示例代码为演示结构,query_order、do_refund 等函数需自行实现。

PREMIUM

需要完整版教程?

包含详细步骤、视频演示、提示词模板和可下载资料包。微信支付即时获取。

购买完整版 ¥29.90