☰
LangGraph MCP智能体开发详解(第四章)-LangGraph接入HTTP MCP智能体
2026/10/1 20:31:38 网站建设 项目流程

1. 为什么本地 MCP Server 跑通了,LangGraph 里还是调不动工具

如果你已经能在本地把 MCP Server 用 stdio 模式跑起来,tools/list也能正常返回工具清单,那说明协议层没问题。但一旦把场景换成「让 LangGraph 的 ReAct 智能体去调用这些工具」,很多人会卡在同一个地方:图能编译、节点能跑、模型也能返回内容,可就是不见工具被真正触发,或者触发了却拿不到结果。

这个问题的本质,是 stdio 和 HTTP/SSE 两种传输方式在 LangGraph 里的接入姿势完全不同。stdio 模式下,MCP 客户端是「拉起一个子进程 + 管道通信」,生命周期跟着你的 Python 进程走;而 HTTP/SSE 模式下,MCP Server 是一个已经在网络上跑着的服务,客户端要做的是「建立一条 SSE 事件流接收服务端推送,再用 POST 往/messages发指令」。前者是进程内握手,后者是网络往返,配置项、连接参数、超时处理都不一样。

我试过把 stdio 的配置直接照搬到 SSE 场景,结果就是连接建立后一直收不到initialize的响应,图节点卡在等待状态。后来才明白,SSE 模式需要显式指定transport: "sse",并且 URL 必须指向/sse端点,而不是根路径。

这一章要解决的问题很具体:你手上已经有一个能用的 HTTP/SSE MCP Server(自托管的也好,平台托管直连的也好),现在要把它接进 LangGraph 的状态图里,让工具调用作为图中的一个节点正常流转。适合的读者是已经写过基础 LangGraph 图、理解StateGraph和ToolNode概念,但对 MCP 的 HTTP 传输层还不熟的开发者。接下来我会给出可复制的客户端配置、SSE 连接参数、节点注册代码,以及一次完整的工具调用往返验证。

2. TaoToken 前置:把模型出口和 MCP 入口分开配

在动手写 LangGraph 节点之前,先把「模型从哪来」这件事定下来。MCP 负责的是工具能力,模型负责的是推理和决策,这两条链路要分开配置,否则排障时会互相干扰。

我自己的做法是:MCP Server 用平台托管直连的 SSE URL,模型侧统一走 TaoToken 的兼容接口。这样做的原因是,LangGraph 的 ReAct 代理在每一轮推理时都要把「工具描述 + 对话上下文」塞进 prompt,模型需要稳定地返回结构化的工具调用意图。如果模型出口不稳定,你会误以为是 MCP 连接出了问题。

TaoToken 的 API 地址是https://taotoken.net/api,兼容 OpenAI 的/v1/chat/completions格式,所以可以直接用langchain_openai.ChatOpenAI来初始化,只需要把base_url指过去。模型 ID 建议选一个支持 function calling 的,比如claude-sonnet-4-20250514这类,具体以你账号里可用的为准。

这里有个容易踩的坑:LangGraph 的create_react_agent在绑定工具时,会把工具的 JSON Schema 一起发给模型。如果模型不支持 tool calling,它会用自然语言描述「我想调用某个工具」,而不是返回tool_calls字段,结果就是ToolNode永远收不到调用请求。所以模型选型这一步不能省。

配置上,我建议把模型和 MCP 的配置都放在环境变量里,代码里只读不写死。这样本地调试和换环境时不用改代码。模型侧需要的是TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL,MCP 侧需要的是MCP_SSE_URL。下面这段是初始化模型客户端的写法:

import os from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="claude-sonnet-4-20250514", api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ.get("TAOTOKEN_BASE_URL", "https://taotoken.net/api"), temperature=0, timeout=60, )

temperature=0是为了让工具调用决策更稳定,减少「同一句话有时调工具有时不调」的抖动。timeout=60是给模型推理留足时间,因为带工具描述的 prompt 通常比较长。

如果你还没有 API Key,可以去 TaoToken 的 API Keys 页面生成一个,然后在接入文档里确认一下当前的 base_url 和可用模型列表。这一步做完,模型出口就固定了,接下来所有问题都可以聚焦在 MCP 的 SSE 接入上。

3. 可复制配置:SSE 客户端参数与 LangGraph 节点注册

这一节是整章的核心,我会把 MCP 客户端配置、SSE 连接参数、工具加载、节点注册这四件事拆开讲,每一块都给可复制的代码。

先说 MCP 的配置文件。平台托管直连的 MCP 服务,通常会在详情页给你一个 SSE URL,形如https://mcp.api-inference.modelscope.net/xxxx/sse。这个 URL 要写进mcp_config.json,结构如下:

{ "mcpServers": { "12306-mcp": { "url": "https://mcp.api-inference.modelscope.net/f38521aeede344/sse", "transport": "sse" } } }

注意两个字段:url必须指向/sse端点,transport必须是"sse"。如果你写成"http"或者省略transport,客户端会默认按 stdio 处理,然后报「找不到 command」的错误。这是最常见的配置错误之一。

接下来是加载 MCP 工具。LangGraph 生态里可以用langchain-mcp-adapters这个包,它提供了MultiServerMCPClient,能直接读上面的 JSON 配置并把工具转成 LangChain 的StructuredTool对象:

import json from langchain_mcp_adapters.client import MultiServerMCPClient with open("mcp_config.json", "r", encoding="utf-8") as f: mcp_config = json.load(f) client = MultiServerMCPClient(mcp_config["mcpServers"]) tools = await client.get_tools() print(f"loaded {len(tools)} tools: {[t.name for t in tools]}")

get_tools()是异步的,因为它要建立 SSE 连接、发initialize、再发tools/list。这一步如果卡住,通常是 SSE URL 不可达或者鉴权失败。跑通后你会看到工具名列表,比如search_train_tickets之类。

拿到工具后,注册进 LangGraph 的 ReAct 代理。这里有两种写法:一种是用create_react_agent一步到位,另一种是手动搭StateGraph并显式加ToolNode。前者适合快速验证,后者适合你要在工具调用前后插入自定义逻辑。先给快速验证版:

from langgraph.prebuilt import create_react_agent agent = create_react_agent(llm, tools) result = await agent.ainvoke({ "messages": [{"role": "user", "content": "帮我查一下明天北京到上海的高铁"}] }) print(result["messages"][-1].content)

如果你要手动控制图结构,可以这样搭:

from langgraph.graph import StateGraph, MessagesState, START, END from langgraph.prebuilt import ToolNode def call_model(state: MessagesState): response = llm.bind_tools(tools).invoke(state["messages"]) return {"messages": [response]} def should_continue(state: MessagesState): last = state["messages"][-1] if getattr(last, "tool_calls", None): return "tools" return END builder = StateGraph(MessagesState) builder.add_node("agent", call_model) builder.add_node("tools", ToolNode(tools)) builder.add_edge(START, "agent") builder.add_conditional_edges("agent", should_continue, {"tools": "tools", END: END}) builder.add_edge("tools", "agent") graph = builder.compile()

这段代码里,call_model节点负责让模型决策,should_continue判断是否有tool_calls,有就路由到tools节点,ToolNode执行完再把结果送回agent。这就是 ReAct 循环的标准结构。MCP 工具在这里和普通 LangChain 工具没有区别,因为适配器已经把协议细节屏蔽掉了。

一个关键参数是 SSE 的连接超时。MultiServerMCPClient默认超时可能偏短,网络抖动时会断流。如果你遇到「连接建立后中途断开」,可以在配置里加timeout字段(单位秒),或者在客户端初始化时传参。另外,SSE 是长连接,如果你的图运行时间很长,要注意服务端是否有空闲断开策略。

4. 验证请求:一次工具调用往返的完整链路

配置写完,必须验证工具调用真的走通了。验证的目标不是「模型回复了一句话」,而是「模型返回了 tool_calls → ToolNode 执行了 MCP 工具 → 工具结果回到模型 → 模型基于结果生成最终回答」。这四步缺一不可。

最直接的验证方式是打开 LangSmith 追踪,或者在代码里打印中间状态。我习惯用astream逐事件观察:

async for event in graph.astream( {"messages": [{"role": "user", "content": "查询明天北京到上海的火车票"}]}, stream_mode="values", ): last = event["messages"][-1] print("---") print("type:", type(last).__name__) if getattr(last, "tool_calls", None): print("tool_calls:", last.tool_calls) print("content:", last.content[:200] if last.content else "")

跑起来后,你应该看到类似这样的输出序列:第一条是AIMessage,带tool_calls字段,里面包含工具名和参数;第二条是ToolMessage,内容是 MCP Server 返回的 JSON 结果;第三条又是AIMessage,这次没有tool_calls,content是基于工具结果生成的最终回答。

如果只看到第一条AIMessage带tool_calls,但没有ToolMessage,说明ToolNode执行失败。常见原因是 MCP 工具的参数 schema 和模型生成的参数不匹配,比如模型传了字符串但 schema 要求整数。这时候去看ToolNode抛出的异常,通常会提示参数校验失败。

