最近准备 AI 智能体相关项目时,我发现一个很现实的痛点:大模型的外部工具调用几乎没有统一标准。不同模型对工具定义、参数格式、调用结果的定义各不相同,写智能体的时候,大量时间都花在“适配不同工具 API”和“处理胶水代码”上。
真正让我把思路理顺的,是系统学习《基于模型上下文协议的 AI 智能体专项课程》。这门课的核心就两个关键词:MCP(Model Context Protocol,模型上下文协议)和 AI 智能体。课程把协议原理、Server 开发、Client 接入、Agent 编排、工程化落地串成了一条完整链路。
本文结合该课程的主线,整理成可直接上手的教程。你会理解 MCP 到底解决什么问题,也能照着示例代码从零写一个 MCP Server,再通过 MCP Client 接入一个简单 AI 智能体。适合正在做 AI 应用开发、智能体平台接入、中间件封装的同学阅读。
1. 为什么 AI 智能体需要 MCP?
1.1 智能体的核心能力是“调用工具”
先看一个典型场景:你想让 AI 助手帮你查询本地文件、查数据库、发邮件。
如果只靠大模型自身,它只能根据训练数据生成文本,无法访问你的本地文件或企业数据库。所以我们必须给大模型“接上”外部能力。业界通常把这种能力叫 Function Calling 或 Tool Calling,也就是让模型在回答问题时,先输出“我想调用哪个工具、传什么参数”,再由程序真正执行工具,把结果交回给模型。
这个机制本身很成熟,但它有一个问题:工具的协议不统一。
- 有的模型要求工具参数用 JSON Schema 描述,有的用简化结构。
- 有的平台用 HTTP 回调,有的要求在函数名前加固定前缀。
- 同样的“查天气”工具,在 A 模型接入和 B 模型接入,要写两套适配代码。
- 当工具数量超过几十个时,每个工具都要自己管理鉴权、限流、错误码、重试逻辑,非常容易失控。
这也是为什么“AI 智能体开发人才需求增长很快”的原因之一:大家都发现智能体落地最花时间的地方不在模型本身,而在工具接入与编排层。
1.2 MCP 是什么
MCP 是 Model Context Protocol,模型上下文协议。它是由 Anthropic 提出并开源的一种开放协议,目标是为大模型应用与外部数据、工具之间建立统一连接标准。
你可以把它理解成 AI 世界的“USB-C 接口”。
- 过去:每个外部系统有各自的数据格式和调用方式,大模型应用要逐个适配。
- 现在:外部系统通过 MCP Server 暴露标准能力,AI 应用通过 MCP Client 统一访问。
不管这个 Server 背后是文件系统、数据库、搜索引擎还是企业内部系统,只要实现了 MCP 协议,AI 应用就能用同一套方式发现和调用它。
《基于模型上下文协议的 AI 智能体专项课程》里反复强调一个观点:MCP 并不是某个特定模型的私有功能,它是一层标准化协议。你可以让任意支持工具调用的大模型通过 MCP Client 连接到同一个 MCP Server。
1.3 MCP 与 Function Calling 的区别
很多初学者会把 MCP 和 Function Calling 混在一起,这里做一个明确区分。
Function Calling 是模型能力。它解决的核心问题是:模型如何识别外部函数、如何生成结构化的调用参数、如何解析工具返回结果。
MCP 是应用层协议。它解决的核心问题是:工具如何注册、如何被客户端发现、用什么传输方式通信、如何鉴权、多个工具如何统一管理。
在智能体项目中,两者通常是协作关系:
LLM 通过 Function Calling 能力决定调用哪个工具 ↓ MCP Client 把请求转发给对应的 MCP Server ↓ MCP Server 执行真实业务逻辑 ↓ 结果按协议返回给 MCP Client ↓ 最终交回给 LLM 继续推理所以,MCP 并不替代 Function Calling,而是让工具调用的“连接层”更规范。
1.4 MCP 的典型应用场景
目前 MCP 在很多方向已经落地,常见场景包括:
- 文件系统访问:让 AI 智能体读取本地文件、搜索项目代码、做批量统计。
- 数据库查询:把 SQL 查询封装成 MCP Tool,智能体可以基于数据结构生成并执行查询。
- 开发工具链:连接 Git、编译工具、构建系统,让 AI 协助完成开发任务。
- 知识库检索:把 RAG 能力封装成 MCP Tool,智能体通过标准接口检索文档。
- 浏览器自动化:通过 MCP Server 控制浏览器,执行信息采集、表单提交等操作。
这些场景有一个共同点:AI 智能体都需要“边思考边调用外部工具”,而 MCP 正好把外部能力统一抽象成了标准工具。
2. MCP 架构与核心概念
2.1 三层架构:Host、Client、Server
MCP 的架构分为三层:
| 角色 | 作用 | 对应示例 |
|---|---|---|
| MCP Host | 用户直接交互的应用,通常是大模型桌面应用或 Web 应用 | Claude 客户端、自研智能体平台 |
| MCP Client | 与 Server 建立一对一的连接,负责协议通信 | Python SDK 中的 ClientSession |
| MCP Server | 暴露工具、资源、提示词的外部程序 | 文件系统 Server、数据库 Server |
一个 Host 可以同时连接多个 MCP Server。例如智能体应用里,既连接文件系统 Server,又连接数据库 Server;每个 Server 可以单独开发、单独部署,也可以由不同团队维护。
在课程中,这个架构经常用一个句话概括:Host 负责交互,Client 负责连接,Server 负责被调用。
2.2 三大原语:Tools、Resources、Prompts
MCP 官方定义了三种基础能力原语。
Tools 工具:是让 AI 智能体主动执行操作的能力。工具通常代表“动作”,比如“发送邮件”“查询订单”“调用计算器”。工具由 Server 注册,由 Client 发起调用,结果返回给 AI 继续推理。
Resources 资源:是向外暴露数据的能力。资源通常代表“静态或半静态的数据”,比如“数据库 schema”“配置文件内容”“项目文档”。在 MCP 出现之前,这些内容通常靠拖拽文件或粘贴到提示词里;有了资源机制,客户端可以动态发现和读取。
Prompts 提示词:是可复用的提示模板。例如“代码审查”“需求分析”等固定流程,可以写成一个 Prompt 模板,由用户手动选中或自动触发。这样不同项目之间可以复用一套提示工程经验。
在智能体开发中,Tools 最受关注,因为绝大多数“干活”能力都要通过工具实现。
2.3 一次完整的调用过程
以“调用一个加法工具”为例,完整链路如下:
1. Client 启动后,与 Server 建立连接并完成初始化握手 2. Client 调用 list_tools 拉取 Server 的能力清单 3. LLM 根据用户问题,决定调用某个工具 4. Client 调用 call_tool,传入工具名和参数 5. Server 执行工具逻辑,返回结构化结果 6. Client 把结果交回给 LLM,LLM 生成最终回答这个流程看起来简单,但协议层的价值就在这:无论工具是本地脚本还是远程 HTTP 服务,无论 Server 用什么语言开发,Client 侧的调用方式都是一致的。
2.4 传输方式与鉴权
MCP 支持多种传输方式,比较常见的有:
stdio 传输:Client 通过标准输入输出与本地子进程通信。这种方式适合开发阶段,因为它不需要启动独立服务,也不需要处理端口和网络权限。
Streamable HTTP 传输:Server 运行在远程服务器上,Client 通过 HTTP 请求访问。适合多个客户端共享同一个 Server,也适合部署到生产环境。
基于 WebSocket 的传输:适合双向实时通信场景,实际项目中根据环境选择。
鉴权方面,远程 MCP Server 通常需要 OAuth 2.0 或自定义 Token,本地 stdio 模式则更多依赖文件系统权限和进程启动用户权限。课程中特别提醒:生产环境一定要区分“可读工具”和“写操作工具”,并为高权限工具单独配置鉴权。
3. 环境准备与项目初始化
3.1 开发环境清单
以下环境适合在 Windows、macOS 或 Linux 上搭建,本文示例以常见环境为例,版本需要根据你的项目实际情况调整。
| 组件 | 说明 |
|---|---|
| Python | 建议 3.9 及以上版本 |
| MCP Python SDK | 通过 pip 安装,用于开发 Server 和 Client |
| Node.js | 可选,部分 MCP CLI 调试工具需要 |
| IDE | VS Code 或任意 Python IDE |
| LLM API | 支持 Function Calling / 工具调用的模型接口 |
建议在项目目录下创建独立的虚拟环境,避免依赖冲突。
3.2 创建项目目录
命令如下:
mkdir mcp-agent-demo cd mcp-agent-demo python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate然后安装 MCP SDK:
pip install mcp如果希望使用命令行工具辅助调试,可以安装 CLI 组件:
pip install "mcp[cli]"如果安装速度慢,可以换成国内镜像源:
pip install mcp -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后,可以检查版本:
pip show mcp3.3 课程主线与本文对应关系
《基于模型上下文协议的 AI 智能体专项课程》通常围绕五条主线展开:
- 协议理解:MCP 是什么、为什么会出现。
- Server 开发:怎么把一个业务能力封装成 MCP Tool。
- Client 接入:怎么让大模型应用通过 Client 连接 Server。
- Agent 编排:怎么设计“推理 -> 调用工具 -> 观察结果”的循环。
- 工程化落地:鉴权、测试、日志、灰度发布。
本文接下来按照第 2、3、4 步做完整实战,第 5 步放在后面的最佳实践部分。
4. 实战:开发一个 MCP Server
4.1 编写 Server 入口
在项目目录下新建server.py。这个文件负责创建 MCP Server,并注册工具。
# server.py from mcp.server.fastmcp import FastMCP # 创建一个 MCP Server,名称可以自定义 mcp = FastMCP("AgentDemoServer")这里的FastMCP是 MCP Python SDK 提供的高层封装,适合快速开发。如果你使用的是旧版 SDK,导入方式可能略有差异,需要以你安装的版本为准。
4.2 添加一个工具
工具函数通过@mcp.tool()注册,函数名就是工具名,docstring 会作为工具描述,供 LLM 判断是否调用。
# server.py @mcp.tool() def add(a: int, b: int) -> int: """计算两个整数的和。""" return a + b这里的关键点是类型注解。MCP SDK 会根据类型注解自动生成 JSON Schema,所以a: int和b: int必须写清楚。不要全部写成str或省略类型,否则 LLM 可能传入错误类型。
再添加一个稍微复杂的工具,比如获取指定时区的当前时间:
# server.py @mcp.tool() def get_current_time(timezone: str = "Asia/Shanghai") -> str: """获取指定时区的当前时间,默认返回东八区时间。""" from datetime import datetime from zoneinfo import ZoneInfo tz = ZoneInfo(timezone) return datetime.now(tz).isoformat()这个工具演示了默认参数和“动态导入依赖”的用法。对于轻量工具,在函数内导入依赖可以减少 Server 启动时间;但如果是经常调用的工具,建议在模块顶部导入。
4.3 注册 Resources 和 Prompts
除了工具,MCP Server 还可以暴露资源。比如下面这个资源返回一段配置信息:
# server.py @mcp.resource("config://demo") def get_config() -> str: """返回 Demo 项目的示例配置。""" return "app_name=mcp-agent-demo\nmode=dev"资源 URI 是自定义的,客户端可以通过read_resource读取。它适合暴露不常改动的元数据,比如数据库表结构、项目说明、权限说明等。
还可以注册 Prompt 模板:
# server.py @mcp.prompt() def review_code() -> str: """代码审查提示模板""" return "请以资深工程师视角,对以下代码做代码审查:{code}"不过在实际智能体项目中,Resources 和 Prompts 的价值更多体现在“把上下文标准化”。如果你只需要工具调用能力,可以暂时不注册这两类资源。
4.4 启动 Server
在文件末尾添加启动入口:
# server.py if __name__ == "__main__": # 使用 stdio 传输,适合客户端通过子进程启动 mcp.run(transport="stdio")直接运行:
python server.py程序会等待标准输入,不会直接打印内容。这是因为 stdio 模式下,Server 与 Client 通过标准输入输出通信,我们需要通过客户端来验证。
5. 实战:编写 MCP Client 并调用工具
5.1 编写 Client
在项目目录下新建client.py,创建一个连接到本地server.py的客户端。
# client.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client # 通过 stdio 启动 server.py 子进程 server_params = StdioServerParameters( command="python", args=["server.py"], ) async def main(): async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # 1. 初始化握手 await session.initialize() # 2. 获取工具列表 tools = await session.list_tools() print("MCP Server 暴露的工具:") for tool in tools.tools: print(f"- {tool.name}: {tool.description}") # 3. 调用工具 result = await session.call_tool("add", {"a": 3, "b": 4}) print("调用 add(3, 4) 的返回结果:", result.content) asyncio.run(main())这段代码做了三件事:
- 通过
stdio_client启动server.py进程。 - 通过
ClientSession完成 MCP 握手并获取工具列表。 - 调用
add工具验证真实调用。
需要注意,不同版本的 Python SDK 在导入路径上可能有区别。如果遇到No module named 'mcp.client.stdio',可以查一下该版本 SDK 的文档,有时客户端模块位于mcp.client.stdio,有时位于顶层。
5.2 运行与预期结果
在虚拟环境中执行:
python client.py预期输出类似:
MCP Server 暴露的工具: - add: 计算两个整数的和。 - get_current_time: 获取指定时区的当前时间,默认返回东八区时间。 调用 add(3, 4) 的返回结果: [TextContent(text='7', type='text')]这里result.content是一个列表,具体输出结构因 SDK 版本而异。如果你打印出来的类型不同,也不要慌,先查看官方文档或打印result的完整结构。
到这里,你已经完成了最基础的 MCP Server + Client 闭环。
5.3 在支持 MCP 的客户端产品中接入
除了自己写 Python Client,很多 AI 客户端产品也支持直接配置 MCP Server。它们通常会读取一个配置文件,内容类似:
{ "mcpServers": { "agent-demo": { "command": "python", "args": ["/absolute/path/to/server.py"] } } }注意,这里的路径建议写成绝对路径,因为客户端进程的启动目录不一定是你执行命令的目录。
接入后,用户可以在对话框里直接让 AI “使用工具”,客户端会自动完成 MCP 的握手、工具发现和调用。对产品验证阶段来说,这种接入方式效率很高。
6. 实战:构建基于 MCP 的 AI 智能体
6.1 智能体主循环设计
有了 MCP Server 和 Client 之后,下一步就是把 LLM 加入进来,组成“能思考、能调用工具”的智能体。
一个基础智能体主循环可以拆成四步:
- 将用户问题放入消息列表。
- 让 LLM 基于当前对话和可用工具生成响应。
- 如果 LLM 决定调用工具,就通过 MCP Client 执行,并把结果追加到消息列表。
- 重复第 2 步,直到 LLM 不再调用工具,输出最终答案。
下面是一个不绑定具体 LLM SDK 的示意代码,重点是讲清楚编排逻辑:
# agent_loop.py —— 示意代码,需要结合具体 LLM SDK 调整 MAX_STEPS = 5 def run_agent(query: str, llm, mcp_client, system_prompt: str = "你是一个智能助手,必要时可以调用工具。"): messages = [ {"role": "system", "content": system_prompt}, {"role": "user", "content": query}, ] # 1. 从 MCP Server 拉取工具列表,并转换成 LLM 可识别的工具 Schema tools_meta = mcp_client.list_tools() tools_schema = [tool.to_schema() for tool in tools_meta.tools] for step in range(MAX_STEPS): # 2. 调用 LLM response = llm.chat(messages=messages, tools=tools_schema) # 3. 如果没有工具调用请求,说明推理结束 if not response.tool_calls: return response.content # 4. 执行工具调用(这里假设只处理第一个工具调用) call = response.tool_calls[0] tool_result = mcp_client.call_tool(call.name, call.arguments) # 5. 把工具结果追加到历史消息中 messages.append({"role": "assistant", "content": response.content}) messages.append({ "role": "tool", "tool_call_id": call.id, "content": tool_result, }) raise RuntimeError("智能体执行步骤超过上限,请检查多轮工具调用逻辑。")这个代码的核心思路是“把 MCP 的工具调用结果当作一条新消息喂回 LLM”。这也是当前主流 Agent 框架普遍采用的模式。
6.2 工具 Schema 的转换
不同 LLM SDK 对工具 Schema 的字段命名并不完全一致。有的要求如下结构:
{ "type": "function", "function": { "name": "add", "description": "计算两个整数的和", "parameters": { "type": "object", "properties": { "a": { "type": "integer" }, "b": { "type": "integer" } }, "required": ["a", "b"] } } }而 MCP 返回的工具定义格式可能略有不同,所以实际开发时要做一层转换。你可以把tools_meta.tools遍历出来,提取name、description、inputSchema,再组装成目标 LLM 需要的格式。
这一步虽然简单,但却是最容易踩坑的地方。课程里建议:把 Schema 转换单独封装成一个函数,并补充单测。
6.3 多工具调用编排
真实场景中,智能体经常需要连续调用多个工具。例如用户问“对比 A 文件和 B 文件的字数”,智能体可能需要:
- 调用文件读取工具,读取 A 文件。
- 调用文件读取工具,读取 B 文件。
- 调用统计工具,计算字数。
- 生成对比结论。
这个流程如果交给 LLM 自动编排,主循环里需要支持“一次响应返回多个工具调用”。你的循环不能只处理tool_calls[0],而应遍历所有工具调用结果,再一次性追加到消息里。
此外,还要设置最大步数,防止智能体在复杂任务中无限循环。上面代码里的MAX_STEPS就是最小保护。
6.4 日志与可观测性
智能体的调试难度明显高于普通后端接口。建议在工具调用前后都记录日志:
[step 1] LLM 决定调用 add,参数 {"a": 3, "b": 4} [step 1] MCP 返回结果 7 [step 2] LLM 生成最终回答这样的日志是整个链路调试的基础。后续如果要接入链路追踪、成本统计、质量评估,也需要先有结构化日志。
7. 常见问题与排查思路
下面整理了一些 MCP + AI 智能体开发中的高频问题,方便你对照排查。
| 问题现象 | 常见原因 | 排查与解决思路 |
|---|---|---|
| 工具列表为空 | Server 未正确注册工具,或注册代码没有执行 | 检查@mcp.tool()装饰器,确认 Server 启动日志正常 |
启动client.py后没有输出 | stdio 模式下 Server 等待输入,或路径配置错误 | 使用绝对路径,并手动运行python server.py验证进程不报错 |
| 调用工具提示参数解析失败 | 函数签名未写类型注解,或参数名与 LLM 传入不一致 | 给函数参数补全类型注解,保持参数名简洁准确 |
| 工具执行报错但客户端无感知 | 工具内部异常被 SDK 包装,日志未打印 | 在工具函数内捕获异常并记录日志,或先做最小复现 |
| 通过 HTTP 连接远程 Server 时鉴权失败 | Token 过期、Scope 不足、OAuth 配置错误 | 检查 Token 有效期,查看 Server 日志中的鉴权信息 |
| Agent 多轮调用后陷入死循环 | 工具返回结果不满足 LLM 停止条件,或上下文过长 | 设置最大步数,精简工具返回内容,增加终止判断 |
| 版本升级后原有代码不可用 | MCP Python SDK 版本快速迭代,接口变化 | 阅读当前版本 changelog,锁定依赖版本 |
遇到问题时,优先看两个地方:一是 Server 进程的终端输出,二是 Client 打印的原始返回结构。MCP 的链路不长,大多数问题都能通过这两步定位。
8. 最佳实践与工程建议
8.1 工具设计原则
工具是智能体能力的放大器,也是风险入口。设计工具时,我建议遵循以下原则。
工具职责单一。一个工具只做一件事,不要搞“超级工具”。比如把“查询用户”“修改用户”“删除用户”写成三个工具,LLM 的选型准确率会更高。
描述尽量具体。LLM 通过描述决定是否调用工具,所以docstring要写清楚“什么时候用、会有什么副作用”。例如:
@mcp.tool() def delete_user(user_id: int) -> bool: """根据用户 ID 删除用户。请谨慎使用,执行后无法恢复。"""参数校验要在工具内部做。不能只依赖 LLM 生成参数,工具内部必须校验边界条件。
8.2 配置与密钥管理
关于 API Key、数据库密码、Token 等敏感信息,绝对不要硬编码在server.py中。推荐使用环境变量或专门的配置中心。
# 启动前设置环境变量 export AGENT_API_KEY=your_api_key export DATABASE_URL=postgres://...在 Server 代码里读取:
import os api_key = os.environ.get("AGENT_API_KEY") if not api_key: raise RuntimeError("AGENT_API_KEY 未设置")涉及生产环境变更时,要坚持最小权限原则:普通查询工具只给只读权限;删除、修改、写数据库这类工单高危险操作,必须有独立的鉴权、审批和记录。
8.3 错误恢复与幂等
智能体与用户交互时,工具调用可能反复失败。你需要考虑错误恢复:
- 工具内部捕获异常后,返回一个“结构化的错误消息”,让 LLM 知道下一步怎么办。
- 对于发送邮件、创建订单这类操作,要设计幂等参数,比如
request_id,避免重复调用产生重复数据。 - 对耗时较长的工具,设置超时时间,避免 LLM 同步等待过久。
8.4 测试与灰度
MCP Server 本质上是一个独立服务,测试策略要跟上。
最小测试集建议覆盖:
- 工具注册是否完整。
- 每个工具的正常路径与异常路径。
- 参数校验是否生效。
- 在 Agent 循环中,工具返回结果能否被 LLM 正确理解。
上线 MCP Server 时,先在一个小范围用户群灰度,观察工具调用成功率、响应延迟和错误日志,再逐步放量。不要一上来就把所有高权限工具暴露给所有客户端。
8.5 版本锁定
MCP 生态还处于快速发展期,SDK 版本更新很快。项目里建议使用requirements.txt或poetry.lock锁定依赖版本,并定期评估升级影响。否则今天还能运行的代码,下周可能因为一个接口变更而失效。
mcp==1.0.0等到你确认新版本兼容后,再统一升级。
9. 总结与学习路线
这篇教程从中背景讲到实战,主要覆盖了以下内容。
首先是概念层:理解了 MCP 是模型上下文协议,它和 Function Calling 是协作关系,MCP 负责统一连接和工具管理,Function Calling 负责模型侧的工具决策。
其次是实战层:从零编写了一个 MCP Server,注册了工具、资源和 Prompt;又编写了 MCP Client,完成握手、拉取工具列表、调用工具;最后设计了一个最简单的 AI 智能体主循环,让 LLM 能基于工具调用结果继续推理。
如果你准备进一步深入,可以按这个顺序拓展:
- 学习更多 MCP 原语:Resources 和 Prompts 的高级用法。
- 尝试远程 MCP Server:用 Streamable HTTP 方式部署一个远程服务,并补充鉴权。
- 学习 Agent 编排框架:LangGraph、Dify、Coze 等,它们已经内置了 MCP 支持。
- 研究 RAG 与 MCP 结合:把知识库检索封装成 MCP Tool,让智能体具备专业知识。
- 关注工程化落地:日志、链路追踪、测试、灰度发布、成本控制。
AI 智能体的开发方式还远没有定型,MCP 是当前最有可能成为“标配”的连接层协议。如果你也在搭建自己的智能体,建议先从小工具、小闭环开始,把 MCP Server、Agent 主循环、日志体系跑通,再逐步扩展能力。
这篇笔记就先写到这里。接下来我会继续整理 MCP 远程部署和鉴权相关的内容,如果期间遇到有代表性的问题,再来和大家分享。