ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

MCP 与 Skill 两种 Agent 工具机制的设计思路:用 TaoToken 统一 Key 跑通对比实验

MCP 与 Skill 两种 Agent 工具机制的设计思路:用 TaoToken 统一 Key 跑通对比实验 1. 为什么要在本地做 MCP 与 Skill 的对比实验MCP 和 Skill 这两个词最近在 Agent 圈子里出现频率很高但很多人对它们的理解停留在概念层面知道 MCP 是 Model Context Protocol知道 Skill 是 Claude Skills 那套东西可真要问「同一个任务交给这两种机制模型的行为到底差在哪」就说不清楚了。我一开始也是这样看了不少文章脑子里还是两团浆糊。后来干脆自己搭了个本地环境用同一个 Key、同一个模型、同一个任务把两种机制各跑一遍才真正把差异看明白。这篇文章就是那次实验的完整记录。核心思路是用 TaoToken 统一管理 API Key避免在多个工具之间来回切换配置然后在本地分别搭起 MCP 服务和 Skill 目录用同一个「查询数据库表结构并生成迁移建议」的任务去触发它们观察模型在工具发现、参数构造、调用时机上的不同表现。适合已经写过一点 Agent 代码、想搞清楚这两种机制底层差异的开发者也适合正在选型、纠结该用哪种方案扩展模型能力的同学。先说结论方向MCP 更像给模型一份严格的函数签名Skill 更像给模型一本操作手册。前者约束强、Token 消耗高、调用稳定后者灵活、省 Token、但依赖模型的判断力。这个差异在实验里会非常直观。整个实验环境不复杂一台本地开发机、Python 3.10、一个 TaoToken 的 API Key、Claude Code 或任意支持 MCP 的客户端。下面从接入配置开始一步步把两种机制跑通。2. TaoToken 统一 Key 接入与前置配置做对比实验最怕的就是变量不统一。如果 MCP 走一个 Key、Skill 走另一个 Key模型版本、限流策略、计费口径都不一样实验结果就没法归因。所以我用 TaoToken 作为统一的接入层所有请求都从同一个 Base URL 和同一个 Key 出去这样 MCP 和 Skill 的差异就纯粹来自机制本身而不是接入层的干扰。TaoToken 在这里扮演的角色是统一的模型接入网关它兼容 Anthropic 和 OpenAI 两种协议风格这对我们很重要——因为 Claude Code 走的是 Anthropic 协议而很多 MCP 客户端走的是 OpenAI 风格用同一个网关就能同时覆盖。先拿 Key。打开 https://taotoken.net/api-keys 登录后创建一个新的 API Key复制出来保存好。注意这个 Key 只在创建时完整显示一次丢了就得重建。拿到 Key 之后配置环境变量。我习惯用.env文件管理避免 Key 硬编码进代码# .env TAOTOKEN_API_KEYsk-你的实际key TAOTOKEN_BASE_URLhttps://taotoken.net/api然后在 Python 里加载import os from dotenv import load_dotenv load_dotenv() API_KEY os.getenv(TAOTOKEN_API_KEY) BASE_URL os.getenv(TAOTOKEN_BASE_URL)如果你用的是 Claude Code配置方式略有不同。Claude Code 读取的是~/.claude/settings.json需要把 Base URL 和 Key 写进去{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的实际key } }这里有个坑要注意Claude Code 的ANTHROPIC_BASE_URL不要带/v1后缀网关会自动处理路径。我一开始多加了/v1结果一直报 404排查了半天。如果你用的是 Cline 或者支持 MCP 的编辑器插件配置项通常是三个Base URL、API Key、Model ID。以 Cline 为例在设置里填配置项值API ProviderOpenAI CompatibleBase URLhttps://taotoken.net/apiAPI Keysk-你的实际keyModel IDclaude-sonnet-4-5 或你需要的模型Model ID 这块建议先用 https://taotoken.net/models 查一下当前可用的模型列表不同时期可选的模型会有变化。填错 Model ID 会直接报model not found这个错误后面排障章节会细说。配置完成后先做一次最小验证确认 Key 和 Base URL 是通的import anthropic client anthropic.Anthropic( api_keyAPI_KEY, base_urlBASE_URL, ) resp client.messages.create( modelclaude-sonnet-4-5, max_tokens100, messages[{role: user, content: 回复两个字通了}], ) print(resp.content[0].text)如果输出「通了」说明接入层没问题可以进入下一步。如果报错先别急着往下走把错误信息对照第 5 节的排障表处理掉。3. MCP 与 Skill 的可复制配置片段这一节是实验的核心。我会分别给出 MCP 服务端和 Skill 目录的完整配置两者都通过上面统一的 TaoToken Key 接入模型。3.1 MCP 服务端配置MCP 的本质是协议化的工具描述所以我们需要起一个 MCP Server把工具以标准 schema 暴露出去。这里用官方 Python SDK 写一个最小服务暴露一个get_table_schema工具# mcp_server.py import asyncio import json from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app Server(demo-mcp-server) # 模拟一个数据库表结构 TABLE_SCHEMA { users: { columns: [ {name: id, type: int, nullable: False}, {name: email, type: varchar(255), nullable: False}, {name: created_at, type: timestamp, nullable: True}, ] }, orders: { columns: [ {name: id, type: int, nullable: False}, {name: user_id, type: int, nullable: False}, {name: amount, type: decimal(10,2), nullable: False}, ] }, } app.list_tools() async def list_tools() - list[Tool]: return [ Tool( nameget_table_schema, description获取指定数据库表的列结构信息用于生成迁移建议, inputSchema{ type: object, properties: { table_name: { type: string, description: 表名例如 users 或 orders, } }, required: [table_name], }, ) ] app.call_tool() async def call_tool(name: str, arguments: dict) - list[TextContent]: if name get_table_schema: table arguments.get(table_name) schema TABLE_SCHEMA.get(table) if not schema: return [TextContent(typetext, textf表 {table} 不存在)] return [TextContent(typetext, textjson.dumps(schema, ensure_asciiFalse))] raise ValueError(f未知工具: {name}) async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: asyncio.run(main())然后在 MCP 客户端以 Claude Code 为例的配置里注册这个服务。Claude Code 的 MCP 配置在~/.claude.json或项目级.mcp.json{ mcpServers: { demo-db: { command: python, args: [/绝对路径/mcp_server.py], env: { TAOTOKEN_API_KEY: sk-你的实际key } } } }注意command和args里的路径要写绝对路径相对路径在 Claude Code 启动时的工作目录下经常找不到文件这是新手最容易踩的坑之一。3.2 Skill 目录配置Skill 的配置简单得多本质就是一个带 frontmatter 的 Markdown 文件。在项目下建一个skills/目录skills/ └── db-migrate/ └── SKILL.mdSKILL.md内容--- name: db-migrate description: 分析数据库表结构并生成迁移建议适用于 schema 变更场景 --- # 数据库迁移助手 ## 任务目标 根据给定的表结构识别潜在的破坏性变更并生成迁移计划。 ## 操作步骤 1. 读取目标表的当前列结构 2. 对比期望结构找出新增、删除、类型变更的列 3. 对每个变更评估是否破坏性如删除列、缩小类型 4. 生成按顺序执行的迁移 SQL 草案 5. 标注需要人工确认的高风险操作 ## 注意事项 - 删除列前必须确认无外键依赖 - 类型变更要考虑数据截断风险 - 迁移脚本要可回滚Skill 的注册方式取决于客户端。Claude Code 会自动扫描~/.claude/skills/和项目下的skills/目录把每个 Skill 的name和description作为「技能目录」注入到系统提示里。模型在启动时只看到目录判断需要时才通过skill(namedb-migrate)加载完整内容。这里的关键差异已经显现MCP 在启动时就把完整的inputSchema注入上下文而 Skill 只注入一行摘要。这个差异直接决定了后面的 Token 消耗和调用行为。3.3 统一 Key 的复用两种机制都通过环境变量读取同一个TAOTOKEN_API_KEYMCP Server 在env里传入Skill 场景下由客户端统一使用settings.json里的 Key。这样无论走哪条路径模型请求都从同一个网关出去实验变量被控制住了。4. 验证请求与成功结果对照配置完成后用同一个任务分别触发 MCP 和 Skill观察行为差异。任务统一为「帮我看看 users 表的结构然后给出迁移建议」。4.1 MCP 路径的验证在 Claude Code 里直接输入任务模型会先看到get_table_schema的完整 schema然后构造调用{ name: get_table_schema, arguments: { table_name: users } }服务端返回{ columns: [ {name: id, type: int, nullable: false}, {name: email, type: varchar(255), nullable: false}, {name: created_at, type: timestamp, nullable: true} ] }模型拿到结构化结果后生成迁移建议。整个过程的特征是参数由 schema 严格约束table_name必须是字符串不会出现模型自己编一个tableName或者传数组的情况。4.2 Skill 路径的验证同样输入任务模型启动时只看到You have the following skills: - db-migrate: 分析数据库表结构并生成迁移建议适用于 schema 变更场景模型判断需要这个技能后触发加载skill(namedb-migrate)系统把SKILL.md全文注入上下文模型按照里面的步骤执行。注意这里模型并不会真的去「调用」一个函数而是按照自然语言描述的步骤自己决定要不要去读表结构、怎么读。如果任务里没有明确提供表结构数据模型可能会反问你或者自己假设一个结构。4.3 行为差异对照把两次运行的日志拉出来对比差异很明显观察维度MCPSkill启动时上下文完整工具 schema仅一行技能摘要参数构造由 schema 约束模型自由发挥调用确定性高格式固定中依赖模型判断首次响应 Token明显更高明显更低错误处理有明确错误语义靠模型自己解释实测下来同一个任务 MCP 路径的输入 Token 大约是 Skill 路径的 2 到 3 倍差距主要来自启动时注入的 schema。但 MCP 路径的调用成功率接近 100%Skill 路径在任务描述模糊时会出现「模型没触发技能」或「触发了但步骤执行不全」的情况。这个结果和设计预期是一致的MCP 用 Token 换稳定性Skill 用灵活性换成本。5. 本篇常见错误排查实验过程中我踩了不少坑这里按报错原文整理成对照表方便你快速定位。5.1 401 UnauthorizedError code: 401 - {error: {message: Invalid API key}}最常见的原因是 Key 没加载进环境变量或者.env文件没被读取。检查两点一是load_dotenv()是否在读取os.getenv之前调用二是 Key 字符串有没有多余空格或换行。Claude Code 场景下检查settings.json里的ANTHROPIC_API_KEY是否拼写正确。5.2 local proxy failed / connection refusedError: local proxy failed: dial tcp 127.0.0.1:xxxx: connect: connection refused这个错误通常出现在 MCP Server 启动失败时。Claude Code 会尝试连接本地 MCP 进程如果mcp_server.py因为语法错误或依赖缺失没起来就会报这个。排查方法先在终端手动跑一遍python mcp_server.py看有没有报错。常见的是ModuleNotFoundError: No module named mcp装一下pip install mcp即可。5.3 reading choices / 响应解析失败Error: reading choices: unexpected end of JSON input这个多半是 Base URL 配错了。如果你在ANTHROPIC_BASE_URL后面加了/v1网关返回的路径和客户端期望的对不上就会解析失败。正确写法是https://taotoken.net/api不带任何后缀。另外确认 Model ID 是网关支持的填错模型名有时也会返回非标准响应体。5.4 OAuth / 认证流程异常Error: OAuth token exchange failedClaude Code 某些版本会尝试走 OAuth 流程如果你用的是 API Key 模式需要在settings.json里显式声明避免它去走 OAuth。确认配置里同时有ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY并且没有残留的 OAuth 相关字段。5.5 Skill 不触发没有报错但模型就是不用 Skill。这通常是因为description写得太模糊模型判断不出当前任务和技能的关联。把description写具体一点比如把「处理数据库」改成「分析数据库表结构并生成迁移建议适用于 schema 变更场景」触发率会明显提升。5.6 MCP 工具列表为空客户端连上了 MCP Server但list_tools返回空。检查app.list_tools()装饰器是否真的被调用以及Tool对象的inputSchema是否符合 JSON Schema 规范。required字段如果写了不存在的属性名某些客户端会静默丢弃整个工具定义。6. 选型建议与后续实验方向跑完这轮对比我对两种机制的适用场景有了更具体的判断。MCP 适合那些「不能出错」的场景数据库操作、Git 操作、基础设施变更。这些任务一旦参数传错后果是实打实的。用 MCP 把 schema 定死模型没有发挥空间反而是一种保护。代价是 Token 成本但在高风险场景下这个成本值得付。Skill 适合流程型、创作型任务报告生成、代码审查、写作模板。这些任务本身就有弹性空间模型按自然语言步骤执行反而更自然。而且 Skill 的延迟加载机制在长对话里优势明显不会一上来就吃掉大量上下文。真正成熟的 Agent 系统往往是两者混用用 MCP 管住关键工具调用用 Skill 承载流程性知识。我现在的项目就是这么做的MCP 负责数据访问层Skill 负责业务逻辑编排各司其职。如果你想继续深入可以试试这几个方向一是把同一个任务拆成多步观察 MCP 和 Skill 在多轮对话里的 Token 累积差异二是给 MCP 工具加上错误返回看模型如何处理结构化错误三是把 Skill 的description做 A/B 测试量化描述质量对触发率的影响。实验代码和配置我都放在本地仓库里你可以照着上面的片段直接复现。接入层统一用 TaoToken 的 Key省去了多套凭证管理的麻烦这点在做对比实验时特别省心。模型对话调试可以用 https://taotoken.net/chat 快速验证 prompt长期跑 Agent 任务的话 Coding Plan 更划算具体接入文档在 https://taotoken.net/doc 有完整说明。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进