1. 先搞清楚MCP Server 到底在解决什么问题如果你最近在折腾 Claude Code、Cursor 这类 AI 编程客户端大概率刷到过 MCP 这个词。MCP 全称 Model Context Protocol翻译过来叫模型上下文协议说白了就是一套让 AI 客户端和外部能力对话的标准化规则。你可以把它理解成 USB 接口以前每个外设都要配一根专属线现在统一成一种插口谁都能插。那 MCP Server 是什么它就是那个“外设”。你写一个 MCP Server对外暴露工具、资源、提示词模板AI 客户端通过标准协议来调用。Claude Code 本身只会聊天和读写它权限内的文件但接上你写的 MCP Server它就能查数据库、调内部接口、读业务文档。这篇要干的事很具体用 Python 从零写一个基于 Stdio 传输的 MCP Server然后把它接进 Claude Code让 Claude Code 通过统一的 Key/API 通道去调用这个外部能力。Stdio 的意思是标准输入输出服务跑在本地子进程里不需要开端口、不需要网络配置客户端和 Server 之间靠 stdin/stdout 传 JSON-RPC 消息。对本地开发来说这是最省事的方案。适合谁看写过一点 Python、装过 Claude Code、想搞明白 MCP 到底怎么跑通一条完整调用链的人。不需要你懂 JSON-RPC 底层细节SDK 都封装好了。跟着敲一遍你会对“AI 客户端怎么发现工具、怎么传参、怎么拿结果”这件事有肌肉记忆。我试过把整个链路拆成六步装依赖、写 Server、配客户端、验证连通、排错、再决定要不要上 Coding Plan 做长期编码。下面一步步来。2. 前置准备Python 环境与 TaoToken 通道动手之前先把地基打好。MCP Server 本身不依赖 TaoToken但你要让 Claude Code 通过统一 Key/API 通道调用模型就得先把 TaoToken 这边配好。两者是配合关系TaoToken 负责模型侧的接入MCP Server 负责能力侧的扩展。2.1 Python 与 MCP SDKPython 版本要求 3.10 及以上MCP 官方 Python SDK 最低就卡在这。Windows、macOS、Linux 都支持。装依赖一条命令pip install mcp如果你用虚拟环境先激活再装。装完可以验证一下python -c import mcp; print(mcp.__version__)能打印出版本号就说明 SDK 到位了。这个 SDK 帮你封装了 JSON-RPC 编解码、传输层、协议握手你只需要关心业务逻辑不用手写底层通信。2.2 TaoToken 侧的准备Claude Code 要调用模型需要配置 API 通道。TaoToken 提供统一的 Key 和 API 入口你可以在控制台创建 API Key然后把它配到 Claude Code 的环境变量或配置文件里。具体入口创建和管理 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档含 Claude Code 配置说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteAPI 基础地址https://taotoken.net/apiClaude Code 的接入方式在文档里有完整说明核心是把 base_url 指向 TaoToken 的 API 地址把 api_key 换成你创建的那把。配好之后 Claude Code 就能正常对话接下来才是接 MCP Server 的事。注意MCP Server 和模型通道是两条独立的链路。MCP Server 跑在本地负责提供工具模型通道负责让 Claude Code 能思考、能决定调哪个工具。两条都通了完整调用链才成立。2.3 项目结构新建一个文件夹结构极简weather-mcp-server/ ├── main.py # MCP 服务主程序 └── weather_doc.txt # 静态资源文件演示 Resources 组件业务数据我们纯本地模拟不调任何外部接口保证离线可用。预设北京、上海、广州、深圳四个城市的天气数据包含温度、天气状况、风力。3. 可复制配置从零写出 MCP Server 骨架这一节是重头戏。我会把完整代码贴出来然后逐块讲清楚每个组件在干嘛。MCP 标准里有六大核心组件传输层、Tools、Resources、Prompts、Roots、Logging。这个 Demo 全部实现不做简化。3.1 完整 main.pyfrom mcp.server import Server from mcp.server.stdio import StdioServerTransport from mcp.types import ( Tool, TextContent, Resource, ResourceTemplate, Prompt, PromptArgument, LoggingLevel ) import asyncio # 1. 初始化服务与模拟数据 app Server(local-weather-mcp-server) WEATHER_DATA { 北京: {temp: 22℃, status: 晴, wind: 微风2级}, 上海: {temp: 25℃, status: 多云, wind: 东风3级}, 广州: {temp: 30℃, status: 雷阵雨, wind: 南风4级}, 深圳: {temp: 29℃, status: 阴, wind: 无风} } DOC_FILE_PATH ./weather_doc.txt # 2. Roots 根目录权限声明 app.list_roots() async def list_roots(): return { roots: [ { uri: ffile://{__file__.rsplit(/, 1)[0]}, name: 天气服务根目录 } ] } # 3. Logging 分级日志 async def send_log(level: LoggingLevel, message: str): await app.send_log_message( levellevel, logger_nameweather-mcp, messagemessage ) # 4. Tools 工具调用 app.list_tools() async def list_tools(): return [ Tool( namequery_weather, description查询指定城市的实时天气支持城市北京、上海、广州、深圳, inputSchema{ type: object, properties: { city: { type: string, description: 需要查询天气的城市名称 } }, required: [city] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): await send_log(LoggingLevel.INFO, f收到工具调用{name}, 参数{arguments}) if name ! query_weather: await send_log(LoggingLevel.ERROR, f未知工具{name}) return [TextContent(typetext, text错误不存在该工具)] city arguments.get(city, ) if city not in WEATHER_DATA: await send_log(LoggingLevel.WARNING, f查询未知城市{city}) return [TextContent(typetext, textf暂不支持【{city}】仅支持北京、上海、广州、深圳)] w WEATHER_DATA[city] result f【{city}】今日天气{w[status]}气温{w[temp]}风力{w[wind]} await send_log(LoggingLevel.INFO, f查询成功{result}) return [TextContent(typetext, textresult)] # 5. Resources 资源读取 app.list_resources() async def list_resources(): return [ Resource( uriffile://{DOC_FILE_PATH}, name天气服务说明文档, descriptionMCP天气服务使用规则说明 ), ResourceTemplate( uriTemplatemcp://weather/intro/{city}, name城市天气动态简介, description根据城市动态生成天气温馨提示 ) ] app.read_resource() async def read_resource(uri: str): await send_log(LoggingLevel.INFO, f读取资源{uri}) if uri.startswith(file://): file_path uri.replace(file://, ) try: with open(file_path, r, encodingutf-8) as f: return f.read() except Exception as e: return f读取文件失败{str(e)} if uri.startswith(mcp://weather/intro/): city uri.split(/)[-1] if city in WEATHER_DATA: return f温馨提示{city}今日{WEATHER_DATA[city][status]}出行注意合理穿搭。 return f暂无【{city}】的天气提示 return 未知资源URI # 6. Prompts 提示词模板 app.list_prompts() async def list_prompts(): return [ Prompt( nameweather_ask_template, description通用天气查询话术模板, arguments[ PromptArgument( nameuser_question, description用户原始提问, requiredTrue ) ] ) ] app.get_prompt() async def get_prompt(name: str, arguments: dict | None None): if name weather_ask_template and arguments: user_q arguments.get(user_question, ) prompt_text f你是专业天气助手请根据用户提问调用天气工具并友好回答。 用户提问{user_q} 要求语言简洁、通俗易懂补充出行建议。.strip() return { description: 天气查询模板, messages: [{role: user, content: prompt_text}] } return {description: 无效模板, messages: []} # 7. Stdio 传输层启动 async def main(): transport StdioServerTransport() await app.connect(transport) await send_log(LoggingLevel.INFO, 天气MCP Server 已启动等待客户端连接...) if __name__ __main__: asyncio.run(main())3.2 静态资源文件 weather_doc.txt 本地天气MCP服务使用说明 1. 支持查询城市北京、上海、广州、深圳 2. 调用工具query_weather 3. 可读取资源服务说明文档、城市动态天气提示 4. 可使用预设提示词模板weather_ask_template3.3 各组件在干嘛传输层用StdioServerTransport()服务启动后通过标准输入输出和客户端通信不开端口安全性高。这是本地 MCP 的标配。Roots 通过list_roots声明服务端只能访问当前项目目录客户端会做路径校验防止越权读文件。这是 MCP 的安全机制别省。Logging 用send_log_message向客户端推送分级日志INFO、WARN、ERROR 三档。在 Claude Code 终端里能直接看到调试时非常有用。Tools 是最常用的能力。list_tools向客户端声明有哪些工具、参数规则是什么大模型会自动解析 JSON Schema 决定怎么传参call_tool是实际执行逻辑返回结构化文本。Resources 分静态和动态。静态资源读本地文件动态资源用 URI 模板按参数生成内容。作用是给模型补充上下文。Prompts 是可复用的提示词模板支持参数渲染避免客户端重复写 Prompt。4. 验证请求把 Server 接进 Claude Code 并跑通代码写完不算完得让 Claude Code 真的能调起来。这一步配错的人最多我按顺序说。4.1 先单独测 Server 能不能启动python main.py服务会进入阻塞状态光标停住不动说明 Stdio 传输层在等客户端连接。这是正常的别以为卡死了。按 CtrlC 退出。4.2 配置 Claude Code 的 MCPClaude Code 支持全局配置和项目局部配置优先级是项目配置大于全局配置。全局路径macOS/Linux~/.claude/mcp.jsonWindowsC:\Users\你的用户名\.claude\mcp.json项目局部配置就在项目目录下建.claude/mcp.json。配置内容{ mcpServers: { local-weather-server: { command: /usr/bin/python3, args: [/你的项目路径/weather-mcp-server/main.py], env: {} } } }Windows 示例{ mcpServers: { local-weather-server: { command: D:\\Python311\\python.exe, args: [D:\\weather-mcp-server\\main.py], env: {} } } }command必须写 Python 解释器的绝对路径别写python这种相对命令环境变量一乱就找不到。args里是 main.py 的绝对路径。4.3 启动并验证终端里执行claude启动 Claude Code然后输入帮我查北京的天气Claude Code 会自动发现 MCP 工具调用query_weather返回类似【北京】今日天气晴气温22℃风力微风2级同时终端会打印 MCP 服务推送的日志能看到 INFO 级别的调用记录。到这一步完整调用链就跑通了你的指令 → Claude Code → MCP Server → 工具执行 → 结果回传。4.4 想验证模型通道是否正常如果你还想单独确认 TaoToken 的模型通道没问题可以用模型对话入口测一下https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果后面你要长期用 Claude Code 做编码、跑 Agent 任务可以考虑 Coding Plan额度更划算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite5. 本篇常见错排查配 MCP 踩坑是常态我把高频问题列出来对着查。5.1 工具没被发现现象Claude Code 里问天气它说没有相关工具。原因通常是 mcp.json 路径写错或者 Python 解释器路径不对。检查command和args是不是绝对路径文件是不是真的存在。改完配置要重启 Claude Code配置不会热加载。5.2 服务启动就退出现象python main.py一闪而过。多半是依赖没装全或者 Python 版本低于 3.10。跑pip install mcp重装再确认版本。5.3 中文乱码现象返回的天气结果里中文变成问号。这是编码问题。读文件时已经指定了encodingutf-8如果还乱检查终端本身的编码设置Windows 下可以chcp 65001切到 UTF-8。5.4 资源读取失败现象调用 Resources 时报文件找不到。DOC_FILE_PATH是相对路径./weather_doc.txt它相对于服务启动时的工作目录。如果你从别的目录启动就找不到。改成绝对路径最稳。5.5 日志看不到现象终端没有 MCP 日志输出。确认send_log调用没被注释且客户端版本支持日志推送。部分老版本客户端不显示 MCP 日志升级即可。5.6 参数传错现象工具被调用了但返回“暂不支持”。大模型可能把城市名识别成“北京市”而不是“北京”。两个办法一是在工具 description 里写清楚支持的城市列表二是在call_tool里做一次归一化比如去掉“市”字再匹配。排障时如果怀疑是 Key 或接入配置的问题直接对照接入文档逐项核对https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite需要重新生成 Key 的话在这里https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite6. 接下来怎么走从 Demo 到真实能力天气 Demo 跑通之后你已经掌握了 MCP 的完整骨架。真正有价值的是把它改造成你需要的工具。几个方向把WEATHER_DATA换成真实数据源比如查内部订单、查监控指标、查数据库。工具逻辑换成对应的查询代码其余结构不用动。传输层从 Stdio 升级到 HTTP/SSE就能做成远程 MCP 服务多个客户端共享同一个 Server。适合团队内部统一能力出口。权限这块Roots 只能限制文件访问范围。如果要做多用户权限管控得在工具逻辑里加身份校验结合 Key 做鉴权。如果你打算把 MCP 能力接进长期的编码工作流比如让 Claude Code 在写代码时自动调用你的内部工具那 Coding Plan 的额度模型更适合这种高频场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite最后说个实操心得MCP Server 的调试日志比断点好用。因为它是子进程断点不好挂但send_log推的日志在客户端终端里一目了然。每次改完工具逻辑先看日志确认参数传对了没再排查业务代码。这个习惯能帮你省掉大量瞎猜的时间。