技术实操:LangChain1.0中MCP的调用流程与报错应对技巧

上一篇文章中,我们介绍了LangChain1.0框架下调用人机交互式大模型的方法。今天,我们聚焦另一个核心实操场景------MCP(Model Context Protocol)的调用流程,以及实践中常见报错的解决方案。

一、基础铺垫:官方****MCP 调用示例

官方文档告诉我们如下调用MCP即可:

python 复制代码
from langchain_mcp_adapters.client import MultiServerMCPClient  
from langchain.agents import create_agent


client = MultiServerMCPClient(  
    {
        "math": {
            "transport": "stdio",  # Local subprocess communication
            "command": "python",
            # Absolute path to your math_server.py file
            "args": ["/path/to/math_server.py"],
        },
        "weather": {
            "transport": "http",  # HTTP-based remote server
            # Ensure you start your weather server on port 8000
            "url": "http://localhost:8000/mcp",
        }
    }
)

tools = await client.get_tools()  
agent = create_agent(
    "claude-sonnet-4-5-20250929",
    tools  
)
math_response = await agent.ainvoke(
    {"messages": [{"role": "user", "content": "what's (3 + 5) x 12?"}]}
)
weather_response = await agent.ainvoke(
    {"messages": [{"role": "user", "content": "what is the weather in nyc?"}]}
)

二、实战改造:对接百度地图****MCP 获取天气

基于官方示例,我们将MCP改造为百度地图MCP(其自带天气查询工具),实现具体业务场景的落地。完整代码如下:

python 复制代码
# 通过langchain智能体调用MCP
import requests
import json
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from langchain_core.tools import tool
from langchain.agents.middleware import HumanInTheLoopMiddleware
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.types import Command
import uuid
import asyncio
import time
from langchain_mcp_adapters.client import MultiServerMCPClient  

model = ChatOpenAI(
    streaming=True,
    model='deepseek-chat', 
    openai_api_key=<API KEY>, 
    openai_api_base='https://api.deepseek.com',
    max_tokens=1024,
    temperature=0.1
)

async def mcp_agent():
    # 我们用两种方式启动 MCP Server:stdio 和 streamable_http
    client = MultiServerMCPClient(  
        {
            "baidu-map": {
                "command": "cmd",
                "args": [
                    "/c",
                    "npx",
                    "-y",
                    "@baidumap/mcp-server-baidu-map"
                ],
                "env": {
                    "BAIDU_MAP_API_KEY": <BAIDU API KEY>
                },
                "transport": "stdio",
            },
        }
    )
    
    tools = await client.get_tools()
    for tool in tools:
        print(tool.name)
    
    agent = create_agent(
        model=model,
        tools=tools,
        system_prompt="你是一个友好的助手",
    )
    return agent

async def use_mcp(messages):
    agent = await mcp_agent()
    response = await agent.ainvoke(messages)
    return response

async def main():
    messages = {"messages": [{"role": "user", "content": "今天杭州天气怎么样?"}]}
    response = await use_mcp(messages)
    print(response["messages"][-1].content)

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

核心说明:MCP调用需异步访问,因此必须使用 ainvoke() 方法,并通过 await 等待返回结果,最终在主函数中用 asyncio.run() 启动异步任务。

三、核心问题:JSON反序列化报错排查与解决

上述代码运行后,出现了关键报错:"Failed to deserialize the JSON body into the target type"(JSON反序列化失败)。

3.1****报错信息核心片段
3.2****问题定位

通过检索发现,该问题大概率与DeepSeek大模型相关(未测试其他模型)。进一步排查环境差异:

  • 报错环境:Python 3.11 + langchain-mcp-adapters 0.2.1
  • 正常环境:Python 3.10 + langchain-mcp-adapters 0.1.11

核心原因:MCP返回的数据格式 [TextContent(type='text', text='students_scores', annotations=None, meta=None)] 无法直接传递给DeepSeek,需封装为 ToolMessage 类型。

3.3****解决方案:自定义拦截器封装数据

