当前位置: 首页 > news >正文

Skills vs MCP:Agent能力扩展的双螺旋

摘要:Skills和MCP是Agent能力扩展的两大路径,各有优劣。本文深度对比Skills与MCP的架构差异、开发模式、适用场景,探讨两者融合使用的双螺旋模型。

Skills vs MCP Agent能力扩展的双螺旋

我在做一个数据分析Agent项目的时候,遇到了一个让我困惑了好几天的问题。我给Agent注册了一个MCP工具叫"query_database",同时又在Skills配置里声明了一个同名能力"query_database"。结果Agent运行的时候,有时候走MCP,有时候走Skills,行为完全不可预测。

折腾了两天我才搞明白,Skills和MCP虽然都是给Agent加能力的,但它们的定位、运行机制和适用场景完全不同。混在一起用会出大问题。今天这篇,我把两者的本质区别、互补关系和正确用法讲清楚。

Skills是什么

Skills是一种声明式的能力描述机制。你不需要写完整的工具实现代码,只需要用一个声明文件告诉Agent"你能干什么"以及"干这个事需要什么参数"。Agent拿到这个声明后,自己根据描述来决定怎么完成这个任务。

举个最直白的例子。你想让Agent具备"发邮件"的能力,用Skills的方式是这样的。

# skill定义文件,声明式地描述Agent能力name:send_email# 能力名称description:"发送邮件给指定收件人"# 能力描述version:"1.0.0"# 版本号# 参数定义,告诉Agent这个能力接受什么输入parameters:to:# 收件人邮箱type:stringrequired:truedescription:"收件人邮箱地址"subject:# 邮件主题type:stringrequired:truedescription:"邮件主题"body:# 邮件正文type:stringrequired:truedescription:"邮件正文内容"# 执行提示,告诉Agent如何完成这个任务# 注意这里不是代码,而是自然语言指令prompt:|你现在需要发送一封邮件。请按照以下步骤操作: 1. 确认收件人邮箱格式正确 2. 构造邮件内容 3. 调用SMTP服务发送 收件人: {{to}} 主题: {{subject}} 正文: {{body}}

你看,这里没有一行可执行代码。Skills只是声明了"我能发邮件"以及"发邮件需要什么参数"。具体怎么发,由Agent自己根据prompt里的自然语言指令去完成。它可能调用某个内置的邮件服务,也可能通过MCP去调一个邮件API。

MCP是什么

MCP我们在前面的文章里讲了很多了。这里简单回顾一下。MCP是协议级的工具调用机制,它定义了Agent如何发现工具、如何调用工具、如何获取工具返回结果的完整协议。

同样的"发邮件"功能,用MCP的方式是这样的。

""" MCP Server实现发邮件工具 和Skills不同,这里有完整的可执行代码 """frommcp.serverimportServerimportsmtplibfromemail.mime.textimportMIMEText# 创建MCP Server实例server=Server("email-server")@server.tool("send_email")asyncdefsend_email(to:str,subject:str,body:str)->str:"""发送邮件的MCP工具实现"""# 构造邮件消息对象msg=MIMEText(body)# 设置邮件正文msg["Subject"]=subject# 设置邮件主题msg["From"]="agent@example.com"# 设置发件人msg["To"]=to# 设置收件人# 连接SMTP服务器并发送withsmtplib.SMTP("smtp.example.com",587)assmtp:smtp.starttls()# 启用TLS加密smtp.login("user","pass")# 登录SMTP服务器smtp.send_message(msg)# 发送邮件# 返回发送结果returnf"邮件已发送至{to}"if__name__=="__main__":# 启动MCP Serverserver.run()

区别一目了然。MCP有完整的可执行代码,你调它就是真的在发邮件。Skills没有代码,只有声明,Agent需要自己想办法去完成。

本质区别对比

我整理了一张详细的对比表,把两者的核心差异列出来。

维度SkillsMCP
本质声明式能力描述协议级工具调用
代码无可执行代码,只有声明和提示词有完整可执行代码
执行方式Agent自行决定如何完成直接调用预定义的函数
灵活性高,Agent可以根据上下文调整低,行为固定
可靠性依赖Agent的推理能力,有不确定性结果确定,每次调用行为一致
开发成本低,写个声明文件就行中,需要写完整实现
适用场景开放式任务、创意类任务确定性操作、系统级操作
调试难度难,行为不完全可预测易,输入输出固定

这张表里最关键的是"执行方式"和"可靠性"这两行。Skills的执行结果取决于Agent当时的推理状态,同一个Skill可能每次执行的路径都不同。MCP则完全不同,同样的输入永远得到同样的输出。

