摘要: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: string
    required: true
    description: "收件人邮箱地址"
  subject:                # 邮件主题
    type: string
    required: true
    description: "邮件主题"
  body:                   # 邮件正文
    type: string
    required: true
    description: "邮件正文内容"
# 执行提示,告诉Agent如何完成这个任务
# 注意这里不是代码,而是自然语言指令
prompt: |
  你现在需要发送一封邮件。请按照以下步骤操作:
  1. 确认收件人邮箱格式正确
  2. 构造邮件内容
  3. 调用SMTP服务发送
  收件人: {{to}}
  主题: {{subject}}
  正文: {{body}}

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

MCP是什么

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

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

"""
MCP Server实现发邮件工具
和Skills不同,这里有完整的可执行代码
"""
from mcp.server import Server
import smtplib
from email.mime.text import MIMEText

# 创建MCP Server实例
server = Server("email-server")

@server.tool("send_email")
async def send_email(to: str, subject: str, body: str) -> str:
    """发送邮件的MCP工具实现"""
    # 构造邮件消息对象
    msg = MIMEText(body)           # 设置邮件正文
    msg["Subject"] = subject       # 设置邮件主题
    msg["From"] = "agent@example.com"  # 设置发件人
    msg["To"] = to                  # 设置收件人
    
    # 连接SMTP服务器并发送
    with smtplib.SMTP("smtp.example.com", 587) as smtp:
        smtp.starttls()             # 启用TLS加密
        smtp.login("user", "pass")  # 登录SMTP服务器
        smtp.send_message(msg)      # 发送邮件
    
    # 返回发送结果
    return f"邮件已发送至 {to}"

if __name__ == "__main__":
    # 启动MCP Server
    server.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: string
    required: true
    description: "诗歌的主题,比如春天、友情、离别"
  style:                     # 诗歌风格
    type: string
    required: false
    default: "古典"
    description: "诗歌风格,可选古典或现代"
# 执行提示词,Agent根据这个提示来执行
prompt: |
  请根据以下信息写一首诗:
  主题: {{topic}}
  风格: {{style}}
  
  要求:
  1. 诗歌要押韵
  2. 意象要生动
  3. 情感要真挚
  4. 控制在4到8句

mcp_server.py

"""
天气查询MCP Server
这是一个有完整可执行代码的工具
"""
import json
import httpx
from mcp.server import Server

# 创建MCP Server实例
server = Server("weather-server")

@server.tool("get_weather")
async def get_weather(city: str) -> str:
    """查询指定城市的天气信息"""
    # 模拟调用天气API
    # 生产环境替换为真实的天气API
    async with httpx.AsyncClient() as client:
        # 调用免费天气API
        resp = await client.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格式结果
        return json.dumps(weather_info, ensure_ascii=False)

if __name__ == "__main__":
    # 启动MCP Server
    server.run()

capability_router.py

"""
能力路由器,负责区分请求应该走Skills还是MCP
这是解决两者冲突的关键组件
"""
import yaml
import os

class CapabilityRouter:
    """能力路由器,统一管理Skills和MCP能力"""
    
    def __init__(self):
        # Skills能力注册表,存储已加载的Skill定义
        self.skills = {}
        # MCP能力注册表,存储已注册的MCP工具
        self.mcp_tools = {}
        # 冲突解决策略配置
        self.conflict_strategy = "mcp_first"  # 默认MCP优先
    
    def load_skills(self, skills_dir: str):
        """从目录加载所有Skills定义文件"""
        # 遍历Skills目录
        for filename in os.listdir(skills_dir):
            # 只处理YAML文件
            if filename.endswith(".yaml") or filename.endswith(".yml"):
                filepath = os.path.join(skills_dir, filename)
                # 读取YAML文件
                with open(filepath, "r", encoding="utf-8") as f:
                    skill = yaml.safe_load(f)
                    # 注册到Skills表
                    self.skills[skill["name"]] = skill
                    print(f"已加载Skill: {skill['name']}")
    
    def register_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}")
    
    def resolve_capability(self, name: str) -> dict:
        """
        解析能力请求,决定走Skills还是MCP
        这是处理同名冲突的核心方法
        """
        # 检查是否同时存在于Skills和MCP中
        in_skills = name in self.skills
        in_mcp = name in self.mcp_tools
        
        if in_skills and in_mcp:
            # 同名冲突,根据策略决定
            if self.conflict_strategy == "mcp_first":
                # MCP优先策略
                print(f"[冲突解决] {name} 同时存在于Skills和MCP,选择MCP")
                return self.mcp_tools[name]
            elif self.conflict_strategy == "skills_first":
                # Skills优先策略
                print(f"[冲突解决] {name} 同时存在于Skills和MCP,选择Skills")
                return self.skills[name]
            else:
                # 报错策略,禁止同名
                raise ValueError(f"能力 {name} 同时存在于Skills和MCP,请解决冲突")
        elif in_skills:
            # 只有Skills中有
            return self.skills[name]
        elif in_mcp:
            # 只有MCP中有
            return self.mcp_tools[name]
        else:
            # 都没有
            return None
    
    def list_all_capabilities(self) -> list:
        """列出所有可用能力"""
        all_caps = []
        # 添加Skills能力
        for name, skill in self.skills.items():
            if name not in self.mcp_tools:  # 排除已由MCP处理的同名能力
                all_caps.append({
                    "name": name,
                    "type": "skill",
                    "description": skill.get("description", "")
                })
        # 添加MCP能力
        for name, tool in self.mcp_tools.items():
            all_caps.append({
                "name": name,
                "type": "mcp",
                "description": tool.get("description", "")
            })
        return all_caps

