LangChain+FastMCP 搭建大模型工具调用服务

为解决大模型外部工具调用、上下文传递标准不统一、适配混乱的痛点,本文基于 MCP 模型上下文协议,使用轻量化 FastMCP 构建 MCP 服务端,借助 langchain‑mcp‑adapters 完成与 LangChain 生态集成。文章介绍 Streamable HTTP 传输模式的适配场景,以天气查询作为实战案例,完成自定义工具、资源接口、提示词模板开发,并演示 LangChain 客户端加载工具、读取资源、解析提示词、多服务协同调用完整流程,附带可运行代码与依赖配置,提供一套轻量化标准化的大模型工具调用实践方案。

本文将以天气查询、系统时间读取为实战案例,完整实现自定义工具开发、静态资源封装、通用提示词模板配置三大核心能力。同时逐层演示 LangChain 客户端加载工具、读取资源、渲染提示词、多服务协同调用的全流程,配套完整可运行代码与依赖环境配置,为开发者提供一套轻量化、标准化、可落地的大模型工具调用实践方案。

MCP 服务核心配置

MCP(Model Context Protocol,模型上下文协议)是一套开源通用的大模型交互标准协议,核心作用是统一规范「应用向大模型交付工具能力、传递上下文信息」的交互格式,彻底解决不同工具、不同框架适配混乱的问题。而 FastMCP 是基于 Python 实现的轻量化 MCP 服务端开发框架,封装了繁杂的协议底层逻辑,支持快速定义工具、静态资源、提示词模板,兼容多种传输模式,大幅降低了 MCP 协议的落地开发门槛。

通常MCP的架构由 MCP Host、MCP Client、MCP Server 三部分组成:

image

在通信层面,MCP 客户端与服务端基于标准化 MCP 协议报文交互,底层支持 Stdio、SSE、Streamable HTTP 三种传输载体,可精准适配不同开发与部署场景:Stdio 适用于本地调试场景、SSE 适配简易流式交互场景、Streamable HTTP 适配生产环境远程部署场景。本文将采用Streamable HTTP模式实现接口通信,该模式也是官方推荐的生产环境首选方案,兼具稳定性与通用性。

环境依赖安装

为保障后续代码案例正常运行,需提前安装 FastMCP、langchain-mcp-adapters 等核心依赖库,推荐使用清华镜像源加速安装,安装命令及完整依赖版本清单如下:

CMD> pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple langchain-mcp-adapters fastmcp
CMD>
CMD> pip list
Package Version
------------------------- -----------
langchain 1.3.15
langchain-core 1.5.5
langchain-mcp-adapters 0.3.2
langchain-openai 1.5.1
langchain-protocol 0.0.18
langgraph 1.2.11
langgraph-checkpoint 4.2.0
langgraph-prebuilt 1.1.0
langgraph-sdk 0.4.2
langsmith 0.11.0
mcp 1.29.0
fastmcp 3.4.7
fastmcp-slim 3.4.7

服务端代码实现

本文搭建两个独立 MCP 服务端,实现能力拆分与多服务协同演示:

  • WeatherMCP 专注模拟天气数据查询,提供温度、湿度、天气状况、风力计算等工具及配套资源、提示词;
  • SystemDateTimeMCP 专注系统时间处理,提供时间获取、时间戳转换、日期计算等核心能力。

两个服务分别占用本地 8001、8002 端口,基于 Streamable HTTP 模式对外提供服务,读者可自行展开复制代码,并将其保存至本地磁盘中。

点击展开WeatherMCP代码
import json
from fastmcp import FastMCP
from typing import Union, Optional

mcp = FastMCP("WeatherMCP")

@mcp.tool()
def get_temperature(city: str) -> str:
"""
获取指定城市的模拟实时温度

Args:
city: 城市名称,中文城市名,必填参数,不允许为空字符串

Returns:
str: 返回格式化温度字符串,格式示例:北京 当前温度 26℃
"""
return f"{city} 当前温度 26℃"

@mcp.tool()
def get_humidity(city: str) -> str:
"""
获取指定城市模拟湿度

Args:
city: 中文城市名称

Returns:
str: 湿度信息
"""
return f"{city} 当前相对湿度 62%"

@mcp.tool()
def get_weather_condition(city: str) -> str:
"""
获取城市天气状况:晴/多云/小雨/大雨

Args:
city: 中文城市名称

Returns:
str: 天气状况文字
"""
return f"{city} 天气:多云转晴"