LangChain新版本支持自定义拦截器,可对工具调用结果进行处理。我们通过拦截器将MCP返回数据拆包并封装为 ToolMessage,解决反序列化问题。优化后完整代码:

python 复制代码
from langchain_openai import ChatOpenAI
from openai import OpenAI
from langchain_mcp_adapters.client import MultiServerMCPClient  
from langchain_mcp_adapters.interceptors import MCPToolCallRequest
from mcp.types import TextContent
import json
from langchain.agents import create_agent, AgentState
import asyncio
from langchain.messages import ToolMessage
from langchain.agents.middleware import before_model, after_model
from langgraph.runtime import Runtime
from typing import Any
#from mcp.types import Message


# 配置大模型服务
llm = ChatOpenAI(
    streaming=True,
    model='deepseek-chat', 
    openai_api_key=<APK KEY>, 
    openai_api_base='https://api.deepseek.com',
    max_tokens=1024,
    temperature=0.1
)

system_prefix = """
        请讲中文
        当用户提问中涉及天气时,需要使用百度地图的MCP工具进行查询;
        当用户提问中涉及电影信息时,需要使用postgresql MCP进行数据查询和操作,仅返回前十条数据,表结构如下:
        ## 电影表(tb_movie)
        "table_name": "tb_movie",
            "description": "电影表",
            "columns":[
                {"column_name": "id","chinese_name": "标识码","data_type": "int"},
                {"column_name": "name","chinese_name": "电影名称","data_type": "varchar"},
                {"column_name": "actor","chinese_name": "主演","data_type": "varchar"},
                {"column_name": "director","chinese_name": "导演","data_type": "varchar"},
                {"column_name": "category","chinese_name": "类型","data_type": "varchar"},
                {"column_name": "country","chinese_name": "制片国家/地区","data_type": "varchar"},
                {"column_name": "language","chinese_name": "语言","data_type": "varchar"},
                {"column_name": "release_date","chinese_name": "上映日期","data_type": "varchar"},
                {"column_name": "runtime","chinese_name": "片长","data_type": "varchar"},
                {"column_name": "abstract","chinese_name": "简介","data_type": "varchar"},
                {"column_name": "score","chinese_name": "评分","data_type": "varchar"}            
            ]
        当用户提及画图时,返回数据按照如下格式输出,输出图片URL后直接结束,不要输出多余的内容:
        1.查询结果:{}
        2.图表展示:{图片URL}
        否则,直接输出返回结果。
    """

async def append_structured_content(request: MCPToolCallRequest, handler):
    """Append structured content from artifact to tool message."""
    result = await handler(request)
    runtime = request.runtime
    print("========================result.content:", result.content[-1].text)
    if result.structuredContent:
        result.content += [
            TextContent(type="text", text=json.dumps(result.structuredContent)),
        ]
    return ToolMessage(content=result.content, tool_call_id=runtime.tool_call_id)

async def mcp_agent():
    client = MultiServerMCPClient(
        {
            "postgres": {
                "command": "cmd",
                "args": [
                    "/c",
                    "npx",
                    "-y",
                    "@modelcontextprotocol/server-postgres",
                    "postgresql://postgres:123456@localhost:5432/movie"
                ],
                "transport": "stdio",
            },
            "mcp-server-chart": {
                "command": "cmd",
                "args": [
                    "/c",
                    "npx",
                    "-y",
                    "@antv/mcp-server-chart"
                ],
                "transport": "stdio",
            },
            "baidu-map": {
                "command": "cmd",
                "args": [
                    "/c",
                    "npx",
                    "-y",
                    "@baidumap/mcp-server-baidu-map"
                ],
                "env": {
                    "BAIDU_MAP_API_KEY": <BAIDU API KEY>
                },
                "transport": "stdio",
            }
        }, tool_interceptors=[append_structured_content]
    )
    tools2 = await client.get_tools()
    for tool in tools2:
        print(tool.name)
    agent = create_agent(
        model=llm,
        tools=tools2,
        system_prompt=system_prefix,
    )
    return agent