两者的互补关系

看到这里你可能会问,既然MCP更可靠,为什么还需要Skills?因为有些场景天然不适合用固定代码来实现。

比如"写一首关于春天的诗"这个能力。你不可能写一个函数来生成诗歌,因为这需要创意和语言理解能力。用Skills的话,你只需要声明这个能力,告诉Agent参数和提示词,Agent自己用大语言模型的能力来完成。

再比如"执行SQL查询"这个能力。这个就不适合用Skills,因为SQL查询是确定性的操作,参数固定、行为固定、结果可预期。用MCP写一个工具来实现最合适。

所以正确的做法是Skills和MCP搭配使用。Skills管那些需要Agent推理和创意的能力,MCP管那些需要精确执行的能力。就像DNA的双螺旋一样,两条链缠绕在一起,各司其职,共同构成Agent的完整能力体系。

能力类型适合Skills适合MCP原因
发送邮件推荐确定性操作,参数固定
写文章推荐需要创意和语言理解
查询数据库推荐SQL执行需要精确
总结文档推荐需要理解语义
文件操作推荐系统级操作
头脑风暴推荐开放式创意任务
调用外部API推荐需要精确的请求和响应处理
角色扮演推荐需要灵活的对话能力

完整项目演示

下面我搭一个完整的项目,同时使用Skills和MCP,让Agent同时具备"写诗"(Skills)和"查天气"(MCP)两种能力。

项目结构

dual_capability_agent/ ├── agent.py # 主Agent逻辑 ├── skills/ # Skills定义目录 │ └── write_poem.yaml # 写诗Skill定义 ├── mcp_server.py # 天气查询MCP Server ├── capability_router.py # 能力路由器 └── requirements.txt

requirements.txt

# Anthropic SDK,用于调用Claude API anthropic==0.34.0 # PyYAML,解析Skills的YAML定义文件 pyyaml==6.0.1 # httpx,异步HTTP请求 httpx==0.27.0

skills/write_poem.yaml

# 写诗Skill的声明式定义# 这个文件不含可执行代码,只有能力描述和提示词name:write_poem# 能力名称description:"根据主题写一首诗"# 能力描述version:"1.0.0"# 版本号# 参数定义parameters:topic:# 诗歌主题type:stringrequired:truedescription:"诗歌的主题,比如春天、友情、离别"style:# 诗歌风格type:stringrequired:falsedefault:"古典"description:"诗歌风格,可选古典或现代"# 执行提示词,Agent根据这个提示来执行prompt:|请根据以下信息写一首诗: 主题: {{topic}} 风格: {{style}}要求: 1. 诗歌要押韵 2. 意象要生动 3. 情感要真挚 4. 控制在4到8句

mcp_server.py

""" 天气查询MCP Server 这是一个有完整可执行代码的工具 """importjsonimporthttpxfrommcp.serverimportServer# 创建MCP Server实例server=Server("weather-server")@server.tool("get_weather")asyncdefget_weather(city:str)->str:"""查询指定城市的天气信息"""# 模拟调用天气API# 生产环境替换为真实的天气APIasyncwithhttpx.AsyncClient()asclient:# 调用免费天气APIresp=awaitclient.get(f"https://wttr.in/{city}",params={"format":"j1"}# 返回JSON格式)# 解析天气数据data=resp.json()# 提取当前天气current=data.get("current_condition",[{}])[0]# 构造天气描述weather_info={"city":city,# 城市名"temp":current.get("temp_C","N/A"),# 温度"humidity":current.get("humidity","N/A"),# 湿度"desc":current.get("weatherDesc",[{}])[0].get("value","未知"),# 天气描述}# 返回JSON格式结果returnjson.dumps(weather_info,ensure_ascii=False)if__name__=="__main__":# 启动MCP Serverserver.run()

capability_router.py