@mcp.tool()
def calculate_wind(speed: float, direction: Optional[str] = "东") -> str:
"""
模拟风力计算工具

Args:
speed: 风速 m/s
direction: 风向,默认东,可选:东、南、西、北

Returns:
str: 风力描述
"""
level = int(speed // 2)
return f"{direction}风,风速{speed}m/s,风力{level}级"

@mcp.resource("config://weather/meta")
def weather_meta() -> str:
"""模拟天气服务元信息资源"""
meta = {
"service_name": "WeatherDemo",
"version": "1.0.0",
"note": "全部为模拟测试数据,非真实气象数据"
}
return json.dumps(meta, ensure_ascii=False, indent=2)

@mcp.resource("config://weather/suggestion")
def weather_suggestion() -> str:
"""天气生活建议资源,穿衣、出行提示"""
data = {
"temp_high": 28,
"temp_low": 18,
"clothes": "薄长袖、短袖,早晚备薄外套",
"uv_tip": "紫外线中等,建议涂防晒",
"rain_tip": "无雨,适合外出游玩"
}
return json.dumps(data, ensure_ascii=False, indent=2)

@mcp.resource("config://weather/wind_level_desc")
def wind_level_desc() -> str:
"""风力等级对照表资源"""
wind_data = {
"0级": "无风",
"1级": "软风",
"2级": "轻风",
"3级": "微风",
"4级": "和风",
"5级": "清风",
"6级": "强风",
"7级": "疾风",
"8级": "大风"
}
return json.dumps(wind_data, ensure_ascii=False, indent=2)

@mcp.resource("file://weather/disclaimer")
def weather_disclaimer() -> str:
"""免责声明文本资源"""
disclaimer_text = """
[免责声明]
本MCP服务所有天气数据均为模拟演示数据,不具备真实气象参考价值。
不可用于生产、出行决策、防灾减灾等实际场景。
仅供MCP协议开发调试学习使用。
""".strip()
return disclaimer_text

@mcp.prompt()
def weather_ask(city: str) -> str:
"""
生成天气查询提示词模板

Args:
city: 城市名
"""
return f"""请帮我查询 {city} 的完整天气,包含温度、湿度、风向,整理成一段通顺自然的中文报告。"""

@mcp.prompt()
def weather_travel_prompt(city: str, travel_day: str) -> str:
"""
出行旅游天气提示模板

Args:
city: 目标城市
travel_day: 出行日期,例如:周六
"""
return f"""
我计划{travel_day}{city}旅行。
请调用天气工具获取该城市温度、湿度、天气状况、风力信息。
结合天气给出出行建议:穿什么衣服、是否带伞、紫外线提醒。
输出简洁易懂,适合普通游客阅读。
""".strip()

@mcp.prompt()
def weather_json_report(city: str) -> str:
"""
获取结构化JSON格式天气报告提示词

Args:
city: 查询城市
"""
return f"""
请查询{city}的全部天气信息:温度、湿度、天气状况、风力。
将结果整理成标准JSON格式返回,不要多余解释文本。
字段包含:city,temperature,humidity,weather,wind_info。
""".strip()

@mcp.prompt()
def weather_alert_prompt(city: str) -> str:
"""
天气风险提醒提示模板

Args:
city: 查询城市
"""
return f"""
帮我分析{city}的天气情况。
判断是否存在高温、大风、降雨等需要注意的风险。
输出风险提示,同时给出对应的生活注意事项。
语言简短,适合作为预警提示。
""".strip()

if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8001, path="/mcp")
点击展开SystemDateTimeMCP代码
import json
import time
from datetime import datetime
from fastmcp import FastMCP
from typing import Optional

mcp = FastMCP("SystemDateTimeMCP")

@mcp.tool()
def get_current_datetime(timezone_offset: Optional[int] = None) -> str:
"""
获取当前系统日期时间,默认本地系统时间,可指定时区偏移小时数

Args:
timezone_offset: 时区偏移小时,例如8代表东八区,None使用本机系统时区
Returns:
str: 格式化的完整日期时间字符串,示例:2026‑08‑19 14:30:22 星期三
"""
if timezone_offset is not None:
now = datetime.utcnow()
now = now.replace(hour=now.hour + timezone_offset)
else:
now = datetime.now()
weekday_map = {0: "星期一", 1: "星期二", 2: "星期三", 3: "星期四", 4: "星期五", 5: "星期六", 6: "星期日"}
wd = weekday_map[now.weekday()]
return f"{now.strftime('%Y‑%m‑%d %H:%M:%S')} {wd}"