如果连tool_calls都没有,模型直接返回了自然语言,那问题在模型侧:要么模型不支持 tool calling,要么工具描述没绑定成功。检查llm.bind_tools(tools)是否真的把工具传进去了,可以打印llm.kwargs确认。

验证成功后,你会看到最终回答里包含了真实的查询结果,比如车次、时间、余票。这时候整条链路就通了:LangGraph 状态图 → 模型决策 → MCP SSE 工具调用 → 结果回填 → 最终回答。整个过程里,MCP 适配器承担了接口映射,把 MCP Server 的工具描述转成 LangGraph 的工具对象,包括名称、说明、输入输出类型;模型根据这些描述决定调哪个工具;LangGraph 负责把调用意图映射成实际函数调用;MCP 客户端把参数通过既定格式发给服务器,结果以 JSON 返回。开发者不需要处理底层序列化和协议细节。

5. 本篇常见错排查:401、连接失败、choices 为空

这一节按真实报错来排。我把接入过程中遇到过的几类问题整理成对照表,你可以直接对号入座。

报错现象可能原因排查动作
401 UnauthorizedMCP SSE URL 需要鉴权,但没带 Token;或 TaoToken API Key 无效检查 MCP 配置里是否需要headers字段带 Token;检查TAOTOKEN_API_KEY是否过期
local proxy failed/ 连接超时SSE URL 不可达,或网络策略限制用curl -N <sse_url>测试能否建立事件流;确认 URL 指向/sse
reading 'choices'报错模型返回体不是标准 OpenAI 格式,或 base_url 配错确认base_url是https://taotoken.net/api,不要多加/v1后缀导致路径重复
OAuth相关报错MCP Server 要求 OAuth 流程,但客户端只配了 URL改用支持 OAuth 的客户端配置,或换一个用 API Key 鉴权的 MCP 服务
tool_calls为空模型不支持 function calling,或工具未绑定换支持 tool calling 的模型;打印tools列表确认非空
ToolMessage参数校验失败模型生成的参数类型和 schema 不匹配检查工具 schema,必要时在工具描述里写清参数格式

重点说两个。第一个是401。MCP 平台托管的 SSE URL 有时是「已鉴权」的,有时需要你在请求头里带 Token。如果配置里只有url和transport,但服务端要求鉴权,就会返回 401。这时候要在mcp_config.json里加headers字段:

{ "mcpServers": { "some-mcp": { "url": "https://example.com/sse", "transport": "sse", "headers": { "Authorization": "Bearer YOUR_MCP_TOKEN" } } } }

第二个是reading 'choices'。这个报错通常出现在模型客户端解析响应时,说明返回的 JSON 里没有choices字段。原因多半是base_url配错了,比如写成了https://taotoken.net/api/v1,而 SDK 内部又拼了一次/v1/chat/completions,变成/api/v1/v1/chat/completions,服务端返回 404 或错误页,解析时就找不到choices。正确写法是base_url="https://taotoken.net/api",让 SDK 自己拼路径。

还有一个隐蔽的坑:SSE 连接建立后,如果长时间没有事件推送,某些网络环境会主动断开。表现是图跑到一半卡住,日志里没有明显报错。解决办法是在 MCP 客户端配置里加心跳或重连参数,或者把长任务拆成多个短请求。这个在本地开发时不容易复现,部署到服务器后才暴露,建议提前在配置里留好超时和重试。

6. 把 MCP 工具接进图之后,下一步怎么走

工具调用跑通之后,你会发现 LangGraph 的价值开始显现:你可以把「查票」这个 MCP 工具和「天气」「日历」等其他 MCP 工具放进同一张图,让模型自己决定调用顺序。也可以在图里加一个「结果校验」节点,在工具返回后先做一轮格式检查,再交给模型总结。这些都是在ToolNode前后插入自定义节点的操作,结构上不影响 MCP 接入本身。

如果你要长期跑编码类或 Agent 类任务,建议把模型出口固定成 Coding Plan 这类稳定通道,避免每次调试都换 Key。MCP 侧则尽量用平台托管直连的 SSE URL,省去自己维护进程和依赖的成本。需要生成或轮换 Key 的时候,直接去 API Keys 页面操作就行,接入文档里有各语言的最小示例,对照着改base_url和模型 ID 即可。

最后留一个实用技巧:把mcp_config.json和.env都加进.gitignore,但额外维护一份mcp_config.example.json提交到仓库。这样团队协作时,别人 clone 下来只需要复制一份改 URL 和 Token,不会因为缺配置而跑不起来。图编排的代码可以复用,配置永远跟着环境走。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询