ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Python的MCP Server开发实战:用uv与Type Hints构建可调试的TaoToken工具服务

2026/10/7 20:02:40 拓冰建站 浏览量
Python的MCP Server开发实战:用uv与Type Hints构建可调试的TaoToken工具服务 1. 从零写一个能调试的 MCP Server为什么我最后选了 uv Type HintsMCP Server 说白了就是给大模型外挂的一双手模型自己不会算数、不会查库、不会调你的内部接口但它能通过 MCP 协议发现你注册的 tool然后按你声明的参数结构发起调用。Python 的 MCP Server 开发核心就三件事——用 SDK 起一个服务、用装饰器注册工具、用 Type Hints 和 Docstring 把工具的语义讲清楚。适合谁适合已经会写 Python 函数、想让自己的脚本被 Claude Desktop、Cline、Codex 这类客户端直接调起来的同学。我一开始也是 pip 一把梭pip install mcp就开干结果在依赖解析和启动方式上反复踩坑本地能跑换台机器就报版本冲突mcp dev能起mcp install又挂。后来换成 uv 管理依赖配合 Type Hints 约束入参出参整个链路才稳定下来。这篇就按「初始化项目 → 写工具 → 本地 stdio 联调 → 接 TaoToken 统一 Key 通道 → 排错」的顺序走一遍代码都能直接复制。先说清楚 MCP Server 到底在干嘛。你可以把它理解成一个「函数注册中心」你写普通 Python 函数加个mcp.tool()装饰器SDK 会自动读取函数的类型注解和文档字符串生成一份 JSON Schema 描述告诉模型「这个工具叫什么、要什么参数、返回什么」。模型看到这份描述后决定什么时候调用它。所以 Type Hints 不是可选项它是模型理解你工具的唯一入口——你写a: int模型就知道要传整数你写a: str | None某些旧版本 typer 直接崩给你看这就是后面要讲的坑。uv 在这里的价值是「可复现」。MCP Server 通常要装进客户端配置里长期运行依赖一旦漂移客户端启动就失败。uv 用pyproject.tomluv.lock锁死版本uv run直接拉起虚拟环境不用手动 activate。对 MCP 这种「配置一次、长期被调用」的场景这点比 pip 省心太多。2. 用 uv 初始化项目并接入 TaoToken 统一 Key 通道这一节把地基打好装 uv、建项目、加依赖再把 TaoToken 的 API 通道配进来。TaoToken 在这里的角色是「统一 Key / API 通道」——你的 MCP 工具如果需要调用大模型能力比如做文本润色、意图识别不用每个客户端各配一套 Key走同一个 Base URL 和 Key 就行。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。先装 uv。macOS / Linux 用官方脚本Windows 用 pip 也行# macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # 或者已经有 Python 环境直接 pip 装 pip install uv装完验证一下uv --version # uv 0.5.x 之类然后初始化项目。注意uv init会生成pyproject.toml、.python-version和main.py我们把它改成 server 目录uv init mcp-tool-server cd mcp-tool-server加 MCP SDK 和 CLI 工具。mcp[cli]这个 extra 会带上mcp命令行用来跑 inspector 和安装到客户端uv add mcp[cli]这一步 uv 会自动创建.venv并写好uv.lock。装完你的pyproject.toml大概长这样注意requires-python和依赖版本[project] name mcp-tool-server version 0.1.0 description A debuggable MCP tool server built with uv and Type Hints readme README.md requires-python 3.10 dependencies [ mcp[cli]1.2.0, httpx0.27.0, ] [build-system] requires [hatchling] build-backend hatchling.build这里我额外加了httpx因为后面工具里要发 HTTP 请求到 TaoToken 的 API 通道。如果你暂时不接模型能力可以不加。接下来配置 TaoToken 的接入信息。MCP Server 本身不强制你用什么模型通道但一旦工具里要调模型就需要 Base URL Key Model ID 三件套。我习惯用环境变量避免把 Key 写进代码# 写入项目根目录的 .env记得加进 .gitignore TAOTOKEN_BASE_URLhttps://taotoken.net/api TAOTOKEN_API_KEYsk-你的Key TAOTOKEN_MODELclaude-sonnet-4-5Key 在控制台创建地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API Keys 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。模型对话调试页在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。注意Base URL 用https://taotoken.net/api不要带末尾斜杠也不要在代码里拼/v1之外的路径具体以接入文档为准。如果你用的是 Claude Code 这类客户端配置片段settings 风格大致如下路径按你本机实际位置改{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-5 } }Cline / Roo 这类 VS Code 插件则是在设置里填 Base URL、API Key、Model ID 三项Base URL 同样是https://taotoken.net/api。Codex 的auth.json结构不同但三件套逻辑一致Base URL、Key、Model ID 缺一不可。这一步先把通道备好下一节写工具时就能直接调用。3. 用 Type Hints 注册工具可复制的 server.py 配置现在写核心的server.py。MCP Python SDK 提供FastMCP用装饰器注册工具SDK 会自动把类型注解和 Docstring 转成模型能读懂的 schema。先看一个最小可运行版本# server.py import sys import httpx from mcp.server.fastmcp import FastMCP mcp FastMCP(TaoTokenTools) mcp.tool() def add(a: int, b: int) - int: Add two integers and return the sum. return a b mcp.tool() def divide(a: float, b: float) - float: Divide a by b. b must not be zero. if b 0: raise ValueError(b must not be zero) return a / b if __name__ __main__: mcp.run()关键点在于mcp.tool()下面的函数签名。a: int, b: int - int会被 SDK 解析成参数类型和返回类型Docstring 变成工具描述。模型就是靠这些信息判断「什么时候该调 add、什么时候该调 divide」。我试过把 Docstring 写成和函数实际行为不一致的描述模型会按描述去调结果拿到意料之外的返回值——所以 Docstring 必须和实现一致。再写一个真正调用 TaoToken 通道的工具演示怎么在 MCP 工具里发 HTTP 请求import os import httpx from mcp.server.fastmcp import FastMCP mcp FastMCP(TaoTokenTools) BASE_URL os.environ.get(TAOTOKEN_BASE_URL, https://taotoken.net/api) API_KEY os.environ.get(TAOTOKEN_API_KEY, ) MODEL os.environ.get(TAOTOKEN_MODEL, claude-sonnet-4-5) mcp.tool() def polish_text(text: str, tone: str professional) - str: Polish the given text into the specified tone. Args: text: The raw text to polish. tone: Target tone, e.g. professional, casual, concise. if not API_KEY: raise RuntimeError(TAOTOKEN_API_KEY is not set) payload { model: MODEL, messages: [ {role: system, content: fRewrite the text in a {tone} tone.}, {role: user, content: text}, ], } headers { Authorization: fBearer {API_KEY}, Content-Type: application/json, } with httpx.Client(timeout60) as client: resp client.post(f{BASE_URL}/v1/messages, jsonpayload, headersheaders) resp.raise_for_status() data resp.json() return data[content][0][text]注意tone: str professional这种带默认值的参数SDK 会把它标成可选参数模型不传也能调。但默认值类型要和注解一致否则 schema 生成会出问题。如果你用 Cline MCP 或 Claude Desktop配置里要写全三件套。以 Claude Desktop 的claude_desktop_config.json为例{ mcpServers: { taotoken-tools: { command: uv, args: [--directory, /绝对路径/mcp-tool-server, run, server.py], env: { TAOTOKEN_BASE_URL: https://taotoken.net/api, TAOTOKEN_API_KEY: sk-你的Key, TAOTOKEN_MODEL: claude-sonnet-4-5 } } } }command用uvargs里--directory指向项目绝对路径这样客户端启动时会自动进虚拟环境跑server.py不用你手动 activate。这套配置对 Cline MCP 也基本通用只是配置文件位置不同。4. 本地 stdio 联调与一次成功请求验证写完代码别急着装进客户端先用mcp dev起 inspector 本地调试。stdio 模式下客户端和 server 通过标准输入输出通信inspector 会给你一个网页界面手动触发工具调用。uv run mcp dev server.py正常输出类似Starting MCP inspector... Proxy server listening on port 3000 MCP Inspector is up and running at http://localhost:5173浏览器打开http://localhost:5173左侧能看到你注册的工具列表点add填a1, b2点 Run右侧返回3。这一步验证的是「工具注册 类型解析 调用链路」全通。再验证polish_text。先在终端导出环境变量或者确认.env已被加载export TAOTOKEN_BASE_URLhttps://taotoken.net/api export TAOTOKEN_API_KEYsk-你的Key export TAOTOKEN_MODELclaude-sonnet-4-5 uv run mcp dev server.py在 inspector 里调polish_texttext填「这个功能挺好用的」tone填casual返回应该是润色后的句子。如果返回 401说明 Key 没读到或无效如果返回连接错误检查 Base URL 是否写成了https://taotoken.net/api。验证通过后装进 Claude Desktopuv run mcp install server.py输出里会看到Added server TaoTokenTools to Claude config。重启 Claude Desktop点输入框旁的工具图标就能看到你注册的工具。输入「帮我算一下 12 除以 4」模型会调用divide并返回3.0。这里有个细节mcp install默认把 server 名写进配置如果你改了FastMCP(TaoTokenTools)里的名字配置里的 key 也会跟着变。装完最好去claude_desktop_config.json核对一遍确认command、args、env三块都对。5. 常见报错排查401、local proxy failed、reading choices、OAuth这一节按真实报错来对。MCP Server 开发最容易卡在四类错误上我一个个拆。第一类401 Unauthorized。工具里调 TaoToken 通道返回 401九成是 Key 没传对。检查三处环境变量名是否和代码里os.environ.get(TAOTOKEN_API_KEY)一致客户端配置的env块里有没有这个变量Key 本身是否在控制台被禁用。注意Authorization头是Bearer sk-xxx别漏了Bearer前缀。第二类local proxy failed / connection refused。这种通常出现在mcp dev启动阶段端口 3000 或 5173 被占用。换个端口uv run mcp dev server.py --port 3001如果是客户端启动 server 时报这个检查args里的路径是不是绝对路径uv --directory后面有没有拼错。相对路径在客户端环境下经常解析失败。第三类reading choices of undefined。这个报错说明你拿到的响应结构不是预期的 OpenAI 风格。TaoToken 的/v1/messages返回的是 Anthropic 风格取文本要用data[content][0][text]如果你走的是/v1/chat/completions才是data[choices][0][message][content]。两种端点返回结构不同取错字段就会报reading choices of undefined或reading content of undefined。先确认你调的是哪个端点再对应取字段。第四类OAuth 相关报错。某些客户端在连接远程 MCP Server 时会走 OAuth 流程本地 stdio 模式一般用不到。如果你看到OAuth字样先确认是不是误配了远程 transport。本地开发用 stdio配置里不要写url字段只写commandargs。还有一个高频坑RuntimeError: Type not yet supported: str | None。这是 typer 旧版本不支持X | None联合类型导致的出现在mcp install或mcp dev启动时。解决办法是升级uv add --upgrade mcp[cli] typer升级后重启终端再跑。这个坑我在旧环境里踩过升级 typer 到 0.12 以上就好了。排查顺序建议先看终端完整 traceback定位是启动阶段还是调用阶段启动阶段多半是依赖/路径问题调用阶段多半是 Key/字段问题。把print(..., filesys.stderr)加在工具函数里日志会打到客户端日志或终端不影响 stdio 协议。6. 把工具服务接进长期编码流Coding Plan 与后续调试工具跑通之后下一步是让它进入日常编码流。如果你只是偶尔调一下inspector 手动触发就够了但如果你想让 MCP 工具长期挂在 Cline、Claude Code 里配合 Agent 做多步任务那就要考虑通道的稳定性和额度。TaoToken 的 Coding Plan 就是为这种长期编码场景准备的入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 。接入方式还是三件套Base URL 用https://taotoken.net/apiKey 用 Coding Plan 对应的 KeyModel ID 按文档填。Claude Code 的配置片段和前面 settings 那段一致只是 Key 换成 Coding Plan 的。Cline MCP 里则是在 provider 设置里选自定义 Base URL填同样的地址和 Key。调试上我有个习惯给每个工具函数加一行 stderr 日志记录入参和耗时。stdio 模式下 stdout 被协议占用日志必须走 stderr否则会污染通信导致客户端解析失败。比如import sys import time mcp.tool() def add(a: int, b: int) - int: Add two integers and return the sum. start time.time() result a b print(f[add] a{a} b{b} result{result} cost{time.time()-start:.3f}s, filesys.stderr) return result这样在客户端日志里能看到每次调用的真实入参排查模型传参错误时特别有用。模型有时候会把字符串1传给int参数SDK 会尝试转换转不了就报错日志里一看便知。最后提醒一点MCP Server 的 Docstring 是给模型看的不是给人看的。写的时候用祈使句、说清楚边界条件比如「b must not be zero」模型在调用前就会自己判断要不要传零。这比在代码里抛异常再让模型重试要高效得多。工具描述写得好模型调用准确率能明显提升这是我在多个项目里验证过的经验。