工具越来越多,管理越来越乱
搭好一个 Agent 之后,第一件事往往是给它加工具:搜索、代码执行、数据库查询、API 调用......
用传统的 Function Calling,工具定义是这样的:
python
@tool
def search_jira(query: str) -> str:
"""搜索 Jira 工单"""
...
@tool
def query_database(sql: str) -> str:
"""执行 SQL 查询"""
...
agent = create_react_agent(model=llm, tools=[search_jira, query_database, ...])
这套方案很好用------直到你开始构建第二个 Agent。
问题出现了 :Agent B 也需要 search_jira。你把函数 import 过去,或者复制一份。然后是 Agent C、Agent D。工具定义开始在代码库里四处漂移。某天你修改了 search_jira 的逻辑,你需要找到所有引用它的地方------它们分散在四个不同的文件里。
这就是 MCP(Model Context Protocol) 要解决的问题:把工具从"每个 Agent 自己定义"变成"统一的服务,所有 Agent 按协议连接"。
MCP 的三层架构
MCP 把工具调用拆成三个角色:
arduino
┌────────────────────────────────────────────────────────────────┐
│ MCP 架构 │
├──────────────────┬──────────────────────────────────────────────┤
│ Host │ 承载 Agent 的环境:Claude Desktop、 │
│ │ Claude Code、自定义 LangChain App │
│ │ 包含一个或多个 MCP Client │
├──────────────────┼──────────────────────────────────────────────┤
│ Client │ 内嵌在 Host 里的协议客户端 │
│ │ 负责与 MCP Server 建立连接和通信 │
├──────────────────┼──────────────────────────────────────────────┤
│ Server │ 独立进程,暴露工具(Tools) │
│ │ 也可以暴露资源(Resources)和提示(Prompts) │
└──────────────────┴──────────────────────────────────────────────┘
通信方式:
本地:stdio(标准输入输出,子进程)
远程:HTTP + SSE(Server-Sent Events)
与 Function Calling 的核心区别在于 Server 是独立进程:
- Function Calling:工具是 Python 函数,写在 Agent 代码里,同进程调用
- MCP:工具是独立服务,Agent(Client)通过 JSON-RPC 协议跨进程调用
独立进程意味着:工具可以用任何语言实现,可以被任意数量的 Agent 共享,更新工具不影响 Agent 代码。
Demo 1:传统 Function Calling 的问题
python
@lc_tool
def calculator(expression: str) -> str:
"""Evaluate a simple arithmetic expression."""
...
@lc_tool
def text_stats(text: str) -> str:
"""Return word count, sentence count, and character count."""
...
@lc_tool
def weather_mock(city: str) -> str:
"""Return mock weather data for a city."""
...
traditional_tools = [calculator, text_stats, weather_mock]
agent = create_react_agent(model=llm, tools=traditional_tools)
实测三个问题:
vbnet
Q: What is 2 ** 10 + 100 / 4?
A: The result of 2 ** 10 + 100 / 4 is 1049.0.
Q: Analyze this text: 'Python is elegant. It is readable. Everyone loves it!'
A: 9 words, 3 sentences, 53 characters.
Q: What's the weather in Beijing?
A: sunny, 25°C, humidity 40%.
功能完全正常。问题在架构层面:
go
工具定义在 THIS file: ['calculator', 'text_stats', 'weather_mock']
如果 Agent B 也需要这些工具:copy-paste 或 re-import
如果修改 calculator 逻辑:需要找到所有用到它的 Agent 文件
如果工具是 TypeScript 实现的:这套方案根本走不通
Demo 2:MCP Server------动态工具发现
用 FastMCP 实现同样的三个工具,作为独立 MCP Server:
python
# tools_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo-tools")
@mcp.tool()
def calculator(expression: str) -> str:
"""Evaluate a simple arithmetic expression (e.g. '2 ** 10', '100 / 7')."""
...
@mcp.tool()
def text_stats(text: str) -> str:
"""Return word count, sentence count, and character count for the given text."""
...
@mcp.tool()
def weather_mock(city: str) -> str:
"""Return mock weather data for a city (demo only --- not real data)."""
...
if __name__ == "__main__":
mcp.run(transport="stdio")
Client 侧连接并发现工具:
python
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
server_params = StdioServerParameters(command="python", args=["tools_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 动态发现工具------client 代码里没有任何工具名硬编码
tools_result = await session.list_tools()
实测 list_tools() 返回:
sql
Server: demo-tools
Discovered 3 tools:
● calculator --- Evaluate a simple arithmetic expression (e.g. '2 ** 10', '100 / 7').
● text_stats --- Return word count, sentence count, and character count for the given text.
● weather_mock --- Return mock weather data for a city (demo only --- not real data).
关键点:client 代码里没有写 calculator、text_stats、weather_mock 这些名字 。它只调用了 list_tools()------工具目录是从 Server 动态获取的。
直接调用工具(不经过 LLM):
scss
calculator('2 ** 10 + 100 / 4') → 2 ** 10 + 100 / 4 = 1049.0
weather_mock('Shanghai') → {"city": "Shanghai", "temp": 22, "condition": "cloudy", "humidity": 75}
text_stats(...) → {"words": 9, "sentences": 3, "chars": 53}
Demo 3:LLM Agent 通过 MCP 调用工具
langchain-mcp-adapters 把 MCP 工具模式自动转换成 LangChain Tool 对象,Agent 代码和 Demo 1 几乎一模一样------区别在于工具来源:
python
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient(
{
"demo-tools": {
"command": "python",
"args": ["tools_server.py"],
"transport": "stdio",
}
}
)
mcp_tools = await client.get_tools()
# → ['calculator', 'text_stats', 'weather_mock']
# 工具定义在 Server 里,不在这个文件里
agent = create_react_agent(model=llm, tools=mcp_tools)
result = await agent.ainvoke({"messages": [HumanMessage(multi_q)]})
注意:MCP 工具是异步的,必须用 await agent.ainvoke(),不能用同步的 agent.invoke()。
实测三问题回答:
vbnet
问题:
1. What is sqrt(144) + 2 ** 8?
2. What's the weather in Shenzhen?
3. Count the stats for: 'MCP is a protocol. It standardizes tools. Agents love it!'
回答:
1. The result of sqrt(144) + 2 ** 8 is 260.
2. (LLM 调用了 weather_mock 工具,但因为工具标注了 "demo only",
LLM 在回答里加了 "not real-time data" 的声明)
3. word count: 15, sentence count: 1, character count: 52
三个工具的 CallToolRequest 都出现在 Server 日志里,说明 Agent 确实通过 MCP 协议调用了工具。
MCP vs Function Calling 对比矩阵
arduino
维度 Function Calling MCP
──────────────────────────────────────────────────────────────────────
工具定义位置 在 Agent 代码里 在独立 MCP Server 里
工具发现方式 import / 硬编码 list_tools()(动态)
多 Agent 复用 copy-paste 或 re-import 所有 Agent 连接同一 Server
更新工具逻辑 修改每个 Agent 文件 只修改 Server
跨语言支持 同语言(Python to Python) 任意语言(JSON-RPC 协议)
调用方式 同进程函数调用 跨进程 JSON-RPC
启动开销 无(函数调用) 有(子进程启动)
适用规模 单 Agent / 小型项目 多 Agent / 团队协作
知名 MCP Server 生态
MCP 的价值很大程度上来自已有的 Server 生态:
vbscript
官方 MCP Servers(@modelcontextprotocol/):
server-filesystem --- 本地文件系统读写
server-github --- GitHub Repo / Issue / PR 操作
server-postgres --- PostgreSQL 查询
server-brave-search --- 网络搜索(需要 Brave API Key)
社区维护:
搜索 awesome-mcp-servers 获取完整列表
包括:Jira、Slack、Notion、Linear、Figma 等
Claude Code 的工具就是通过 MCP 接入的:
→ 文件读写、终端命令、代码搜索......都是 MCP Server 工具
MCP Server 开发 Checklist
工具设计
- 每个工具做一件事,输入/输出有明确 Schema
-
description写清楚工具能做什么、不能做什么(LLM 依赖这个决策是否调用) - 敏感操作(写文件、执行命令)加确认步骤或白名单校验
Server 实现
- FastMCP 适合快速开发;需要更细粒度控制时用底层
mcp.server.Server - 工具函数返回字符串(MCP 协议的 TextContent 类型)
- 错误处理:返回描述性错误字符串,不要让异常传播到协议层
传输选择
- 本地工具 → stdio(子进程启动,零配置)
- 远程/共享工具 → HTTP + SSE(需要认证和网络配置)
- 生产环境 → 考虑连接池和超时设置
客户端集成
-
langchain-mcp-adapters用于 LangChain/LangGraph 集成 - MCP 工具是异步的:用
await agent.ainvoke(),不用agent.invoke() - 多个 Server 时,
MultiServerMCPClient统一管理连接
总结
五个核心结论:
- MCP 解决的不是"怎么调工具",而是"工具怎么管理和共享":Function Calling 是单 Agent 内的工具绑定,MCP 是跨 Agent 的工具服务化
- 动态发现是关键能力 :
list_tools()让 Agent 不需要知道工具名就能发现和使用工具 - 独立进程带来跨语言能力:JavaScript 实现的 MCP Server 可以被 Python Agent 调用,反之亦然
- Claude Code 的工具就是 MCP:你用 Claude Code 读文件、执行命令,背后走的就是这套协议
- 异步调用是 MCP 工具的约定 :MCP 工具通过异步调用执行,LangChain 集成时必须用
ainvoke()
下一篇:A2A 协议与 Agent 网络 ------ Agent 和 Agent 之间怎么协作?MCP 解决的是 Agent ↔ 工具,A2A 解决的是 Agent ↔ Agent。
参考资料
更多实用知识和有趣产品,欢迎访问我的个人主页