经常有人问我:MCP和Skills到底是不是竞争关系?是不是有了MCP就没必要再学Skills了?这两个词在AI应用开发圈子里被反复提起,尤其是在构建多代理(Multi-Agent)系统、搞SubAgent架构的时候,很多团队的架构选型会卡在这个岔路口。
我自己在搭一套面向复杂任务拆解的Agent系统时,把MCP和Skills各用在了不同的层,跑了几个月下来最深的体会是:这不是二选一的问题,而是两种不同粒度的抽象工具。MCP解决的是"模型如何拿到外部能力",Skills解决的是"模型如何在内部复用高质量行为路径"。当你想构建一个高效、可扩展、好维护的SubAgent架构时,两者缺一个都会明显感觉系统少了半边天。
这篇文章我会把自己拆解这套架构的完整思考、代码示例、踩坑记录都摊开来讲。无论你是刚开始了解Agent开发,还是已经在多个Agent之间做调度编排,相信都能从中找到可以直接落地的东西。
1. MCP与Skills的本质逻辑:解决的不是同一个问题
先说结论:MCP和Skills定位完全不同,二者是互补关系。一个管外部连接,一个管内部复用。
1.1 MCP的定位:一套外部能力的"设备接口标准"
MCP全称是Model Context Protocol,它解决的是模型与外部工具、数据源之间的标准化通信问题。在没有MCP之前,每个Agent要接一个外部API就得写一套自定义的调用封装,不同的Agent之间还无法共享这些封装。MCP的出现把这件事变成了"插线即用"。
我习惯把它类比成USB-C接口。以前出门带一堆充电线,不同设备各有各的接口,现在统一了,一个接口能接显示器、硬盘、手机、甚至扩展坞上的风扇。MCP做的是同一件事:它统一了模型与外部工具之间的协议,让工具变成一个个可以即插即用的标准接口。比如你有一个代码仓库管理工具、一个数据库查询器、一个搜索服务,只要它们都实现了MCP协议,Agent就能通过同一套机制调用它们。
从技术形态上看,一个MCP Server通常通过stdio或HTTP暴露一组工具(tools)、资源(resources)和提示(prompts)。对于构建SubAgent的人来说,MCP server更像是给每个Worker配备的"外设",它们负责具体的执行能力,比如搜文档、算指标、调数据库。
1.2 Skills的本质:预置的"高杠杆行为路径"
Skills则是另一码事。它是把某类任务的高质量解决路径写成了结构化的指令包,例如一个Markdown文件或者JSON配置,告诉模型"遇到这类问题时,按照这个脉络去处理,不要直接暴力作答"。
我的理解是:Skills是给模型注入的"方法论插件"。比如写代码评审这类任务,你可以写一个Code Review Skill,里面定义先读变更上下文、再检查单元测试覆盖、最后按严重性输出问题清单。这个Skill不需要连接任何外部工具,它只是在模型推理过程中起引导作用,让输出的质量从"随机发挥"变成"稳定在80分以上"。
Skills最大的价值在于沉淀经验。一个团队对某类任务的执行规范、输出格式、避坑要点,可以全部写进Skill里,然后被任意Agent加载使用。它让组织知识以可移植的形式流动起来,而不是封存在某个人的脑子里。
1.3 为什么这两个概念总被混在一起谈
我在团队内做过一次调研,发现大部分人混淆MCP和Skills的原因主要有三点。
第一,两者的边界在"工具调用"场景发生了重叠。一个Skill里经常会在处理步骤中提到"调用某某工具",这个工具背后恰好是MCP接进来的。于是很多人误以为MCP是Skills的一部分,或者反过来认为Skills只是MCP的一种简化表达。
第二,各个大模型厂商都在推行自己的"Skills兼容层",宣称可以通用的路线,实际落地时却在命名和实现上五花八门。这种生态不成熟让不少人产生了"只要会一个就等于会另一个"的错觉。
第三,很多人是从演示Demo里学习的。Demo里往往既没有深度的外部工具接入,也没有复杂的任务拆解,一个Skill带着一次Tool Call就把任务演示完了。看了这类内容自然很难区分两者的真实分工。
说到底,MCP和Skills解决的是两个维度的问题。MCP是水平维度的扩展——你能连接多少个外部系统;Skills是垂直维度的深化——你对某类问题的内部处理达到了多高的成熟度。清晰地区分这两者,是搭建SubAgent架构的第一步。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. SubAgent架构的核心价值:为什么单一Agent撑不住复杂任务
在讨论MCP和Skills如何使用之前,必须先回答一个问题:为什么非得拆出来SubAgent不可?
2.1 单一Agent的三大瓶颈
我最初做的那套系统是一个巨大的单一Agent,所有工具、所有知识、所有流程全部塞进一个上下文里。测试阶段跑得挺好,一到真实任务就把问题暴露了。
第一个瓶颈是上下文膨胀。每个外部工具的返回结果、中间推理过程、历史会话记录全部堆积在上下文窗口里。到第20轮对话的时候,模型已经开始"遗忘"最初的任务目标了。上下文窗口不是无限的,就算窗口够了,模型从海量内容中提取关键信息的能力也会下降。
第二个瓶颈是工具冲突。不同工具之间的调用参数有细微差别,当工具数量超过某个阈值时,模型在规划阶段的指令选择就开始出错。它会纠结到底该用"查询用户订单"这个工具,还是用"查询用户信息"这个工具,甚至出现工具名混淆。
第三个瓶颈是反馈回路混乱。单一Agent在面对多步骤任务时,如果某一步出错了,它会试图在同一个流程里反复修正,而不是隔离开来重试。这种混沌的反馈机制让排查问题变得非常困难,出现一个错误时根本分不清是工具的问题还是模型规划的问题。
2.2 SubAgent拆分的两种模式
针对上述瓶颈,SubAgent拆分大致有两种模式。我在实践中认为两种模式各有适用场景。
一种叫任务垂直化拆分,就是按照业务流程不同环节去拆。举个例子,一个市场调研系统,可以拆成"数据采集Agent"、"数据清洗Agent"、"报告生成Agent"。每个Agent只负责一个业务环节,输入输出边界清晰,环节之间天然解耦。这种方式适合业务流程固定、环节之间顺序依赖比较强的场景。
另一种叫能力水平化拆分,就是按照技能类型去拆。比如一个内容创作平台,可以拆出"文案生成Agent"、"视觉设计Agent"、"SEO优化Agent"。它们都可以接收同一份素材,但各自用不同的能力去处理。这种方式适合并行处理特征明显、输出需要多维度汇合的场景。
2.3 Supervisor + Worker:一种被验证的SubAgent协作模式
在实践SubAgent架构时,我最常用的是Supervisor加Worker模式。核心思想是:有一个主控Agent负责接收任务、拆解计划、分发子任务、收集结果、组织最终输出;多个Worker Agent各自执行业务领域内的具体任务。
MCP和Skills在这个模式中的位置非常清楚:
- Supervisor层:加载全局Skills,比如任务拆解Skill、路由决策Skill、结果审查Skill。它需要的是"判断力",所以Skills是核心。
- Worker层:按需加载领域Skill,并通过MCP接入外部工具完成实际执行。它需要的是"执行力",所以MCP是核心。
这种分层设计的好处在于:全局Skill对每个Agent都生效,领域Skill只对特定Worker生效,MCP工具则在Worker内部保持隔离。整个架构的调试粒度一下子变得清晰了——问题出在决策层还是执行层,一眼就能定位。
从资源效率角度看,SubAgent架构还有另一个隐性优势:每个Worker可以按需加载自己的上下文,Supervisor只维护任务级别的状态,而不是把每个Worker的全部上下文都塞进自己窗口里。这让整个系统能在更长链条的任务中保持稳定的决策质量。
3. Skills在SubAgent里的落地设计:粒度、描述与路由
既然Skill在SubAgent系统里这么重要,那怎么设计Skill才能让它真正发挥作用?我在这部分踩过不少坑,总结下来三个维度最关键。
3.1 Skill粒度:过大容易耦合,过小容易碎片化
Skill的粒度设计是最考验功力的一环。粒度太大,一个Skill里面包含了任务拆解、代码修改、测试执行等多个环节,Agent在执行时反而会混乱,因为它不知道该在哪个步骤停下来。粒度太小,系统需要维护大量碎片化Skill,路由决策负担极重。
我个人的经验判断标准是:如果一个Skill能被压缩成三句话讲清楚,说明它过于简单,不如直接写进系统提示词;如果一个Skill需要超过800字才能描述完整流程,说明它承载了太多职责,应该拆开。
以代码评审Skill为例,合理的划分是:一个"变更差异分析Skill"负责读懂改了什么、影响面在哪里;一个"测试覆盖评估Skill"负责判断改动是否配套了足够的测试;一个"风险报告输出Skill"负责按统一格式输出评审结果。三个Skill协同工作,但各自边界清晰。
3.2 描述质量决定命中率:写不好描述就是让模型瞎猜
Skill的描述文本是模型判断"该不该用我这个Skill"的唯一依据。描述质量直接决定了路由命中率。
我以前写过一条糟糕的描述:"Handle the review process."这种描述几乎没有信息量,模型完全不知道该在什么场景下触发它。
后来我按这个模式重写:"Use this skill when you need to conduct a code review on recent changes submitted to the repository. The review covers code correctness, test coverage, security issues, and performance risks. The output should follow the structured severity-based format defined in the skill."
两种描述的效果差距是巨大的。前一种模型大概只有30%的概率在合适时机主动触发,后一种能到85%以上。所以我的建议是:描述里必须包含"什么时候用"、"覆盖哪些检查面"、"输出长什么样"三个核心要素。
3.3 Skills的路由触发策略:别过度依赖模型自觉
如果同一个Agent加载了5个以上的Skills,路由决策的复杂度就会指数级上升。模型有可能会同时选中两个功能重叠的Skill,导致输出里的信息互相打架。
我在实践中采用的策略是三级路由:
- 关键词硬路由:任务文本里出现明确关键词时,不走模型判断,直接注入对应Skill。
- 意图软路由:没有明确关键词时,把任务描述和所有Skill描述一起交给模型打分,选择得分最高的一个。
- 人工强制路由:在编排层直接指定某个Worker必须使用哪个Skill,绕过模型决策。
这种三级策略组合下来,Skill的命中率稳定提升了很多。其实很多人忽略了:路由策略的选择本身也是系统设计的一部分,不应该完全交给模型自由发挥。
4. 一个最小可用的MCP Server接入实战:把代码写出来再谈架构
前面聊了那么多抽象概念,这一节落地。我以Python生态为例,带大家从零写一个最小的MCP Server,然后接到SubAgent架构的Worker里。这一节的内容我在很多团队分享过,照着走基本不会出问题。
4.1 环境准备:安装核心依赖
首先需要安装MCP的Python SDK。我用的版本是1.0+,包名是mcp。如果你用的是uv这个Python包管理器,一条命令搞定:
bash复制uv add mcp
如果是普通pip环境,执行:
bash复制pip install "mcp[cli]"
装完后可以用mcp命令行工具做基本的调试。这里提醒一下:MCP生态更新速度很快,务必锁定版本号,别用latest,否则过两个月再来跑代码,接口可能就变了。
4.2 编写一个带业务逻辑的MCP Server
下面这段代码实现了一个"查询模拟库存"的服务。它暴露了一个工具,接收产品ID,返回库存数量。代码尽量简化,核心是展示MCP Server的结构。
python复制from mcp.server.fastmcp import FastMCP
mcp = FastMCP("InventoryWorker")
@mcp.tool()
async def query_stock(product_id: str) -> dict:
"""查询指定产品在模拟库存系统中的实时库存数量。"""
# 实际项目中这里会去调真实库存服务
mock_db = {
"P1001": {"name": "无线键盘", "stock": 42},
"P1002": {"name": "鼠标垫", "stock": 127},
"P1003": {"name": "显示器支架", "stock": 8},
}
if product_id not in mock_db:
return {"code": 404, "message": f"未找到产品 {product_id}"}
return {"code": 0, "data": mock_db[product_id]}
if __name__ == "__main__":
mcp.run(transport="stdio")
这段代码里最关键的是@mcp.tool()这个装饰器。它把普通函数注册成了MCP协议中的"工具",模型那边通过工具名和描述就能知道这个工具是干什么的。
函数上方的docstring一定要认真写,它会被当作工具描述传给模型。描述里包含哪些信息,模型调用这个工具的准确率就有多高。我见过太多人忽略docstring,直接写个pass,结果模型根本不会主动调用。
4.3 在Worker Agent中挂载MCP Server并调用
接着把这个Server接入Worker。下面这段代码模拟的是一个Worker Agent启动时连接MCP Server,并调用tools的过程。
python复制from mcp.client.stdio import stdio_client, StdioServerParameters
from mcp import ClientSession
server_params = StdioServerParameters(
command="python",
args=["inventory_server.py"],
)
async def call_inventory_tool(product_id: str):
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool(
"query_stock",
arguments={"product_id": product_id}
)
return result.content
这段代码的核心是stdio_client和ClientSession。stdio_client负责启动子进程并与它建立标准输入输出通道,ClientSession封装了MCP协议的握手和调用逻辑。
值得留意的是:这里我用的是stdio传输方式。这种方式适合Worker Agent和MCP Server同机部署的场景,资源开销小、隔离性好。如果MCP Server部署在其他机器上,可以用StreamableHttpServerParameters走HTTP传输,但会增加网络延迟和鉴权成本,本地够用就先用stdio。
4.4 接入SubAgent架构时的三个关键改动
直接把上面的代码塞进生产级SubAgent系统是不够的,至少还需要三个层面的改动。
第一,连接生命周期管理。生产环境不能每次调用都重新启动子进程,那开销太大了。我会用连接池常驻,MCP Server进程随Worker启动,多个请求复用同一个ClientSession。
第二,错误重试与超时控制。MCP调用不像本地函数调用,子进程可能因为各种原因挂掉或者卡住。我习惯在每个call上包裹超时控制和有限次数的重试,超时时间根据工具性质区别对待,查询类3秒,写入类5秒。
第三,工具名冲突处理。当Worker挂了多个MCP Server时,可能会出现相同的工具名。比如订单服务有个get_status,物流服务也可能有个get_status。在注册给Agent之前就要做前缀隔离,改成order_get_status和logistics_get_status,否则模型脚本化工具选择时会出问题。
5. 调度与通信设计:SubAgent之间怎么协同才高效
SubAgent架构的难点不只是写好单个Agent,更在于多个Agent之间怎么调度、怎么传数据、怎么同步状态。这一节聊聊我在调度层总结的经验。
5.1 同步与异步的取舍:不是所有任务都该等结果
SubAgent之间的任务依赖关系决定了调度策略。我用一张表来梳理常见场景:
| 场景特征 | 推荐调度方式 | 理由 |
|---|---|---|
| 后续步骤必须依赖前一步结果 | 同步阻塞 | 数据依赖强,无法并行 |
| 多个子任务互相独立 | 异步并行 | 充分利用资源,缩短总耗时 |
| 结果需要实时推送给用户 | 同步流式 | 体验优先 |
| 离线批处理任务 | 异步队列 | 不阻塞主流程,可重试 |
一个常见的坑:为了追求并发把有依赖关系的任务也强行并行化,最后不得不用复杂的合并逻辑去处理错位的结果。依赖关系是客观存在的,不该臆造并行。
5.2 上下文边界的设计:状态必须持有在明确的Owner手里
SubAgent系统最容易被忽视的问题是"上下文到底归谁管"。我在第一版设计里,Supervisor和Worker各维护一份自己的上下文副本,结果就出现了同一份数据在两边不一致的情况。后来改成职责分明的方式:
- Supervisor只维护"任务分解树"和"子任务状态",不关心中间过程数据。
- Worker负责维护自己执行过程中的完整上下文,只在完成时返回结构化结果。
- 跨Agent传递的数据统一采用"接口协议"定义,任何额外的中间变量都不允许私自传递。
这个设计最大的价值在于排障。一旦某个Worker出错,我只需要看它自己的上下文日志,不需要在整个系统里追踪数据流。
5.3 结果聚合与冲突消解:多人协作必然需要裁判
当多个Worker并行产出结果时,聚合阶段很容易出现内容冲突。比如两个Worker都负责市场分析,一个认为市场趋势向上,另一个认为平稳,Supervisor该听谁的?
我的做法是引入"置信度分级"机制。每个Worker返回结果时必须带上自己对该结论的置信度评估和执行过程的完整摘要。Supervisor在聚合阶段会优先采纳置信度高且执行过程没有异常的结果。如果两个结果的置信度都很高但矛盾,Supervisor会触发一个"冲突消解Skill",把矛盾点重新派给第三个独立Worker做仲裁。
这种机制的性价比极高。实现成本不高,但显著降低了"假阳性结论"出现的概率。
5.4 MCP在SubAgent之间的共享与隔离策略
最后说一下MCP工具在多个SubAgent之间的共享问题。最直接的方式是每个Worker独立启动自己的MCP Server进程,Server之间互不可见。这套方案隔离性好,但资源开销大。
折中方案是共享一个常驻MCP Server进程,通过Client Session做逻辑隔离。实现上可以让每个Worker拿到一个独立的Session,互不干扰。这种方案适合对资源敏感、但工具之间没有互相污染风险的场景。
第三种方案是给工具加命名空间前缀。比如文档处理Worker只允许使用带doc_前缀的工具,数据查询Worker只用data_前缀的工具。这种约束可以在注册到Agent时通过白名单实现,相当于人为加了一道权限边界。
6. 实测中最常踩的坑:排查链路与修复实录
前面都是方法论和架构设计,最后分享几个我在实际运行中遇到的具体问题和完整排查过程。这些问题极具代表性,几乎每个搞SubAgent系统的人都会碰到。
6.1 Skill命中失效:描述重叠引发的路由混乱
有段时间系统频繁出现"任务被分配给错误的Worker"的情况。比如代码评审类任务被分到了文档处理Worker。
排查的第一步是查看Supervisor当时的路由决策日志,发现模型选择了文档处理相关的Skill。进一步看模型给出的选择理由,它认为"代码评审"这个输入里的"评审"和技能库里的"文档一致性审查"的触发条件"审查"语义重叠,于是混淆了。
根因是Skill描述语义边界不清。修复方案是:在每条Skill描述里增加"不适用场景"说明,明确排除语义相似的相邻技能。比如在文档Skill末尾加上"此Skill不适用于源代码变更的评审,该类任务请使用代码评审Skill"。加了这行话之后,路由准确率从76%提高到93%以上。
6.2 MCP Server连接失败:进程生命周期管理不当
一次压测中,高并发请求导致大量MCP调用报"connection closed"错误。起初怀疑是MCP SDK的bug,查了日志发现所有子进程都被操作系统杀掉了。
根因是子进程的内存占用超过了系统限制。因为每次请求我都创建新的Server进程,进程数量叠加导致内存飙升。修复方案是引入连接缓存机制,复用已有的子进程,并且把连接空闲超时设置到合理范围,长时间不用的连接主动释放。
修复后内存占用曲线平稳了很多。这个教训告诉我:MCP Server的进程生命周期管理是整个系统稳定性的隐形瓶颈,光盯着业务逻辑写代码远远不够。
6.3 SubAgent间数据口径不一致:接口协议定义的重要性
还有一个让我印象深刻的线上事故:两个Worker返回的"用户数量"数值不一致,一个结果是2.3万,一个是23万,相差正好10倍。追查发现是一个Worker直接调了原始数据库里的记录数,另一个Worker调了某业务指标层的汇总数,两者的统计口径本来就不同。
修复方案是:在系统里增加了统一的数据口径字典,所有跨Agent传输的指标必须先经过口径注册,不同的口径定义不允许直接混用。并且在Supervisor聚合阶段增加数值合理性检查,超出预期范围的结果自动拦截并提醒人工复核。
这个坑给我的启示是:两个Worker之间传的不仅是数据,更是对数据的解释。接口协议里必须在数据结构之外,定义数据的语义规则,否则系统就像一个语言不通的跨国团队在合作,表面上在沟通,实际上一团乱麻。
6.4 排查链路总结
我把上面几次排障过程归纳成一套通用的排查套路,现在每次系统出问题都按这条链路走:
- 先看路由决策层的日志,确认任务是否被分发到了正确的Worker,排除了Skill路由问题。
- 再看Worker执行层的日志,确认工具调用是否成功、返回结果是否符合预期。
- 接着看数据聚合层的输出,确认最终结果的数值和结构是否合理。
- 最后检查基础设施层,确认进程、网络、内存等资源状态是否健康。
顺着这个链路可以快速缩小问题范围。很多情况下问题并不在Agent的"智能"上,而是在工程基础建设上。SubAgent架构跑得稳不稳,大概率取决于这些看似不起眼的细节。
构建一套高效SubAgent架构没有银弹。MCP、Skills、调度策略、进程管理,每一块都需要根据实际业务场景精细设计,并且在真实的流量下不断打磨。
我个人在实际操作中最深的体会是:别迷信任何单一技术概念。MCP再优秀,也只解决了工具连接的问题;Skills再精巧,也只沉淀了行为方法。真正让整套系统高效运转的,是设计者对这个架构每一层的清晰认知,以及对边界条件的严格把控。下次再有人问你MCP和Skills选哪个,你可以笃定地告诉他:两者都留下,各安其位,架构自然就顺了。
