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 三部分组成:

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

环境依赖安装

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

bash 复制代码
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代码

python 复制代码
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代码

python 复制代码
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 服务,启动日志如下,代表服务监听成功、可正常接收客户端请求:

bash 复制代码
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() 读取服务端全部可调用工具,并批量打印工具详情、参数约束、必填规则,方便开发者清晰掌握可用能力,也是大模型智能调度工具的核心依据。

python 复制代码
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())

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

bash 复制代码
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一步到位加载资源,两种方式均可,开发者可按需选用。

python 复制代码
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())

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

bash 复制代码
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()这个官方接口实现提示词填充。

python 复制代码
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())

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

bash 复制代码
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 自动解析用户需求,调度对应工具完成查询并整合结果输出。

python 复制代码
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())

工具协同调用输出效果:

bash 复制代码
CMD> python main.py

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

提示词模板落地调用

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

python 复制代码
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())

模板调用输出效果:

bash 复制代码
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 工具完成用户需求响应。

python 复制代码
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())

交互式对话运行效果:

bash 复制代码
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以上的模型来测试。

相关推荐
lyshark1 天前
千问大模型完整RLHF全参数微调指南
大模型应用技术实践
lyshark3 天前
千问大模型二次LoRA‑SFT指令微调指南
大模型应用技术实践
lyshark9 天前
轻量化小模型MiniMind从训练到落地指南
大模型应用技术实践
lyshark13 天前
Ubuntu 大模型HF转GGUF全流程实践指南
大模型应用技术实践·linux 系统运维技术实践
lyshark14 天前
LangChain 消息流输出与结构化处理
大模型应用技术实践
lyshark15 天前
Python 原生封装 Llama.cpp 大模型推理接口
大模型应用技术实践
lyshark16 天前
LangGraph Server Agent 框架本地部署指南
大模型应用技术实践
lyshark17 天前
LangChain 实现AdvancedRAG增强向量检索生成
大模型应用技术实践
lyshark18 天前
LangChain 实现NaiveRAG朴素向量检索生成
大模型应用技术实践
lyshark20 天前
LangGraph+PostgreSQL 会话记忆持久化存储
大模型应用技术实践