☰
模型上下文协议(MCP)实战:从零搭建可控AI智能体工具调用
2026/10/10 3:35:33 网站建设 项目流程

模型上下文协议(Model Context Protocol,简称 MCP)正在成为 AI 智能体开发中被反复讨论的基础协议。很多开发者已经过了给智能体写 prompt 看效果的阶段,真正麻烦的是让智能体安全、稳定地调用本地或远程工具,读取外部数据,再根据执行结果继续决策。MCP 的意义在于,它把“智能体如何找到工具、如何调用工具、如何拿到结果”这件事,从每个项目各自实现的私有逻辑,变成了一套可复用、可调试、可测试的标准交互方式。

下面直接进入技术主线:先讲 MCP 解决什么问题,再给出一个可以直接运行的最小 MCP Server 和 Client 示例,然后把工具接入一个带模型决策的 Agent 主循环,最后讨论测试、落地和工程化过程中真正会遇到的坑。这套内容适合想系统学习 MCP、正在做智能体工作流搭建,或者需要在项目中接入工具的开发者。

跟着动手完成后,你会得到三个可带走的结果:一个本地可运行的 MCP 工具服务;一个能调用该工具的最小智能体循环;一套用于排查协议调用、数据处理和工程落地问题的检查清单。

1. AI 智能体为什么需要模型上下文协议

1.1 没有统一协议之前,工具接入有多麻烦

在 MCP 出现之前,要让一个智能体调用外部能力,通常的做法是“每个工具单独对接”。搜索功能要对接搜索 API,数据库查询要写 SQL 执行器,办公软件要调用各自的 SDK,每个内部系统还要看它提供的是 HTTP 接口、命令行还是消息队列。每接入一个新工具,都要写一套参数解析、鉴权、错误处理和返回格式转换的代码。工具少的时候还能维护,工具一多,互不相同的数据结构和调用约定会让工程变得很难扩展。

一个更实际的问题是,工具调用结果并不是模型天然理解的格式。有的接口返回 JSON,有的返回 XML,有的返回纯文本,有的直接抛异常。智能体要正确使用这些结果,必须在提示词里反复强调返回格式,还要在代码里做大量防御性处理。这也是很多团队做智能体工作流时真正消耗时间的地方:模型决策本身不难,难的是让工具接入变成一条稳定、可复用的流水线。

1.2 MCP 的定位:把工具、数据、提示词标准化

MCP 的通俗理解是:给 AI 智能体和外部能力之间定义一套“通用插座”。智能体不需要知道某个工具的 SDK 怎么装、鉴权怎么做、入参格式是什么,只需要通过这套协议查询“这个服务器提供了哪些工具”,再按标准格式发起调用,就能拿到标准化结果。

从技术定位上讲,MCP 是一种基于 JSON-RPC 2.0 的开放协议。它把三种原语统一起来:Tools是模型可以执行的动作,Resources是模型可以读取的上下文数据,Prompts是预设的对话或任务模板。智能体通过这套原语与 MCP Server 通信,不需要关心服务器背后连接的是数据库、文件系统还是第三方 API。

这个设计最重要的价值是解耦。模型供应商、智能体框架、工具提供方只要都遵循同一份规范,就可以在生态内自由组合。这也是各种智能体工作流平台开始围绕 MCP 做集成的根本原因:一次接入协议,后续新增工具的成本可以显著下降。

1.3 一次 MCP 工具调用背后的四个参与方

从上到下看,一次 MCP 调用通常涉及四个角色:

  • Host:运行智能体、承载用户对话和业务流程的主程序。Host 负责管理会话,也负责决定何时调用工具。
  • Client:Host 内部与 MCP Server 保持 1:1 连接的协议客户端,负责发送请求、接收响应。
  • Server:暴露工具、资源或提示词的服务端,可以运行在本地进程,也可以运行在远端。
  • 原语:Server 提供的 Tools、Resources、Prompts,它们是实际被模型使用的能力和数据。

可以把这个模型理解成数据库连接:Host 是应用程序,Client 是驱动,Server 是数据库实例,原语是表和字段。应用程序不直接写数据库私有协议,而是通过标准驱动访问。MCP 对智能体的意义与此类似。

这里要澄清一点:协议解决的是交互标准化问题,不解决模型能力问题。一个模型如果本身不会判断该调用哪个工具,即使接入 MCP 也做不出好的智能体。所以后面第 4 章会专门讲 agent 主循环,那是决定智能体质量的另一部分。

