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

发布时间:2026/8/14 17:08:00
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实例serverServer(email-server)server.tool(send_email)asyncdefsend_email(to:str,subject:str,body:str)-str:发送邮件的MCP工具实现# 构造邮件消息对象msgMIMEText(body)# 设置邮件正文msg[Subject]subject# 设置邮件主题msg[From]agentexample.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.txtrequirements.txt# Anthropic SDK用于调用Claude API anthropic0.34.0 # PyYAML解析Skills的YAML定义文件 pyyaml6.0.1 # httpx异步HTTP请求 httpx0.27.0skills/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实例serverServer(weather-server)server.tool(get_weather)asyncdefget_weather(city:str)-str:查询指定城市的天气信息# 模拟调用天气API# 生产环境替换为真实的天气APIasyncwithhttpx.AsyncClient()asclient:# 调用免费天气APIrespawaitclient.get(fhttps://wttr.in/{city},params{format:j1}# 返回JSON格式)# 解析天气数据dataresp.json()# 提取当前天气currentdata.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_asciiFalse)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_strategymcp_first# 默认MCP优先defload_skills(self,skills_dir:str):从目录加载所有Skills定义文件# 遍历Skills目录forfilenameinos.listdir(skills_dir):# 只处理YAML文件iffilename.endswith(.yaml)orfilename.endswith(.yml):filepathos.path.join(skills_dir,filename)# 读取YAML文件withopen(filepath,r,encodingutf-8)asf:skillyaml.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_skillsnameinself.skills in_mcpnameinself.mcp_toolsifin_skillsandin_mcp:# 同名冲突根据策略决定ifself.conflict_strategymcp_first:# MCP优先策略print(f[冲突解决]{name}同时存在于Skills和MCP选择MCP)returnself.mcp_tools[name]elifself.conflict_strategyskills_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_capsagent.py 主Agent同时使用Skills和MCP两种能力 演示两种能力的协同工作 importjsonfromcapability_routerimportCapabilityRouterclassDualCapabilityAgent:同时支持Skills和MCP的Agentdef__init__(self):# 创建能力路由器self.routerCapabilityRouter()# 加载Skills定义self.router.load_skills(skills)# 注册MCP工具self.router.register_mcp_tool(nameget_weather,# 工具名description查询城市天气,# 工具描述endpointhttp://localhost:8000# MCP Server地址)asyncdefexecute(self,capability_name:str,params:dict):执行一个能力请求# 通过路由器解析能力capself.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 Serverrespawaitclient.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_templateskill.get(prompt,)# 用参数填充提示词模板promptprompt_templateforkey,valueinparams.items():# 替换模板中的占位符promptprompt.replace(f{{{{{key}}}}},str(value))# 生产环境这里要调用LLM来执行# 简化版直接返回构造好的提示词return{capability:skill[name],type:skill,prompt:prompt,note:实际执行需要调用LLM}deflist_capabilities(self):列出Agent的所有能力capsself.router.list_all_capabilities()print(Agent当前可用能力:)forcapincaps:print(f [{cap[type].upper()}]{cap[name]}:{cap[description]})returncaps# 运行演示asyncdefmain():主入口函数agentDualCapabilityAgent()# 列出所有能力agent.list_capabilities()# 执行Skill写诗print(\n--- 执行Skill: write_poem ---)poem_resultawaitagent.execute(write_poem,{topic:秋天,style:古典})print(f结果:{json.dumps(poem_result,ensure_asciiFalse,indent2)})# 执行MCP查天气print(\n--- 执行MCP: get_weather ---)weather_resultawaitagent.execute(get_weather,{city:Beijing})print(f结果:{json.dumps(weather_result,ensure_asciiFalse,indent2)})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_skillsnameinself.skills# 是否在Skills中in_mcpnameinself.mcp_tools# 是否在MCP中ifin_skillsandin_mcp:# 同名冲突根据策略决定ifself.conflict_strategymcp_first:# MCP优先因为MCP更可靠returnself.mcp_tools[name]elifself.conflict_strategyskills_first:# Skills优先returnself.skills[name]elifself.conflict_strategyerror:# 直接报错强制开发者解决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_strategyerror:# 严格模式直接拒绝注册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需要同时操作外部系统和生成内容团队中有人擅长写声明有人擅长写代码各取所长常见问题与避坑QSkills可以替代MCP吗不能。Skills没有可执行代码它依赖Agent的推理能力来完成。对于需要精确执行的操作Skills的不可靠性是不可接受的。Q一个能力能否同时用Skills和MCP两种方式实现可以但不建议。如果你确实需要两种方式都支持务必使用不同的名称比如analyze_data_skill和analyze_data_mcp然后在路由器里做区分。QSkills的提示词写多长合适控制在200字以内。太短了Agent不知道该干什么太长了会干扰Agent的其他指令。核心信息是做什么和怎么做参数细节由声明文件的parameters部分处理。QMCP工具和Skills哪个开发效率更高Skills更快。写个YAML文件就行不用写代码不用调试。但代价是执行结果不够可靠。如果是快速验证想法先用Skills跑通流程确认需求后再用MCP重写。小结Skills和MCP是Agent能力扩展的两种互补方式。Skills是声明式的灵活但不精确适合创意类任务。MCP是协议级的精确但开发成本稍高适合确定性操作。两者搭配使用时一定要建立命名规范和冲突解决机制否则同名冲突会让你 debug 到怀疑人生。下一篇我们聊MCP工具市场生态看看开源社区里有哪些现成的MCP Server可以直接用以及怎么安全地使用第三方MCP工具。相关推荐Agent协议生态全景MCP/A2A/ARD/AP2/ACP对比与选型MCP工具市场生态发现、分享与使用开源MCP ServerTools原语深度解析从定义到调用全流程