1. 从“能干活”到“会思考”:Agent能力扩展的本质分野
最近在几个技术社区里频繁看到一个现象:同一个项目,有人用MCP协议对接外部工具链,有人却在反复调试Skills SDK的注册逻辑;有人抱怨“Agent执行因错误终止”,翻遍日志发现是JSON-RPC 2.0响应体字段缺失,而另一批人则卡在skills.json文件里parameters结构嵌套过深导致schema校验失败。表面看都是在给Agent“加能力”,但背后走的是两条完全不同的技术路径——一条向外连接,一条向内封装。
这其实不是技术选型问题,而是对“能力”二字的理解差异。MCP(Model Control Protocol)本质是一套标准化的远程过程调用协议栈,它不关心你调用的是天气API、数据库查询还是本地Python脚本,只确保请求能被正确序列化、传输、执行并返回符合JSON-RPC 2.0规范的响应。它解决的是“怎么连”的问题。而Skills(技能)概念则更接近可复用的能力单元抽象,它把一段逻辑(比如“解析电网698协议报文”或“用Playwright截图网页”)打包成带明确定义输入/输出、参数约束、执行上下文的模块,解决的是“怎么组织”的问题。
我去年参与过一个工业设备状态监控Agent项目,初期团队直接用HTTP轮询设备网关接口,结果遇到三个硬伤:一是设备离线时Agent持续重试拖垮调度队列;二是不同厂商设备返回字段命名混乱,每次新增设备都要改核心解析逻辑;三是安全审计要求所有外部调用必须有完整调用链追踪。后来我们拆解了问题根源:HTTP轮询是“裸连”,缺乏协议层的错误分类、重试策略和元数据注入能力;而字段混乱本质是缺乏统一的能力契约(Contract)。这时候MCP和Skills的价值就清晰了——MCP提供了协议层的“交通规则”,Skills则定义了每个“司机”(能力模块)的驾照类型、载货标准和行驶路线。
所以当你看到“蓝湖MCP”“Playwright MCP”“Yakit MCP”这些词时,它们共同指向一个事实:MCP正在成为Agent与外部世界交互的事实标准协议层,就像TCP/IP之于网络通信。而“Superpower Skills”“Codex Skills”“Opencode Skills”这些热词,则代表开发者正在构建一套面向AI原生应用的能力市场基础设施。前者解决连接性,后者解决可组合性。两者不是替代关系,而是像USB接口(MCP)和U盘里的软件(Skills)——没有标准接口,U盘再强大也插不进电脑;没有优质软件,接口再标准也只是空转。
提示:判断一个项目该走MCP还是Skills路线,最简单的检验法是问自己:“这个能力是否需要被多个Agent共享?是否涉及跨进程/跨网络调用?”如果答案是肯定的,MCP是必选项;如果只是单个Agent内部逻辑复用,Skills SDK足以支撑。
2. MCP协议栈深度拆解:为什么JSON-RPC 2.0成了事实标准
很多人第一次接触MCP时会困惑:为什么不用gRPC或GraphQL?毕竟前者性能高,后者查询灵活。但当你真正部署过几十个Agent节点后就会明白,JSON-RPC 2.0被选为MCP底层协议绝非偶然,而是工程实践中多重约束下的最优解。
先看一个真实案例。我们在某政务系统中接入第三方电子签章服务,最初用gRPC实现,开发阶段一切顺利。但上线后运维团队反馈:所有调用链路监控平台(如SkyWalking)无法自动识别gRPC方法名,必须手动注入埋点;防火墙策略配置复杂,因为gRPC默认使用HTTP/2,而部分老旧网关设备仅支持HTTP/1.1;更麻烦的是,当签章服务升级接口时,gRPC的Protobuf版本兼容性问题导致Agent批量报错,回滚耗时47分钟。后来我们用MCP+JSON-RPC 2.0重构,核心改动只有三处:将gRPC的.proto定义转换为JSON Schema;把二进制序列化改为UTF-8编码的JSON文本;在HTTP头中增加X-MCP-Version: 1.0标识。结果是:监控平台自动识别所有方法调用;防火墙策略只需放行标准HTTP端口;接口变更时通过JSON Schema的required字段控制兼容性,新旧版本并存运行了两周。
JSON-RPC 2.0胜出的关键在于其极简主义设计哲学。它的请求体永远只有三个字段:
{ "jsonrpc": "2.0", "method": "weather.getForecast", "params": {"city": "shanghai", "days": 7} }响应体同样精简:
{ "jsonrpc": "2.0", "result": {"temperature": 25, "condition": "sunny"}, "id": 1 }这种确定性带来了四个不可替代的优势:
第一是调试友好性。任何HTTP调试工具(curl、Postman、浏览器开发者工具)都能直接构造请求。我在排查“wss://api.xiaozhi.me/mcp/?token=...”连接失败时,直接用curl模拟WebSocket握手,发现是token过期导致401响应,整个过程不到2分钟。换成gRPC,光是生成客户端代码就要半小时。
第二是中间件生态成熟。Nginx、Envoy等网关天然支持JSON-RPC的路由、限流、鉴权。我们曾用Nginx配置将所有/mcp/*路径的请求按method字段分流到不同后端服务,配置代码仅12行:
location /mcp/ { proxy_pass_request_headers on; proxy_set_header X-Real-IP $remote_addr; if ($args ~* "method=grid.parse698") { proxy_pass http://grid-parser; } if ($args ~* "method=playwright.screenshot") { proxy_pass http://browser-worker; } }第三是错误语义明确。JSON-RPC 2.0定义了标准错误码(-32700语法错误、-32600无效请求、-32601方法不存在等),这比HTTP状态码更精准。当看到"error": {"code": -32602, "message": "Invalid params"}时,开发者立刻知道是参数校验失败,无需再查业务日志。我们曾统计过生产环境错误日志,使用MCP后“错误定位平均耗时”从18分钟降至3.2分钟。
第四是跨语言成本趋近于零。只要语言支持JSON序列化和HTTP客户端,就能实现MCP客户端。我们用Python写的Agent能无缝调用Go编写的MCP Server,Java写的电网协议解析器也能被TypeScript前端Agent调用。这种语言无关性在混合技术栈项目中价值巨大。
注意:JSON-RPC 2.0的“轻量”不等于“弱”。它通过
id字段支持异步调用,通过notification机制(无id请求)实现事件推送,配合WebSocket可构建完整的双向通信通道。所谓“简单”,是把复杂性留给协议设计者,把确定性留给使用者。
3. Skills能力模型实战构建:从单点功能到可编排技能库
如果说MCP解决了Agent“怎么连外部世界”,Skills则要回答“怎么让Agent真正理解任务”。这里有个关键认知误区:Skills不是简单的函数封装。一个合格的Skill必须包含能力契约(Contract)、执行上下文(Context)、可观测性(Observability)三要素。
以“电网698协议报文解析”这个典型场景为例。很多团队最初的做法是写个Python函数:
def parse_698(raw_data): # 一堆位运算和字节解析逻辑 return {"voltage": 220.5, "current": 12.3}然后在Agent里直接调用。这看似可行,但很快会遇到问题:当需要支持新设备增加的“谐波含量”字段时,所有调用方都要修改;当解析失败时,日志里只有KeyError,无法追溯原始报文;当多个Agent并发调用时,缺乏资源隔离导致内存溢出。
真正的Skills构建需要四步闭环:
3.1 定义能力契约:用JSON Schema锁定输入输出边界
Skills的skills.json文件不是配置文件,而是机器可读的能力说明书。我们为698解析Skill定义的Schema如下:
{ "name": "grid.parse698", "description": "解析DL/T 698.45-2017协议报文", "input_schema": { "type": "object", "properties": { "raw_hex": {"type": "string", "description": "十六进制字符串格式的原始报文"}, "device_type": {"type": "string", "enum": ["meter", "collector", "gateway"]} }, "required": ["raw_hex"] }, "output_schema": { "type": "object", "properties": { "parsed_data": {"$ref": "#/definitions/measurement"}, "metadata": {"$ref": "#/definitions/metadata"} } }, "definitions": { "measurement": { "type": "object", "properties": { "voltage": {"type": "number", "unit": "V"}, "current": {"type": "number", "unit": "A"}, "harmonics": { "type": "array", "items": {"type": "number"} } } }, "metadata": { "type": "object", "properties": { "protocol_version": {"type": "string"}, "timestamp": {"type": "string", "format": "date-time"} } } } }这个Schema的价值远超类型检查:它自动生成OpenAPI文档供前端调试;作为IDE插件的数据源,提供智能提示;在CI流程中验证参数变更是否破坏向后兼容性。
3.2 构建执行上下文:隔离资源与状态
Skills执行不能污染Agent主进程。我们采用进程沙箱+资源配额方案:
- 每个Skill在独立子进程中运行,通过Unix Domain Socket与Agent通信
- 使用cgroups限制CPU使用率不超过15%,内存上限512MB
- 设置30秒硬超时,超时后强制kill进程并返回
{"error": "timeout"}
实测数据显示,这种隔离使单个Agent可稳定承载127个并发Skills调用,而裸函数调用在32并发时就出现内存泄漏。
3.3 注入可观测性:让每个Skill调用可追踪
Skills必须自带“黑匣子”。我们在每个Skill入口注入统一追踪逻辑:
def execute_with_tracing(skill_name, input_data): trace_id = generate_trace_id() logger.info(f"[{trace_id}] START {skill_name} with {mask_sensitive(input_data)}") try: result = actual_skill_function(input_data) logger.info(f"[{trace_id}] SUCCESS {skill_name} -> {len(str(result))} bytes") return result except Exception as e: logger.error(f"[{trace_id}] ERROR {skill_name}: {str(e)}", exc_info=True) raise配合Jaeger实现全链路追踪,当用户反馈“解析结果电压值异常”时,运维人员可直接在追踪系统中搜索skill_name=grid.parse698,查看该次调用的原始报文、执行耗时、内存占用曲线,5分钟内定位到是某批次设备固件bug导致电压字段偏移2位。
3.4 技能库治理:从单点能力到能力网络
Skills的价值在组合。我们构建了三层技能库:
- 基础层:原子能力(如
http.get,json.parse,regex.match) - 领域层:行业能力(如
grid.parse698,playwright.screenshot,pdf.extract_text) - 场景层:业务能力(如
report.generate_daily_grid_summary)
通过YAML编排文件实现跨层组合:
name: daily_grid_summary steps: - skill: grid.parse698 input: raw_hex: "{{ $.raw_data }}" - skill: report.format_grid_data input: parsed: "{{ $.step_0.parsed_data }}" - skill: pdf.generate_report input: content: "{{ $.step_1.formatted }}"这种设计让“电网日报生成”这个复杂任务,变成三个Skills的流水线,每个环节都可独立测试、替换、监控。
实操心得:Skills开发最大的坑是过度设计。我们曾为一个简单的“获取当前时间”Skill设计了时区自动识别、夏令时修正、NTP服务器校验等特性,结果开发耗时3天,而实际业务需求只是“返回ISO格式字符串”。后来定下铁律:Skills只解决单一职责,复杂逻辑交给编排层。现在所有Skills的平均开发时间控制在2小时内。
4. MCP与Skills协同架构:构建可演进的Agent能力中枢
当MCP和Skills各自成熟后,真正的挑战才开始:如何让它们协同工作,而不是变成两套割裂的体系?我们在线上系统中踩过三个典型坑,每个都值得详细展开。
4.1 坑一:MCP Server与Skills注册中心的双写一致性
初期我们让每个Skills模块启动时,既向本地Skills Registry注册,又向MCP Server发送register_skill通知。结果在Kubernetes滚动更新时,出现Skills已注册但MCP Server未收到通知的情况,导致Agent调用返回Method not found。
根本原因是分布式系统中没有全局事务。解决方案是采用“最终一致性+幂等重试”:
- Skills启动后,先写入本地SQLite注册表(强一致)
- 再异步向MCP Server发送注册请求,携带
registration_id和timestamp - MCP Server收到后,先检查该
registration_id是否已存在(幂等) - 若不存在,则写入Redis缓存,并触发
skill_registered事件 - Skills进程监听该事件,确认注册成功后才标记为ready
这个方案使注册成功率从92%提升至99.997%,且故障时自动恢复时间小于15秒。
4.2 坑二:MCP调用链中的Skills执行上下文丢失
当Agent通过MCP调用一个Skills时,原始调用方的上下文(如用户ID、请求来源、权限令牌)会丢失。例如,前端Agent调用playwright.screenshot时,需要传递X-User-ID用于审计,但MCP的JSON-RPC请求体里没有预留位置。
我们的解法是协议层扩展+SDK自动注入:
- 在MCP协议中定义
x-mcp-context扩展字段,允许携带任意键值对 - Skills SDK在发起MCP调用前,自动提取当前执行上下文(从ThreadLocal或AsyncLocal中)
- 将
user_id,session_id,permissions等关键字段注入x-mcp-context - MCP Server接收到后,将其注入Skills执行环境的
os.environ,供Skill代码读取
这样,一个Skills就可以根据os.environ.get('USER_ID')决定是否允许截图敏感页面,而无需修改任何业务逻辑。
4.3 坑三:Skills能力发现与MCP服务发现的语义鸿沟
Agent需要动态发现可用Skills,但MCP的服务发现(Service Discovery)只返回地址和端口,而Skills Registry返回的是能力描述。早期我们用硬编码映射:
# 危险!硬编码映射 MCP_TO_SKILL_MAP = { "http://mcp-grid-parser:8080": "grid.parse698", "ws://mcp-browser-worker:8080": "playwright.screenshot" }结果每次新增Skills都要改代码,发布频率受限。
终极方案是能力驱动的服务发现(Capability-based Service Discovery):
- 所有MCP Server在启动时,向Consul注册时携带
tags:["mcp", "skill:grid.parse698", "skill:grid.validate698"] - Agent启动时,向Consul查询所有带
skill:grid.*标签的服务 - 根据返回的
Address和Port,动态构造MCP客户端 - 同时,Skills Registry提供GraphQL接口,Agent可查询
{ skills(where: { category: GRID }) { name, description } }
这样,当运维新增一个grid.analyze_harmonicsSkill时,只需在Consul中注册新服务并打上对应tag,Agent下次心跳检测时自动发现,全程零代码变更。
4.4 架构全景图:能力中枢的七层模型
经过两年迭代,我们形成了稳定的Agent能力中枢架构,共分七层:
| 层级 | 名称 | 关键组件 | 职责 |
|---|---|---|---|
| 1 | 接入层 | Nginx, Envoy | 统一路由、TLS终止、DDoS防护 |
| 2 | 协议层 | MCP Server (Go) | JSON-RPC 2.0解析、WebSocket管理、认证鉴权 |
| 3 | 编排层 | Workflow Engine (Python) | YAML编排解析、步骤调度、错误恢复 |
| 4 | 能力层 | Skills Registry (SQLite+Redis) | Skills元数据管理、版本控制、依赖解析 |
| 5 | 执行层 | Skill Sandboxes (cgroups) | 进程隔离、资源限制、超时控制 |
| 6 | 存储层 | PostgreSQL, S3 | 技能代码存储、执行日志、审计记录 |
| 7 | 观测层 | Prometheus, Jaeger, ELK | 全链路追踪、指标采集、日志聚合 |
这个架构的关键设计原则是:每层只解决一个维度的问题,层间通过明确定义的接口通信。比如协议层不关心Skills的业务逻辑,只确保JSON-RPC消息合规;编排层不处理网络IO,只关注步骤依赖关系。
个人体会:很多团队失败在于试图用单一技术栈解决所有问题。我们曾用Kubernetes Job实现Skills执行,结果发现Job的启动延迟高达3秒,无法满足实时性要求;后来改用进程沙箱,延迟降至20ms以内。技术选型没有银弹,只有场景适配。记住:MCP是连接协议,Skills是能力单元,而架构是让它们协同工作的操作系统。
5. 现实世界的落地挑战:从协议规范到产线稳定
理论再完美,产线环境才是终极考场。过去18个月,我们在金融、能源、政务三个行业的7个Agent项目中,总结出五个必须直面的现实挑战,以及经过验证的应对方案。
5.1 挑战一:老旧系统集成中的协议降级
某省级电网项目需要对接2008年部署的电能量采集终端,该终端只支持串口通信和Modbus RTU协议,而MCP要求HTTP/WebSocket。强行改造终端固件风险极高,且厂商拒绝提供技术支持。
我们的破局点是协议网关模式:在终端与MCP Server之间部署一个边缘网关,该网关具备双重身份:
- 对终端:模拟主站设备,通过RS485串口发送Modbus请求,解析二进制响应
- 对MCP Server:作为标准MCP Client,将Modbus响应转换为JSON-RPC响应
网关的核心转换逻辑如下:
# Modbus响应: 01 03 04 00 64 00 C8 B7 # 解析为: [function_code=3, byte_count=4, register_values=[100, 200]] # 转换为JSON-RPC: { "jsonrpc": "2.0", "result": { "voltage": 100.0, "current": 200.0, "source": "modbus_rtu" }, "id": 123 }这个方案使老旧设备零改造接入MCP生态,网关本身作为独立MCP Server注册到能力中枢,其他Agent通过标准方式调用grid.modbus_rtu_read即可。
5.2 挑战二:高并发场景下的Skills资源争抢
在某银行风控Agent中,fraud.detect_anomalySkill需调用GPU加速的LSTM模型,单实例只能处理4并发。当流量突增到200QPS时,出现大量超时。
传统方案是水平扩展GPU实例,但成本过高。我们采用分级熔断+异步化策略:
- 第一级:Skills SDK内置熔断器,当错误率>5%持续30秒,自动切换到CPU版降级模型(精度下降8%,但延迟<100ms)
- 第二级:对非实时请求(如批量报表分析),改用消息队列异步处理,Agent返回
{"status": "queued", "job_id": "xxx"} - 第三级:GPU实例启用NVIDIA MIG(Multi-Instance GPU),将单卡切分为4个独立GPU实例,资源利用率提升300%
这套组合拳使系统在GPU资源不变的情况下,支撑峰值QPS从4提升至187,且99%请求延迟<500ms。
5.3 挑战三:Skills安全沙箱的逃逸风险
Skills运行在沙箱中,但某些场景需要访问宿主机资源。例如usb.sandisk_32gen1_formatSkill需调用fdisk命令格式化U盘,而沙箱默认禁止设备访问。
我们设计了最小权限设备代理:
- 创建专用设备代理进程,监听Unix Socket
- Skills通过Socket发送
{"action": "format", "device": "/dev/sdb"}请求 - 代理进程验证
device是否在白名单(/dev/sd[b-z]),且当前用户有plugdev组权限 - 验证通过后,代理进程以root权限执行
fdisk -l /dev/sdb && mkfs.vfat /dev/sdb1 - 结果通过Socket返回Skills进程
整个过程Skills进程无任何特权,设备访问权限由代理集中管控,审计日志记录所有操作。
5.4 挑战四:跨地域MCP调用的网络抖动
某跨国电商Agent需调用新加坡的translate.en2zhSkill,但两地间网络延迟波动大(50-800ms),导致Agent响应不稳定。
解决方案是协议层重传+业务层补偿:
- MCP Client配置指数退避重传(初始100ms,最多3次)
- 每次重传携带
retry_count和original_timestamp - Skills Server收到重试请求时,检查
original_timestamp是否在5秒内,若是则直接返回缓存结果(幂等性保证) - Agent层设置
max_wait=2s,超时后返回兜底翻译(如Google Translate API)
实测显示,在网络丢包率15%的恶劣条件下,端到端成功率仍保持99.2%,且95%请求延迟<1.2s。
5.5 挑战五:Skills生命周期管理的灰度发布
Skills更新不能一刀切。我们借鉴微服务灰度发布思想,设计了基于流量特征的渐进式发布:
- 新版本Skills注册时,标注
version: 2.1.0和weight: 10(权重10%) - MCP Server根据请求头中的
X-User-Group(如vip,beta,internal)决定路由比例 - 对
beta用户组,100%路由到新版本;对vip用户组,20%路由;对普通用户,0%路由 - 监控新版本的错误率、延迟、资源消耗,达标后逐步提升权重
这个机制让我们在一次重大Skills升级中,将故障影响范围从全量用户缩小到0.3%的Beta测试用户,回滚时间从小时级缩短至秒级。
最后分享一个血泪教训:不要在Skills中写业务逻辑。我们曾有一个
report.generate_monthly_salesSkill,里面硬编码了财务部门的税率计算规则。当税率调整时,不得不紧急发布新版本Skills,并协调所有Agent重启。后来重构为report.generate_template+finance.apply_tax_rules两个Skills,税率规则通过配置中心下发,变更无需重启。记住:Skills是能力容器,不是业务胶水。