
1. 为什么我要把 LangChain 和 FastMCP 拼在一起大模型本身只会“说话”不会查天气、读时间、算日期。想让它真正干活就得给它挂上外部工具。问题在于每接一个工具就写一套适配代码工具一多项目里全是胶水逻辑换框架就得重写。MCPModel Context Protocol模型上下文协议就是来解决这件事的它把“工具、资源、提示词”三类能力用统一协议描述出来客户端按协议发现和调用服务端按协议暴露能力双方解耦。FastMCP 是 Python 里写 MCP 服务端最省事的框架几行装饰器就能把普通函数变成可被大模型调用的工具。LangChain 这边用langchain-mcp-adapters做桥接把 MCP 服务端的工具直接转成 LangChain 的 Tool 对象塞进 Agent 就能用。这套组合适合谁适合正在做 Agent、想让模型调用本地或远程能力、又不想被某一家框架绑死的开发者。这篇我按“能跑通”的标准来写先讲清楚场景和前置准备再给可复制的配置骨架然后是两个 MCP 服务端的完整代码接着用 LangChain 客户端把工具、资源、提示词都加载一遍最后跑一次真实的工具调用链路并把我踩过的坑列出来。模型通道这块我用 TaoToken 统一 Key 来配省得在多个模型供应商之间来回切。2. TaoToken 前置统一 Key 与 API 通道准备在写代码之前先把模型通道准备好。我这次的做法是MCP 服务端只负责暴露工具模型调用统一走 TaoToken 的 API 通道这样换模型只改一个 base_url 和 key不用动业务代码。TaoToken 的定位是统一的大模型 API 接入层你拿到一个 Key就能通过兼容 OpenAI 的接口去调不同模型。对 LangChain 来说这意味着ChatOpenAI里的base_url指向 TaoToken 的 API 地址api_key填你申请到的 Key其余代码不用改。具体操作路径打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册账号进控制台 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite 创建 API KeyKey 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 生成后复制保存页面只显示一次接口文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 对接细节以文档为准API 基础地址是 https://taotoken.net/api 注意这个地址不带 UTM 参数直接用于代码里的base_url。注意Key 不要写死在提交到 Git 的代码里用环境变量或本地配置文件读取。下面示例里我用os.environ读取你本地跑的时候先export一下。如果你只是想先验证模型通不通可以直接用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息试试确认 Key 有效再进代码环节。长期做编码类 Agent 的话可以看下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 按套餐走比单次调用省心。3. 可复制配置config.toml 与 settings.json 骨架我习惯把“服务端端口、模型通道、客户端连接”三类配置分开避免代码里到处是魔法字符串。下面给两份骨架直接复制改值即可。3.1 config.tomlMCP 服务端与模型通道# config.toml # MCP 服务端配置 [server.weather] name WeatherMCP host 0.0.0.0 port 8001 path /mcp transport streamable-http [server.datetime] name SystemDateTimeMCP host 0.0.0.0 port 8002 path /mcp transport streamable-http # 模型通道配置走 TaoToken 统一 Key [llm] base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY model gpt-4o-mini temperature 0.1 max_tokens 1024 # 客户端要连接的 MCP 服务列表 [client.servers.weather] transport http url http://127.0.0.1:8001/mcp [client.servers.datetime] transport http url http://127.0.0.1:8002/mcp3.2 settings.json客户端运行时参数{ mcp_client: { servers: { weather: { transport: http, url: http://127.0.0.1:8001/mcp }, datetime: { transport: http, url: http://127.0.0.1:8002/mcp } }, timeout_seconds: 30, retry: 2 }, agent: { system_prompt: 你是工具调用Agent。需要外部信息时必须输出结构化 tool_calls禁止编造数据拿到工具结果后再整理成自然语言回答。, thread_id: mcp-chat-001 } }这两份配置的作用config.toml管服务端怎么起、模型走哪个通道settings.json管客户端连哪些服务、Agent 的行为约束。实际项目里你可以只留一份我这里分开是为了演示“服务端与客户端配置解耦”。3.3 依赖安装pip install -i https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple \ fastmcp langchain-mcp-adapters langchain langchain-openai langgraph装完可以pip list确认版本fastmcp、langchain-mcp-adapters、langchain-openai三个是核心缺一个后面都跑不起来。4. 服务端用 FastMCP 暴露工具、资源、提示词MCP 把服务端能力分成三类Tools可执行函数、Resources静态只读数据、Prompts提示词模板。我建两个服务端一个管天气一个管时间分别占 8001 和 8002 端口用 Streamable HTTP 传输。这个模式适合远程部署比 Stdio 更适合生产。4.1 WeatherMCP天气查询服务# WeatherMCP.py import json from typing import Optional from fastmcp import FastMCP mcp FastMCP(WeatherMCP) mcp.tool() def get_temperature(city: str) - str: 获取指定城市的模拟实时温度 Args: city: 中文城市名必填 return f{city} 当前温度 26℃ mcp.tool() def get_humidity(city: str) - str: 获取指定城市模拟湿度 return f{city} 当前相对湿度 62% mcp.tool() def get_weather_condition(city: str) - str: 获取城市天气状况晴/多云/小雨/大雨 return f{city} 天气多云转晴 mcp.tool() def calculate_wind(speed: float, direction: Optional[str] 东) - str: 模拟风力计算 Args: speed: 风速 m/s direction: 风向默认东 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_asciiFalse, indent2) mcp.resource(file://weather/disclaimer) def weather_disclaimer() - str: 免责声明文本 return [免责声明] 本服务天气数据均为模拟演示数据仅供开发调试学习使用。 mcp.prompt() def weather_ask(city: str) - str: 生成天气查询提示词模板 return f请帮我查询 {city} 的完整天气包含温度、湿度、风向整理成一段通顺自然的中文报告。 if __name__ __main__: mcp.run(transportstreamable-http, host0.0.0.0, port8001, path/mcp)4.2 SystemDateTimeMCP时间服务# SystemDateTimeMCP.py import json import time from datetime import datetime, timedelta from typing import Optional from fastmcp import FastMCP mcp FastMCP(SystemDateTimeMCP) mcp.tool() def get_current_datetime(timezone_offset: Optional[int] None) - str: 获取当前系统日期时间可指定时区偏移小时数 if timezone_offset is not None: now datetime.utcnow() now now.replace(hournow.hour timezone_offset) else: now datetime.now() weekday_map {0: 星期一, 1: 星期二, 2: 星期三, 3: 星期四, 4: 星期五, 5: 星期六, 6: 星期日} return f{now.strftime(%Y-%m-%d %H:%M:%S)} {weekday_map[now.weekday()]} mcp.tool() def get_timestamp(ms: bool False) - int: 获取 Unix 时间戳msTrue 返回毫秒 return int(time.time() * 1000) if ms else int(time.time()) mcp.tool() def format_timestamp(timestamp: int, ms: bool False) - str: 将时间戳转换为可读日期时间字符串 ts timestamp / 1000 if ms else timestamp return datetime.fromtimestamp(ts).strftime(%Y-%m-%d %H:%M:%S) mcp.tool() def date_calc(base_date_str: str, days: int) - str: 日期加减计算输入 yyyy-MM-dd 格式日期增减 N 天 base datetime.strptime(base_date_str, %Y-%m-%d) return (base timedelta(daysdays)).strftime(%Y-%m-%d) mcp.resource(config://datetime/service_meta) def datetime_service_meta() - str: 时间服务元信息 meta { service_name: SystemDateTimeMCP, version: 1.0.0, note: 读取运行服务机器的本地系统时间 } return json.dumps(meta, ensure_asciiFalse, indent2) mcp.prompt() def prompt_now_info() - str: 获取当前完整时间信息提示词 return 请调用工具获取当前系统完整时间包含可读日期时间、时间戳秒与毫秒整理成清晰中文报告。 if __name__ __main__: mcp.run(transportstreamable-http, host0.0.0.0, port8002, path/mcp)两个脚本分别跑起来看到Uvicorn running on http://0.0.0.0:8001和8002就说明服务端监听成功。python WeatherMCP.py python SystemDateTimeMCP.py5. 客户端LangChain 加载工具、资源、提示词服务端起好了接下来用langchain-mcp-adapters把能力拉过来。这一步是整个链路的关键工具能不能被 Agent 用上全看这里加载对不对。5.1 加载工具并打印参数约束# load_tools.py 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}, datetime: {transport: http, url: http://127.0.0.1:8002/mcp}, }) tools await client.get_tools() for idx, tool in enumerate(tools, 1): print(f[{idx}] {tool.name} - {tool.description}) schema tool.args_schema required set(schema.get(required, [])) for name, info in schema.get(properties, {}).items(): flag 必填 if name in required else 可选 print(f {name} [{info.get(type)}] {flag}) print(f总计工具数量{len(tools)}) if __name__ __main__: asyncio.run(main())跑完应该看到 9 个工具天气 4 个 时间 5 个每个工具的参数类型和必填规则都打印出来。这份输出就是大模型决定“调哪个工具、填什么参数”的依据。5.2 读取静态资源# load_resources.py import asyncio import json from langchain_mcp_adapters.client import MultiServerMCPClient async def main(): client MultiServerMCPClient({ weather: {transport: http, url: http://127.0.0.1:8001/mcp}, }) blobs await client.get_resources(weather) for blob in blobs: uri str(blob.metadata[uri]) try: content json.loads(blob.data) except json.JSONDecodeError: content blob.data print(fURI: {uri}\n{content}\n) if __name__ __main__: asyncio.run(main())资源是只读的适合放服务元信息、免责声明、常量表这类东西。大模型可以直接引用不用你硬编码进提示词。5.3 渲染提示词模板# load_prompts.py import asyncio from langchain_mcp_adapters.client import MultiServerMCPClient from langchain_mcp_adapters.prompts import load_mcp_prompt async def main(): client MultiServerMCPClient({ weather: {transport: http, url: http://127.0.0.1:8001/mcp}, }) 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())输出会是一条HumanMessage内容就是模板填充后的完整提问。提示词放服务端统一管理客户端只传参数格式不会乱。6. 验证请求一次完整的工具调用链路前面都是“加载”现在跑一次真正的调用。用 TaoToken 的通道配模型让 Agent 自己决定调哪个工具。# agent_run.py import asyncio import os 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 model ChatOpenAI( modelgpt-4o-mini, base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY], temperature0.1, max_tokens1024, ) 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加载工具数量{len(tools)}) agent create_agent(modelmodel, toolstools) resp await agent.ainvoke( {messages: 当前系统日期与时间是多少北京天气怎么样} ) for msg in resp[messages]: if isinstance(msg, HumanMessage): print(f[Human]: {msg.content}) elif isinstance(msg, AIMessage) and msg.content.strip(): print(f[AI]: {msg.content}) if __name__ __main__: asyncio.run(main())预期返回Agent 先调get_current_datetime拿到时间再调get_weather_condition拿到天气最后整合成一段话类似“当前时间是 2026-08-19 14:11:37 星期三北京天气多云转晴”。如果你看到工具调用日志里出现tool_calls并且ToolMessage里有返回内容说明整条链路通了。想更直观地验证模型通道本身可以先用模型对话页面 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一句“你好”确认 Key 和 base_url 没问题再回来跑 Agent。7. 本篇常见错排查报错一Connection refused或MCP服务连接失败先确认 8001、8002 两个服务端脚本在跑netstat -ano | findstr 8001Windows或lsof -i:8001macOS/Linux看端口有没有监听。客户端 URL 里的127.0.0.1别写成localhost某些环境解析会出问题。报错二工具数量是 0多半是MultiServerMCPClient的 key 名和get_resources/session里传的名字对不上。key 是你在客户端自定义的服务别名session(weather)里的weather必须和初始化时的 key 一致。报错三模型不调工具只在文本里说“我将调用…”这是模型能力问题。小参数模型比如 1.5B 级别经常只输出文本、不生成结构化tool_calls。解决办法是在 system prompt 里明确要求“必须输出 tool_calls禁止只在文本中说要调用工具”或者直接换 8B 以上的模型。我实测下来模型越大工具调用越稳。报错四多轮对话里上下文丢失比如第一轮查了时间第二轮问“那天气呢”模型忘了时间。这不是代码 bug是轻量模型记忆能力有限。生产环境建议用带 checkpointer 的create_agent加InMemorySaver并换更大模型。报错五401 Unauthorized检查TAOTOKEN_API_KEY环境变量有没有 export 成功echo $TAOTOKEN_API_KEY看有没有值。Key 复制时别带空格base_url用https://taotoken.net/api不要多加/v1之外的路径。报错六资源读取返回空get_resources传的是服务别名不是 URI。想按 URI 过滤用load_mcp_resources(session, uris[file://weather/disclaimer])这种写法。排障时如果怀疑是 Key 或通道问题去 API Keys 页面 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 重新生成一个 Key 试试或者对照接入文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 检查参数格式。8. 继续往下走把通道和 Agent 固定下来跑通一次调用只是起点。真正做项目时我建议把两件事固定下来一是模型通道统一走 TaoToken换模型只改model字段base_url和 Key 不动二是 MCP 服务端按能力拆分天气、时间、数据库各一个服务客户端按需连接别把所有工具塞一个服务里。如果你要长期跑编码类 Agent可以看下 Coding Plan https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 按套餐走比单次调用更可控。接入细节和参数说明以官方文档 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 为准遇到报错先查文档再排查代码。最后留一个我常用的调试习惯每次改完 MCP 服务端先单独跑load_tools.py确认工具数量和参数没变再跑 Agent。这样出问题时能快速定位是服务端没起好还是模型没调对。