@mcp.tool()
def get_timestamp(ms: bool = False) -> int:
"""
获取Unix时间戳

Args:
ms: 是否返回毫秒时间戳,True返回毫秒,False返回秒
Returns:
int: unix时间戳
"""
if ms:
return int(time.time() * 1000)
return int(time.time())

@mcp.tool()
def format_timestamp(timestamp: int, ms: bool = False) -> str:
"""
将时间戳转换为可读日期时间字符串

Args:
timestamp: unix时间戳
ms: 输入时间戳是否为毫秒,True则视为毫秒时间戳
Returns:
str: 格式化时间字符串
"""
if ms:
ts = timestamp / 1000
else:
ts = timestamp
dt = datetime.fromtimestamp(ts)
return dt.strftime("%Y‑%m‑%d %H:%M:%S")

@mcp.tool()
def get_date_parts() -> str:
"""
获取当前时间拆解后的年、月、日、时、分、秒、星期数字,返回JSON字符串
"""
now = datetime.now()
data = {
"year": now.year,
"month": now.month,
"day": now.day,
"hour": now.hour,
"minute": now.minute,
"second": now.second,
"weekday_num": now.weekday(),
"isoweekday": now.isoweekday()
}
return json.dumps(data, ensure_ascii=False, indent=2)

@mcp.tool()
def date_calc(base_date_str: str, days: int) -> str:
"""
日期加减计算,输入 yyyy‑MM‑dd 格式日期,增减N天,返回新日期

Args:
base_date_str: 基准日期,格式 yyyy‑MM‑dd
days: 增加/减少天数,正数向后,负数向前
Returns:
str: 计算后的日期字符串 yyyy‑MM‑dd
"""
from datetime import timedelta
base = datetime.strptime(base_date_str, "%Y‑%m‑%d")
res = base + timedelta(days=days)
return res.strftime("%Y‑%m‑%d")

@mcp.resource("config://datetime/service_meta")
def datetime_service_meta() -> str:
"""日期时间MCP服务元信息资源"""
meta = {
"service_name": "SystemDateTimeMCP",
"version": "1.0.0",
"description": "获取系统时间、时间戳、日期计算演示MCP服务",
"note": "读取运行服务机器的本地系统时间"
}
return json.dumps(meta, ensure_ascii=False, indent=2)

@mcp.resource("config://datetime/format_examples")
def datetime_format_examples() -> str:
"""时间格式化示例参考资源"""
examples = {
"%Y‑%m‑%d": "年‑月‑日",
"%H:%M:%S": "时:分:秒(24小时)",
"%Y‑%m‑%d %H:%M:%S": "完整日期时间",
"%Y%m%d": "紧凑日期",
"%H%M%S": "紧凑时间"
}
return json.dumps(examples, ensure_ascii=False, indent=2)

@mcp.resource("file://datetime/disclaimer")
def datetime_disclaimer() -> str:
"""时间服务免责声明"""
text = """
[DateTimeMCP 说明]
时间取自运行MCP服务主机的本地系统时钟。
如果主机系统时间不准,则返回时间会存在偏差。
仅供开发调试学习使用。
""".strip()
return text

@mcp.prompt()
def prompt_now_info() -> str:
"""获取当前完整时间信息提示词"""
return """
请调用工具获取当前系统完整时间,包含可读日期时间、时间戳(秒与毫秒),拆解年月日时分秒,整理成清晰中文报告。
"""

@mcp.prompt()
def prompt_date_calc_report(base_date: str, offset_days: int) -> str:
"""日期计算报告提示词"""
return f"""
基准日期 {base_date},计算偏移 {offset_days} 天之后的日期。
调用date_calc工具完成计算,同时输出原始基准日期与结果日期,简单说明。
"""

@mcp.prompt()
def prompt_datetime_json() -> str:
"""输出JSON格式时间信息"""
return """
调用相关时间工具获取全部当前时间信息,输出纯JSON,不要多余文字。
字段:datetime_str, timestamp_sec, timestamp_ms, year, month, day, hour, minute, second
"""

@mcp.prompt()
def prompt_timestamp_convert(input_ts: int, is_ms: bool) -> str:
"""时间戳转可读时间提示词"""
return f"""
输入时间戳 {input_ts}{'是毫秒时间戳' if is_ms else '是秒时间戳'}
调用format_timestamp工具将时间戳转为人类可读时间,展示转换前后对比。
"""

if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8002, path="/mcp")