""" 能力路由器,负责区分请求应该走Skills还是MCP 这是解决两者冲突的关键组件 """importyamlimportosclassCapabilityRouter:"""能力路由器,统一管理Skills和MCP能力"""def__init__(self):# Skills能力注册表,存储已加载的Skill定义self.skills={}# MCP能力注册表,存储已注册的MCP工具self.mcp_tools={}# 冲突解决策略配置self.conflict_strategy="mcp_first"# 默认MCP优先defload_skills(self,skills_dir:str):"""从目录加载所有Skills定义文件"""# 遍历Skills目录forfilenameinos.listdir(skills_dir):# 只处理YAML文件iffilename.endswith(".yaml")orfilename.endswith(".yml"):filepath=os.path.join(skills_dir,filename)# 读取YAML文件withopen(filepath,"r",encoding="utf-8")asf:skill=yaml.safe_load(f)# 注册到Skills表self.skills[skill["name"]]=skillprint(f"已加载Skill:{skill['name']}")defregister_mcp_tool(self,name:str,description:str,endpoint:str):"""注册一个MCP工具"""# 添加到MCP工具表self.mcp_tools[name]={"name":name,# 工具名"description":description,# 工具描述"endpoint":endpoint,# 工具服务地址"type":"mcp"# 标记为MCP类型}print(f"已注册MCP工具:{name}")defresolve_capability(self,name:str)->dict:""" 解析能力请求,决定走Skills还是MCP 这是处理同名冲突的核心方法 """# 检查是否同时存在于Skills和MCP中in_skills=nameinself.skills in_mcp=nameinself.mcp_toolsifin_skillsandin_mcp:# 同名冲突,根据策略决定ifself.conflict_strategy=="mcp_first":# MCP优先策略print(f"[冲突解决]{name}同时存在于Skills和MCP,选择MCP")returnself.mcp_tools[name]elifself.conflict_strategy=="skills_first":# Skills优先策略print(f"[冲突解决]{name}同时存在于Skills和MCP,选择Skills")returnself.skills[name]else:# 报错策略,禁止同名raiseValueError(f"能力{name}同时存在于Skills和MCP,请解决冲突")elifin_skills:# 只有Skills中有returnself.skills[name]elifin_mcp:# 只有MCP中有returnself.mcp_tools[name]else:# 都没有returnNonedeflist_all_capabilities(self)->list:"""列出所有可用能力"""all_caps=[]# 添加Skills能力forname,skillinself.skills.items():ifnamenotinself.mcp_tools:# 排除已由MCP处理的同名能力all_caps.append({"name":name,"type":"skill","description":skill.get("description","")})# 添加MCP能力forname,toolinself.mcp_tools.items():all_caps.append({"name":name,"type":"mcp","description":tool.get("description","")})returnall_caps

agent.py

""" 主Agent,同时使用Skills和MCP两种能力 演示两种能力的协同工作 """importjsonfromcapability_routerimportCapabilityRouterclassDualCapabilityAgent:"""同时支持Skills和MCP的Agent"""def__init__(self):# 创建能力路由器self.router=CapabilityRouter()# 加载Skills定义self.router.load_skills("skills")# 注册MCP工具self.router.register_mcp_tool(name="get_weather",# 工具名description="查询城市天气",# 工具描述endpoint="http://localhost:8000"# MCP Server地址)asyncdefexecute(self,capability_name:str,params:dict):"""执行一个能力请求"""# 通过路由器解析能力cap=self.router.resolve_capability(capability_name)ifcapisNone:# 能力不存在return{"error":f"能力{capability_name}不存在"}ifcap.get("type")=="mcp":# 走MCP路径,调用工具returnawaitself._call_mcp(cap,params)else:# 走Skills路径,用提示词执行returnawaitself._execute_skill(cap,params)asyncdef_call_mcp(self,tool:dict,params:dict):"""调用MCP工具"""importhttpx# 构造MCP调用请求asyncwithhttpx.AsyncClient()asclient:# 发送工具调用请求到MCP Serverresp=awaitclient.post(f"{tool['endpoint']}/mcp/tools/call",json={"tool_name":tool["name"],"arguments":params})returnresp.json()asyncdef_execute_skill(self,skill:dict,params:dict):"""执行Skill"""# 获取Skill的提示词模板prompt_template=skill.get("prompt","")# 用参数填充提示词模板prompt=prompt_templateforkey,valueinparams.items():# 替换模板中的占位符prompt=prompt.replace(f"{{{{{key}}}}}",str(value))# 生产环境这里要调用LLM来执行# 简化版直接返回构造好的提示词return{"capability":skill["name"],"type":"skill","prompt":prompt,"note":"实际执行需要调用LLM"}deflist_capabilities(self):"""列出Agent的所有能力"""caps=self.router.list_all_capabilities()print("Agent当前可用能力:")forcapincaps:print(f" [{cap['type'].upper()}]{cap['name']}:{cap['description']}")returncaps# 运行演示asyncdefmain():"""主入口函数"""agent=DualCapabilityAgent()# 列出所有能力agent.list_capabilities()# 执行Skill:写诗print("\n--- 执行Skill: write_poem ---")poem_result=awaitagent.execute("write_poem",{"topic":"秋天","style":"古典"})print(f"结果:{json.dumps(poem_result,ensure_ascii=False,indent=2)}")# 执行MCP:查天气print("\n--- 执行MCP: get_weather ---")weather_result=awaitagent.execute("get_weather",{"city":"Beijing"})print(f"结果:{json.dumps(weather_result,ensure_ascii=False,indent=2)}")if__name__=="__main__":importasyncio asyncio.run(main())

