MCP 和 Agent Skills 都能让 AI 调用外部能力,二者边界到底怎么划?产品逻辑不复杂:贴一个视频链接进去,程序先下载字幕,再交给大模型提炼要点,最后生成思维导图。难的是这些能力分散在多个服务里,我还想让 Claude 和 Cursor 都能直接调用。一开始我给每个助手各写一套自定义函数,结果换一个客户端就要重写一遍对接,维护成本直线上升,才决定把两套机制分开用:MCP 当”工具总线”,解决连接问题;Agent Skills 当”业务 SOP”,解决流程问题。下面把两者的本质、原理和我在项目里的落地取舍一次讲清楚。
一、什么是 MCP
MCP(Model Context Protocol)是 Anthropic 提出的”AI 工具调用标准化协议”,让 LLM 能与外部工具/数据源交互。我最初没用它,直接给大模型写函数调用定义,等接入第二个、第三个外部服务时才发现问题:每个服务都要单独维护一份调用规范,客户端一换全部失效。MCP 的价值在于把”接口协议”从具体的客户端和服务里抽出来,相当于给 AI 工具装了一个统一插口,接入新服务时不用再为每个助手单独适配。
┌─────────────────┐
│ LLM (Claude) │
└─────────────────┘
↓ MCP 协议
┌─────────────────┐
│ MCP Server 1 │ ← 工具:yt-dlp 封装
│ MCP Server 2 │ ← 工具:DeepSeek 调用
│ MCP Server 3 │ ← 工具:Stripe API
└─────────────────┘
每个 MCP Server 暴露”工具(tools)”给 LLM,LLM 通过 MCP 协议发现并调用。这样任何支持 MCP 的客户端都能连上同一个服务端,下载、字幕、总结这些能力只写一份,换界面不用改核心逻辑。我接完第一个 MCP Server 后,第二个服务的接入时间从几天缩到几小时,这才是它真正省事的地方。
二、什么是 Agent Skills
Agent Skills 是”预定义的专家能力包”,让 Agent 快速具备某领域专长。它和 MCP 解决的是完全不同的问题:MCP 回答”我能连上什么”,Skills 回答”这件事按什么流程做”。我在给团队配开发助手时,把下载器的处理步骤、错误处理约定、代码规范都写进一个 Skill,助手加载后就按项目惯例工作,而不是每次凭训练数据里的通用经验瞎猜。
Agent + Skill 1 = "前端专家"
Agent + Skill 2 = "后端专家"
Agent + Skill 3 = "DevOps 专家"
Skills 是”指令集 + 工具集 + 知识库”的组合,Agent 加载 Skill 后就具备该能力。我踩过的坑是把 Skill 当 MCP 用,把工具调用细节也塞进 Skill 描述里,结果助手要么不会主动调工具,要么描述太长占上下文。后来才明白,工具接入交给 MCP,Skills 只负责把流程和标准说清楚,两者分开写才不乱。
三、MCP vs Skills 对比
选型的时候我对着这张表纠结了很久,最后定下的原则是:涉及外部系统接入走 MCP,涉及领域流程与项目惯例走 Skills。下面从七个维度对比两者差异。
| 维度 | MCP | Agent Skills |
|---|---|---|
| 本质 | 工具调用协议 | 能力扩展包 |
| 提供方 | 协议 + Server | 指令 + 知识 + 工具 |
| 触发 | LLM 决定调用 | 显式/自动加载 |
| 协议 | JSON-RPC over stdio/HTTP | 纯 Prompt + 文件 |
| 跨平台 | 任何 LLM/工具 | 取决于 Agent 实现 |
| 状态 | 有状态(长连接) | 通常无状态 |
| 适用 | 工具集成 | 领域专长 |
对比下来,差异落在”连接”和”流程”这两个词上:MCP 关心连接稳定与协议统一,Skills 关心流程标准与知识沉淀,两者不在同一层,可以叠加使用。项目里常见的组合是 MCP 提供工具,Skills 教 Agent 怎么组合这些工具完成任务。
四、MCP 的工作原理
1. MCP Server
MCP 的服务端就是一段普通程序,通过 stdio 或 HTTP 跑起来。下面这段是我总结器里 MCP Server 的核心代码,声明了两个工具:一个下载并总结视频,一个生成思维导图。
# mcp_server.py
from mcp.server import Server, stdio
app = Server("video-summarizer")
@app.list_tools()
async def list_tools():
return [
{
"name": "summarize_video",
"description": "总结视频字幕",
"inputSchema": {
"type": "object",
"properties": {
"url": {"type": "string"},
"language": {"type": "string", "default": "zh"}
},
"required": ["url"]
}
},
{
"name": "generate_mindmap",
"description": "生成思维导图",
"inputSchema": {
"type": "object",
"properties": {
"summary": {"type": "string"}
},
"required": ["summary"]
}
}
]
@app.call_tool()
async def call_tool(name, arguments):
if name == "summarize_video":
info = await downloader.get_info(arguments["url"])
summary = await ai_service.summarize(info.subtitle)
return {"content": [{"type": "text", "text": summary}]}
elif name == "generate_mindmap":
mindmap = await ai_service.generate_mindmap(arguments["summary"])
return {"content": [{"type": "text", "text": mindmap}]}
这段最容易出错的是 inputSchema 里的 required 字段,漏写会让 LLM 自己猜参数,调用时反复报缺参。我第一次就把 url 的 required 写漏了,助手调工具十次里三次失败。把 schema 写完整后,调用成功率明显提升,这个细节值得一开始就注意。
2. LLM 调用 MCP
客户端这边不用手写每个工具的调用逻辑,服务端启动后工具定义会自动注入。下面这个示例是 Claude 客户端的写法,模型收到用户指令后自行决定调哪个工具、传什么参数。
# 客户端(Claude/Cursor)
import anthropic
client = anthropic.Anthropic()
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
tools=[
# MCP server 自动注入的工具定义
],
messages=[
{
"role": "user",
"content": "总结这个视频 https://..."
}
]
)
LLM 看到工具定义后,决定调用哪个、传什么参数。实际跑下来,模型对”描述清晰、参数明确”的工具判断更准,描述含糊的工具常常被忽略。所以工具描述值得多花点字数,这直接影响调用准确率。
五、Agent Skills 的工作原理
1. Skill 结构
Skills 本质是项目里的几个文件,加载成本比 MCP 低得多。我按照下面的目录组织,把说明、脚本、示例分开,改一处不影响其他部分。
skills/
├── video-downloader/
│ ├── SKILL.md # Skill 描述
│ ├── instructions.md # 详细指令
│ ├── scripts/ # 辅助脚本
│ └── examples/ # 示例
2. SKILL.md 示例
SKILL.md 是 Skill 的入口,先写清楚能干什么、按什么流程做,再给出代码样例和约束。
# Video Downloader Skill
## 能力
- 多平台视频信息获取
- 视频/音频/字幕下载
- 元数据提取
## 工作流
1. 接收 URL
2. 识别平台
3. 调用对应下载器
4. 返回元信息
## 代码示例
```python
from app.services.downloader import VideoDownloader
downloader = VideoDownloader()
info = await downloader.get_info(url)
约束
- 30s 超时
- 失败重试 2 次
- 不下载付费内容
约束部分很重要。我把"不下载付费内容""失败重试 2 次"写进去后,助手遇到异常不再盲目重试,行为稳定了不少。如果漏掉约束,模型会按自己的理解处理边界情况,结果不可控。
### 3. 加载 Skill
加载 Skill 就是把它读进上下文,我封装了一个 VideoAgent 类,启动时把 Skill 的指令拼进提示词。
```python
# Agent 启动时加载 Skill
class VideoAgent:
def __init__(self):
self.skill = self.load_skill('video-downloader')
async def run(self, url: str):
# Skill 提供指令 + 工具
prompt = f"""
{self.skill.instructions}
用户任务:总结视频 {url}
"""
return await llm.generate(prompt)
这段的注意点是 Skill 不要全量塞进上下文,指令文件越大越占 token。我后来改成按任务只加载相关 Skill,上下文占用降了不少,长会话里明显更从容。
六、平台对 MCP 的使用
平台用 MCP 把核心功能暴露给 Claude/Cursor,让开发助手在聊天里直接操作业务能力,不用来回切工具。
1. MCP Server 工具列表
我把总结器的五个核心能力统一成工具暴露出去,下载、总结、导图、问答、搜索各一个,名字一眼能看出用途。
TOOLS = [
{
"name": "get_video_info",
"description": "获取视频元信息(标题、时长、缩略图)"
},
{
"name": "summarize_video",
"description": "AI 总结视频"
},
{
"name": "generate_mindmap",
"description": "生成思维导图"
},
{
"name": "answer_question",
"description": "基于视频内容回答用户问题"
},
{
"name": "search_videos",
"description": "搜索用户的视频历史"
}
]
工具命名保持”动词 + 对象”的模式,模型识别起来最快。我之前起过 video_stuff 这种含糊名字,助手根本不知道什么时候该用它,改成 get/summarize/generate 开头后,命中率明显上来了。
2. Cursor 中使用
Cursor 通过 .cursor/mcp.json 发现服务端,配好就能在聊天里直接用。
{
"mcpServers": {
"video-summarizer": {
"command": "python",
"args": ["-m", "app.mcp_server"]
}
}
}
Cursor 自动发现 MCP server,用户在聊天中就能用:
总结 https://www.youtube.com/watch?v=xxx
这里有个容易忽略的细节:json 里的 command 要指向实际可执行的入口。我第一次配成模块路径却漏了 -m,服务端一直起不来,报错也看不太懂;改成 python -m app.mcp_server 后一次通过,排错花了不少时间。
七、平台对 Agent Skills 的使用
平台写代码时加载技能,让助手一进来就懂项目的规范和惯例:
skills/
├── backend-dev/
│ ├── SKILL.md
│ └── instructions.md
├── frontend-dev/
├── ai-prompt/
└── video-downloader/
Skill 示例
下面这个 Skill 定义的是”修改下载器”这件事的流程,从读代码到写测试都约定好,避免每次改动靠人肉叮嘱。
# video-downloader Skill
## 适用场景
- 添加新平台支持
- 修改下载器逻辑
- 修复下载 bug
## 工作流
1. 阅读现有 downloader.py
2. 添加新平台:
- URL 识别规则
- 平台特定配置(UA/Referer)
- 错误处理
3. 写测试
4. 验证成功下载
## 代码风格
- 异步函数
- 异常用具体类型
- 日志用结构化
- 不要修改 base 类
主 Agent 加载此 Skill 后,就能”以视频下载器专家”的视角工作。我的实际体会是,加载 Skill 后助手写出的代码风格统一多了,提交的改动也更能直接合并,省掉了反复 review 改风格的环节。
八、什么场景用 MCP
适合
- AI 编程助手(Cursor/Claude Code)调用工具;
- LLM 与外部数据源对接;
- 跨平台工具标准化。
不适合
- 一次性脚本(直接调函数);
- 高频内部调用(协议开销);
- 实时性要求极高(毫秒级)。
选 MCP 前先问一句:这个能力要不要跨客户端复用。只在单个项目里用一次的脚本,直接调函数更省事。我早期为了图整齐,把内部函数也包成 MCP,结果协议开销让每次调用多出几百毫秒,得不偿失,后来才把这一层拆掉。
九、什么场景用 Skills
适合
- 领域专长(”我是视频下载专家”);
- 工作流模板(”按这个流程开发”);
- 知识沉淀(”这些是项目惯例”)。
不适合
- 工具调用(用 MCP);
- 简单提示(直接写 prompt);
- 频繁变动的逻辑(写代码更直接)。
Skills 适合内容变化慢、需要反复复用的部分。如果某段逻辑天天改,把它固化进 Skill 反而要频繁更新文件,不如直接写在代码里。我做了一次”哪些知识值得沉淀成 Skill”的筛选,最终只保留三四个稳定的,其余一律不碰。
十、平台完整架构
把两者合在一起,就是总结器的完整调用链:
用户 → Web 端
↓
Claude/Cursor(开发时)
↓ MCP
MCP Server(暴露 5 个工具)
↓ 调用
业务服务(下载、AI、支付)
↓
Database / External API
主 Agent 加载 Skills 后,有”项目惯例”知识 + 能调 MCP 工具 = 高效开发。到这里,MCP 和 Skills 在项目里的分工链路就完整了:Skills 教方法,MCP 给通路,业务服务负责落地。
十一、MCP 实战细节
1. 工具描述要清晰
工具描述直接影响模型何时调用、怎么传参。下面正反两个例子对比很明显。
# 好的描述
{
"name": "summarize_video",
"description": "总结指定 URL 的视频。返回内容包括:"
"1) 一句话总结 2) 核心要点 3) 关键时间点。"
"需要 10-30 秒。"
}
# 不好的描述
{
"name": "summarize",
"description": "总结" # 太模糊
}
改完描述后我专门跑了一组对比:模糊描述的调用准确率不到一半,写清楚触发条件和返回结构后,模型几乎每次都选对工具。描述里带上耗时、返回结构这类信息,能让模型判断得更准。
2. 错误信息要友好
工具报错时,把可操作的建议一并返回,模型能自己纠正。
try:
result = await call_tool(name, args)
except ToolError as e:
return {
"content": [{
"type": "text",
"text": f"工具调用失败: {e}\n"
f"建议:检查 URL 是否正确,或换其他视频"
}],
"isError": True
}
这个改动带来的变化是,失败后模型会主动换思路而不是原地重试,用户感知到的失败率低了很多。
3. 长操作要异步
视频下载动辄几十秒,不能阻塞等待,改为先返回任务编号,再轮询结果。
@app.call_tool()
async def call_tool(name, args):
if name == "summarize_video":
# 启动后台任务,立即返回 task_id
task_id = await task_service.start(args)
return {
"content": [{
"type": "text",
"text": f"任务已启动,task_id={task_id}\n"
f"用 get_task_result 查询进度"
}]
}
十二、Skills 实战细节
1. SKILL.md 模板
给团队建 Skill 时,我固定用这套模板,字段齐全,别人接手也能看懂。
# [Skill Name]
## 适用场景
- ...
## 工作流
1. ...
2. ...
## 关键文件
- `path/to/file.py`:功能说明
- `path/to/other.py`:功能说明
## 代码示例
[具体代码]
## 约束
- 不要...
- 必须...
- 性能要求...
2. Skill 版本管理
Skill 也是要迭代的,我在目录里按版本归档,旧版本留作回退。
skills/
├── video-downloader/
│ ├── v1/
│ │ └── SKILL.md
│ └── v2/
│ └── SKILL.md
3. Skill 自动发现
加载所有 Skill 时按目录扫描就行,不需要手动逐个注册。
def load_all_skills():
skills = {}
for skill_dir in glob('skills/*/'):
skills[skill_dir.name] = parse_skill(skill_dir)
return skills
十三、对比其他方案
除了 MCP 和 Skills,还有几种常见的扩展方式,选型时按这张表对号入座。
| 方案 | 何时选 |
|---|---|
| MCP | 跨平台工具集成、协议标准化 |
| Skills | 领域专长、项目惯例 |
| Function Calling | 简单工具调用 |
| Plugins(IDE) | 编辑器集成 |
| Webhooks | 服务间异步通知 |
结论是别迷信单一方案:同个项目里 MCP、Skills、Function Calling 经常共存,各自负责擅长的层。选型的关键是看”连接””流程””单次调用”哪个是主要矛盾。
十四、未来趋势
- MCP 标准化:可能成为行业标准协议;
- Skills 市场:类似 npm 的 Skills 共享生态;
- 多 Agent 协作:MCP + Skills + 多 Agent 框架融合。
十五、踩坑
1. MCP Server 启动慢
服务端启动时加载了太多大文件,工具列表迟迟出不来。改成静态工具列表后,启动明显加快。
# 启动时不要加载大文件
@app.list_tools()
async def list_tools():
return TOOLS # 静态
2. Skills 太多
20+ Skills 让 Agent 困惑。控制在 5-10 个核心 Skills,多了反而互相干扰。
3. MCP 协议开销
每个工具调用 100-300ms 协议开销。频繁调用合并成一次批量请求更划算。
4. Skills 上下文爆
Skills 全部塞进 prompt 占用 token。按需加载,用哪个加载哪个。
十六、最佳实践
- MCP 工具描述详细,LLM 才知道何时调用;
- Skills 简明扼要,聚焦核心工作流;
- 错误信息友好,LLM 能自我纠正;
- 异步长任务用 task_id 模式;
- 关键操作加人工确认。
常见问题(FAQ)
Q1:MCP 与 Function Calling 区别?
MCP 是协议标准,跨平台;Function Calling 是 LLM 提供商的能力。
Q2:MCP 必须用 Claude 吗?
不,Anthropic 提的协议,理论任何 LLM 都能用。
Q3:Skills 越多越好吗?
不是,5-10 个最佳,超过反而干扰。