如果你已经写过一个带工具的 Agent,应该很熟悉下面这套流程:
- 为每个工具写一个 Python 函数。
- 为函数补一份模型能够理解的参数 Schema。
- 把 Schema 交给模型,让模型决定什么时候调用。
- 根据模型返回的工具名找到对应函数并执行。
- 把执行结果重新放回对话,再让模型继续回答。
工具只有两三个时,这样做没有问题。但当 Agent 需要连接 GitHub、PostgreSQL、Slack、本地文件和公司内部系统时,问题就出现了:每接入一个系统,都要重新编写工具定义、连接代码和结果转换逻辑;换一个 Agent 项目,同样的工作还要再做一次。
MCP 真正解决的是:如何把工具和数据从某个 Agent 项目中独立出来,让不同的 AI 应用都能用同一种方式发现和调用它们。
这篇文章会先把 MCP 的工作方式讲清楚,再介绍从哪里查找现成的 MCP Server,最后使用 FastMCP 搭建一个服务,并把它接入真正的 LangChain Agent。
什么是 MCP
MCP 的全称是 Model Context Protocol,中文通常翻译为“模型上下文协议”。它是一套连接 AI 应用与外部系统的开放协议。
这里的“外部系统”可以是本地文件、数据库、浏览器、代码仓库,也可以是一个通过网络访问的业务服务。MCP 不负责实现这些系统本身,而是约定 AI 应用如何发现它们提供的能力、如何发起调用,以及如何接收结果。
很多介绍会把 MCP 比作 AI 领域的 USB-C。这个比喻强调的是“接口统一”:电脑不需要为每一种外设设计不同形状的插口,Agent 也不应该为每一个外部服务重新设计连接方式。
但 MCP 和 USB-C 有一个很大的不同:Agent 不仅要连接服务,还要理解服务能做什么。因此,MCP Server 除了接收调用,还会向客户端描述:
- 自己提供了哪些工具;
- 每个工具解决什么问题;
- 工具需要哪些参数;
- 可以读取哪些资源;
- 可以向用户提供哪些提示词模板。
Agent 在连接 Server 以后,可以先发现能力,再把这些能力动态交给模型,而不是把所有工具硬编码进 Agent。
MCP 的核心架构
MCP 采用 Client-Server 架构,但完整结构里不只有 Client 和 Server,还包含一个 Host。
用户
↓
MCP Host(Agent 应用)
├── MCP Client A ── MCP Server A ── 本地文件
├── MCP Client B ── MCP Server B ── PostgreSQL
└── MCP Client C ── MCP Server C ── GitHub API
Host:运行 Agent 的应用
Host 是用户直接使用的 AI 应用,也是模型和 Agent 循环真正运行的地方。Claude Desktop、AI IDE、桌面助手,或者我们自己编写的 LangChain Agent,都可以扮演 Host。
Host 负责的事情通常包括:
- 接收用户输入;
- 调用大模型;
- 管理对话上下文;
- 决定连接哪些 MCP Server;
- 控制哪些工具可以交给模型;
- 在敏感操作前询问用户;
- 把工具执行结果重新交给模型。
MCP Server 不负责决定整个 Agent 的行为。它只提供能力,至于模型什么时候使用、调用后是否继续推理,以及是否需要用户确认,都是 Host 的职责。
Client:Host 内部的协议连接器
MCP Client 运行在 Host 内部,负责和 MCP Server 通信。
当 Host 需要连接三个 MCP Server 时,通常会创建三条独立的 Client-Server 连接。Client 会完成初始化、能力发现、工具调用和消息收发。对 Agent 开发者来说,Client 最重要的作用是把 MCP Server 提供的能力转换成 Agent 可以使用的工具。
后面的代码中,MultiServerMCPClient 就承担这个角色:它连接 MCP Server,读取工具列表,再把这些工具转换成 LangChain Tool。
Server:能力的提供者
MCP Server 是一个独立程序。它可以和 Agent 运行在同一台机器,也可以部署在远程服务器上。
Server 负责两件事:
- 描述自己提供的能力。
- 收到调用后执行真正的业务逻辑。
例如,一个 GitHub MCP Server 可以提供“查询 Issue”“创建 Issue”“读取 Pull Request”等工具。Agent 不需要知道 GitHub API 的地址、认证头和返回格式,只需要按照 MCP 暴露的工具 Schema 发起调用。
这里要特别注意:MCP Server 不是模型,也不是 Agent。 Server 不会因为用户说了一句话就自行规划任务。它通常只是在 Client 请求某个工具后,执行一次确定的操作并返回结果。
一次连接是如何建立的
Client 连接 Server 后,会先完成初始化,而不是直接调用工具。
初始化阶段双方会交换协议版本、实现信息和支持的能力。比如 Server 会声明自己是否提供 Tools、Resources 或 Prompts,Client 也会说明自己是否支持 Roots、Sampling 等客户端能力。
初始化完成后,Client 才会调用 tools/list 等方法读取能力列表。以后 Server 的工具列表发生变化,也可以通过通知让 Client 重新获取。
从 Agent 开发者的角度,可以把一次完整连接理解为:
建立连接
→ 协商协议版本和能力
→ 获取工具列表
→ 把工具交给模型
→ 模型选择工具
→ MCP Server 执行
→ 结果返回模型
→ 关闭连接
MCP 的协议消息采用 JSON-RPC 2.0。JSON-RPC 定义了请求、响应和通知的基本格式;MCP 则在此之上定义了 initialize、tools/list、tools/call、resources/read 等具体方法。
入门阶段不需要手写 JSON-RPC 报文。官方 SDK 和 Agent 适配器会处理这些细节,但理解这层关系有助于排查“连接成功却拿不到工具”或“协议版本不兼容”之类的问题。
MCP 提供了哪些能力
MCP Server 最核心的三类能力是 Tools、Resources 和 Prompts。除此之外,MCP Client 还可以向 Server 提供 Roots、Sampling 等能力。
这些概念看起来都与“给模型更多上下文”有关,但它们的控制方和使用场景不同。
| 能力 | 提供方 | 主要控制方 | 解决的问题 |
|---|---|---|---|
| Tools | Server | 模型 | 让模型执行操作 |
| Resources | Server | Host 应用 | 向模型提供可读取的数据 |
| Prompts | Server | 用户 | 提供可复用的提示模板 |
| Roots | Client | Host 应用 | 告诉 Server 应关注哪些目录或 URI |
| Sampling | Client | Host/用户 | 允许 Server 请求 Host 调用模型 |
Tools:让模型采取行动
Tool 是 MCP 中最常见的能力。Server 会为每个 Tool 提供名称、描述和输入 Schema,Client 通过 tools/list 获取它们,通过 tools/call 执行某一个工具。
Tool 被称为“模型控制”的能力,因为通常是模型根据用户问题决定是否调用。例如用户问“SKU-1001 还有多少库存”,模型可以选择 get_product_stock;用户只是说“你好”时,模型则不需要调用任何工具。
Tool 可以只是一个加法函数,也可以执行复杂操作:
- 查询数据库;
- 调用第三方 API;
- 创建工单;
- 修改文件;
- 发送邮件;
- 执行部署。
也因为 Tool 可能修改外部状态,Host 不能把所有决定都交给模型。发送消息、支付、删除数据等高风险操作,应该在执行前增加权限检查或人工确认。
工具描述也不是普通的代码注释。它实际上是写给模型看的使用说明。描述必须告诉模型“什么时候应该使用这个工具”“参数各自代表什么”“工具不会做什么”。描述含糊时,即使函数实现完全正确,Agent 仍然可能选错工具。
Resources:向 Host 暴露可读取的数据
Resource 表示 Server 可以提供给 Host 的内容,例如文件、数据库记录、日志、API 响应或图片。每个 Resource 由 URI 标识,例如:
file:///project/README.md
inventory://products/SKU-1001
logs://production/2026-07-30
Resource 与 Tool 的区别不只是“一个读数据,一个执行动作”。更关键的区别是控制权:Resource 通常由 Host 应用决定什么时候读取、如何展示,以及是否加入模型上下文。
例如,一个 IDE 可以把当前文件作为 Resource 提供给模型;一个知识库客户端可以允许用户手动选择某篇文档。Server 只负责声明和返回资源,最终怎样使用由 Host 决定。
Resources 既可以是固定 URI,也可以使用 URI Template 表达一组动态资源。Server 还可以通知 Client 资源列表或内容发生了变化。
如果你希望模型自己根据问题自动查询某份数据,Tool 往往更合适;如果你希望 Host 或用户明确选择一份上下文,Resource 更符合语义。
Prompts:由用户选择的提示模板
Prompt 是 Server 提供的可复用提示词或工作流模板。它可以接受参数、引用 Resource,也可以生成多轮消息。
例如,代码仓库 MCP Server 可以提供:
/review-pull-request
/explain-module
/generate-release-notes
Prompt 被称为“用户控制”的能力,因为它通常会显示成菜单项或斜杠命令,由用户主动选择。Tool 的目标是让模型决定“调用什么”,Prompt 的目标则是让用户快速启动一套预先设计好的工作流。
并不是所有 MCP Host 都完整支持 Prompts 和 Resources。接入某个客户端前,需要查看它的 MCP 能力支持范围,不能因为 Server 实现了某项能力,就默认所有客户端都能展示。
Roots:告诉 Server 操作边界
Roots 由 Client 提供,用来告诉 Server 当前任务关注哪些 URI。最常见的是项目目录:
file:///Users/example/projects/my-app
代码类 MCP Server 可以据此知道当前工作区在哪里,而不是扫描整个文件系统。一个 Client 也可以提供多个 Root,例如前端仓库和后端仓库。
Roots 主要表达“建议关注的边界”,不应该被当成唯一的安全机制。Server 仍然需要自行验证路径和权限,不能只因为某个 URI 被声明为 Root,就默认其中所有操作都是安全的。
Sampling:让 Server 反过来请求模型
通常的调用方向是 Host 调用 Server。Sampling 则允许 Server 向 Client 请求一次模型生成。
例如,一个数据分析 Server 在完成初步计算后,可能请求 Host 中的模型总结结果,而不需要 Server 自己持有模型 API Key。模型选择、权限控制和用户确认仍然掌握在 Host 手中。
Sampling 适合更复杂的 Agent 工作流,但客户端支持并不统一。对于第一版 MCP Server,先把 Tools 设计好通常更重要。
MCP 的传输方式
协议规定“消息表达什么”,传输层决定“消息怎样从 Client 到达 Server”。
MCP 的消息使用 JSON-RPC 2.0,但 JSON-RPC 可以承载在不同传输方式之上。当前最常见的是 stdio 和 Streamable HTTP,旧项目中还会见到 SSE。
stdio:本地 Server 最常用的方式
stdio 是 standard input/output 的缩写,即标准输入和标准输出。
使用 stdio 时,Host 会按照配置启动一个本地子进程,然后通过 stdin 向进程发送 MCP 消息,通过 stdout 接收响应:
Agent Host
├── 启动:uv run server.py
├── stdin ──> MCP Server
└── stdout <── MCP Server
stdio 不需要监听端口,也不需要额外实现身份认证,非常适合本地文件、Git、SQLite、命令行工具等场景。
它还有一个非常重要的约束:stdout 是协议通道,不能随意 print() 调试信息。 一段普通文本混入 JSON-RPC 消息后,Client 可能无法解析响应。调试日志应该写到 stderr,或者使用日志框架输出到文件。
stdio Server 通常跟随 Host 生命周期:Host 启动它,Host 退出时连接和子进程也随之关闭。它适合用户机器上的个人 Agent,不适合让多台机器共享同一个服务。
Streamable HTTP:远程 MCP 服务
当 MCP Server 需要独立部署、供多个 Agent 使用时,可以通过 Streamable HTTP 提供服务。
Agent A ──HTTP──┐
Agent B ──HTTP──┼──> 远程 MCP Server ──> 企业数据库/API
Agent C ──HTTP──┘
远程 Server 不再由 Host 作为子进程启动,而是长期运行在服务器上。Client 只需要知道 MCP Endpoint,例如 https://example.com/mcp。
但 HTTP 并不自动意味着“可以安全部署到公网”。远程 MCP 还需要处理:
- HTTPS;
- 身份认证;
- 用户和租户级授权;
- 凭据隔离;
- 超时、重试和限流;
- 日志、监控与审计;
- Tool 调用的幂等性。
本地 stdio 示例改成 HTTP 传输,只解决了“能够通过网络连接”,并没有自动补齐这些生产能力。
如何找到现成的 MCP
如果只是想给 Agent 增加 GitHub、浏览器、数据库或文件系统能力,通常不需要从头开发 MCP Server。可以先从下面这些地址查找。
官方 MCP Registry
地址:https://registry.modelcontextprotocol.io/
这是 MCP 项目的官方 Registry,用来收录 Server 的名称、版本、包信息、远程地址和源码仓库。它更接近一个标准化元数据注册中心,适合确认某个 Server 是否已经按官方格式发布。
Registry 的 API 文档位于:
https://registry.modelcontextprotocol.io/docs
如果你需要在自己的 Agent 平台中实现自动搜索或安装 MCP,可以直接消费 Registry API。
MCP 官方参考 Server 仓库
地址:https://github.com/modelcontextprotocol/servers
这个仓库包含 MCP 的参考实现,并链接到一部分已经迁移到各自官方仓库的 Server。它适合学习标准写法,也适合确认某类基础能力是否已经有参考实现。
需要注意,“出现在列表中”不等于“适合直接用于生产”。安装前仍然要进入具体仓库检查维护状态、权限范围和部署说明。
Smithery
Smithery 提供 MCP Server 搜索、分类、安装说明和托管能力,适合按 GitHub、数据库、浏览器、搜索等使用场景查找 Server。
PulseMCP
地址:https://www.pulsemcp.com/servers
PulseMCP 是一个规模较大的 MCP Server 目录,可以按类别和关键词查找。它适合做广泛发现,但目录收录量大,质量与维护状态需要逐项判断。
Glama MCP Servers
地址:https://glama.ai/mcp/servers
Glama 提供 MCP Server 目录、分类与源码信息,适合搜索社区实现并比较相似 Server。
MCP.so
MCP.so 是社区 MCP 目录,可以按名称和功能搜索 Server,也提供部分安装配置示例。
无论从哪个目录找到 Server,最后都应该回到它的源码仓库或服务商官方文档,确认以下信息:
- 发布者是不是目标服务的官方团队;
- 最近一次提交和发布是什么时候;
- Server 会读取哪些文件、环境变量和凭据;
- 有哪些 Tool 会修改或删除外部数据;
- 使用 stdio 还是 Streamable HTTP;
- 是否需要把 API Key 交给第三方托管服务。
Registry 解决的是“发现”,不是“安全背书”。MCP Server 本质上仍然是一段会在本机运行或接触业务数据的代码。
使用 FastMCP 搭建一个能被 Agent 调用的 MCP Server
这一节不再把“Client 能调用 Tool”当成终点。我们的目标是完成下面这条真实链路:
用户询问库存
→ Agent 获取 MCP Tools
→ 模型选择 get_product_stock
→ LangChain MCP Adapter 调用 Server
→ Server 查询库存并返回
→ Agent 根据结果回答用户
我们会创建两个文件:
mcp-inventory/
├── server.py # FastMCP Server,提供库存工具
└── agent.py # LangChain Agent,加载并使用 MCP 工具
示例使用 OpenAI 模型。换成其他支持 Tool Calling 的 LangChain Chat Model 时,MCP Server 不需要修改,这正是解耦带来的价值。
1. 创建项目并安装依赖
uv init mcp-inventory
cd mcp-inventory
uv add "mcp[cli]" langchain langchain-openai langchain-mcp-adapters
设置模型密钥:
export OPENAI_API_KEY="你的 API Key"
实际项目应把密钥放在安全的环境变量或密钥管理服务中,不要写进代码或提交到 Git。
2. 使用 FastMCP 实现库存工具
创建 server.py:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("inventory")
PRODUCTS = {
"SKU-1001": {
"name": "机械键盘",
"stock": 8,
"warehouse": "上海仓",
},
"SKU-1002": {
"name": "无线鼠标",
"stock": 0,
"warehouse": "北京仓",
},
}
@mcp.tool()
def get_product_stock(sku: str) -> dict:
"""查询指定 SKU 的当前库存。
当用户询问某个商品是否有货、剩余数量或所在仓库时使用。
sku 必须是类似 SKU-1001 的完整商品编号。
此工具只查询库存,不会创建、修改或扣减库存。
"""
normalized_sku = sku.strip().upper()
product = PRODUCTS.get(normalized_sku)
if product is None:
return {
"found": False,
"sku": normalized_sku,
"message": "没有找到该 SKU",
}
return {
"found": True,
"sku": normalized_sku,
**product,
}
@mcp.tool()
def list_out_of_stock_products() -> list[dict]:
"""列出当前所有库存为 0 的商品。
当用户询问哪些商品缺货、需要补货或库存为零时使用。
此工具只返回缺货商品,不会自动创建补货单。
"""
return [
{"sku": sku, **product}
for sku, product in PRODUCTS.items()
if product["stock"] == 0
]
if __name__ == "__main__":
mcp.run(transport="stdio")
@mcp.tool() 会根据函数签名生成输入 Schema。sku: str 会变成字符串参数,函数返回值则作为 Tool Result 返回给 Client。
这个示例的 docstring 比普通函数注释更详细,因为它的读者不是另一个 Python 开发者,而是模型。模型需要从描述中判断:
- 用户表达什么意图时应该调用;
- 参数应该使用什么格式;
- 工具能做什么;
- 工具不会产生哪些副作用。
get_product_stock 和 list_out_of_stock_products 看起来都与库存有关,但触发条件不同。把边界写清楚,可以减少模型在两个工具之间选错的概率。
返回值也使用了结构化字典,而不是一句模糊文本。这样 Agent 能明确区分 found、stock 和 warehouse,后续还可以根据 stock == 0 决定是否建议补货。
3. 在 Agent 中加载 MCP Tools
创建 agent.py:
import asyncio
from pathlib import Path
from langchain.agents import create_agent
from langchain_mcp_adapters.client import MultiServerMCPClient
async def main() -> None:
server_path = Path(__file__).with_name("server.py").resolve()
mcp_client = MultiServerMCPClient(
{
"inventory": {
"command": "uv",
"args": ["run", str(server_path)],
"transport": "stdio",
}
}
)
# 连接 MCP Server,读取 tools/list,
# 并转换成 LangChain Agent 可以使用的 Tools。
tools = await mcp_client.get_tools()
agent = create_agent(
model="openai:gpt-4.1",
tools=tools,
system_prompt=(
"你是库存助手。回答库存问题时必须使用库存工具,"
"不要根据商品名称或常识猜测库存。"
),
)
result = await agent.ainvoke(
{
"messages": [
{
"role": "user",
"content": "SKU-1002 还有货吗?如果没有,请告诉我它在哪个仓库。",
}
]
}
)
print(result["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(main())
运行 Agent:
uv run agent.py
一次实际运行中,Agent 的内部过程是:
1. MultiServerMCPClient 启动 server.py
2. Client 与 FastMCP Server 完成初始化
3. Client 通过 tools/list 获取两个库存工具
4. Adapter 把 MCP Tools 转换成 LangChain Tools
5. create_agent 把这些工具交给模型
6. 模型根据用户问题选择 get_product_stock
7. Agent 通过 MCP 调用工具,参数为 {"sku": "SKU-1002"}
8. Server 返回 stock=0、warehouse=北京仓
9. 工具结果进入 Agent 消息历史
10. 模型生成最终回答
最终回答会类似:
SKU-1002(无线鼠标)目前库存为 0,已经缺货。该商品归属北京仓。
这里和上一版“手动调用 session.call_tool()”最大的区别是:我们没有在 Agent 代码中指定要调用哪个工具。
session.call_tool("get_product_stock", ...) 只能证明 Client 和 Server 能通信;现在的代码则是由模型理解用户问题、选择 Tool、生成参数,再由 Agent 执行并继续推理。这才是 MCP Tool 在 Agent 中的完整使用方式。
4. MCP 在 Agent 代码中替代了什么
如果不使用 MCP,我们需要在 agent.py 中导入库存函数,再用 LangChain 的 @tool 包装它。换到另一个 Agent 项目时,还要复制同一份业务代码。
使用 MCP 后,Agent 只关心:
tools = await mcp_client.get_tools()
agent = create_agent(model="openai:gpt-4.1", tools=tools)
库存查询的实现、输入校验和返回格式都留在 Server 中。如果以后把内存字典换成 PostgreSQL,只要工具名称和 Schema 保持兼容,Agent 侧不需要修改。
另一个 Agent 也可以连接同一个 Server。它可以使用不同模型、不同 System Prompt,甚至使用 LangGraph 编排更复杂的工作流,而库存能力仍然只维护一份。
5. 同时连接多个 MCP Server
MultiServerMCPClient 不只可以连接一个 Server。下面的配置同时连接本地库存 Server 和远程物流 Server:
mcp_client = MultiServerMCPClient(
{
"inventory": {
"command": "uv",
"args": ["run", "/absolute/path/server.py"],
"transport": "stdio",
},
"shipping": {
"url": "https://shipping.example.com/mcp",
"transport": "http",
},
}
)
tools = await mcp_client.get_tools()
加载完成后,Agent 可以先查库存,再查询物流时效。Agent 不需要知道两个工具来自不同进程、使用不同传输方式,适配器会把它们统一转换为 LangChain Tools。
但工具并不是越多越好。一次给模型几十甚至上百个名称相似的 Tool,会增加上下文消耗和选择错误。生产系统通常需要按用户权限、当前任务和业务域筛选工具,而不是把所有 Server 的所有能力一次性交给模型。
如何设计一个 Agent 真正好用的 MCP Tool
FastMCP 可以让一个普通 Python 函数很快变成 Tool,但“能够调用”和“Agent 能够正确使用”是两件事。
工具应该围绕 Agent 的任务来设计
传统 API 往往按照后端资源组织,例如:
GET /products
GET /warehouses
GET /inventory-records
Agent Tool 更适合围绕用户任务组织。例如用户想知道商品是否有货,一个 get_product_stock Tool 直接返回商品、数量和仓库,通常比让 Agent 依次调用三个底层接口更可靠。
工具过细会让 Agent 承担大量编排工作;工具过大又会让行为不透明、难以控制。一个实用的判断标准是:一次 Tool 调用应该完成一个清晰、可描述、可验证的用户意图。
名称和描述要能区分相似工具
query_data、run_task 这类名称对模型几乎没有帮助。更好的名称应该直接表达动作和对象:
get_product_stock
list_out_of_stock_products
create_inventory_restock_request
描述中还要明确使用条件和限制。如果两个 Tool 的描述大量重叠,模型就更容易选错。
返回模型完成任务所需的上下文
Tool Result 不能只考虑程序是否能解析,还要考虑模型下一步需要什么信息。
如果创建补货单后只返回 success,模型无法告诉用户创建了哪一张单、补了什么商品,也无法在下一步查询状态。更合理的结果至少应包含:
{
"success": true,
"request_id": "RESTOCK-2048",
"sku": "SKU-1002",
"quantity": 50,
"status": "pending"
}
但返回内容也不是越多越好。把完整数据库记录或几十 KB 日志都塞进 Tool Result,会迅速占满模型上下文。应该优先返回与任务有关的字段,对长内容进行分页、截断或提供 Resource URI。
错误也要对模型有意义
“Internal Server Error”只能告诉 Agent 调用失败,却不能帮助它决定下一步。
更好的错误应说明:
- 什么地方出错;
- 是否可以重试;
- 参数应该如何修改;
- 是否需要用户补充信息;
- 是否因为权限不足而不能继续。
例如:
{
"error": "invalid_sku",
"message": "SKU 必须使用 SKU-数字 的格式,例如 SKU-1001",
"retryable": true
}
模型看到这种结果后,可以修正参数或向用户询问,而不是重复提交同一个错误请求。
对有副作用的工具保留人工确认
查询库存是只读操作,创建采购单、扣减库存和取消订单则会改变外部状态。
Server 应该验证输入和权限,Host 也应该在高风险工具执行前展示工具名和参数,让用户确认。不能仅凭“模型应该不会乱调用”来保证安全。
常见问题
Agent 找不到 MCP Tool
先确认 server.py 能通过配置中的同一条命令启动。stdio 场景下最常见的问题不是 Tool 代码,而是 Python 环境、相对路径和工作目录不一致。
在桌面应用或 Web Server 中,进程继承到的 PATH 可能与终端不同。必要时使用 uv、Python 和 Server 文件的绝对路径。
Server 启动后立即退出
通常是缺少依赖、脚本路径错误或初始化期间抛出异常。stdio Server 的错误日志应查看 stderr,不要为了调试在 stdout 中加入 print()。
Client 能列出 Tool,但模型不调用
这说明 MCP 连接大概率没有问题,问题在模型决策层。检查:
- Tool 名称是否表达了明确动作;
- docstring 是否说明了使用时机;
- 参数说明是否足够清楚;
- System Prompt 是否与工具行为冲突;
- 是否一次给模型提供了太多相似工具。
Tool 被调用了,但参数经常错误
尽量使用明确的类型、枚举和格式说明,并在 Server 中做输入验证。对于模型无法从用户问题中推断的必填信息,不要期待模型凭空生成,应返回可理解的错误或让 Agent 向用户追问。
应该使用 Tool 还是 Resource
如果模型需要根据任务自动发起查询或执行动作,使用 Tool。
如果内容更像一份可以浏览、选择和加入上下文的资料,使用 Resource。
如果希望用户从菜单或斜杠命令启动一套固定工作流,使用 Prompt。
总结
MCP 的核心不是 @mcp.tool() 这个装饰器,而是把 Agent 与工具实现之间的连接方式标准化。
MCP Server 负责描述和执行能力,MCP Client 负责连接与调用,Host 则运行模型和 Agent 循环。Tools、Resources、Prompts 分别服务于模型控制、应用控制和用户控制;Roots 与 Sampling 则让 Client 能够向 Server 提供边界和模型能力。
在真正的 Agent 中,完整过程不是“手动调用一次 MCP Tool”,而是:
发现 Tools
→ 交给模型
→ 模型选择 Tool
→ MCP 执行
→ 结果返回模型
→ Agent 继续推理
当你理解了这条链路,就能清楚地判断 MCP 在系统中的位置:它负责让能力可以被发现、连接和复用;至于 Agent 如何规划、何时调用、是否需要确认,以及怎样组合多个工具,仍然由 Host 中的 Agent 逻辑决定。