本文面向了解 Python 基础、希望理解 AI 工具调用的读者。全文用“查询订单状态”作为例子,先解释 MCP 解决什么问题,再拆解架构、能力类型和调用流程,最后用 Python 完成一个可以本地运行的 MCP Server 与 Client。示例使用模拟数据,不需要数据库、模型密钥或网络服务。
一、先从一个普通函数开始
假设用户对 AI 说:
帮我查一下订单 A1024 发货了吗?
大模型能够理解这句话,但它并不知道你的订单数据。订单状态可能保存在数据库、订单 API 或企业内部系统中。要回答真实结果,AI 应用必须调用程序,而不是让模型根据常识猜测。
最简单的查询逻辑可以只是一个 Python 函数:
orders = { "A1024": "已发货", "A1025": "待发货", } def get_order_status(order_id: str) -> str: if order_id not in orders: raise ValueError("订单不存在") return orders[order_id] print(get_order_status("A1024")) # 已发货这个函数本身没有问题。问题出在“连接”上:
- AI 应用怎样知道这个函数存在?
- 模型怎样知道参数叫
order_id,并且它是字符串? - 应用怎样启动或连接这个程序?
- 调用结果怎样返回给模型?
- 订单不存在、权限不足或调用超时时,谁负责处理?
当然可以为每个系统单独设计接口。但当一个 AI 应用要连接订单、日历、知识库和文件系统时,开发者就要重复处理工具描述、连接管理、参数传递和错误返回。MCP 试图把这些通用的连接规则标准化。
二、MCP 到底是什么
MCP 是Model Context Protocol的缩写,中文通常译为“模型上下文协议”。它是一套让 AI 应用与外部数据、工具和提示模板进行标准化交互的协议。
可以把它理解为 AI 应用和外部能力之间的一种通用插座:
AI 应用 <--统一协议--> MCP Server <--业务代码--> 数据库 / API / 文件 / 系统这个类比只强调“连接方式统一”,不代表插上以后就自动拥有权限,也不代表所有业务系统都能零配置兼容。认证、授权、数据校验、业务规则和部署方式,仍然需要开发者设计。
MCP 也不等于以下概念:
- 它不是大模型。模型负责理解问题、决定是否需要调用工具并组织回答。
- 它不是业务系统。真正的订单查询、库存扣减或文件读取仍由业务代码执行。
- 它不是 Agent。Agent 是一种应用形态,MCP 只是其中可能使用的连接协议。
- 它不是安全边界。是否允许调用、能读到哪些数据,最终仍由 Host 和 Server 的权限控制决定。
MCP 的核心价值,是让“能力如何被发现、描述、调用和返回”有一套共同语言。
三、五个角色:用户、模型、Host、Client 和 Server
实际理解 MCP 时,最容易混淆的是角色。可以按下面的关系来记:
| 角色 | 主要职责 | 订单示例 |
|---|---|---|
| 用户 | 提出目标,必要时确认敏感操作 | “查询 A1024” |
| 模型 | 理解自然语言,选择是否调用以及调用哪个工具 | 识别出订单号并选择查询工具 |
| Host | AI 应用本身,协调模型、用户界面、权限和多个连接 | 聊天应用或企业 AI 助手 |
| Client | Host 中负责连接某个 MCP Server 的组件 | 订单连接器 |
| Server | 暴露工具、资源和提示模板,并执行对应代码 | 订单 MCP Server |
一个 Host 可以同时包含多个 Client,每个 Client 连接一个或多个 MCP Server。Server 可以是本机进程,也可以是远程服务;这里的“Server”指协议中的服务端角色,不一定意味着单独购买一台服务器。
订单查询的结构大致如下:
用户 │ ▼ Host(AI 应用) <------> 大模型 │ └── Client │ MCP 请求与响应 ▼ 订单 MCP Server │ ▼ 订单 API / 数据库关键点是:模型通常不会直接连接数据库。模型提出结构化的调用意图,Host 决定是否允许,Client 负责协议通信,Server 执行业务逻辑。
四、Server 能提供什么
MCP Server 最常见的三类能力是 Tools、Resources 和 Prompts。它们都可以被发现和读取,但用途不同。
| 能力 | 解决的问题 | 订单示例 | 是否通常会产生副作用 |
|---|---|---|---|
| Tool 工具 | “我可以执行什么操作?” | 查询订单、取消订单 | 可能有,也可能没有 |
| Resource 资源 | “我可以读取什么上下文?” | 订单状态说明、配置文档 | 通常是读取 |
| Prompt 提示模板 | “这类任务可以使用什么指令模板?” | 生成订单客服回复的模板 | 本身不执行业务操作 |
1. Tool:可调用的操作
Tool 是最接近函数的能力。它一般有名称、说明、参数结构和执行结果。例如:
工具名:get_order_status 说明:根据订单编号查询订单状态,仅查询,不修改订单 参数:order_id,字符串,必填模型可以根据工具说明生成调用参数,但应用仍应检查参数和权限。工具描述写得越清晰,模型越容易正确使用;不过清晰的描述不能代替服务端校验。
2. Resource:可读取的上下文
Resource 用来提供模型或应用可以读取的资料。它通常由 URI 标识,例如order://status-guide。这里的 URI 是资源名称,不一定是网页地址,也不代表一定要通过浏览器访问。
订单状态说明适合放在 Resource 中,因为它是供应用参考的知识:
待发货:尚未交给物流。 已发货:已交给物流,但不代表已经签收。Resource 不应被简单理解为“只读工具”。它们的发现、读取和呈现方式不同,具体行为取决于 Host 的实现。
3. Prompt:可复用的提示模板
Prompt 是一段结构化、可复用的交互模板。例如客服场景可能需要:
请先查询订单的真实状态,再用简洁中文回复客户。 如果查询失败,请明确说明无法确认,不要猜测发货情况。读取 Prompt 只会得到提示内容,不会自动调用订单工具,也不会自动生成最终答案。Host 可以把它展示给用户,也可以将它纳入后续对话流程。
五、一个最小可运行的 MCP Server
下面使用 Python MCP SDK 中常见的FastMCP接口。为了让示例容易复现,订单数据仍然放在内存字典中。
将代码保存为order_server.py:
from mcp.server.fastmcp import FastMCP mcp = FastMCP("订单助手") orders = { "A1024": "已发货", "A1025": "待发货", } @mcp.tool() def get_order_status(order_id: str) -> str: """根据订单编号查询订单状态,仅查询,不修改订单。""" status = orders.get(order_id) if status is None: raise ValueError(f"订单不存在:{order_id}") return status @mcp.resource("order://status-guide") def status_guide() -> str: """提供订单状态的含义说明。""" return "待发货:尚未交给物流;已发货:已交给物流,但不代表已签收。" @mcp.prompt() def reply_to_customer(order_id: str) -> str: """生成订单客服回复前的提示模板。""" return ( f"请先查询订单 {order_id} 的真实状态,再用简洁中文回复客户。" "如果查询失败,请说明无法确认,不要猜测发货情况。" ) if __name__ == "__main__": mcp.run(transport="stdio")逐段看这份代码:
FastMCP("订单助手")创建一个 MCP Server 实例。@mcp.tool()将普通 Python 函数注册成工具。函数名、文档字符串和类型标注会帮助 SDK 生成工具描述和参数信息。@mcp.resource(...)注册一个可读取的资源,并为它指定资源标识。@mcp.prompt()注册一个提示模板。它只返回文本,不会自己执行查询。mcp.run(transport="stdio")让 Server 通过标准输入输出与 Host 通信。协议消息会经过标准输入输出传递,因此服务端不要用print()输出普通日志;调试日志应写入标准错误或日志文件。
这里的get_order_status()仍然是业务函数。将来接入真实系统时,可以把字典替换为数据库查询或 HTTP API 调用,而对 Host 暴露的工具名称和参数保持稳定。
六、MCP 是怎样完成一次调用的
用户说“查询 A1024”后,一次典型的工具调用可以拆成下面几步:
- Host 启动或连接订单 MCP Server,并完成必要的初始化和授权。
- Client 向 Server 询问可用工具,获得工具名称、说明和输入参数结构。
- Host 把这些工具信息转换成模型能理解的工具定义。
- 模型判断需要调用
get_order_status,并生成参数{"order_id": "A1024"}。 - Host 检查工具是否允许当前用户调用,必要时向用户请求确认。
- Client 将工具名称和参数发给 Server。
- Server 执行 Python 函数,把成功结果或错误返回给 Client。
- Host 将结果放回对话上下文,模型根据真实结果生成最终回答。
可以用伪代码表示 Host 的核心协调逻辑:
# 这是流程示意,不是某个模型厂商的完整 API。 tools = await client.list_tools() tool_call = await model.choose_tool(user_message, tools) if application_allows(tool_call): result = await client.call_tool( tool_call.name, tool_call.arguments, ) answer = await model.answer_with_tool_result(result) else: answer = "当前操作未获准执行。"这段流程体现了一个重要边界:
模型:提出调用意图 Host:决定是否允许 Client:传输协议消息 Server:执行实际代码 模型:解释返回结果因此,模型说“请调用取消订单”并不等于订单已经取消。是否真正执行,取决于 Host 的审批逻辑以及 Server 的权限和业务校验。
七、用 Python Client 调用 Server
1. 创建虚拟环境并安装依赖
建议使用 Python 3.10 或更高版本,并在单独的示例目录中创建虚拟环境:
python -m venv .venv .\.venv\Scripts\python.exe -m pip install mcpmacOS 或 Linux 可以使用:
python3 -m venv .venv ./.venv/bin/python -m pip install mcp本文不固定 SDK 的小版本号,因为 SDK 会持续演进。若你要写教程或生产项目,建议在项目的依赖文件中锁定经过验证的版本,并以该版本的 API 为准。
2. 编写 Client
为了聚焦“发现和调用”这件事,下面的 Client 通过 stdio 启动 Server 子进程。这种方式更接近许多本地 AI Host 的连接方式。
将代码保存为demo_client.py:
import asyncio from mcp import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client async def main() -> None: server_params = StdioServerParameters( command=".venv\Scripts\python.exe", args=["order_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("可用工具:", [tool.name for tool in tools.tools]) result = await session.call_tool( "get_order_status", arguments={"order_id": "A1024"}, ) if result.isError: print("查询失败:", result.content) return print("查询结果:", result.content) if __name__ == "__main__": asyncio.run(main())这个 Client 做了四件事:启动 Server 子进程、建立标准输入输出连接、初始化会话、发现并调用工具。它没有接入大模型,所以订单号和工具名称由代码直接指定;真实 Host 才会把工具列表交给模型,并把模型生成的调用请求交给 Client。
如果你的系统环境不是 Windows,需要把command改成虚拟环境中的 Python 路径,例如./.venv/bin/python。也可以根据 SDK 版本提供的传输方式,改用其他 Client 连接方式。
3. 运行示例
在两个文件所在的目录执行:
.\.venv\Scripts\python.exe demo_client.py预期能看到类似输出:
可用工具: ['get_order_status'] 查询结果: [TextContent(type='text', text='已发货', ...)]你可以把A1024改成A1025,观察“待发货”结果;也可以改成不存在的订单号,观察错误如何返回。这个实验比直接看装饰器更重要,因为它展示了 MCP 的三个关键动作:发现能力、按结构化参数调用、接收结构化结果。
八、MCP、业务 API 和 Function Calling 的区别
这三个概念经常一起出现,但处在不同层次:
| 概念 | 解决的问题 | 订单例子 |
|---|---|---|
| 业务 API | 业务系统怎样提供能力 | 订单服务返回发货状态 |
| Function Calling / Tool Calling | 模型怎样表达结构化调用意图 | 模型选择查询工具并给出订单号 |
| MCP | AI 应用怎样统一发现和调用外部能力 | Client 发现工具并请求 Server 执行 |
可以把一次调用看成一条链:
模型的调用意图 ↓ Host 的工具调用接口 ↓ MCP Client / MCP Server ↓ 业务 API 或数据库MCP 不会自动替代订单 API。一个 MCP Tool 的内部实现完全可以调用现有 API,也可以直接访问数据库。MCP 主要减少的是每个 AI 应用分别适配每个系统的工作量。
如果应用只连接一个固定函数,直接使用 Function Calling 可能已经足够;如果需要让多个 AI 应用以统一方式接入多个外部系统,MCP 的标准化价值会更明显。
九、如何判断一个能力是否适合做成 Tool
并不是把所有函数都暴露给模型就好。一个适合暴露的工具通常具有以下特征:
- 目标明确:工具名称和说明能准确表达用途。
- 参数有限:模型可以从对话中可靠地获得参数,或知道缺什么信息。
- 结果可解释:返回结果结构清楚,模型能够据此回答用户。
- 权限可控制:服务端可以判断当前用户能否执行。
- 失败可处理:不存在、超时、权限不足等情况都有明确错误。
例如,“查询订单状态”通常适合做成只读工具;“取消订单”虽然也可以做成工具,但应额外考虑用户确认、幂等性、订单状态校验和审计记录。工具说明中的“仅查询”只是给模型的提示,真正的只读保证必须由业务代码实现。
十、安全与生产实践
本文的订单数据是公开的模拟数据,因此省略了许多生产问题。真实 MCP Server 至少需要考虑:
身份与权限
只知道订单号不代表用户有权查看订单。Server 应从可信的会话身份中获得用户信息,并检查订单归属、组织范围或角色权限,不能只相信模型传入的参数。
输入校验
对订单号做格式校验,对分页、日期范围和数量限制设置上限。不要因为参数已经由模型生成,就跳过普通 API 的校验。
敏感操作确认
查询、修改、删除、退款的风险不同。取消订单、发起退款等操作应由 Host 明确展示影响,并在执行前要求用户确认。对于高风险场景,还可以要求二次认证或人工审批。
数据最小化
工具只返回完成任务所需的数据。查询订单状态不必把收货地址、手机号和完整支付信息一起返回。需要展示给模型的内容越少,泄露和误用的风险越低。
错误与审计
区分“订单不存在”“无权访问”“系统超时”和“服务暂时不可用”。同时记录调用者、工具名、参数摘要、结果状态和时间,便于排查问题;日志中不要直接写入不必要的敏感信息。
传输与部署
本地 stdio 适合个人工具和开发环境。远程部署还需要处理身份认证、加密传输、连接生命周期、并发、限流和版本兼容。协议本身不会替你完成这些运维工作。
十一、常见误解
“用了 MCP,模型就能访问所有数据”
不是。模型只能看到 Host 提供给它的工具和资源,Server 也应该只暴露允许访问的能力。
“Tool 一定是写操作,Resource 一定是读操作”
不是。Tool 可以是只读查询,Resource 也不仅仅是普通文件。两者的核心差异是交互定位和协议语义,而不是简单的读写标签。
“MCP Server 就是数据库代理”
不一定。Server 可以连接数据库、HTTP API、本地文件、命令行程序或其他服务,也可以只提供静态提示模板。
“模型决定调用,就已经执行成功”
不是。模型的调用请求只是意图。Host 的审批、Client 的通信、Server 的校验和业务系统的最终响应都可能使调用被拒绝或失败。
十二、总结
从普通 Python 函数到 MCP Server,业务能力本身没有神奇变化。变化的是:这个能力拥有了标准化的描述、发现、调用和结果返回方式。
理解 MCP,可以先记住下面这条链:
用户提出目标 → 模型选择工具 → Host 检查权限与确认 → Client 按 MCP 发起调用 → Server 执行业务代码 → 结果返回模型 → 模型生成回答一句话概括:MCP 不是让模型凭空获得能力,而是让 AI 应用用统一方式接入已有的数据与工具。
本文示例没有连接真实模型,也没有实现生产级认证、授权和远程部署。它的目标是先把协议角色和一次工具调用的完整路径建立起来;掌握这条路径后,再接入真实订单 API、模型和权限系统,会更容易判断每一层应该负责什么。