async def use_mcp(query:str):
    agent = await mcp_agent()
    #response = await agent.ainvoke(messages)
    try:
        response = await agent.ainvoke(
            {"messages": [{"role": "user", "content": str}]}
        )
        print("============================================================")
        print(response['messages'][-1].content)
    except Exception as e:
        print(str(e))
        return None
    return response

if __name__ == "__main__":
    asyncio.run(use_mcp("今天杭州天气怎么样?"))
3.4****验证结果

运行优化后代码,成功获取正确天气信息:

可以看到,我们得到了正确的返回结果。这里的核心是append_structured_content()这个方法,把MCPToolCallRequest请求的返回值进行解析并包装成ToolMessage,并传递到MultiServerMCPClient的tool_interceptors中,从而获得正确的结果。

**四、进阶场景:**MCP 工具的人机交互调用

官方文档中MCP的人机交互(Human-In-The-Loop)示例为同步调用,但MCP本身是异步访问的,直接复用会出现兼容性问题。我们需要改造实现异步场景下的人机交互。

4.1****官方示例
python 复制代码
from langgraph.types import Command

# Human-in-the-loop leverages LangGraph's persistence layer.
# You must provide a thread ID to associate the execution with a conversation thread,
# so the conversation can be paused and resumed (as is needed for human review).
config = {"configurable": {"thread_id": "some_id"}} 
# Run the graph until the interrupt is hit.
result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "Delete old records from the database",
            }
        ]
    },
    config=config 
)

# The interrupt contains the full HITL request with action_requests and review_configs
print(result['__interrupt__'])

# Resume with approval decision
agent.invoke(
    Command( 
        resume={"decisions": [{"type": "approve"}]}  # or "reject"
    ), 
    config=config # Same thread ID to resume the paused conversation
)
4.2****异步改造初次尝试与报错
python 复制代码
async def get_agent():
    tools = await client.get_tools()
    agent = create_agent(
        model=model,
        tools=tools,
        middleware=[
        HumanInTheLoopMiddleware( 
            interrupt_on={
                # 需要审批,允许approve,reject两种审批类型
                "generate_bar_chart": {"allowed_decisions": ["approve", "reject"]},
            },
            description_prefix="Tool execution pending approval",
        ),
    ],
    checkpointer=InMemorySaver(),
    system_prompt='''当用户提及画图时,返回数据按照如下格式输出,输出图片URL后直接结束,不要输出多余的内容:
                    图表展示:{图片URL}'''
    )
    for tool in tools:
        print(tool.name)
    return agent

async def action_tool():
    tool_agent = await get_agent()
    config = {'configurable': {'thread_id': str(uuid.uuid4())}}
    result = tool_agent.ainvoke(
        {"messages": [{
            "role": "user",
            "content": "帮我生成一个柱状图,数据如下:有10个城市,每个城市的人口是1000,100,90,80,70,60,50,40,30,20。"
        }]},
        config=config,
    )
    # Resume with approval decision
    result = tool_agent.invoke(
        Command(
            resume={"decisions": [{"type": "approve"}]}  # or "edit", "reject"
        ), 
        config=config
    )
    print(result['messages'][-1].content)

if __name__ == '__main__':
    asyncio.run(action_tool())

按异步思路改造后,出现报错:"StructuredTool does not support sync invocation"(结构化工具不支持同步操作),核心原因是人机交互中间件的同步调用与MCP的异步特性冲突。

4.3****解决方案:异步 MCP 工具封装为同步工具

核心思路:将异步的MCP工具调用封装为同步工具,在同步工具内部通过 asyncio.run() 执行异步任务,实现与同步人机交互中间件的兼容。完整代码:

python 复制代码
# 通过langchain智能体调用MCP
.... # 导入包和大模型的初始化代码省略了

client = MultiServerMCPClient(
    {
        "mcp-server-chart": {
            "command": "cmd",
            "args": [
                "/c",
                "npx",
                "-y",
                "@antv/mcp-server-chart"
            ],
            "transport": "stdio",
        }
    }
)

