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

1379525-20260819110534339-656030605

在通信层面,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以上的模型来测试。

posted @ 2026-08-19 15:43  lyshark  阅读(9)  评论(0)    收藏  举报