先说一个我最近经常被问到的问题:“Agent Skills是不是要取代MCP?”问的人多了,我发现大家其实是被两个概念的命名带偏了——都带“Agent/模型”的影子,都像是给AI加能力,很容易被当成同一类东西。实际上在真实项目里,这俩完全不在一个层面上,也没法互相替代。
我最早把两者彻底区分开,是在一次Agent平台的技术方案评审上。当时团队想做一个内部数据库巡检助手:既能查元数据、拉慢查询,又希望AI按固定流程输出报告。一开始我们只接了一个MCP Server,结果发现工具是能调了,但AI每次调用的顺序、报告的结构、异常的判断标准都不稳定,今天一个样明天一个样。后来引入Agent Skills把流程固化成标准作业程序,才真正把“能调工具”变成了“会干活”。
这篇文章就以“Agent Skills和MCP的关系”为主线,把两者的定位差异、协作方式、落地踩坑一次性讲透。适合正在做Agent平台、写MCP Server,或者刚被Agent Skills这个概念搞晕的开发者。看完你至少能分清:什么时候该写Skill,什么时候该做MCP Server,以及它们是怎么在同一个Agent里配合的。
1. 先搞清楚两个概念到底在解决什么问题
1.1 Agent Skills:Agent的“行为模块化”
Agent Skills最早由Anthropic在2025年10月推出,后来Claude Code、Cline、Roo Code这些主流Agent工具都跟进了类似机制。它的本质很简单:把一段"做事的方法"固化成一个可复用的技能包,当Agent遇到匹配的场景时,按需读取这个技能包并按里面的步骤执行。
一个标准的Skill目录长这样:
skills/ code-review/ SKILL.md # 技能定义 scripts/ # 可选:辅助脚本 assets/ # 可选:模板、文档 database-inspection/ SKILL.md assets/ report_template.mdSKILL.md是关键文件,用YAML frontmatter声明name和description,正文写具体的执行指令。模型会先读description判断“当前任务是否匹配这个技能”,匹配后才会加载正文——这也就是所谓的"按需加载,靠近使用时注入上下文",而不是把所有技能一股脑塞进系统提示词里。
举个例子,我一个代码审查Skill的description会写:"当用户要求检查代码质量、审查Pull Request、找潜在Bug时使用。典型触发语:'帮我review一下这段代码'、'这PR有没有问题'。"正文则规定先看整体逻辑、再查错误处理、最后给分级建议。这套东西解决的核心问题是:同样的任务每次让Agent自由发挥,结果就会漂移。有了Skill,能把最佳实践固定下来,让Agent的行为稳定、可复制、可审计。
1.2 MCP:Agent的“外部世界连接标准”
MCP(Model Context Protocol)是Anthropic在2024年11月开源的协议,全称是模型上下文协议。它的目标非常明确:标准化AI应用与外部工具、数据源、文件系统之间的交互方式。很多人把它类比成“AI界的USB-C”,我觉得这个类比很贴切——以前每个工具都要单独给AI做私有集成,现在只要实现MCP接口,任何支持MCP的Agent都能即插即用。
MCP的架构包含四个角色:Host是宿主应用(比如Claude Desktop、IDE插件);Client负责在Host里管理连接;Server是工具提供方,把能力包装成标准接口;Transport负责底层通信,本地常用stdio,远程用HTTP/SSE。
这个体系里有三个核心原语:
- Tools:可被AI调用的操作,比如查询数据库、打开浏览器、发送消息
- Resources:可被AI读取的数据,比如配置文件、日志文件、数据库Schema
- Prompts:Server端预置的提示模板,告诉AI“这个工具通常怎么用”
MCP解决的是工具接入碎片化的问题。你想想,光是浏览器自动化就有Playwright MCP、Chrome DevTools MCP、Browser Use MCP好几个选择,数据库类MCP更是数不清。每个都走标准协议,Agent接起来成本就低很多。热词里提到的同花顺MCP、Burp Suite MCP、蓝湖MCP、Cheat Engine桥接MCP、通义灵码连Oracle,本质上都是"把某个域的能力包装成MCP Server,让AI能调用"。
1.3 一张表说清定位差异
| 维度 | Agent Skills | MCP |
|---|---|---|
| 定位 | Agent内部的行为能力封装 | Agent与外部系统之间的连接协议 |
| 解决的问题 | “怎么思考、怎么组织动作” | “怎么触达外部工具和数据” |
| 载体 | SKILL.md + 可选脚本、模板 | Server + Client + Transport |
| 是否需要网络 | 不需要,纯本地指令 | 需要Server在对应环境运行 |
| 加载方式 | Agent按需注入上下文 | 工具列表动态发现、远程调用 |
| 所属层次 | 认知层 / 行为层 | 连接层 / 协议层 |
| 典型场景 | 代码审查、报告生成、多步工作流 | 查数据库、操作浏览器、文件读写 |
看完这张表你就能明白,这俩根本不是竞争关系。Skills管的是Agent“内在的如何做”,MCP管的是Agent“外在的用什么做”。一个解决聪明的问题,一个解决连接的问题。
2. 两个东西不是二选一,而是互补关系
2.1 Skills决定“怎么做”,MCP决定“拿什么做”
我先说一个没有MCP时Skills的局限。写一个“生成数据库巡检报告”的Skill,如果底下没有任何真实工具,Agent能做的只是基于它的训练知识“编”一份报告,字段可能是编的、数据可能是编的、结论更是编的。Skill再规范,也改变不了“巧妇难为无米之炊”的事实。MCP就是那口锅里的米,它让Agent真正能拿到外部数据。
反过来,只有MCP没有Skills也有困境。我见过一些团队把MCP Server接好之后,满怀期待让AI操作,结果发现AI确实会调用工具,但调用得很随意:说好了先查元数据再判断,结果它上来就拉全表数据;说要聚焦慢查询,结果它把每个表都扫一遍,token烧得飞快。这时候你就明白,工具是能力,但能力不等于稳定的工作方法。谁来决定先做什么后做什么、做到什么程度算完成?这是Skills的活。
一个健康的分工是:Skills负责“工作流编排”,MCP负责“原子能力供给”。Skill里的instructions写明步骤和判断标准,MCP的工具负责具体的执行细节。
2.2 真正的协同:Skill做编排,MCP做执行
下面这段是两者实际的协同逻辑。以数据库巡检为例,一个Skill的instructions里会写:
- 先调用
db-inspection_list_tables获取全部表,判断表数量是否异常(超出基线数量一般意味着有临时表没清理) - 对核心业务表调用
db-inspection_table_schema,检查是否有主键、字段类型是否合理、索引是否缺失 - 调用
db-inspection_slow_query_check获取最近30分钟慢查询,重点关注执行时间超过2秒的SQL - 按照
assets/report_template.md的格式输出巡检报告 - 发现问题时调用
db-inspection_notify发送告警
注意第1、2、3、5步,全部是MCP工具。但决定“什么时候调用哪个工具、调用顺序如何、结果怎么解读”的,是SKILL.md里的指令。这就是两者的交汇方式:Skill是剧本,MCP是演员。
还有一个关键机制叫allowed-tools。Skill定义里可以声明白名单,只允许Agent在技能执行期间调用指定的那几个工具,防止跑偏。比如“数据库巡检”技能只允许操作db-inspection这个Server下的工具,绝不允许去调用什么浏览器工具、文件删除工具。这是安全设计上非常重要的一道闸门。
2.3 最容易踩的3个误区
误区一:用Skills替代MCP,把工具逻辑写进SKILL.md。有人图省事,不写MCP Server,直接在Skill里写“你需要模拟运行一条SQL”,让模型凭空猜测查询结果。这非常不可靠,生成的数据没人敢信,而且白白消耗大量token。凡是需要真实访问外部系统的,老老实实走MCP。
误区二:用MCP替代Skills,把所有业务流程封装成一个个MCP Tool。这个方向也错。如果业务流程全写在工具实现里,意味着每改一次流程就要改一次代码、重启一次Server。而且工具会越堆越多,Agent挑选工具的难度也急剧上升。业务规则放Skills层,工具保持原子化,才是正确的拆法。
误区三:混淆MCP的Prompts和Agent Skills。MCP的Prompts是Server端提供的提示模板,告诉你“某工具通常怎么用”;Agent Skills是Agent端的行为定义,告诉你“某类任务该怎么完成”。前者是说明书,后者是操作手册,不是一个层面的东西,别混着谈。
3. 实操:搭建一个会查数据库的自动化运维助手
3.1 需求拆解与分层方案
我拿一个真实的内部工具举例,这个方案我实际跑过,在团队里用了挺长时间。需求大概是:让AI在收到“帮我做数据库巡检”“看看数据库有没有问题”这类请求时,自动连上MySQL、查表结构、查慢查询、生成一份格式固定的巡检报告,并在发现问题时告警。
按上一节的思路拆成两层。MCP层:一个Python写的数据库巡检Server,暴露五个原子工具——server_status、list_tables、table_schema、slow_query_check、notify。Skills层:一个名为database_inspection的技能,负责明确触发条件、编排调用顺序、规定报告格式、限定允许使用的工具列表。
3.2 用FastMCP实现数据库Server
实现MCP Server我用的是FastMCP,它把底层协议细节封装得很好,写起来跟装饰一个普通函数差不多。下面是一个简化版的核心代码:
# db_server.py from fastmcp import FastMCP import pymysql mcp = FastMCP("db-inspection-server") # 数据库连接统一走这里,避免重复创建连接 def get_conn(db: str): return pymysql.connect( host="127.0.0.1", port=3306, user="readonly_user", password="******", database=db, charset="utf8mb4", connect_timeout=5, read_timeout=10 ) @mcp.tool() def server_status() -> dict: """返回MCP Server运行状态及数据库连通性,Agent在任务开始前应优先调用此工具确认环境可用。""" try: conn = get_conn("information_schema") conn.close() return {"status": "ok", "database": "reachable"} except Exception as e: return {"status": "error", "message": str(e)} @mcp.tool() def list_tables(db: str) -> list[str]: """列出指定数据库中所有业务表名。入参db为目标数据库名。""" conn = get_conn(db) with conn.cursor() as cur: cur.execute( "SELECT table_name FROM information_schema.tables " "WHERE table_schema=%s ORDER BY table_name", (db,) ) rows = [r[0] for r in cur.fetchall()] conn.close() return rows[:200] # 截断:最多返回200个表 @mcp.tool() def table_schema(db: str, table: str) -> str: """返回单张表的字段、类型、主键和索引信息。入参db为数据库名,table为表名。""" conn = get_conn(db) with conn.cursor() as cur: cur.execute("SHOW CREATE TABLE `%s`" % table) # 实际请用参数化方式 row = cur.fetchone() conn.close() return row[1] if row else "table not found" @mcp.tool() def slow_query_check(db: str, minutes: int = 30) -> list[dict]: """查询最近N分钟慢查询日志,默认30分钟窗口。重点关注执行时间超过2秒的SQL。""" conn = get_conn(db) with conn.cursor() as cur: cur.execute( "SELECT start_time, query_time, sql_text FROM slow_log " "WHERE start_time > NOW() - INTERVAL %s MINUTE " "ORDER BY query_time DESC LIMIT 50", (minutes,) ) rows = [{"time": r[0], "duration": r[1], "sql": r[2]} for r in cur.fetchall()] conn.close() return rows @mcp.tool() def notify(channel: str, message: str) -> str: """发送告警消息到指定渠道。channel为渠道名,message为消息正文。""" # 实际对接企业微信/钉钉机器人 return f"notification sent to {channel}"这段代码有些细节值得说明。第一,所有工具都做了结果截断:list_tables最多返回200个表,slow_query_check最多返回50条记录。为什么要截断?因为工具返回的所有内容都会进Agent的上下文,如果不设上限,一张超大表或几百条慢查询日志就能把上下文撑爆,反过来影响模型理解。第二,server_status这个工具看着多余,实际非常有用。Agent在做任何复杂操作之前先调它确认Server和数据库都活着,能把一大半“工具没反应”的排查时间省下来。
3.3 编写database_inspection技能
MCP Server写完了,下面是Skill部分。目录结构很简单:
skills/database-inspection/ SKILL.md assets/report_template.mdSKILL.md的核心内容是这样设计的:
--- name: database_inspection description: 对指定MySQL数据库执行健康巡检,生成巡检报告。当用户要求检查数据库状态、慢查询、表结构异常、做数据库巡检时使用。典型触发语:"帮我检查一下订单库"、"数据库有没有问题"、"做一次巡检"。 allowed-tools: - db-inspection_server_status - db-inspection_list_tables - db-inspection_table_schema - db-inspection_slow_query_check - db-inspection_notify --- # 数据库巡检流程 1. 先调用 db-inspection_server_status 确认MCP Server和数据库连通性,如果返回 status 不为 ok,直接向用户说明环境不可用并结束。 2. 调用 db-inspection_list_tables 获取全部业务表。如果表数量超过该库基线值的120%,重点关注是否存在未清理的临时表。 3. 对每张核心业务表调用 db-inspection_table_schema,检查: - 是否存在主键,无主键的表标记为高风险; - 是否存在明显缺失索引的字段(如高频查询字段无索引); - 字段类型是否与业务语义匹配。 4. 调用 db-inspection_slow_query_check 获取最近30分钟慢查询。对执行时间超过2秒的SQL逐条分析,判断是全表扫描、缺索引还是并发问题。 5. 按 assets/report_template.md 的格式输出巡检报告,包含:巡检时间、库名、表数量、风险项清单、慢查询统计、改进建议。 6. 如果发现任何高风险项,调用 db-inspection_notify 发送告警,消息内容为风险摘要。汇报模板report_template.md我会写成一个Markdown骨架,包含标题、巡检时间、风险级别、明细表格这些固定结构,确保每次输出的报告长得一样,下游解析也方便。
这里有一个必须强调的细节:allowed-tools里工具名的格式是MCP Server名_工具名,中间用下划线连接。比如Server在配置里叫db-inspection,工具叫list_tables,那白名单里就是db-inspection_list_tables。这个命名规则因客户端而异,但FastMCP和Claude生态基本都遵循这个约定。写错的话Agent会报“工具不存在”,这是我在群里看到问得最多的问题之一。
3.4 配置接入与运行效果实录
配置MCP Server的方式取决于你用的客户端。Claude Desktop是在claude_desktop_config.json里加一段:
{ "mcpServers": { "db-inspection": { "command": "python", "args": ["db_server.py"], "env": { "DB_HOST": "127.0.0.1", "DB_USER": "readonly_user" } } } }如果用Cline或Roo Code这类VS Code插件,在配置界面里填同样的内容就行。关键是把db-inspection这个名字和代码里的Server ID保持一致,否则白名单匹配不上。
运行效果大致是这样。用户说:“帮我检查一下订单库。”Agent会先判断这个请求匹配了database_inspection这个Skill的description,于是加载SKILL.md正文,然后按照流程第1步调用server_status,确认正常后调用list_tables,接着逐个检查表结构、查慢查询,最后按模板输出报告。整个过程中,模型每一步的调用顺序都被Skill的指令约束住了,不会跑偏;而真正取数、查询的能力来自MCP工具。两者配合,行为和结果都稳定。
3.5 关键参数背后的取舍逻辑
有几个参数我特意调整过,这里说说当时的思考过程。
slow_query_check的时间窗口默认30分钟,超时时间设10秒。设太短查不到有效数据,设太长日志量大会拖慢响应,30分钟是我们运维同事“发现异常后想先看最近情况”的合理粒度。list_tables返回上限200个表。一般业务库超过200张表已经算复杂了,再高的场景应该按业务模块拆分,而不是无限返回。- SQL查询一律用参数绑定,
table_schema里我写的是SHOW CREATE TABLE,实际上线时表名不能直接拼接用户输入,必须做白名单校验或参数化处理。安全这块别偷懒,Agent工具特别是数据库类工具,一定要用只读账号,权限最小化。 - 工具返回的每条慢查询我加了时长字段,方便模型快速排序,不用在长文本里硬找。这个细节属于“工具设计为模型服务”的思路:MCP工具不仅要实现功能,还要考虑返回结构对模型友好。
4. 常见问题与排查技巧实录
4.1 问题排查速查表
| 问题 | 常见原因 | 解决思路 |
|---|---|---|
| Skill一直不触发 | description写得太抽象,没有典型触发语 | 在description里加2到3个用户真实说法例句 |
| Agent调用MCP工具报not found | allowed-tools里的工具名格式不对 | 确认工具名是“Server名_工具名”,且Server名与配置一致 |
| MCP工具返回内容巨大,上下文爆炸 | 工具没做结果截断 | Server层对行数和字段数都设上限 |
| SKILL.md加载后Agent执行顺序还是乱 | instructions里没写清明确的先后顺序 | 第一行直接写“先调用xxx,然后…”,别用模糊表述 |
| 配置了Skill但完全不生效 | skills目录名或SKILL.md路径不对 | 确认目录名是skills,SKILL.md必须放在该技能的根目录 |
| stdio模式的MCP Server启动失败 | 依赖缺失或Python解释器路径不对 | 先手动在终端跑一遍命令,确认能启动再配给客户端 |
| Agent拿到MCP工具后乱调用 | 工具description太宽泛 | 每个工具的description写清楚参数含义、适用场景和典型用例 |
| 远程MCP连接被拒 | 端口或WSS地址配置错误,或Token失效 | 用MCP Inspector单独连接测试,先排除Server侧问题 |
| 多Skill存在时误触发 | description描述重叠度太高 | 每个Skill的触发场景要写差异化,必要时设互斥条件 |
4.2 几条压箱底的避坑经验
第一条,MCP工具要原子化,业务规则别写死在Server里。我一开始图省事,把整个巡检流程写成了一个工具,结果每次改报告格式都要改代码重启Server。改成Skills层之后,改流程只改SKILL.md,几分钟搞定,效果完全不一样。
第二条,allowed-tools一定要写,哪怕麻烦。不写白名单,意味着Skill运行时Agent可以调用客户端里的任何工具,一旦某个工具描述被误触发,可能造成不可逆操作。白名单是低成本高收益的安全闸门。
第三条,给每个MCP Server配一个server_status或_health工具。这个习惯帮我省了无数排查时间。Agent每次开工前先确认Server存活,错了也知道是环境问题还是逻辑问题。
第四条,Skill不要贪多。表面上看技能越多Agent越强大,实际上Agent在并行判断哪个description匹配时,技能数量太多会增加决策负担。我的经验是一个技能只干一件事,description短而准,正文控制在“30秒内读完”的长度。
第五条,定期看触发率。在Claude Code这类工具里能看Skill调用日志,凡是触发率很低的Skill,大概率是description写得太烂或场景没有实际需求,删掉或重写,别留着吃灰。
最后分享一个实用的经验
我在实际项目里折腾了有一阵子,最后形成的判断是:MCP接入要克制,Skills固化要大胆。先挑两三个真正高频的外部能力做成MCP Server,跑通之后再把这些能力串成Skill。起步阶段别追求数量,一个库巡检Skill、一个代码审查Skill、一个通知Skill,基本上就能覆盖大部分自动化场景。
还有一件事值得提,就是概念本身也在演进。今天聊的Agent Skills和MCP,未来可能都会融合出新的形态,但底层那个分工逻辑大概率不变——连接外部世界的归协议层,稳定行为模式的归技能层。理解这一层,比记住任何一个API细节都更重要。