拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MCP 与 LangChain 工具调用机制差异:用 TaoToken 统一 Key 跑通两条链路

MCP 与 LangChain 工具调用机制差异:用 TaoToken 统一 Key 跑通两条链路 1. 为什么要在同一个项目里同时跑 MCP 和 LangChain如果你最近在折腾 Agent 应用大概率会遇到一个选择困难工具调用到底用 MCPModel Context Protocol模型上下文协议还是 LangChain前者是 Anthropic 推的开放标准把工具封装成独立服务通过 JSON-RPC 2.0 通信后者是成熟的 Python 框架工具就是代码里一个tool装饰的函数注册进 Agent 就能用。我试过把两条链路放在同一个项目里对比发现它们最本质的区别不在 API 长相而在「工具住在哪里」。LangChain 的工具住在你的进程里调用就是一次本地函数执行MCP 的工具住在另一个进程甚至另一台机器上调用是一次带生命周期的网络协议交互。这个差异会直接影响你的部署方式、调试手段和扩展成本。这篇面向需要在同一项目中对比两种链路的开发者。我会用 TaoToken 作为统一的 Key 和 API 通道分别接入 LangChain 的 tool calling 和 MCP 的 tools/call给出可复制的config.toml与settings.json骨架然后发起一次真实的工具调用请求把请求结构、路由方式和返回结果摆在一起看差异。适合已经写过简单 Agent、想搞清楚「协议化工具调用」到底多做了哪些事的人。需要提前说明TaoToken 在这里的角色是统一模型入口两条链路都通过它拿模型能力这样对比时变量只剩工具调用机制本身不会因为换了模型供应商导致结果不可比。2. TaoToken 前置统一 Key 与两条链路的接入点2.1 为什么对比实验需要统一 Key做机制对比最怕变量污染。如果 LangChain 走 A 家的模型、MCP 走 B 家的模型那请求结构差异里就混进了供应商格式差异根本分不清是协议本身的不同还是 API 封装的不同。TaoToken 提供 OpenAI 兼容的接口两条链路都能指向同一个 base_url 和同一个 Key模型也选同一个这样工具调用的请求体差异就纯粹来自框架和协议层。TaoToken 的 API 地址是https://taotoken.net/api兼容 OpenAI 的/v1/chat/completions路径。LangChain 用ChatOpenAI直接接MCP 那条链路里模型侧同样走这个地址工具侧才走 MCP Server。2.2 拿 Key 与确认模型先到控制台创建 API Key地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite。创建后复制出来形如sk-开头的一串。建议单独建一个用于实验的 Key方便后面看调用日志。模型方面选一个支持 function calling / tool use 的就行。工具调用能力是两条链路的共同前提如果模型本身不返回 tool_calls 结构后面所有对比都无从谈起。你可以在模型对话页先手动发一条带 tools 定义的请求确认模型能正常返回工具调用意图地址是https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。2.3 两条链路的接入点差异LangChain 链路模型 本地工具函数全部在你自己的 Python 进程里。TaoToken 只负责模型推理工具执行是本地requests.get之类。MCP 链路模型仍然走 TaoToken但工具被抽到一个独立的 MCP Server 进程通过 stdio 或 SSE 通信。你的主程序里有一个 MCP Client它先tools/list发现工具再tools/call发起调用。把这两条画在一起TaoToken 是它们共享的「模型出口」而工具入口一个是本地函数表一个是协议端点。这就是后面所有配置文件的组织逻辑。3. 可复制配置config.toml 与 settings.json 骨架3.1 项目目录结构先约定一个最小可跑的结构避免配置文件散落各处找不到mcp-vs-langchain/ ├── config.toml # 统一配置Key、base_url、模型名 ├── settings.json # MCP Server 注册表 ├── langchain_agent.py # LangChain 链路 ├── mcp_client.py # MCP 链路 └── mcp_servers/ └── weather_server.py # 一个最小 MCP Server3.2 config.toml两条链路共享的模型配置# config.toml [llm] base_url https://taotoken.net/api api_key sk-你的Key model gpt-4o-mini # 换成你账号下支持 tool calling 的模型 temperature 0 [langchain] tool_timeout 10 [mcp] transport stdio # 本地实验先用 stdio远程再换 sse server_startup_timeout 15base_url不带/v1因为 OpenAI SDK 和 LangChain 的ChatOpenAI会自动补/v1/chat/completions。这一点很容易踩坑写成https://taotoken.net/api/v1会变成/api/v1/v1/...。3.3 settings.jsonMCP Server 注册表MCP 的客户端配置通常是一个 JSON声明要启动哪些 Server、用什么命令启动。这个骨架可以直接抄{ mcpServers: { weather: { command: python, args: [mcp_servers/weather_server.py], env: { PYTHONUNBUFFERED: 1 } } } }commandargs是 stdio 传输的启动方式客户端会 fork 这个进程通过 stdin/stdout 收发 JSON-RPC 消息。env里加PYTHONUNBUFFERED是为了日志能实时刷出来调试时很关键。3.4 一个最小 MCP Server为了让对比能跑起来写一个只暴露一个工具的 Server# mcp_servers/weather_server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(weather) mcp.tool() def get_weather(city: str) - dict: 查询指定城市的天气。 # 真实项目里这里调外部 API实验用固定值即可 return {city: city, temperature: 26, condition: sunny} if __name__ __main__: mcp.run(transportstdio)注意mcp.tool()装饰后函数签名和 docstring 会被自动转成工具的 JSON Schema这就是 MCP 的tools/list返回的内容。LangChain 那边tool装饰器做的是同一件事区别在于一个注册到本地列表一个注册到协议端点。4. 两条链路的代码与验证请求4.1 LangChain 链路本地函数注册# langchain_agent.py import tomllib from langchain_openai import ChatOpenAI from langchain_core.tools import tool from langchain.agents import create_tool_calling_agent, AgentExecutor from langchain_core.prompts import ChatPromptTemplate with open(config.toml, rb) as f: cfg tomllib.load(f) llm ChatOpenAI( base_urlcfg[llm][base_url], api_keycfg[llm][api_key], modelcfg[llm][model], temperaturecfg[llm][temperature], ) tool def get_weather(city: str) - dict: 查询指定城市的天气。 return {city: city, temperature: 26, condition: sunny} prompt ChatPromptTemplate.from_messages([ (system, 你可以调用工具回答用户问题。), (human, {input}), (placeholder, {agent_scratchpad}), ]) agent create_tool_calling_agent(llm, [get_weather], prompt) executor AgentExecutor(agentagent, tools[get_weather], verboseTrue) result executor.invoke({input: 北京天气如何}) print(result[output])跑起来后verboseTrue会打印出模型返回的 tool_calls 结构形如{name: get_weather, args: {city: 北京}, id: call_xxx}。LangChain 拿到这个结构后直接在本地查tools列表找到同名函数执行把结果塞回消息历史再让模型生成最终回答。4.2 MCP 链路协议发现与调用# mcp_client.py import asyncio, json, tomllib from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from openai import OpenAI with open(config.toml, rb) as f: cfg tomllib.load(f) client OpenAI( base_urlcfg[llm][base_url], api_keycfg[llm][api_key], ) async def main(): params StdioServerParameters( commandpython, args[mcp_servers/weather_server.py], ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools_resp await session.list_tools() print(发现工具:, [t.name for t in tools_resp.tools]) # 把 MCP 工具转成 OpenAI tools 格式 openai_tools [{ type: function, function: { name: t.name, description: t.description, parameters: t.inputSchema, }, } for t in tools_resp.tools] resp client.chat.completions.create( modelcfg[llm][model], messages[{role: user, content: 北京天气如何}], toolsopenai_tools, ) call resp.choices[0].message.tool_calls[0] print(模型请求调用:, call.function.name, call.function.arguments) result await session.call_tool( call.function.name, json.loads(call.function.arguments), ) print(MCP 返回:, result.content[0].text) asyncio.run(main())4.3 请求结构对比把两条链路的实际请求抓出来看差异集中在工具描述的来源和调用的路由上对比项LangChainMCP工具描述来源代码内tool装饰器tools/list协议响应模型请求体相同都是 OpenAI tools 格式相同由 MCP schema 转换而来调用路由本地字典按 name 查找JSON-RPCtools/call发往 Server执行位置当前进程独立进程/远程服务返回结构函数返回值直接入消息JSON-RPC result 再解析模型看到的 tools 数组几乎一样因为 MCP 的inputSchema本身就是 JSON Schema转成 OpenAI 格式是无损的。真正的分水岭在模型返回 tool_calls 之后LangChain 是一次本地函数调用MCP 是一次跨进程的协议往返。4.4 验证动作与预期结果跑langchain_agent.py你应该看到 verbose 输出里先出现 tool_calls然后get_weather被本地执行最后模型整合出「北京今天晴26 度」。跑mcp_client.py你应该先看到「发现工具: [get_weather]」这是tools/list的结果然后「模型请求调用: get_weather {city: 北京}」最后「MCP 返回: {...}」这是tools/call的 JSON-RPC 响应体。两条链路最终都能回答天气问题但 MCP 多了一次initialize握手和一次tools/list发现。这就是协议化带来的固定开销也是它换来动态扩展能力的代价。5. 本篇常见错排查5.1 base_url 写错导致 404最常见的报错是Error code: 404多半是base_url写成了https://taotoken.net/api/v1。OpenAI SDK 会自己拼/v1/chat/completions你只需要给到https://taotoken.net/api。LangChain 的ChatOpenAI同理。5.2 MCP Server 启动超时如果stdio_client卡住不动先单独在终端跑python mcp_servers/weather_server.py确认它能正常启动不报 ImportError。mcp包需要单独安装pip install mcp。另外PYTHONUNBUFFERED1没设的话日志可能被缓冲住看起来像卡死。5.3 模型不返回 tool_calls如果模型直接回答了「北京天气晴」而没有走工具说明模型没被触发工具调用。检查两点一是模型本身是否支持 tool calling二是 tools 数组是否真的传进去了。可以在模型对话页手动构造一次带 tools 的请求验证地址https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite。5.4 MCP 工具 schema 转换失败t.inputSchema如果缺type: object或properties转成 OpenAI 格式时会被拒。FastMCP 一般会自动生成合规 schema但如果你手写 Server 或用其他语言实现要确保返回的是标准 JSON Schema。报错通常长这样Invalid schema for function get_weather。5.5 两条链路结果不一致如果 LangChain 能答、MCP 报Tool not found检查call_tool传的 name 是否和tools/list返回的完全一致大小写敏感。另一个坑是参数类型模型可能把city传成数字MCP Server 侧如果做了严格类型校验会直接拒绝而 LangChain 的本地函数可能因为 Python 动态类型蒙混过关。这种差异恰恰说明协议化调用对 schema 的约束更硬。6. 把统一 Key 用在长期编码与 Agent 项目里两条链路跑通后你会发现真正影响日常开发效率的不是单次调用而是反复调试工具 schema、切换模型、管理多个 Key 的琐碎成本。TaoToken 在这里的价值是把模型出口收敛成一个LangChain 和 MCP 都指向同一个 base_url换模型只改config.toml一行。如果你打算把这种对比架构用到长期编码或 Agent 项目里可以看下 Coding Plan地址是https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite。它适合需要持续跑 Agent、频繁调工具的场景省去每次实验都重新配 Key 的麻烦。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有 OpenAI 兼容接口的完整参数说明包括 tools 字段的格式要求。API Key 管理页是https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite建议给实验项目单独建 Key方便按项目看用量。最后留一个实操建议把config.toml里的model做成环境变量覆盖这样同一份代码可以在不同模型间快速切换对比工具调用行为时特别有用。MCP 的settings.json也可以按环境拆成settings.dev.json和settings.prod.json本地用 stdio线上换 SSE客户端代码几乎不用改。
返回列表