ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

模型上下文协议(MCP)实战:从零搭建可控AI智能体工具调用

模型上下文协议(MCP)实战:从零搭建可控AI智能体工具调用 模型上下文协议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 负责管理会话也负责决定何时调用工具。ClientHost 内部与 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 生态先创建虚拟环境并安装 SDKpython -m venv .venv source .venv/bin/activate # Windows 使用 .venv\Scripts\activate pip install mcp[cli]如果你使用 TypeScriptmkdir 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 工具调用包含以下步骤客户端启动 MCP Server 进程或建立远程连接。双方通过 initialize 请求完成协议握手交换协议版本和能力信息。客户端发送 tools/list 请求获取服务端可用的工具列表。客户端发送 tools/call 请求携带工具名和参数。服务端执行工具返回内容或错误信息。客户端把结果包装成模型可读的上下文回到智能体主循环。这个流程和普通 HTTP 接口调用不同它默认是长连接且消息采用 JSON-RPC 2.0 格式。所以排查问题时不要只盯着业务代码还要看消息层是否正确。可以先用一张表建立整体印象步骤方法作用常见错误握手initialize协商版本与能力协议版本不匹配列工具tools/list获取可用工具列表服务端启动失败调用工具tools/call执行具体工具参数格式不对读取资源resources/read读取上下文数据资源不存在通知notifications/...单向状态更新客户端未订阅3. 最小案例用 Python 写一个 MCP Server 和 Client3.1 服务端实现用 FastMCP 注册工具下面用一个计算器工具作为例子。计算器逻辑简单便于把注意力放在协议上而不是业务本身。先创建server.pyfrom 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.pyimport asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params StdioServerParameters( commandpython, 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(typetext, text3)]...)如果输出不正确从几个方面检查server.py是否能独立运行客户端启动server.py时路径是否正确Python 环境里是否安装了同一个 mcp 包工具名是否和 client 里调用的一致。3.4 为什么示例代码可能和最新 SDK 有差异写这一类示例时有一个常见困惑网上教程的 API 写法五花八门有的用mcp.run(transportstdio)有的用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_steps5): 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_asciiFalse) }) 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报错。排查顺序确认 client 和 server 使用的 SDK 版本是否在同一协议版本区间。确认server.py能被直接启动命令行直接python server.py是否能正常输出。确认StdioServerParameters里的command和args路径正确尤其是相对路径在不同启动目录下会变化。确认传输方式一致stdio client 不能直接连 HTTP server除非使用对应的 HTTP 传输 client。最后看日志或消息判断是进程启动失败、握手失败还是业务异常。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 智能体开发不建议只停留在看课程或读文档。比较有效的顺序是先用 MCP Inspector 观察官方示例理解initialize、tools/list、tools/call三种消息。自己写一个最小 Server增加三个不同类型工具查询类、计算类、带外部状态类。再写一个 Client验证工具注册、调用、异常分支。把 Client 接进 agent loop用真实模型测试工具决策。执行一轮完整测试输入校验、超时、错误恢复、上下文控制。
返回列表