效果验证

先启动MCP Server,再运行Agent。

# 终端1:启动天气查询MCP Serverpython mcp_server.py# 终端2:运行Agentpython agent.py

预期输出是Agent先列出所有能力,然后分别执行写诗Skill和查天气MCP工具,两种能力各走各的路径,互不干扰。

独家踩坑经验 同名能力优先级冲突

回到我开头说的那个问题。Skills和MCP同时注册同名能力时,会发生什么?

实际情况是这样的。我在能力路由器里同时注册了一个叫"query_database"的Skill和一个叫"query_database"的MCP工具。Agent运行时,能力解析的逻辑可能是这样的。

# 错误示范:没有冲突解决机制的路由逻辑defresolve_capability_buggy(name:str):"""有bug的能力解析,没有处理同名冲突"""# 先查Skills,找到就返回ifnameinskills:returnskills[name]# 再查MCP,找到就返回ifnameinmcp_tools:returnmcp_tools[name]returnNone

这个逻辑看着没问题,但实际运行时非常不稳定。因为Skills和MCP的加载顺序不确定,如果MCP先加载完,Skills还在加载中,这时请求进来可能走到了不同的分支。更可怕的是,如果是多线程环境,两个注册操作同时发生,结果完全不可预测。

我排查这个问题的时候,Agent有时候用Skills的提示词去"想象"数据库查询结果,有时候又正确走了MCP调真实数据库。两种结果差异巨大,但日志里看不出区别,因为能力名字是一样的。

我的解决方案是在能力路由器里加一个明确的冲突解决策略。上面代码中的CapabilityRouter类已经实现了这一点,核心逻辑如下。

# 正确做法:显式冲突解决defresolve_capability(self,name:str)->dict:"""正确的能力解析,显式处理同名冲突"""in_skills=nameinself.skills# 是否在Skills中in_mcp=nameinself.mcp_tools# 是否在MCP中ifin_skillsandin_mcp:# 同名冲突,根据策略决定ifself.conflict_strategy=="mcp_first":# MCP优先,因为MCP更可靠returnself.mcp_tools[name]elifself.conflict_strategy=="skills_first":# Skills优先returnself.skills[name]elifself.conflict_strategy=="error":# 直接报错,强制开发者解决raiseValueError(f"能力{name}同时存在于Skills和MCP中,"f"请在注册时使用不同的名称")# 只有一个来源的情况,直接返回elifin_skills:returnself.skills[name]elifin_mcp:returnself.mcp_tools[name]returnNone

除了在路由器层面解决,我还建议在注册阶段就做检查,提前发现冲突。

defregister_mcp_tool(self,name:str,description:str,endpoint:str):"""注册MCP工具时检查是否与Skills冲突"""# 检查是否与已有Skill同名ifnameinself.skills:# 打印警告信息print(f"[警告] MCP工具{name}与已注册的Skill同名!")print(f" Skill描述:{self.skills[name].get('description')}")print(f" MCP描述:{description}")print(f" 当前冲突策略:{self.conflict_strategy}")# 根据策略决定是否继续注册ifself.conflict_strategy=="error":# 严格模式,直接拒绝注册raiseValueError(f"能力名{name}冲突,拒绝注册")# 执行注册self.mcp_tools[name]={"name":name,"description":description,"endpoint":endpoint,"type":"mcp"}

这个问题解决后,我的Agent行为终于可预测了。核心教训就是,当你同时使用Skills和MCP时,一定要在系统初始化时就建立命名规范。比如所有MCP工具名加mcp_前缀,所有Skills名加skill_前缀。虽然不那么优雅,但能彻底避免冲突。

什么时候用Skills什么时候用MCP

最后给你一个实用的判断准则。

用MCP的场景

  • 需要精确执行的操作,如数据库查询、文件读写、API调用
  • 结果必须可复现,同样的输入必须得到同样的输出
  • 涉及外部系统交互,需要认证和错误处理
  • 性能敏感的场景,不能依赖Agent的推理速度

用Skills的场景

  • 需要创意和语言理解的任务,如写作、总结、翻译
  • 开放式任务,执行路径不固定
  • 需要根据上下文灵活调整行为的场景
  • 快速原型验证,不想写太多代码

