
1. 为什么 Cline 里 Parameters 总是 No description如果你最近在本地写 MCP Server大概率踩过这个坑工具函数写完了参数类型也标了结果在 Cline 或 Windsurf 的工具面板里一看Parameters 那一栏齐刷刷写着No description。模型拿到这个工具定义只能靠参数名去猜——image_url到底是网络地址还是本地路径width单位是像素还是百分比猜错了就报错猜对了也是运气。这个问题的本质不在 MCP 协议而在你给工具函数加的类型注解方式。MCP Server 在启动时会读取函数的签名把它转成 JSON Schema 暴露给客户端。如果你只写了image_url: str那生成的 schema 里就只有type: string没有description字段。客户端拿不到描述自然显示No description模型也就失去了判断依据。我试过最直接的对比同一个deal_image工具第一版用裸类型注解Cline 面板里三个参数全是No description改成Annotated[str, Field(description...)]之后重新加载描述立刻出现在面板上模型调用时传参的准确率肉眼可见地提升。这不是玄学是 schema 里多了一个字段带来的确定性差异。这篇面向的是已经在写 MCP Server、并且用 Cline MCP 或 Windsurf BYOK 做客户端的人。你需要的不只是「加个 description」这么简单而是搞清楚 Annotated 和 Pydantic Field 两种写法各自生成的 schema 长什么样、在什么场景下选哪种、以及怎么通过一次真实工具调用验证描述确实被读进去了。下面从环境准备开始一步步把 Parameters 描述补全最后用 TaoToken 的统一通道发一次请求看返回的入参 JSON 里描述字段是否到位。2. TaoToken 统一 Key 与 API 通道准备在动手改代码之前先把调用通道理顺。MCP Server 本身是本地进程但你要验证「描述是否被正确读取」需要一个能发起工具调用的模型端。这里用 TaoToken 的统一 Key 和 API 通道好处是 Base URL 和 Key 一套配置通吃 Claude、GPT 等模型不用为每个模型单独换 endpoint。TaoToken 的定位是统一模型接入层官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 。你需要在控制台创建一个 API Key然后把它填到客户端的配置里。对于 Cline MCP 和 Windsurf BYOK 这类场景关键三件套是 Base URL、API Key、Model ID缺一不可。具体操作路径打开 https://taotoken.net/api-keys 新建一个 Key复制出来先存好。然后到 https://taotoken.net/console 确认账户状态正常。模型 ID 可以在 https://taotoken.net/doc 的模型列表里查比如claude-sonnet-4-20250514这类标识。如果你打算长期跑编码类 Agent 任务可以看下 https://taotoken.net/coding-plan 按用量规划比单次调用更划算。配置的时候有个细节容易忽略Base URL 要填到/api这一层不要多加/v1或/chat/completions客户端会自动拼接。Key 的权限范围建议只勾选需要的模型避免一个 Key 泄露影响全部额度。Model ID 必须和文档里完全一致大小写和连字符都不能错否则会返回模型不存在的错误。准备好这三样之后先别急着改 MCP 代码。你可以用 https://taotoken.net/model-chat 做一次纯文本对话确认 Key 和通道是通的。这一步能排除掉网络和鉴权问题后面调试 MCP 参数描述时就不会把「Key 错了」和「描述没生效」混在一起排查。通道通了再回到 MCP Server 里补 Parameters 描述验证链路才干净。3. 用 Annotated 与 Pydantic Field 补全 Parameters 描述这一节是核心直接给可复制的配置片段。MCP Server 的工具体系基于 Pydantic所以参数描述的写法本质上就是 Pydantic 的写法。两种主流方式Annotated 和 Field 默认值。先看 Annotated这是更推荐的现代写法把类型提示和元数据分开可读性更好。from typing import Annotated, Literal, Any from pydantic import Field from mcp.server.fastmcp import FastMCP mcp FastMCP(image-tools) mcp.tool( namedeal_image, description处理图片支持缩放和格式转换 ) def deal_image( image_url: Annotated[str, Field(description要处理的图片 URL支持 http/https)], resize: Annotated[bool, Field(description是否缩放图片)] False, width: Annotated[int, Field(description目标宽度单位像素, ge1, le2000)] 800, format: Annotated[ Literal[jpeg, png, webp], Field(description输出图片格式) ] jpeg ) - dict: 处理图片并返回结果路径。 # 实现略 return {ok: True, width: width, format: format}这段代码里每个参数都被Annotated[类型, Field(...)]包住Field里的description就是最终会出现在 JSON Schema 里的描述文本。ge和le是数值范围约束也会一并写进 schema模型看到minimum: 1, maximum: 2000就知道不能传 0 或 9999。Literal配合Field会生成enum字段模型只能在三个格式里选不会瞎编一个gif出来。第二种写法是直接用Field作为默认值适合参数不多、想写得更紧凑的场景mcp.tool( namegetDataFromSql, description从数据库查询数据 ) def get_data_from_sql( query: str Field(description搜索查询字符串), limit: int Field(10, description返回结果最大条数, ge1, le100) ) - list: 根据查询条件检索数据库。 # 实现略 return []注意这里query没有默认值Field(description...)直接放在类型后面作为参数默认值。这种写法在 Pydantic v2 里是合法的生成的 schema 和 Annotated 写法完全一致。区别在于 Annotated 把元数据和类型绑定Field 默认值把元数据和默认值绑定。如果参数有默认值且你想强调默认值Field 写法更直观如果参数是必填且元数据复杂Annotated 更清晰。再看一个实际项目里的注册函数把工具名、描述和参数描述都写全def register(mcp): mcp.tool( nametext2Voice, description文字转语音 ) def text2Voice( text: Annotated[str, Field(description传入文本建议不超过 500 字)] ) - dict[str, Any] | None: 文字转语音并返回音频文件信息。 res utils.Text2Voice(texttext, downloadTrue) logging.info(ftext2Voice res: {res}) return res这里text参数的描述是「传入文本建议不超过 500 字」模型在调用时会把这个描述作为提示知道要控制文本长度。如果你不写描述模型可能塞进去一整篇文章导致 TTS 接口超时或截断。描述不只是给人看的它是模型决策的一部分。关于Field的常用参数可以对照下面这张表参数作用生成的 schema 字段description参数说明descriptionge大于等于minimumle小于等于maximumgt / lt严格大于/小于exclusiveMinimum / exclusiveMaximummin_length / max_length字符串长度minLength / maxLengthpattern正则约束patterndefault默认值default把这些填全MCP 客户端拿到的 Parameters 就不再是No description而是每个参数都有明确说明、约束和默认值。模型调用时传参的准确率会明显提升尤其是参数名有歧义的时候描述就是唯一的消歧依据。4. 发起工具调用验证描述是否被读取代码改完重启 MCP Server接下来要验证描述真的进了 schema。最直接的方式是让模型发起一次工具调用然后看返回的入参 JSON。这里用 TaoToken 的模型对话入口配合 Cline MCP 做一次实测。先在 Cline 的 MCP 配置里加上你的本地 Server。Cline 的 MCP 配置文件通常在~/.cline/mcp_settings.json或项目下的.cline/mcp.json格式如下{ mcpServers: { image-tools: { command: python, args: [-m, your_mcp_server], env: { PYTHONUNBUFFERED: 1 } } } }保存后重启 Cline在工具面板里找到deal_image展开 Parameters应该能看到每个参数的描述文本。如果还是No description说明 Server 没重启或者 schema 没刷新先排查这个。确认面板显示正常后在 Cline 对话框里发一条指令「帮我把 https://example.com/a.jpg 缩放到宽度 1200输出 webp 格式」。模型会调用deal_image工具Cline 会弹出工具调用确认里面显示实际传入的参数 JSON。你要看的就是这个 JSON 里每个字段的值是否符合描述约束image_url是完整 URLwidth是 1200 且在 1 到 2000 之间format是webp而不是别的。如果你想更直接地看 schema可以在 MCP Server 启动后用 TaoToken 的 API 发一次请求让模型返回它看到的工具定义。用 curl 示例curl https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: claude-sonnet-4-20250514, messages: [ {role: user, content: 列出 deal_image 工具的参数描述} ], tools: [ { type: function, function: { name: deal_image, description: 处理图片支持缩放和格式转换, parameters: { type: object, properties: { image_url: {type: string, description: 要处理的图片 URL支持 http/https}, resize: {type: boolean, description: 是否缩放图片, default: false}, width: {type: integer, description: 目标宽度单位像素, minimum: 1, maximum: 2000, default: 800}, format: {type: string, enum: [jpeg, png, webp], description: 输出图片格式, default: jpeg} }, required: [image_url] } } } ] }返回的choices[0].message.tool_calls里会包含模型决定调用的参数。如果描述被正确读取模型传的width不会超出 2000format不会出现gif。你可以故意把描述去掉再发一次同样的请求对比模型传参的差异——没有描述时模型更容易传错格式或超范围的值。实测下来描述补全后模型一次调用成功的概率从「经常要重试」变成「基本一次过」。尤其是format这种枚举参数有描述和没描述的差别最明显没描述时模型可能传jpg有描述时它知道只能从jpeg/png/webp里选。5. 常见报错与排查对照改完代码到验证成功之间通常会撞上几个典型报错。下面按真实遇到的顺序列出来对照排查。401 Unauthorized这个最常见出现在用 TaoToken 发请求时。原因通常是 Key 没填对、Key 被禁用、或者 Base URL 写错。检查Authorization: Bearer后面的 Key 是否和控制台里的一致Base URL 是否是https://taotoken.net/api而不是别的路径。如果 Key 刚创建等几秒再试有时候有缓存延迟。local proxy failed / connection refusedCline 连不上本地 MCP Server。先确认 Server 进程在跑ps aux | grep your_mcp_server看一下。然后检查mcp_settings.json里的command和args是否指向正确的 Python 解释器和模块路径。如果 Server 启动时报了 import 错误Cline 这边只会显示连接失败实际原因在 Server 的 stderr 里把PYTHONUNBUFFERED1加上能看到实时日志。reading choices of undefined这个报错说明 API 返回的结构和你预期的不一样。常见原因是 Model ID 写错了TaoToken 返回了一个错误对象而不是正常的 chat completion 结构。去 https://taotoken.net/doc 核对模型 ID确保和文档里完全一致。另一个可能是请求体里tools字段格式不对检查parameters里的 JSON Schema 是否合法。OAuth 相关报错如果你用的是 Claude Code 或某些需要 OAuth 的客户端可能会遇到 token 过期或 scope 不足。这种情况建议改用 API Key 方式接入在 https://taotoken.net/api-keys 创建一个专用 Key填到客户端的 API Key 字段避开 OAuth 流程。Claude Code 的接入文档在 https://taotoken.net/doc 里有说明按步骤配 Base URL、Key、Model ID 三件套即可。Parameters 仍然显示 No description代码改了但面板没变。先确认 Server 真的重启了Python 进程是新的。然后检查mcp.tool装饰器是否在函数定义之前Annotated和Field是否正确导入。如果用的是Field默认值写法确认 Pydantic 版本是 v2v1 的Field行为不同。最后有些客户端会缓存工具 schema清一下客户端缓存或重启客户端。模型传参仍然出错描述写了但模型不遵守。检查描述文本是否足够明确比如「宽度」不如「目标宽度单位像素范围 1-2000」清晰。枚举参数一定要用Literal或enum光靠描述文字约束力不够。如果参数之间有依赖关系在工具描述里说明比如「format 为 webp 时 width 不能超过 1600」。排查顺序建议从外到内先确认 TaoToken 通道通用 model-chat 发一条纯文本再确认 MCP Server 能启动看日志再确认 schema 里有描述看客户端面板最后确认模型调用时传参正确看入参 JSON。一层层排除不要跳步。6. 把描述写进工具定义让调用不再靠猜回到最开始的问题Cline 里 Parameters 显示No description模型调用靠猜。根因是工具函数的类型注解没有携带描述元数据MCP Server 生成的 JSON Schema 里缺了description字段。解法就是用Annotated或Field把描述、约束、默认值补全让 schema 自解释。具体操作上优先用Annotated[类型, Field(description...)]的写法把类型和元数据分开可读性和可维护性都更好。参数有默认值时可以用Field默认值写法更紧凑。枚举参数一定用Literal配合Field数值参数把ge/le填上字符串参数考虑min_length/max_length。这些元数据会原样进入 JSON Schema客户端面板和模型都能读到。验证环节用 TaoToken 的统一通道发一次工具调用看返回的入参 JSON 是否符合描述约束。Base URL 用https://taotoken.net/apiKey 在 https://taotoken.net/api-keys 创建Model ID 在 https://taotoken.net/doc 核对。三件套配好之后Cline MCP 和 Windsurf BYOK 都能用同一套配置。最后给一个实用建议把工具描述当成 API 文档来写但读者是模型。模型不会问你「这个参数什么意思」它只会根据描述做决策。描述写得越明确调用越准。我现在的习惯是每个参数至少写清楚「是什么、什么格式、什么范围」枚举参数把可选值列全有依赖关系的在工具描述里说明。这样下来工具调用的返工率能降一大截。