这份文档不是“直接给你一坨代码”,而是带你从最小概念开始,一层一层把 MCP 作业搭起来。
你现在先记住一句话:
MCP 就是把你写好的 Python 函数,包装成一个标准服务,让 Agent 可以自动发现、自动调用。
老师的作业是:
用 LangGraph 做一个多角色旅行规划 Agent,通过 MCP 调用工具,生成旅行方案。
听起来很大,其实拆开只有 5 步:
1. 先写普通 Python 函数
2. 把普通函数注册成 MCP Tool
3. 启动 MCP Server
4. MCP Client 获取工具列表
5. 把工具交给 Agent / LangGraph 使用
假设我们有一个普通 Python 函数:
def calculate_bmi(weight_kg: float, height_cm: float) -> str:
height_m = height_cm / 100
bmi = weight_kg / (height_m ** 2)
return f"BMI = {bmi:.1f}"
这个函数本质上就是一个“工具”:
如果你自己调用它,就是:
print(calculate_bmi(100, 192))
但 Agent 不会直接知道这个函数存在。你得告诉 Agent:
“我这里有个工具,名字叫 calculate_bmi,它需要 weight_kg 和 height_cm 两个参数。”
以前我们会用 LangChain 的 @tool 手动注册。
MCP 的做法是:把这些工具放进一个 MCP Server 里,让客户端自动发现。
你可以把 MCP Server 理解成“工具盒子”。
这个工具盒子里可以放很多工具:
在代码里,创建 MCP Server 的方式是:
from fastmcp import FastMCP
mcp = FastMCP(name="travel-tools")
这句话的意思是:
“我要创建一个 MCP 工具服务,名字叫 travel-tools。”
关键只有一个装饰器:
@mcp.tool()
例如:
@mcp.tool()
def search_hotels(city: str, nights: int = 2, budget_per_night: int = 400) -> str:
"""
查询目的地住宿区域建议。
Args:
city: 目的地城市。
nights: 入住晚数。
budget_per_night: 每晚住宿预算,单位元。
"""
return f"{city} 住 {nights} 晚,建议预算 {budget_per_night} 元/晚。"
这里发生了三件事:
@mcp.tool() 把函数注册成 MCP 工具。search_hotels 会变成工具名。所以你写 MCP 工具时,要特别注意两点:
city: str因为 MCP Client 要把你的工具转换成模型能理解的工具 schema。
比如这个函数:
def search_hotels(city: str, nights: int = 2) -> str:
MCP 大概能理解成:
工具名:search_hotels
参数:
- city:字符串
- nights:整数,默认 2
返回:字符串
如果你不写类型,模型就更容易传错参数。
刚开始你只需要这样记:
Tool:让模型自动调用,用来“做事”
Resource:让程序手动读取,用来“提供资料”
例子:
查酒店、查交通、算预算 → Tool
旅行规划原则、公司制度、固定说明文档 → Resource
我们的作业里:
search_transport 是 Toolsearch_hotels 是 Toolrecommend_attractions 是 Toolestimate_trip_budget 是 Tooltravel_policy://planning_rules 是 Resource我们的 MCP Server 最后有一句:
mcp.run(transport="stdio")
stdio 可以理解成“本地进程通信”。
也就是说:
Agent 程序启动
→ 自动启动 travel_mcp_server.py
→ 通过标准输入/输出传 JSON-RPC 消息
→ 调用工具
→ 拿回结果
stdio 的好处:
Server 是工具盒子。
Client 是去连接工具盒子的人。
代码里是:
client = MultiServerMCPClient(
{
"travel-tools": {
"transport": "stdio",
"command": sys.executable,
"args": [str(server_path)],
}
}
)
这段意思是:
“我要连接一个叫 travel-tools 的 MCP Server。它是 stdio 模式。启动命令是当前 Python 解释器,启动文件是 travel_mcp_server.py。”
然后:
tools = await client.get_tools()
意思是:
“去 MCP Server 问一下:你有哪些工具?”
这一步就是 MCP 的动态发现。
MCP Client 拿到工具以后,会把工具交给大模型 Agent:
agent = create_agent(
model=model,
tools=tools,
system_prompt="你是交通规划师..."
)
当用户问:
我想从成都去重庆玩 3 天,帮我规划交通。
模型会判断:
我需要调用 search_transport
参数 origin = 成都
参数 destination = 重庆
这一步就是 Function Calling。
然后真正执行工具的是 MCP。
所以再记一次:
Function Calling:模型决定调哪个工具
MCP:负责找到工具、执行工具、返回结果
LangGraph 可以把一个大任务拆成多个节点。
我们的作业拆成:
transport_planner 交通规划师
hotel_planner 住宿规划师
attraction_planner 景点规划师
budget_planner 预算规划师
final_planner 总规划师
每个节点做一件事。
例如交通节点:
async def transport_node(state):
content = await ask_role(
model,
tools,
"你是交通规划师。必须优先调用 MCP 交通工具...",
state["user_request"],
)
return {"transport_plan": content}
这个节点的意思是:
“让交通规划师 Agent 读取用户需求,调用 MCP 工具,输出交通方案。”
最后总规划师节点把前面几个角色的结果汇总成完整方案。
不要一上来就看 travel_agent_langgraph.py,会晕。
建议顺序:
travel_mcp_server.py 里的 search_hotelssearch_transportmcp.run(transport="stdio")travel_agent_langgraph.py 里的 build_mcp_clienttools = await client.get_tools()以后你自己封装 MCP 工具,最小模板就是:
from fastmcp import FastMCP
mcp = FastMCP(name="my-tools")
@mcp.tool()
def my_tool(name: str) -> str:
"""
说明这个工具是干什么的。
Args:
name: 参数说明。
"""
return f"你好,{name}"
if __name__ == "__main__":
mcp.run(transport="stdio")
你可以把它想象成:
普通函数 + @mcp.tool() + mcp.run()
这就是最小 MCP Server。
你可以这样说:
我先用 FastMCP 封装了一个本地旅行工具服务,里面有交通查询、酒店查询、景点推荐和预算估算四个 Tool。
然后在 LangGraph 主程序里用 MultiServerMCPClient 连接这个 MCP Server,并通过 get_tools 动态获取工具列表。
接着我把这些工具交给不同角色的 Agent,包括交通规划师、住宿规划师、景点规划师和预算规划师。
每个角色根据用户需求调用 MCP 工具,最后由总规划师节点汇总成完整旅行方案。
在这个流程里,Function Calling 负责让模型判断要调用哪个工具和传什么参数;
MCP 负责工具发现、工具调用和结果返回。