两者都用的场景

  • 复杂工作流,既有确定性步骤又有创意性步骤
  • Agent需要同时操作外部系统和生成内容
  • 团队中有人擅长写声明有人擅长写代码,各取所长

常见问题与避坑

Q:Skills可以替代MCP吗?

不能。Skills没有可执行代码,它依赖Agent的推理能力来完成。对于需要精确执行的操作,Skills的不可靠性是不可接受的。

Q:一个能力能否同时用Skills和MCP两种方式实现?

可以但不建议。如果你确实需要两种方式都支持,务必使用不同的名称,比如"analyze_data_skill"和"analyze_data_mcp",然后在路由器里做区分。

Q:Skills的提示词写多长合适?

控制在200字以内。太短了Agent不知道该干什么,太长了会干扰Agent的其他指令。核心信息是"做什么"和"怎么做",参数细节由声明文件的parameters部分处理。

Q:MCP工具和Skills哪个开发效率更高?

Skills更快。写个YAML文件就行,不用写代码不用调试。但代价是执行结果不够可靠。如果是快速验证想法,先用Skills跑通流程,确认需求后再用MCP重写。

小结

Skills和MCP是Agent能力扩展的两种互补方式。Skills是声明式的,灵活但不精确,适合创意类任务。MCP是协议级的,精确但开发成本稍高,适合确定性操作。两者搭配使用时,一定要建立命名规范和冲突解决机制,否则同名冲突会让你 debug 到怀疑人生。

下一篇我们聊MCP工具市场生态,看看开源社区里有哪些现成的MCP Server可以直接用,以及怎么安全地使用第三方MCP工具。


相关推荐

  • Agent协议生态全景:MCP/A2A/ARD/AP2/ACP对比与选型
    • MCP工具市场生态:发现、分享与使用开源MCP Server
    • Tools原语深度解析:从定义到调用全流程
http://www.cnnetsun.cn/news/4026289.html

相关文章:

  • UVM Adapter:寄存器模型与总线的“翻译官”
  • ChatGPT转 word 工具推荐:首选「AI 导出鸭」平板版,专为iPad/安卓平板深度适配ChatGPT等主流AI,一键无损导出Word,完整保留公式、代码与流程图,让大屏导出更高效。
  • 依赖数据迁移工具做增量同步有哪些易错点?调整数据迁移工具策略怎么保证断点续传可靠?
  • 工业物联网设备接入从2天压缩到2分钟:AI原生物联底座实测
  • 不手写推导模型和 Jacobian,如何把 OCP/MPC 模型生成可部署 C++ SDK
  • 浏览器直达服务器串口:百敖基于 openUBMC 的 Web SOL 技术实践
  • 企业微信 iPad 协议 SCRM 开发效果实测
  • RAG的向量数据库:为LLM提供语义搜索能力
  • 【C++】13. 继承
  • Go 语言入门笔记(一):从零搭环境到跑通第一个程序
  • Windows 11 越来越卡?Win11Debloat 一键系统优化与隐私清理终极指南
  • 流放之路价格查询终极助手:这款免费开源工具,5分钟告别手动比价
  • 猫抓资源嗅探扩展使用路线图:3个成长阶段,把网页视频音频图片全拿下
  • 不用模拟器也能在 Windows 上装安卓应用:APK Installer 三步入门指南
  • 动态规划专练:力扣第1035、392题
  • 十大最佳 AI 编码工具
  • 混合办公两难:视频会议私有化部署频频失守的真相
  • 第一次让 AI 帮你写代码:用 JavaScript 完成一个待办事项清单
  • C语言基础:操作符详解(一)
  • 中级开发者用 AI,最容易掉进的 5 个坑
  • 从Cursor到多智能体:2026年AI全栈开发工具到底该怎么选?
  • 误删、格式化、硬盘罢工之后:TestDisk与PhotoRec免费数据恢复工具完整自救手册
  • 微信投票活动全攻略:从选工具到落地执行
  • maxGraph图表库实战:几分钟上手,从零构建可交互的流程图应用
  • WebPShop 插件完整使用指南:Photoshop 导出 WebP 与制作动画的免费教程
  • 从技术骨干到管理者,最大的转变不是能力,是角色和逻辑
  • 技术解析|音频分割在哪里切才不爆音?过零点与淡入淡出的四个关键细节
  • Windows 10/11 窗口毛玻璃特效实操指南:效果怎么选、怎么落地一次讲透
  • G-Helper完整上手指南:10分钟掌握华硕笔记本的功耗、风扇与显卡控制
  • 算法(23):heapsort-8.3一种先构建再排序的排序方法