async def use_mcp(messages):
    tools = await client.get_tools()
    agent = create_agent(
        model=model,
        tools=tools,
        system_prompt='''当用户提及画图时,返回数据按照如下格式输出,输出图片URL后直接结束,不要输出多余的内容:
                       图表展示:{图片URL}'''
    )
    for tool in tools:
        print(tool.name)
    print(f"messages:{messages}")

    response = await agent.ainvoke({
        "messages": [{"role": "user", "content": messages}]
    })
    print(f"response:{response['messages'][-1].content}")
    return response

@tool(description="生成图表工具")
def generate_chart(query: str) -> str:
    """generate chart"""
    print("==============generate_chart===============")
    response = asyncio.run(use_mcp(query))
    print(f"response==============:{response}")
    return response


# 创建带工具调用的Agent
tool_agent = create_agent(
    model=model,
    tools=[generate_chart],
    middleware=[
        HumanInTheLoopMiddleware( 
            interrupt_on={
                # 需要审批,允许approve,reject两种审批类型
                "generate_chart": {"allowed_decisions": ["approve", "reject"]},
            },
            description_prefix="Tool execution pending approval",
        ),
    ],
    checkpointer=InMemorySaver(),
    system_prompt="你是一个友好的助手,请根据用户问题作答,当用户想要画图时,就调用生成图表工具来实现",
)

# 运行Agent
def action_tool():
    config = {'configurable': {'thread_id': str(uuid.uuid4())}}
    result = tool_agent.invoke(
        {"messages": [{
            "role": "user",
            "content": "帮我生成一个柱状图,数据如下:有10个城市,每个城市的人口是1000,100,90,80,70,60,50,40,30,20。"
        }]},
        config=config,
    )

    print(result.get('__interrupt__'))

    # Resume with approval decision
    result = tool_agent.invoke(
        Command(
            resume={"decisions": [{"type": "approve"}]}  # or "edit", "reject"
        ), 
        config=config
    )

    print(result['messages'][-1].content)


if __name__ == "__main__":
    action_tool()
4.4****验证结果

运行后成功返回图表URL,浏览器访问即可查看10个城市人口分布柱状图,实现了"异步MCP工具+同步人机交互"的需求。

五、核心总结

  1. LangChain1.0调用MCP需遵循异步规范,使用 ainvoke() + asyncio.run() 组合;
  2. DeepSeek大模型与MCP返回数据存在格式兼容问题,可通过自定义拦截器封装 ToolMessage 解决;
  3. 异步MCP工具对接人机交互中间件时,需将异步调用封装为同步工具,兼容中间件的同步特性;
  4. 版本差异(Python、langchain-mcp-adapters)可能引发隐藏问题,实践中需注意环境一致性。
相关推荐
创客匠人老蒋13 小时前
从“经验驱动”到“系统智能”:实体门店经营的结构性升级
大数据·人工智能
安达发公司13 小时前
安达发|APS自动排产排程排单软件:让汽车零部件厂排产不“卡壳”
大数据·人工智能·汽车·aps高级排程·aps排程软件·aps自动排产排程排单软件
草莓熊Lotso13 小时前
脉脉独家【AI创作者xAMA】| 多维价值与深远影响
运维·服务器·数据库·人工智能·脉脉
V搜xhliang024613 小时前
常规超声联合影像组学预测肾透明细胞癌核分级的列线图模型构建和验证
人工智能·计算机视觉
柠檬071113 小时前
opencv 未知函数记录-detailEnhance
人工智能·opencv·计算机视觉
SunnyRivers13 小时前
LangChain核心组件之Short-term memory
langchain·短期记忆
空山新雨后、13 小时前
ComfyUI、Stable Diffusion 与 ControlNet解读
人工智能
Hcoco_me13 小时前
大模型面试题42:从小白视角递进讲解大模型训练的重计算
人工智能·rnn·深度学习·lstm·transformer
喜欢吃豆14 小时前
代理式 CI/CD 的崛起:Claude Code Action 深度技术分析报告
人工智能·ci/cd·架构·大模型
2301_7644413314 小时前
基于HVNS算法和分类装载策略的仓储系统仿真平台
人工智能·算法·分类