分别独立运行两个服务端脚本,即可在本地开启对应端口的 MCP 服务,启动日志如下,代表服务监听成功、可正常接收客户端请求:
CMD> python WeatherMCP.py
Started server process
Waiting for application startup.
Application startup complete.
Uvicorn running on [http://0.0.0.0:8001]

CMD> python SystemDateTimeMCP.py
Started server process
Waiting for application startup.
Application startup complete.
Uvicorn running on [http://0.0.0.0:8002]

MCP 客户端核心能力:资源加载

依托 langchain-mcp-adapters 适配器,LangChain 客户端可实现三大核心能力:工具集加载调用、静态资源读取、提示词模板加载渲染。客户端通过 Streamable HTTP 协议对接本地两个 MCP 服务端点,彻底实现工具能力、静态配置、提示词模板与业务客户端解耦,无需本地硬编码任何工具逻辑、配置参数与提示词内容,大幅提升代码复用性与标准化程度。

MCP 协议将服务端能力统一拆分为三类核心实体:Tools(可执行工具函数)、Resources(静态只读资源)、Prompts(标准化提示模板),客户端可自动发现、加载、调用所有实体能力。

加载可执行工具函数

MCP 协议中,Tool 是服务端封装的可远程调用函数,服务端会标准化声明工具名称、功能描述、JSON Schema 参数约束。客户端加载工具元数据后,可直接对接 LangChain Agent,由大模型自主判断工具选择、参数填充,再通过 MCP 协议转发请求至服务端执行,实现智能化工具调用。

下述案例基于 Streamable HTTP 通信模式,通过 client.get_tools() 读取服务端全部可调用工具,并批量打印工具详情、参数约束、必填规则,方便开发者清晰掌握可用能力,也是大模型智能调度工具的核心依据。

import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient

async def main():
client = MultiServerMCPClient(
{
"weather": {
"transport": "http",
"url": "http://127.0.0.1:8001/mcp",
}
}
)

tools = await client.get_tools()

for idx, tool in enumerate(tools, 1):
print(f"[{idx}] 工具名称: {tool.name}")
print(f" 功能描述: {tool.description}")
schema = tool.args_schema
required_fields = set(schema.get("required", []))
props = schema.get("properties", {})
print(" 参数列表:")
if not props:
print(" (无参数)")
else:
for param_name, param_info in props.items():
is_required = "必填" if param_name in required_fields else "可选"
typ = param_info.get("type", "unknown")
desc = param_info.get("description", "")
default_val = param_info.get("default", "无")
anyof = param_info.get("anyOf")
if anyof:
typ = " | ".join([t.get("type") for t in anyof if "type" in t])
print(f" · {param_name} [{typ}] {is_required}{desc},默认值={default_val}")
print("-" * 80)
print(f"\n>>> 总计工具数量:{len(tools)}")

if __name__ == "__main__":
asyncio.run(main())

代码运行后,将完整输出所有工具的详细信息,包含功能说明、参数类型、必填规则,输出结果如下:

CMD> python main.py

[1] 工具名称: get_temperature
功能描述: 获取指定城市的模拟实时温度
参数列表:
· city [string] 必填:城市名称,中文城市名,例如:北京、上海、广州;必填参数,不允许为空字符串,默认值=无
--------------------------------------------------------------------------------
[2] 工具名称: get_humidity
功能描述: 获取指定城市模拟湿度
参数列表:
· city [string] 必填:中文城市名称,默认值=无
--------------------------------------------------------------------------------
[3] 工具名称: get_weather_condition
功能描述: 获取城市天气状况:晴/多云/小雨/大雨
参数列表:
· city [string] 必填:中文城市名称,默认值=无
--------------------------------------------------------------------------------
[4] 工具名称: calculate_wind
功能描述: 模拟风力计算工具
参数列表:
· speed [number] 必填:风速 m/s,默认值=无
· direction [string | null] 可选:风向,默认东,可选:东、南、西、北,默认值=东
--------------------------------------------------------------------------------

>>> 总计工具数量:4

加载静态只读资源

MCP 协议中的 Resource 是服务端托管的静态只读数据,无业务逻辑计算能力,主要用于存储服务配置、常量参数、参考文档、声明文本等固定资源,供大模型直接引用学习。资源通过专属 URI 前缀区分类型:config:// 用于存放服务元信息、业务配置、常量数据;file:// 用于存放免责声明、说明文档等文本资源。

本文提供两种资源加载方式,适配不同业务场景,第一种使用client.get_resources("weather")方法加载,传入的参数为MultiServerMCPClient初始化的不同的MCP服务端名称,第二种方式则是直接使用load_mcp_resources一步到位加载资源,两种方式均可,开发者可按需选用。

import asyncio
import json
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain_mcp_adapters.resources import load_mcp_resources

async def main():
client = MultiServerMCPClient(
{
"weather": {
"transport": "http",
"url": "http://127.0.0.1:8001/mcp",
}
}
)

# 第一种加载方式
blobs = await client.get_resources("weather")
resource_list = []

for blob in blobs:
uri = str(blob.metadata["uri"])
raw_data = blob.data
parsed = None
try:
parsed = json.loads(raw_data)
except json.JSONDecodeError:
parsed = raw_data

item = {
"uri": uri,
"raw": raw_data,
"content": parsed,
"mimetype": blob.mimetype
}
resource_list.append(item)

for item in resource_list:
print(f"\nURI: {item['uri']}")
print(item["content"])

# 第二种加载方式
async with client.session("weather") as session:
blobs = await load_mcp_resources(session)
for blob in blobs:
print(blob)

# 第三种加载方式
async with client.session("weather") as session:
blobs = await load_mcp_resources(session,uris=["file://weather/disclaimer"])
for blob in blobs:
print(blob)

if __name__ == "__main__":
asyncio.run(main())

运行代码后,客户端将成功读取天气服务端所有静态资源,包含服务元数据、出行建议、风力等级对照表、免责声明等,输出结果如下:

CMD> python main.py

URI: config://weather/meta
{
'service_name': 'WeatherDemo',
'version': '1.0.0',
'note': '全部为模拟测试数据,非真实气象数据'
}

URI: config://weather/suggestion
{
'temp_high': 28,
'temp_low': 18,
'clothes': '薄长袖、短袖,早晚备薄外套',
'uv_tip': '紫外线中等,建议涂防晒',
'rain_tip': '无雨,适合外出游玩'
}

URI: config://weather/wind_level_desc
{
'0级': '无风',
'1级': '软风',
'2级': '轻风',
'3级': '微风',
'4级': '和风',
'5级': '清风',
'6级': '强风',
'7级': '疾风',
'8级': '大风'
}

URI: file://weather/disclaimer
[免责声明]
本MCP服务所有天气数据均为模拟演示数据,不具备真实气象参考价值。
不可用于生产、出行决策、防灾减灾等实际场景。
仅供MCP协议开发调试学习使用。

加载提示词模板

MCP 协议支持在服务端统一托管可复用的 Prompt 模板,模板内置变量占位符,客户端仅需传入对应业务参数,即可由服务端完成变量渲染,直接返回格式化后的完整提示词。该方式可实现提示词标准化统一管理,避免本地提示词杂乱、格式不统一的问题,同时支持利用提示词模版实现Skill技能包的加载及调用。

本文提供两种提示词加载渲染方案,适配不同开发需求,第一种直接使用await session.get_prompt()得到对应的提示词函数,传入相应的参数完成依次填充调用,并使用mcp_prompt_msg_to_langchain将数据转换为HumanMessage返回,第二种则是直接调用load_mcp_prompt()这个官方接口实现提示词填充。

import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from langchain_mcp_adapters.prompts import load_mcp_prompt

def mcp_prompt_msg_to_langchain(mcp_msg):
"""转换MCP PromptMessage → LangChain Message 消息实例"""
content_obj = mcp_msg.content
if isinstance(content_obj, list):
text_parts = [c.text for c in content_obj if hasattr(c, "text")]
text = "".join(text_parts)
else:
text = content_obj.text

if mcp_msg.role == "user":
return HumanMessage(content=text)
else:
# 可扩展 SystemMessage / AIMessage
return HumanMessage(content=text)

async def main():
client = MultiServerMCPClient(
{
"weather": {
"transport": "http",
"url": "http://127.0.0.1:8001/mcp",
}
}
)

# 第一种加载方式
lc_messages = []
async with client.session("weather") as session:
prompt_result = await session.get_prompt(
name="weather_ask",
arguments={"city": "济南"}
)
for msg in prompt_result.messages:
# print(f"role={msg.role}, raw_content={msg.content}")
lc_msg = mcp_prompt_msg_to_langchain(msg)
lc_messages.append(lc_msg)
print("方式1: ", lc_messages)

# 第二种加载方式
async with client.session("weather") as session:
messages = await load_mcp_prompt(
session,
"weather_travel_prompt",
arguments={"city": "北京","travel_day":"2026-8-20"}
)
print("方式2: ", messages)

if __name__ == "__main__":
asyncio.run(main())

代码运行后,两种方式均可完成模板变量填充与提示词渲染,输出标准化的用户提问内容,结果如下:

CMD> python main.py

方式1: [HumanMessage(content='请帮我查询 济南 的完整天气,包含温度、湿度、风向,整理成一段通顺自然的中文报告。', additional_kwargs={}, response_metadata={})]
方式2: [HumanMessage(content='我计划2026-8-20去北京旅行。\n输出简洁易懂,适合普通游客阅读。', additional_kwargs={}, response_metadata={})]

MCP 资源协同调用实战

完成基础能力加载后,即可实现多 MCP 服务协同调用,让 LangChain Agent 自动感知多服务工具能力,根据用户自然语言提问自主选择对应工具,完成天气查询、时间获取等复合需求,实现端到端智能化工具调用。

基础工具协同调用

客户端同时对接天气、时间两个 MCP 服务,加载全部工具能力,通过本地大模型驱动 Agent 自动解析用户需求,调度对应工具完成查询并整合结果输出。

import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage

basic_model = ChatOpenAI(
model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
base_url="http://127.0.0.1:11433/v1",
api_key="dummy",
temperature=0.1,
max_tokens=512,
)

async def main():
client = MultiServerMCPClient(
{
"weather": {
"transport": "http",
"url": "http://127.0.0.1:8001/mcp",
},
"datetime": {
"transport": "http",
"url": "http://127.0.0.1:8002/mcp",
},
}
)

tools = await client.get_tools()
print(f"\n>>> 总计工具数量:{len(tools)}")

agent = create_agent(
model=basic_model,
tools=tools
)

response = await agent.ainvoke(
{"messages": "当前系统日期与时间是多少?北京天气怎么样?"}
)

# 只提取 HumanMessage / AIMessage 的 content
for msg in response["messages"]:
if isinstance(msg, HumanMessage):
print(f"[Human]: {msg.content}")
elif isinstance(msg, AIMessage):
if msg.content.strip():
print(f"[AI]: {msg.content}")

if __name__ == "__main__":
asyncio.run(main())

工具协同调用输出效果:

CMD> python main.py

>>> 总计工具数量:9
[Human]: 当前系统日期与时间是多少?北京天气怎么样?
[AI]:
当前系统日期与时间是:2026-08-19 13:30:51 星期三。
北京的天气状况是:多云转晴。

提示词模板落地调用

结合前文加载的服务端提示词模板,可快速生成标准化用户提问,交由 Agent 执行工具调用,统一输出格式、规范回答逻辑,避免用户提问不标准导致的工具调用异常。

import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain_mcp_adapters.prompts import load_mcp_prompt

basic_model = ChatOpenAI(
model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
base_url="http://127.0.0.1:11433/v1",
api_key="dummy",
temperature=0.1,
max_tokens=512,
)

def mcp_prompt_msg_to_langchain(mcp_msg):
"""转换mcp.types.PromptMessage 为 langchain消息实例"""
content_obj = mcp_msg.content
if isinstance(content_obj, list):
text_parts = [c.text for c in content_obj if hasattr(c, "text")]
text = "".join(text_parts)
else:
text = content_obj.text
if mcp_msg.role == "user":
return HumanMessage(content=text)
else:
return HumanMessage(content=text)

async def main():
client = MultiServerMCPClient(
{
"weather": {
"transport": "http",
"url": "http://127.0.0.1:8001/mcp",
},
"datetime": {
"transport": "http",
"url": "http://127.0.0.1:8002/mcp",
},
}
)

tools = await client.get_tools()

# 填充参数调用生成提示词模版
lc_messages = []
async with client.session("weather") as session:
prompt_result = await session.get_prompt(
name="weather_ask",
arguments={"city": "济南"}
)
for msg in prompt_result.messages:
lc_msg = mcp_prompt_msg_to_langchain(msg)
lc_messages.append(lc_msg)

print("生成的提示词:", lc_messages)

agent = create_agent(
model=basic_model,
tools=tools
)
resp = await agent.ainvoke({"messages": lc_messages})
final = resp["messages"][-1]
print("Agent输出最终结果:", final.content)

# 第二种构建方式
'''
async with client.session("weather") as session:
messages = await load_mcp_prompt(
session,
"weather_ask",
arguments={"city": "山东"}
)
print(messages)
'''

if __name__ == "__main__":
asyncio.run(main())

模板调用输出效果:

CMD> python main.py

生成的提示词: [HumanMessage(content='请帮我查询 济南 的完整天气,包含温度、湿度、风向,整理成一段通顺自然的中文报告。', additional_kwargs={}, response_metadata={})]
Agent输出最终结果: 为了提供济南的完整天气报告,我将调用以下工具:

1. 获取济南的模拟实时温度
2. 获取济南的模拟湿度
3. 获取济南的天气状况

现在开始获取数据:

1. 获取济南的模拟实时温度
{{"name": "get_temperature", "arguments": {"city": "济南"}}}}

2. 获取济南的模拟湿度
{{"name": "get_humidity", "arguments": {"city": "济南"}}}}

