1. 为什么你的智能体需要一个 MCP
很多人搭 AI 智能体时都会卡在同一个地方:模型本身很聪明,但它只能聊天,碰不到真实世界的数据。你想让它查一下 GitHub 仓库的 issue、读一下本地文件、调一下内部接口,就得自己写一堆函数,再手动塞进 prompt 里,工具一多就乱成一锅粥。
MCP(Model Context Protocol,模型上下文协议)就是来解决这个问题的。它把「外部系统能干什么」标准化成一个个 Tool,让大模型用统一的方式去发现和调用。你可以把它理解成智能体世界的 USB-C 接口:以前每个设备一个专用口,现在统一成一个标准,插上就能用。
这篇文章面向已经会写 Python、跑过 LangChain 或类似框架,但还没真正跑通过一个 MCP Server 的同学。我会先拆清楚 Host / Client / Server 三方到底谁在干什么,再带你手写一个最小可运行的本地 MCP Server,用 TaoToken 的统一 Key 接上模型,最后验证工具列表能不能被拉出来、调用能不能回显。全程可复制,踩坑点我也会标出来。
2. MCP 的通信模型:Host、Client、Server 到底谁是谁
刚接触 MCP 最容易懵的就是这三个角色。我用一个生活化的类比帮你定住:
Host 是「宿主应用」,也就是你最终在用的那个智能体程序,比如一个命令行助手、一个 IDE 插件、一个聊天机器人。它负责跟用户交互,决定什么时候该调工具。
Client 是「能力接入层」,它住在 Host 里面,专门负责跟各个 MCP Server 建立连接、拉取工具列表、把工具翻译成 Host 能用的格式(比如 LangChain Tool)。一个 Host 可以同时挂多个 Client,每个 Client 连一个 Server。
Server 是「工具提供方」,它把某个外部系统(GitHub、文件系统、数据库、你自己的业务接口)封装成标准 Tool 对外暴露。Server 不关心谁在调它,只负责按协议响应「你有哪些工具」和「帮我执行这个工具」。
一次完整的工具调用流程是这样的:用户提问 → Host 把问题交给模型 → 模型决定要用某个工具 → Host 通过 Client 找到对应 Server → Client 发请求给 Server → Server 执行真实操作并返回结果 → 结果回灌给模型 → 模型生成最终回答。
这里有个关键点:模型本身不直接连 Server,它只是「决定调哪个工具、传什么参数」。真正的连接和执行由 Client 和 Server 完成。这个分层设计的好处是,你换模型、换 Host 都不用动 Server,工具生态可以复用。
MCP Server 的获取方式主要有四种,我列个表方便你选:
| 方式 | 适用场景 | 部署成本 | 稳定性 |
|---|---|---|---|
| 远程托管服务 | 通用工具,想零部署 | 最低 | 依赖服务商 |
| 包管理器一键安装 | npm/pip/go 生态的通用工具 | 低 | 高 |
| Docker 容器运行 | 需要环境隔离 | 中 | 很高 |
| 源码克隆 + 编译 | 二次开发、私有工具 | 高 | 自己掌控 |
新手建议从包管理器一键安装起步,跑通流程后再考虑 Docker 或自研。
3. 前置准备:用 TaoToken 统一 Key 管住所有模型调用
在写 Server 之前,先把模型接入这块理顺。智能体开发最烦的事情之一就是:今天用这个模型、明天换那个模型,Key 和 base_url 到处散落,改一处漏一处。
我的做法是用 TaoToken 做统一入口。它提供一个兼容 OpenAI 协议的 API 地址,你只要把 base_url 指过去,用同一个 Key 就能切换不同模型,代码里不用改来改去。
先拿到你的 Key:打开 https://taotoken.net/api-keys ,登录后创建一个 API Key,复制保存好。这个 Key 就是你后面所有模型调用的凭证。
然后在项目里配置环境变量,别把 Key 硬编码进代码:
export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"如果你用的是 Python,装好依赖:
pip install langchain langchain-openai langchain-mcp-adapters mcp这里langchain-mcp-adapters是关键,它负责把 MCP Server 暴露的工具转成 LangChain Tool,省得你自己写适配层。mcp是官方 SDK,写 Server 要用。
注意:base_url 结尾不要多加
/v1,TaoToken 的兼容层已经处理好了路径,多写反而会 404。这个坑我见过不少人踩。
配置这块建议单独放一个config.toml,把模型参数和 MCP Server 配置分开管理,后面换模型只改这一处:
[llm] model = "gpt-4o-mini" base_url = "https://taotoken.net/api" api_key_env = "TAOTOKEN_API_KEY" temperature = 0.2 [mcp.servers.github] transport = "stdio" command = "npx" args = ["-y", "@modelcontextprotocol/server-github"] enabled = true description = "GitHub MCP Server for repository operations"transport = "stdio"表示用标准输入输出跟 Server 通信,这是本地 Server 最常用的方式。command和args就是启动这个 Server 的命令,跟你在终端里敲的一样。
4. 手写一个最小 MCP Server 并接入智能体
现在进入正题。我先带你写一个自己的本地 MCP Server,功能很简单:提供一个「查天气」的工具,返回假数据,目的是让你看清 Server 的结构。跑通之后你换成真实接口就行。
新建weather_server.py:
from mcp.server.fastmcp import FastMCP mcp = FastMCP("weather-server") @mcp.tool() def get_weather(city: str) -> str: """查询指定城市的天气。city 是城市名,比如 Beijing。""" fake_data = { "Beijing": "晴,18°C", "Shanghai": "多云,22°C", "Shenzhen": "小雨,26°C", } return fake_data.get(city, f"{city} 暂无数据") if __name__ == "__main__": mcp.run(transport="stdio")就这么几行。FastMCP是官方 SDK 提供的高层封装,@mcp.tool()装饰器把一个普通函数注册成 MCP 工具,函数的 docstring 会自动变成工具描述,模型就是靠这个描述判断什么时候该调它。所以 docstring 一定要写清楚用途和参数含义,别偷懒。
启动这个 Server:
python weather_server.py它不会打印什么,因为它在等 stdio 输入。这说明 Server 起来了,正常。
接下来写 Client 端,把 Server 的工具拉出来并注入智能体。新建agent.py:
import asyncio import os from langchain_openai import ChatOpenAI from langchain_mcp_adapters.client import MultiServerMCPClient from langgraph.prebuilt import create_react_agent SERVERS = { "weather": { "transport": "stdio", "command": "python", "args": ["weather_server.py"], } } async def build_agent(): client = MultiServerMCPClient(SERVERS) tools = await client.get_tools() print(f"loaded {len(tools)} mcp tools") for t in tools: print(f" - {t.name}: {t.description}") llm = ChatOpenAI( model="gpt-4o-mini", api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"], temperature=0.2, ) agent = create_react_agent(llm, tools) return agent async def main(): agent = await build_agent() resp = await agent.ainvoke( {"messages": [{"role": "user", "content": "深圳今天天气怎么样?"}]} ) print(resp["messages"][-1].content) if __name__ == "__main__": asyncio.run(main())这里有几个设计点值得说。MultiServerMCPClient接收一个字典,key 是 Server 名字,value 是启动配置,跟前面config.toml里的结构一致。await client.get_tools()是异步的,它会把所有 Server 的工具拉下来并转成 LangChain Tool。
注意build_agent是 async 函数,因为拉工具列表是异步操作。Python 的__init__不支持异步,所以初始化逻辑必须放在异步类方法或异步函数里,这是很多人第一次写会卡住的地方。
5. 验证:工具列表和调用回显
跑起来看看:
python agent.py你应该先看到工具列表输出:
loaded 1 mcp tools - get_weather: 查询指定城市的天气。city 是城市名,比如 Beijing。然后模型会决定调用get_weather,传入city="Shenzhen",Server 返回「小雨,26°C」,最终输出类似:
深圳今天是小雨,气温 26°C。看到这个回显,说明整条链路通了:模型 → Client → Server → 工具执行 → 结果回灌 → 模型总结。你可以把get_weather里的假数据换成真实天气 API,或者再加几个工具,比如查时间、读文件,验证多工具场景。
如果你想更直观地调试工具调用过程,可以在ainvoke后打印完整消息链:
for msg in resp["messages"]: print(type(msg).__name__, getattr(msg, "content", ""))这样你能看到 AIMessage 里带的 tool_calls,确认模型确实选了正确的工具和参数。
6. 本篇常见报错排查
报错一:ModuleNotFoundError: No module named 'mcp'说明 SDK 没装。跑pip install mcp。如果你用的是虚拟环境,确认装在了当前环境里。
报错二:npx: command not found这是用 GitHub 官方 Server 时会遇到的,需要 Node.js 环境。装好 Node 后npx就有了。如果你不想装 Node,就先用我上面那个 Python 写的 weather Server 练手。
报错三:工具列表是空的,loaded 0 mcp tools最常见的原因是 Server 启动命令写错了,或者args里的路径不对。MultiServerMCPClient启动 Server 失败时不一定报错,只是拉不到工具。你可以先在终端手动敲一遍command + args,确认 Server 能起来。
报错四:401 Unauthorized或模型调用失败检查TAOTOKEN_API_KEY环境变量有没有正确导出,base_url是不是https://taotoken.net/api。如果你在代码里直接读os.environ,确认运行脚本的终端里export过。
报错五:RuntimeError: no running event loop说明你在同步上下文里调了异步函数。get_tools()和ainvoke()都是异步的,必须放在async def里用await,最外层用asyncio.run()包起来。
报错六:工具被调用了但参数不对多半是 docstring 写得太模糊,模型猜错了参数含义。把参数说明写具体,比如「city 是城市英文名,首字母大写」,模型准确率会明显提升。
排障时如果怀疑是 Key 或接入配置的问题,可以直接去 https://taotoken.net/api-keys 重新生成一个 Key 对比测试,排除凭证因素。接入细节和协议兼容性可以查 https://taotoken.net/doc 。
跑通这个最小 Server 之后,下一步就是把它换成真实工具,比如接 GitHub 做仓库操作、接本地文件系统做读写。工具多了之后,建议用 Coding Plan 来管理长期的编码和 Agent 任务,把模型调用和工具编排统一起来,省得每个项目重复配一遍。