agent.py

"""
主Agent,同时使用Skills和MCP两种能力
演示两种能力的协同工作
"""
import json
from capability_router import CapabilityRouter

class DualCapabilityAgent:
    """同时支持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地址
        )
    
    async def execute(self, capability_name: str, params: dict):
        """执行一个能力请求"""
        # 通过路由器解析能力
        cap = self.router.resolve_capability(capability_name)
        
        if cap is None:
            # 能力不存在
            return {"error": f"能力 {capability_name} 不存在"}
        
        if cap.get("type") == "mcp":
            # 走MCP路径,调用工具
            return await self._call_mcp(cap, params)
        else:
            # 走Skills路径,用提示词执行
            return await self._execute_skill(cap, params)
    
    async def _call_mcp(self, tool: dict, params: dict):
        """调用MCP工具"""
        import httpx
        # 构造MCP调用请求
        async with httpx.AsyncClient() as client:
            # 发送工具调用请求到MCP Server
            resp = await client.post(
                f"{tool['endpoint']}/mcp/tools/call",
                json={
                    "tool_name": tool["name"],
                    "arguments": params
                }
            )
            return resp.json()
    
    async def _execute_skill(self, skill: dict, params: dict):
        """执行Skill"""
        # 获取Skill的提示词模板
        prompt_template = skill.get("prompt", "")
        # 用参数填充提示词模板
        prompt = prompt_template
        for key, value in params.items():
            # 替换模板中的占位符
            prompt = prompt.replace(f"{{{{{key}}}}}", str(value))
        
        # 生产环境这里要调用LLM来执行
        # 简化版直接返回构造好的提示词
        return {
            "capability": skill["name"],
            "type": "skill",
            "prompt": prompt,
            "note": "实际执行需要调用LLM"
        }
    
    def list_capabilities(self):
        """列出Agent的所有能力"""
        caps = self.router.list_all_capabilities()
        print("Agent当前可用能力:")
        for cap in caps:
            print(f"  [{cap['type'].upper()}] {cap['name']}: {cap['description']}")
        return caps

# 运行演示
async def main():
    """主入口函数"""
    agent = DualCapabilityAgent()
    
    # 列出所有能力
    agent.list_capabilities()
    
    # 执行Skill:写诗
    print("\n--- 执行Skill: write_poem ---")
    poem_result = await agent.execute("write_poem", {
        "topic": "秋天",
        "style": "古典"
    })
    print(f"结果: {json.dumps(poem_result, ensure_ascii=False, indent=2)}")
    
    # 执行MCP:查天气
    print("\n--- 执行MCP: get_weather ---")
    weather_result = await agent.execute("get_weather", {
        "city": "Beijing"
    })
    print(f"结果: {json.dumps(weather_result, ensure_ascii=False, indent=2)}")

if __name__ == "__main__":
    import asyncio
    asyncio.run(main())

效果验证

先启动MCP Server,再运行Agent。

# 终端1:启动天气查询MCP Server
python mcp_server.py

# 终端2:运行Agent
python agent.py

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

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

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

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

# 错误示范:没有冲突解决机制的路由逻辑
def resolve_capability_buggy(name: str):
    """有bug的能力解析,没有处理同名冲突"""
    # 先查Skills,找到就返回
    if name in skills:
        return skills[name]
    # 再查MCP,找到就返回
    if name in mcp_tools:
        return mcp_tools[name]
    return None

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

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

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

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

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

def register_mcp_tool(self, name: str, description: str, endpoint: str):
    """注册MCP工具时检查是否与Skills冲突"""
    # 检查是否与已有Skill同名
    if name in self.skills:
        # 打印警告信息
        print(f"[警告] MCP工具 {name} 与已注册的Skill同名!")
        print(f"  Skill描述: {self.skills[name].get('description')}")
        print(f"  MCP描述: {description}")
        print(f"  当前冲突策略: {self.conflict_strategy}")
        # 根据策略决定是否继续注册
        if self.conflict_strategy == "error":
            # 严格模式,直接拒绝注册
            raise ValueError(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工具。


相关推荐

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