3. 获取济南的天气状况
{{"name": "get_weather_condition", "arguments": {"city": "济南"}}}}

请稍等,我将获取这些数据并整理成报告。
整体来看,济南的天气较为舒适,适合户外活动。风力适中,需要注意防风保暖。

交互式迭代对话

基于 LangGraph 内存记忆组件,搭建可迭代的交互式对话会话,支持多轮连续提问、上下文记忆、会话重置,完整模拟真实大模型应用交互场景,持续调用 MCP 工具完成用户需求响应。

import asyncio
import uuid
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage, ToolMessage, SystemMessage
from langgraph.checkpoint.memory import InMemorySaver

SYSTEM_PROMPT = """
你是工具调用Agent,有可用工具列表。
用户提问需要外部信息时,必须输出结构化工具调用tool_calls,禁止只在文本中说要调用工具
不要预编造数据,全部信息必须通过调用工具获取。
拿到工具返回结果之后,完整总结,再整理自然语言回答用户。
"""

basic_model = ChatOpenAI(
model="qwen2.5-1.5b-instruct-q4_k_m.gguf",
base_url="http://127.0.0.1:11433/v1",
api_key="dummy",
temperature=0.0,
max_tokens=1024,
)

async def interactive_loop():
client = MultiServerMCPClient(
{
"weather": {
"transport": "http",
"url": "http://127.0.0.1:8001/mcp",
},
"datetime": {
"transport": "http",
"url": "http://127.0.0.1:8002/mcp",
},
}
)