2. 搭建 MCP 学习环境,先把协议跑通再谈智能体

2.1 运行时与版本要求

MCP 官方提供了 Python SDK 和 TypeScript SDK,学习阶段建议选你最熟悉的语言。Python 环境一般需要 3.10 以上,TypeScript 环境需要 Node.js 18 以上,具体以官方 README 为准。下面的示例基于 Python 生态,先创建虚拟环境并安装 SDK:

python -m venv .venv source .venv/bin/activate # Windows 使用 .venv\Scripts\activate pip install "mcp[cli]"

如果你使用 TypeScript:

mkdir mcp-demo && cd mcp-demo npm init -y npm install @modelcontextprotocol/sdk

注意:MCP 协议和 SDK 目前仍在快速迭代中。安装依赖前先读对应 SDK 的 README,确认当前版本支持的 API 写法,不要直接照抄旧文章里的代码。

学习环境最好用虚拟环境隔离依赖。不是所有机器都能保证系统 Python 干净,把 mcp 包装进虚拟环境,后续排查问题时能少很多干扰。

2.2 安装 MCP Inspector 等调试工具

除了 SDK,官方还提供可视化调试工具 MCP Inspector,用于查看服务端注册了哪些工具、发送指定参数调用工具、观察原始 JSON-RPC 消息。对理解协议非常有帮助。以 Python 服务为例,启动方式通常是:

npx @modelcontextprotocol/inspector python server.py

这个命令会启动一个本地 Web 页面,在页面上选择传输方式(stdio / HTTP / SSE),填好命令和参数,就可以连接服务端。Inspector 的具体包名和参数可能随版本变化,以官方文档为准。

学习阶段可以先不急着写客户端代码。用 Inspector 验证服务端工具可用,再去看原始请求和响应,能更快建立“协议长什么样”的直觉。

2.3 理解 MCP 的通信流程:初始化、列工具、调用工具

一次典型 MCP 工具调用包含以下步骤:

  1. 客户端启动 MCP Server 进程或建立远程连接。
  2. 双方通过 initialize 请求完成协议握手,交换协议版本和能力信息。
  3. 客户端发送 tools/list 请求,获取服务端可用的工具列表。
  4. 客户端发送 tools/call 请求,携带工具名和参数。
  5. 服务端执行工具,返回内容或错误信息。
  6. 客户端把结果包装成模型可读的上下文,回到智能体主循环。

这个流程和普通 HTTP 接口调用不同,它默认是长连接,且消息采用 JSON-RPC 2.0 格式。所以排查问题时不要只盯着业务代码,还要看消息层是否正确。可以先用一张表建立整体印象:

步骤方法作用常见错误
握手initialize协商版本与能力协议版本不匹配
列工具tools/list获取可用工具列表服务端启动失败
调用工具tools/call执行具体工具参数格式不对
读取资源resources/read读取上下文数据资源不存在
通知notifications/...单向状态更新客户端未订阅

3. 最小案例:用 Python 写一个 MCP Server 和 Client

3.1 服务端实现:用 FastMCP 注册工具

下面用一个计算器工具作为例子。计算器逻辑简单,便于把注意力放在协议上,而不是业务本身。先创建server.py:

from mcp.server.fastmcp import FastMCP mcp = FastMCP("calc-demo") @mcp.tool() def add(a: int, b: int) -> int: """计算两个整数的和。""" return a + b @mcp.tool() def divide(a: float, b: float) -> float: """计算两个数的商,b 不为 0。""" if b == 0: raise ValueError("b 不能为 0") return a / b if __name__ == "__main__": mcp.run()

这段代码做了几件重要的事:

  • FastMCP简化了服务端定义,注册工具只需要用装饰器。
  • 函数的类型注解会被转换成工具的入参 JSON Schema,所以类型要写清楚。
  • docstring 会作为工具描述传给模型,模型靠这些描述决定何时调用工具,所以描述必须准确。

这里要特别注意:不要在 docstring 里写模糊的话。模型看到的是“计算两个整数的和”,它才知道什么场景调用 add。如果描述写了“处理数字”,模型可能会在错误的场景调用它。

3.2 客户端实现:stdio 方式连接并调用

