1. 为什么你的第一个 AI Agent 工具服务卡在了“工具接不进去”MCPModel Context Protocol说白了就是 AI 模型和外部工具之间的“USB 接口”。以前你写一个 AI Agent想让它查天气、读数据库、调内部接口每个模型厂商都得自己写一套 Function Calling 的适配层现在有了 MCP 这个开放协议工具服务写一次Claude、支持 MCP 的客户端都能直接挂载调用。这篇要交付的东西很具体用 Python 从零写一个可被 Claude 调用的 MCP Server把模型请求统一走 TaoToken 的 Key/API 通道最后给你一份能直接复制的config.toml和settings.json骨架跑通一次真实的工具调用。适合谁看如果你已经会一点 Python听过 MCP 但没真正跑起来过或者你手上有一堆内部 API 想包成 Agent 工具这篇就是给你写的。整个过程我建议你跟着敲不要只复制因为 MCP 的坑基本都在配置路径和启动命令上光看是看不出来的。先说清楚一个容易混的点MCP Server 本身不负责“思考”它只负责暴露工具Tool、资源Resource、提示模板Prompt这三类能力。真正决定“什么时候调用哪个工具”的是模型侧也就是 Claude 这类客户端。所以你的 Server 写得再花哨如果客户端配置里没挂上模型根本看不见它。这也是为什么很多人写完server.py一运行终端啥也不输出以为写错了——其实 stdio 模式下它就是在等客户端通过标准输入发消息不是给你打印日志用的。我试过最省事的路径是Python 3.12 uv 管理依赖 FastMCP 装饰器写法。uv 比 pip 快很多而且uv run能直接带着虚拟环境跑脚本省掉激活环境的步骤。下面所有命令你都可以直接粘。2. 用 TaoToken 统一 Key 接入 MCP 工具服务的前置准备在写代码之前先把“模型通道”这件事定下来。MCP Server 是工具侧但你要验证工具能不能被调用总得有个模型客户端去发起请求。这里用 TaoToken 做统一入口的好处是一个 Key 走通模型对话和后续的 Agent 调用不用在多个平台之间来回切配置。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。你需要准备三样东西第一Python 3.10 以上推荐 3.12。低版本在async和类型注解上会有些别扭。第二uv 包管理器。安装命令分平台# macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows PowerShell powershell -c irm https://astral.sh/uv/install.ps1 | iex第三一个 TaoToken 的 API Key。去控制台创建路径是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Key 的管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。拿到之后先别急着写进代码用环境变量存后面配置里引用变量名避免 Key 硬编码进 Git。初始化项目uv init mcp-server-demo cd mcp-server-demo uv add mcp anthropic这里mcp是官方 SDKanthropic包在你需要写一个“模型侧客户端”做本地验证时会用到。如果你只打算用 Claude Desktop 挂载anthropic可以先不加但建议留着方便后面写自动化测试。关于模型 IDTaoToken 通道里常用的对话模型你可以先在模型对话页确认一下当前可用的名称地址是 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。配置里我会用占位符your-model-id你替换成实际值即可。这一步别跳过模型 ID 写错是最常见的 404 来源。3. 可复制的 config.toml 与 settings.json 配置骨架这一节是全文最该收藏的部分。MCP 的配置分散在两个地方一个是客户端挂载 Server 的配置Claude Desktop 用claude_desktop_config.json很多 CLI 工具用config.toml另一个是模型通道的配置常见于settings.json或auth.json。我把两套骨架都给你路径和字段名保持和实际一致。先写server.py这是工具服务的核心# server.py from mcp.server.fastmcp import FastMCP mcp FastMCP(weather-server) mcp.tool() async def get_weather(city: str) - str: 获取指定城市的天气信息 mock_data { 北京: 晴28°C湿度 45%, 上海: 多云26°C湿度 65%, 深圳: 雷阵雨30°C湿度 80%, } return mock_data.get(city, f{city}暂无数据) mcp.tool() async def list_cities() - str: 返回支持查询的城市列表 return 北京、上海、深圳 if __name__ __main__: mcp.run(transportstdio)mcp.tool()装饰器会自动把函数注册成工具函数的 docstring 就是工具描述模型靠这段描述判断什么时候调用。所以 docstring 别写废话写清楚“这个工具干什么、参数是什么”。然后是config.toml用于支持 TOML 配置的 MCP 客户端# config.toml [mcp_servers.weather] command uv args [run, python, server.py] cwd /Users/yourname/projects/mcp-server-demo env { TAOTOKEN_API_KEY ${TAOTOKEN_API_KEY} } [model] base_url https://taotoken.net/api api_key ${TAOTOKEN_API_KEY} model_id your-model-id注意cwd必须写绝对路径相对路径在客户端启动子进程时经常解析失败这是踩过的坑里排前三的。env里引用环境变量别把 Key 明文写进去。再给一份settings.json适合 Claude Code 这类用 JSON 配置的工具{ mcpServers: { weather: { command: uv, args: [run, python, server.py], cwd: /Users/yourname/projects/mcp-server-demo, env: { TAOTOKEN_API_KEY: ${TAOTOKEN_API_KEY} } } }, model: { baseUrl: https://taotoken.net/api, apiKey: ${TAOTOKEN_API_KEY}, modelId: your-model-id } }如果你用的是 Claude Desktop配置文件路径是# macOS ~/Library/Application Support/Claude/claude_desktop_config.json # Windows %APPDATA%\Claude\claude_desktop_config.json把上面settings.json里mcpServers那段贴进去即可。三件套记住Base URL 填https://taotoken.net/apiKey 填你的 TaoToken KeyModel ID 填实际模型名。这三样缺一个模型侧就调不通。4. 启动 MCP 服务并验证一次真实工具调用配置写完先单独验证 Server 能不能起来。在项目目录执行uv run python server.pystdio 模式下它不会打印任何东西光标停住就是正常。如果你看到报错多半是mcp包没装好或者 Python 版本太低。按CtrlC退出。接着做一次本地调用验证。写一个client_test.py用官方 SDK 以 stdio 方式连上你的 Server列出工具并调用一次# client_test.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): params StdioServerParameters( commanduv, args[run, python, server.py], ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() print(可用工具, [t.name for t in tools.tools]) result await session.call_tool(get_weather, {city: 北京}) print(调用结果, result.content[0].text) asyncio.run(main())运行uv run python client_test.py正常输出应该是可用工具 [get_weather, list_cities] 调用结果 晴28°C湿度 45%看到这两行说明你的 MCP Server 已经能被标准客户端发现并调用了。这一步是整个教程的分水岭——工具侧通了剩下的就是把模型侧接上。模型侧验证用 TaoToken 通道发一次请求确认 Key 和模型 ID 没问题。你可以直接在模型对话页 https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 里问一句“北京天气怎么样”前提是客户端已经挂载了你的 weather Server。如果模型回复里出现了“晴28°C”说明从模型到工具再到返回的整条链路打通了。如果你更想用命令行验证可以用curl直接打 APIcurl https://taotoken.net/api/v1/messages \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -H content-type: application/json \ -d { model: your-model-id, max_tokens: 256, messages: [{role: user, content: 你好}] }返回里有content字段就说明通道正常。这一步和 MCP 无关但它是排查“到底是工具问题还是模型通道问题”的关键分界线。5. 本篇常见报错排查401、local proxy failed、reading choices、OAuth配置跑不通的时候报错信息往往很含糊。我把几个高频错误和对应原因列出来你对着改。401 UnauthorizedKey 没传进去或者传了但格式不对。检查三处环境变量TAOTOKEN_API_KEY是否在当前 shell 里export过config.toml/settings.json里引用变量名的写法是否正确${VAR}和$VAR在不同客户端里支持度不一样不确定就写死测试一次请求头字段名对不对Anthropic 风格是x-api-keyOpenAI 风格是Authorization: Bearer。TaoToken 的 API 基址是https://taotoken.net/api别多加/v1也别少加路径拼错也会返回 401 或 404。local proxy failed / connection refused客户端启动 MCP Server 子进程失败。九成是cwd路径不对或者command找不到。uv如果不在系统 PATH 里客户端就起不来。解决办法是把command换成uv的绝对路径用which uv查出来填进去。另外 Windows 上路径反斜杠要转义建议统一用正斜杠。reading choices of undefined这是 OpenAI 兼容格式的响应解析错误通常出现在你把 Anthropic 格式的响应喂给了期望 OpenAI 格式的客户端或者反过来。检查你的客户端到底走哪种协议。TaoToken 的/api基址下Anthropic 风格走/v1/messagesOpenAI 风格走/v1/chat/completions别混用。OAuth 相关报错如果你用的是 Claude Code 这类带 OAuth 登录的工具它可能优先走官方登录态而不是你的 API Key。这时候要在配置里显式指定apiKey和baseUrl覆盖掉默认的 OAuth 流程。Claude Code 的配置里如果同时存在 OAuth token 和 API Key行为取决于版本建议清掉旧的登录缓存再试。工具列表为空客户端连上了 Server但list_tools返回空。检查mcp.tool()装饰器有没有漏写函数是不是async以及mcp.run()的 transport 是不是和客户端一致stdio 对 stdio。还有一点Server 启动时如果有 import 错误进程会直接退出客户端那边表现就是“连上了但没工具”实际去看 Server 的 stderr 才能看到真实报错。排查顺序建议固定成先uv run python server.py确认 Server 能起再client_test.py确认工具能列能调最后才去查模型侧配置。这样能把问题范围一步步缩小不至于一上来就怀疑 Key。6. 把工具服务接进长期编码流下一步怎么走跑通第一个工具之后你大概率会想加更多工具读本地文件、查数据库、调内部 HTTP 接口。MCP 的扩展方式很直接继续用mcp.tool()往server.py里加函数就行每个函数一个职责docstring 写清楚参数含义。Resource 和 Prompt 也可以加上mcp.resource(config://app) async def get_config(): 返回应用配置信息 return {version: 1.0, debug: False} mcp.prompt() async def weather_report(city: str) - str: 生成天气报告的提示模板 return f请根据以下信息为 {city} 生成一份简洁的天气报告包含出行建议。Resource 让模型能读你的数据Prompt 让你能定义标准化的交互模板。这两个不是必须的但加上之后 Agent 的能力边界会宽很多。如果你打算把 MCP 工具服务用在日常编码和 Agent 工作流里长期跑的话建议了解一下 Coding Plan地址是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 它更适合持续性的编码场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 遇到配置字段不确定的时候直接查文档比猜快。最后一个实用建议把server.py里的 mock 数据换成真实 API 调用时记得加超时和异常捕获。MCP 工具调用是同步等待的你的工具卡住模型侧也会卡住。给每个外部请求设 5 到 10 秒超时失败时返回明确的错误字符串而不是抛异常这样模型能根据错误信息决定要不要重试或换工具。这个细节在 demo 阶段无所谓但一旦上生产就是稳定性的分水岭。