try:
tools = await client.get_tools()
print(f"[*] 加载完成 工具总数:{len(tools)}")
for idx, t in enumerate(tools, 1):
print(f" -{idx}. {t.name} : {t.description}")
print()

# 内存记忆检查点
checkpointer = InMemorySaver()
agent = create_agent(
model=basic_model,
tools=tools,
checkpointer=checkpointer
)
thread_config = {
"configurable": {
"thread_id": "mcp‑chat‑001"
}
}

while True:
user_input = input("\n请输入你的问题(exit退出) > ").strip()
if user_input.lower() in ("exit", "quit"):
print("[-]退出会话")
break
if user_input.lower() == "clear":
thread_config["configurable"]["thread_id"] = f"mcp‑chat‑{uuid.uuid4()}"
print("[*] 已清空会话记忆,开启全新对话!")
continue
if not user_input:
continue

print("\n----------开始执行Agent流程----------")
print(f"[用户提问]:{user_input}")

try:
resp = await agent.ainvoke(
{
"messages": [
SystemMessage(content=SYSTEM_PROMPT),
HumanMessage(content=user_input)
]
},
config=thread_config
)
messages = resp["messages"]

for idx, msg in enumerate(messages):
t_name = type(msg).__name__
print(f"\n---消息[{idx}] 类型:{t_name}---")