MCP Server 可以运行在本地子进程中,通过标准输入输出通信,这种传输方式叫 stdio。下面写client.py:

import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params = StdioServerParameters( command="python", args=["server.py"] ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print("可用工具:") for tool in tools.tools: print(f"- {tool.name}: {tool.description}") result = await session.call_tool("add", {"a": 1, "b": 2}) print("add 调用结果:", result) print(result.content) if __name__ == "__main__": asyncio.run(main())

关键点:

  • stdio_client负责启动子进程并建立 stdin/stdout 管道。
  • ClientSession维护协议会话,initialize必须最先调用。
  • list_tools返回工具列表,包含名称、描述和输入 Schema。
  • call_tool的第一个参数是工具名,第二个是参数 JSON 对象。

这个代码是“最小闭环”:能启动服务端、握手、列工具、调用工具、打印结果。把这段跑通,MCP 的基础链路就通了。

3.3 运行验证和预期日志

运行方式:

python client.py

正常情况下会输出类似内容:

可用工具: - add: 计算两个整数的和。 - divide: 计算两个数的商,b 不为 0。 add 调用结果:CallToolResult(content=[TextContent(type='text', text='3')]...)

如果输出不正确,从几个方面检查:server.py是否能独立运行;客户端启动server.py时路径是否正确;Python 环境里是否安装了同一个 mcp 包;工具名是否和 client 里调用的一致。

3.4 为什么示例代码可能和最新 SDK 有差异

写这一类示例时有一个常见困惑:网上教程的 API 写法五花八门,有的用mcp.run(transport="stdio"),有的用Server注册 handler,有的用FastMCP。这主要是因为 MCP SDK 迭代快,不同版本接口不同。

应对方法是不依赖单一答案。学习时优先看官方 examples 目录和当前安装版本的源码,确认当前环境支持哪种写法。pip show mcp可以查看已安装版本。这样即使 API 变了,排查路径仍然有效。

这里也给出一个复用建议:把 Server 端代码和 Client 端代码放在同一目录,先跑通 stdio,再尝试 HTTP/SSE 连接。不要一上来就做远程部署,本地链路没通,远程排查难度会加倍。

4. 把 MCP 工具接进智能体工作流

4.1 从“工具能被调用”到“模型会决定调用”

前面第 3 章演示的是客户端手动调用工具。真实智能体不是这样的。真实流程是:用户提出需求,模型根据对话内容判断需要哪些工具,生成一个工具调用请求,智能体框架执行工具,再把结果返回给模型继续推理。

这个“模型决策 -> 工具执行 -> 结果回填 -> 模型再决策”的循环,是智能体工作流的核心,也常被称作 agent loop。MCP 解决了这个循环中的“工具执行”标准化问题,但模型决策部分取决于你选用的模型和提示词设计。热词里常说的“AI 智能体工作流搭建”,本质上就是把这段循环搭出来,再配合工具注册、结果校验和终止条件。

4.2 一个最小 Agent 循环的实现思路

下面给出一个不依赖具体模型 SDK 的最小循环示例。它把 LLM 调用封装成一个占位函数,你需要替换成自己的模型端点。这里用 OpenAI-compatible 接口的常见请求格式做演示,实际模型可能要求不同的消息结构。

import json def call_llm(messages, tools): """调用模型。这里需要按你使用的模型服务填充请求地址、密钥和请求体。""" # 演示结构,生产环境请替换为真实请求,并处理超时、重试和错误。 raise NotImplementedError("请替换为真实模型调用") # 返回值示例: # { # "content": "需要回复用户的内容", # "tool_calls": [ # {"id": "call_1", "function": {"name": "add", "arguments": "{\"a\": 1, \"b\": 2}"}} # ] # } def run_agent(user_input, tools, max_steps=5): messages = [{"role": "user", "content": user_input}] for _ in range(max_steps): response = call_llm(messages, tools) tool_calls = response.get("tool_calls", []) if not tool_calls: return response.get("content", "") messages.append({ "role": "assistant", "content": response.get("content") or "", "tool_calls": tool_calls }) for call in tool_calls: name = call["function"]["name"] args = json.loads(call["function"]["arguments"]) result = dispatch_tool(name, args) messages.append({ "role": "tool", "tool_call_id": call["id"], "content": json.dumps(result, ensure_ascii=False) }) return "达到最大步数,请重试或调整任务。"

这个代码重点展示三个环节:

  • 模型返回可能包含tool_calls,也可能没有,没有就说明任务可以结束。
  • 每次工具调用结果都要追加回messages,模型才能基于结果继续推理。
  • 必须设max_steps,防止模型连续调用工具造成死循环,这是成本和安全的第一道防线。

注意:max_steps 必须设置,尤其是接入生产模型后。模型连续调用工具并不罕见,没有限制会导致成本失控或长时间不返回结果。

dispatch_tool部分可以直接复用第 3 章的 MCP client,把session传进来,按工具名和参数调用。

4.3 工作流搭建时要处理的三个具体问题

第一是工具选择。工具不是越多越好。工具列表会占用模型上下文,描述写不清楚还会误导模型。建议每增加一个工具,都模拟一轮真实对话,观察模型是否会错误触发它。

第二是结果格式。工具返回结果要尽量结构化。文本适合人读,但模型后续判断时,JSON 往往更可靠。如果工具返回很长,还要考虑截断或摘要,避免撑爆上下文。

第三是错误反馈。工具执行失败时不要直接给用户看异常栈,而要把错误转换成模型能理解的描述,比如“查询超时,服务端没有返回数据,建议稍后再试”。这样模型才知道下一步该怎么办。

到这里,智能体工作流的最小闭环已经完整:MCP 负责工具层,agent loop 负责决策层。接下来可以进入测试和工程化。

5. 智能体的测试:数据处理和链路校验要怎么做

5.1 测试分层:先测工具、再测协议、最后测业务

智能体测试不能只看“最终回答对不对”。一个回答正确可能是模型运气好,一个回答错误也可能是提示词问题而不是工具问题。所以建议按三层测试:

  • 工具层:测试工具函数本身的输入、输出、异常。
  • 协议层:测试 MCP 是否正常初始化、列工具、调用工具,消息格式是否正确。
  • 业务层:测试端到端智能体流程,包括模型是否选择正确工具、结果回填是否正常、最终答案是否符合预期。

注意:协议层测试建议在持续集成里跑。它比端到端业务测试便宜,也能在最早阶段暴露工具注册或消息格式问题。

每层职责不同,出现问题时也能快速定位。

5.2 数据处理测试的典型场景

数据处理是智能体测试里最容易被低估的部分。常见场景包括:

  • 工具返回类型与模型预期不一致。比如服务端返回字符串"3",模型以为是整数 3。
  • 参数边界。比如除以 0、超大整数、负数、空字符串。
  • 超时。工具长时间不返回时,智能体是继续等待还是忽略。
  • 上下文膨胀。工具返回 10 万字符,模型上下文放不下。
  • 并发。多个用户同时调用同一个工具,服务端会不会串数据。
  • 异常消息。工具抛出异常后,模型还能不能正常继续对话。

每个场景都应该有一条对应的测试用例。下面是一份可复用清单:

测试维度测试场景预期行为
输入校验缺少必填参数返回参数错误,不执行工具
类型校验参数类型传错服务端按 Schema 校验并报错
边界值除 0、空字符串、大数返回明确错误信息
超时工具阻塞超过阈值client 超时并返回可读错误
结果格式返回 JSON 含中文模型能正确解析并使用
上下文长度返回超长文本触发截断或摘要,不撑爆上下文
权限未授权用户调用受限工具拒绝调用并记录日志
并发多会话同时调用结果互不干扰

5.3 一个可复用的智能体测试清单

写测试时不要只测 happy path。至少覆盖以下检查点:

  • 服务端能独立启动,且能在启动异常时给出退出原因。
  • 客户端能和不同版本服务端完成握手,版本不兼容时有明确报错。
  • 每个工具都有至少一条成功用例和一条失败用例。
  • 模型在不需要工具时不会强行调用工具。
  • 工具返回错误后,模型能根据错误信息重新尝试或向用户说明。
  • 达到max_steps后流程能正常终止。
  • 日志中能记录每次工具调用的工具名、参数、耗时、结果状态。

实际项目里,建议把协议层测试和业务层测试分开跑。协议层跑得频繁且便宜,业务层因为依赖模型,成本高,可以按优先级和回归频率安排。

6. 生产环境落地:常见坑和排查路径

6.1 版本不兼容、路径错误和传输方式选错

现象:客户端连上服务端后tools/list返回空,或者initialize报错。

排查顺序:

  1. 确认 client 和 server 使用的 SDK 版本是否在同一协议版本区间。
  2. 确认server.py能被直接启动,命令行直接python server.py是否能正常输出。
  3. 确认StdioServerParameters里的command和args路径正确,尤其是相对路径在不同启动目录下会变化。
  4. 确认传输方式一致:stdio client 不能直接连 HTTP server,除非使用对应的 HTTP 传输 client。
  5. 最后看日志或消息,判断是进程启动失败、握手失败还是业务异常。

6.2 工具超时、权限和成本控制

生产环境里,MCP Server 暴露的工具不是内部函数,而是被模型间接调用的外部入口。模型可能误调用高成本工具,也可能因为提示词被注入而触发不该触发的操作。

建议在工具入口做四件事:

  • 超时:每个工具设置自己的超时时间,避免模型等待一个永远不会返回的结果。
  • 鉴权:区分用户会话,工具内部校验当前会话是否有权限调用。
  • 审批:高风险操作,比如删除、转账、发消息,增加人工确认步骤。
  • 限额:记录每个用户、每个会话的工具调用次数和总成本。

错误现象往往是“模型想调用工具,但工具执行报了权限错误,然后模型开始反复重试”。处理方式是把错误描述写清楚,并限制重试次数。

6.3 上下文膨胀、循环调用和模型幻觉

这是智能体工作流里最常见的三类问题。

上下文膨胀的表现是:工具返回结果太长,模型上下文被占满,后续对话质量下降,甚至直接报长度超限。解决方式是对工具结果做摘要、截断,或者改成“按需读取”模式,比如先返回摘要,模型想了解细节再调用资源读取。

循环调用的表现是:模型反复调用同一个工具,每次参数都差不多,既不结束也不前进。原因可能是工具返回结果没有解决模型的问题,也可能是模型在错误地使用工具。解决方式是限制max_steps、增加相似调用检测,连续三次相同调用直接终止。

模型幻觉在工具场景中的典型表现是:模型没有真正调用工具,却根据训练数据编造了一个结果。排查时需要对比日志,确认最终回答里提到的数据,是否真的来自某次工具返回。防止幻觉的关键是在提示词里约束模型“没有工具结果不能编造数据”,同时在应用层做结果校验。

6.4 从日志到监控的排查链路

生产环境智能体必须有日志。建议至少记录:

  • 每次模型请求的输入输出和 token 数。
  • 每次工具调用的工具名、参数、耗时、状态。
  • 每轮对话的完整 message 快照,注意脱敏。
  • 异常堆栈、超时记录、重试记录。

排查时按时间线回放:用户问题 -> 模型决策 -> 工具调用 -> 模型再决策 -> 最终回答。只要每个环节都有日志,问题就能定位到具体环节。

7. 让智能体从“能跑”到“可控”:工程化建议

7.1 harness 思想:给智能体加约束和观测

在智能体工程化讨论中,常会提到 harness 这个概念。它的意思是不要直接把模型裸奔地放到用户面前,而是给智能体加一层外壳,包含约束、观测、审批、评价这些能力。MCP 是工具接入层,agent loop 是决策层,harness 则是围绕两者建立的安全和运维边界。这个思路对应了很多落地团队强调的“构建可控 AI 智能体的系统工程实践”。

具体可以落地为:

  • 工具注册表:统一记录每个工具的用途、参数、权限、成本、状态。
  • 输入输出过滤:对工具的入参和出参做校验,防止提示词注入。
  • 评价与回放:记录模型每个步骤,出问题时可以回放,可以评估。
  • 降级方案:模型服务不可用时,是降级到人工处理,还是返回固定提示。

7.2 推荐的学习路径与练习方式

如果想系统学习模型上下文协议和 AI 智能体开发,不建议只停留在看课程或读文档。比较有效的顺序是:

  1. 先用 MCP Inspector 观察官方示例,理解initialize、tools/list、tools/call三种消息。
  2. 自己写一个最小 Server,增加三个不同类型工具:查询类、计算类、带外部状态类。
  3. 再写一个 Client,验证工具注册、调用、异常分支。
  4. 把 Client 接进 agent loop,用真实模型测试工具决策。
  5. 执行一轮完整测试:输入校验、超时、错误恢复、上下文控制。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询