if isinstance(msg, HumanMessage):
print(f"Human: {msg.content}")
elif isinstance(msg, AIMessage):
print(f"AI content: {msg.content}")
if msg.tool_calls:
print(f"AI 请求调用工具:")
for call in msg.tool_calls:
print(f" 工具名={call['name']}, 参数={call['args']}")
else:
print("[-] AIMessage 没有生成 tool_calls,不会执行MCP工具!")
elif isinstance(msg, ToolMessage):
print(f"[*] 工具返回结果[{msg.name}]:\n{msg.content}")
else:
print(f"{t_name} content: {msg.content}")

final_ai = None
for m in reversed(messages):
if isinstance(m, AIMessage) and m.content.strip():
final_ai = m
break
if final_ai:
print("\n[+] 最终回答")
print(final_ai.content)

except Exception as e:
print(f"\n[-] Agent执行异常:{repr(e)}")

except Exception as conn_err:
print(f"\n[-] MCP服务连接失败,请确认8001、8002端口MCP服务已启动!")
print(f"错误信息:{conn_err}")

if __name__ == "__main__":
asyncio.run(interactive_loop())

交互式对话运行效果:

CMD> python main.py

[*] 加载完成 工具总数:9
-1. get_temperature : 获取指定城市的模拟实时温度
-2. get_humidity : 获取指定城市模拟湿度
-3. get_weather_condition : 获取城市天气状况:晴/多云/小雨/大雨
-4. calculate_wind : 模拟风力计算工具
-5. get_current_datetime : 获取当前系统日期时间,默认本地系统时间,可指定时区偏移小时数
-6. get_timestamp : 获取Unix时间戳
-7. format_timestamp : 将时间戳转换为可读日期时间字符串
-8. get_date_parts : 获取当前时间拆解后的年、月、日、时、分、秒、星期数字,返回JSON字符串
-9. date_calc : 日期加减计算,输入 yyyy‑MM‑dd 格式日期,增减N天,返回新日期

请输入你的问题(exit退出) > 查询当前时间与日期,并查看济南天气

----------开始执行Agent流程----------
[用户提问]:查询当前时间与日期,并查看济南天气

---消息[0] 类型:SystemMessage---
SystemMessage content:
你是工具调用Agent,有可用工具列表。
用户提问需要外部信息时,必须输出结构化工具调用tool_calls,禁止只在文本中说要调用工具
不要预编造数据,全部信息必须通过调用工具获取。
拿到工具返回结果之后,再整理自然语言回答用户。

---消息[1] 类型:HumanMessage---
Human: 查询当前时间与日期,并查看济南天气

---消息[2] 类型:AIMessage---
AI content: 为了获取当前时间与日期,我将调用一个获取当前系统日期时间的工具。
{"name": "get_current_datetime", "arguments": {"timezone_offset": null}}
AI 请求调用工具:
工具名=get_current_datetime, 参数={'timezone_offset': None}

---消息[3] 类型:ToolMessage---
[*] 工具返回结果[get_current_datetime]:
[{'type': 'text', 'text': '2026‑08‑19 14:11:37 星期三', 'id': 'lc_3ded815c-af83-4e7c-8ad1-3f9812967374'}]

---消息[4] 类型:AIMessage---
AI content: 当前时间是2026年8月19日14:11:37,星期三。
接下来,我将调用一个获取济南天气的工具。
{"name": "get_weather_condition", "arguments": {"city": "济南"}}
AI 请求调用工具:
工具名=get_weather_condition, 参数={'city': '济南'}

---消息[5] 类型:ToolMessage---
[*] 工具返回结果[get_weather_condition]:
[{'type': 'text', 'text': '济南 天气:多云转晴', 'id': 'lc_25a49010-62b2-49d7-a560-e47247085f69'}]

---消息[6] 类型:AIMessage---
AI content: 济南当前的天气状况是多云转晴。
[-] AIMessage 没有生成 tool_calls,不会执行MCP工具!
[+] 最终回答
济南当前的天气状况是多云转晴。
请输入你的问题(exit退出) > 当前日期时间呢?合在一起回复

---消息[7] 类型:SystemMessage---
SystemMessage content:
你是工具调用Agent,有可用工具列表。
用户提问需要外部信息时,必须输出结构化工具调用tool_calls,禁止只在文本中说要调用工具
不要预编造数据,全部信息必须通过调用工具获取。
拿到工具返回结果之后,再整理自然语言回答用户。

---消息[8] 类型:HumanMessage---
Human: 当前日期时间呢?合在一起回复

---消息[9] 类型:AIMessage---
AI content: 当前日期时间是2026年8月19日14:11:37,星期三。济南当前的天气状况是多云转晴。
[-] AIMessage 没有生成 tool_calls,不会执行MCP工具!

[+] 最终回答
当前日期时间是2026年8月19日14:11:37,星期三。济南当前的天气状况是多云转晴。

在上述交互式对话测试中,出现了工具返回的时间信息上下文丢失的问题:模型成功调用时间工具、获取了完整时间数据,但在二次提问整合信息时,无法读取历史上下文的时间结果,仅输出最新的天气查询结果。

该问题并非代码逻辑缺陷,核心原因是本次测试使用的 qwen2.5‑1.5b 轻量化模型上下文理解能力、记忆留存能力有限,导致部分上下文信息丢失,若在生产环境下建议直接更换8B以上的模型来测试。