• 约 18 分钟阅读

更新于

别把所有扩展都叫 Skill:讲清工具、MCP 与插件的边界

Skill 是任务方法,工具是执行接口,MCP 是连接协议,插件是宿主扩展包。用一个文档审查案例,讲清四者的关系、常见误区和架构取舍。

目录

别把所有扩展都叫 Skill:讲清工具、MCP 与插件的边界

给 Agent 增加一项能力,到底应该写 Skill、注册工具、接入 MCP,还是做一个插件?

这个问题看起来像技术选型,实际上先要解决概念问题:这四个词并不处于同一个抽象层次。 有的描述工作方法,有的提供执行动作,有的规定通信方式,有的负责把组件交付给用户。没有分清它们,讨论很容易变成“哪种机制更先进”,而不是“当前需求缺少什么”。

在之前的轻量级 Agent 文章中,我用过“Skill 插件系统”这个说法,强调技能可以按需加载和替换。对小型实现,这样表达足够直观;进入团队协作、外部服务和权限管理后,就需要更精确的区分。

本文用一个贯穿案例展开:读取项目说明,检查是否包含“背景”和“风险”,最后生成审查报告。 案例使用自建的最小检查器,不涉及真实业务资料。我们先讲清概念,再分析四个常见误区,最后讨论三种可选架构。

1. Skill、工具、MCP 与插件,分别是什么?

Skill:可加载的任务方法

Skill 可以理解为给 Agent 使用的任务手册。它不仅说明“你要扮演什么角色”,还交代什么时候采用这套方法、任务按什么顺序进行、遇到异常如何处理,以及最终输出什么。

以 Agent Skills 规范为例,技能是一个至少包含 SKILL.md 的目录。文件头部提供名称和描述,正文保存操作说明;目录还可以附带脚本、参考资料和模板。宿主先提供简短元数据,任务需要时再加载详细内容,这种组织方式称为渐进式加载。[1]

文档审查 Skill 可以写明:先确认用户指定的文件,再执行结构检查,最后分别给出缺失章节和内容建议。

这里承载的是如何完成任务,不是一种新的模型能力或独立执行引擎。即使 Skill 携带脚本,也仍要通过宿主提供的执行能力运行。说明中出现 read_file,不意味着宿主就自动拥有了读取文件的工具。

工具:具有输入输出约定的执行接口

工具是 Agent 调用执行代码的接口。可以先把它理解为一个对外开放的动作:读取文件、查询记录、计算结果,或者执行我们的文档检查。

以 MCP 暴露的工具为例,定义包含名称、描述和输入 schema,还可以声明输出 schema;客户端发送调用请求,由服务端执行后返回结果。[5] 其中,schema 是对数据结构的约定,例如要求输入具有一个字符串类型的 text 字段。

必须区分三个环节:

  • 工具描述:告诉模型这项能力能做什么、适合何时使用。
  • 调用请求:表达本次选择的动作和参数。
  • 工具实现:真正读取数据、执行计算或产生副作用的代码。

在本文案例中,check_sections(text) 接收文本,返回是否通过以及缺失的章节。模型可以决定何时调用,但实际检查由代码完成。

工具不必经由 MCP 接入。自建 Agent 可以直接注册本地函数,也可以通过已有的命令执行工具运行脚本。反过来,一个普通函数不会因为取了合适的名字,就自动成为模型可用的工具;宿主还需要完成描述注册、参数处理、执行绑定和结果回传。

MCP:宿主与能力提供方的通信协议

MCP 的全称是 Model Context Protocol。它规定客户端与服务端如何交换上下文、发现能力和发起调用,不规定整个 Agent 应如何推理或组织业务流程。[2]

理解它,先分清三个角色:

  • Host(宿主):用户使用的 AI 应用,负责协调模型和外部能力。
  • Client(客户端):宿主中与某个服务端通信的组件。
  • Server(服务端):通过协议提供能力和上下文的程序。[2]

MCP 服务端不只提供工具,也可以提供资源和提示模板。资源用于提供上下文,提示模板用于组织交互;它们不应全部被理解成“可执行函数”。协议支持本地 stdio 和 Streamable HTTP 两种传输方式,因此服务端既可以是本机子进程,也可以部署在远端。[2]

把检查器封装成 MCP 服务端后,支持对应能力的宿主可以按共同协议发现、调用它。但检查哪些章节,仍由业务代码决定;先检查再解释,仍由工作流组织。

MCP 是通信约定,MCP Server 是实现约定的程序。协议让接入方式一致,不替代业务实现。

插件:面向特定宿主的扩展包

插件是宿主支持的扩展与交付单元。以 Claude Code 为例,一个插件目录可以包含 Skill、Agent 定义、Hook 和 MCP 服务等组件,作为一个整体安装和加载;清单用于声明名称、版本等元数据。[3]

在文档审查案例中,插件可以把 Skill、检查脚本和说明一起交给同事。需要连接外部服务时,再纳入相应配置。它解决的是如何组合和交付能力,不是重新实现检查算法。

插件还要与 marketplace 区分:前者是扩展包,后者是列出插件及获取位置的目录。[3] 找到了插件、安装了插件、成功加载组件,是不同的状态。

“插件”也不是本文四者中的通用通信标准。讨论包结构和加载行为时,必须说明目标宿主,不能把 Claude Code 的约定直接推广到所有 Agent 产品。

四者不是四选一

现在可以把同一个需求拆开:

  • Skill 描述“审查按什么步骤进行”。
  • 工具执行“检查这份文本”。
  • MCP 规定“独立程序之间如何发现和调用能力”。
  • 插件负责“如何把相关组件交付给目标宿主”。

这是一种职责划分,不是必须逐层经过的流水线。Skill 可以调用已有工具,不额外接入 MCP;MCP 服务可以单独使用,不封装为插件;插件也可以只提供 Skill。[1][2][3]

因此,真正的选型问题不是“用哪一个替代其他三个”,而是“当前缺少工作方法、执行接口、连接方式,还是交付机制”。

2. 误区一:把确定性规则全部写进 Skill

文档审查中,“先确认文件”“解释缺失项”“未经确认不修改原稿”,适合放进工作说明。下面是一份最小的 skills/doc-review/SKILL.md:

---
name: doc-review
description: 检查项目说明的必需章节,并解释结构检查结果。
---

1. 确认用户指定的文档,不扫描无关目录。
2. 使用获准的读取工具取得文本;内容为空时明确报告。
3. 通过获准的执行工具运行本技能的 scripts/checker.py,
   将文档文本传入标准输入,读取 JSON 输出及退出状态。
4. 将检查结果与内容建议分开,不把结构通过说成质量合格。
5. 默认在对话中输出报告,不修改原稿或上传外部服务。

但“必须包含背景和风险两个标题”,具有固定的判断条件。与其不断强调“务必认真检查”,不如让一段代码负责这个判断。

将下面的代码保存为 checker.py;放入技能目录时,位置为 scripts/checker.py。示例只使用 Python 标准库。

import json
import sys


def check_sections(text: str) -> dict:
    if not isinstance(text, str):
        raise TypeError("text must be a string")
    required = ("背景", "风险")
    lines = {line.strip() for line in text.splitlines()}
    missing = [name for name in required if f"## {name}" not in lines]
    return {"rule_version": "1", "passed": not missing, "missing": missing}


if __name__ == "__main__":
    result = check_sections(sys.stdin.read())
    print(json.dumps(result, ensure_ascii=False))
    raise SystemExit(0 if result["passed"] else 1)

这里刻意采用精确行匹配:只识别去除首尾空白后的 ## 背景 和 ## 风险。它不是完整 Markdown 解析器,无法排除代码围栏里的同名行,也不判断章节内容是否有意义。需要语法级检查时,应换成真正的解析器,而不是继续给这个函数增加能力声明。

这个拆分带来一条清晰边界:模型负责理解和解释,代码负责固定规则。 标题是否存在与风险分析是否充分,是两种不同的判断,不应共用一个“文档合格”的结论。

3. 误区二:把本地函数一律包装成远程服务

检查器只消费文本并返回结果,没有共享状态,也不依赖远程数据。此时保留函数与命令行入口,就能验证核心行为,无需先部署网络服务。

将下面的代码保存为 check_cli.py,与 checker.py 放在同一目录,运行 python check_cli.py:

import json
import subprocess
import sys

cases = [
    ("## 背景\n说明\n## 风险\n说明", 0, []),
    ("## 背景\n说明", 1, ["风险"]),
]
for text, expected_code, expected_missing in cases:
    result = subprocess.run(
        [sys.executable, "checker.py"],
        input=text, text=True, capture_output=True, check=False,
    )
    assert result.returncode == expected_code, result.stderr
    assert json.loads(result.stdout)["missing"] == expected_missing
print("CLI checks passed")

通过检查后,脚本会打印 CLI checks passed。这里同时验证了 JSON 结果与退出状态:退出码 1 表示文档未通过规则,不表示检查器崩溃。宿主调用命令行入口时,必须保留这种业务含义,不能把所有非零退出码都转成同一句“工具失败”。

引入服务边界应有明确理由。例如多个宿主需要共同接口,规则需要由独立团队维护,或者数据必须在指定环境内处理。网络服务会额外引入连接、身份和故障处理职责,不能仅因为“以后可能复用”就默认远程化。

尤其要分开两个决定:是否使用 MCP,与是否远程部署。 本地 MCP 服务端同样可以通过 stdio 工作。[2] 需要协议复用,不等于必须让资料离开本机。

4. 误区三:把 MCP 接入当作完整交付

把检查器暴露为 MCP 工具,解决了发现和调用的问题。tools/list 用于列出工具,tools/call 用于调用工具;这些协议能力并不等于完整的审查流程。[2][5]

例如,工具成功返回 passed: false,说明文档未通过检查。报告应明确列出缺失项,而不是替用户润色后宣布“审查通过”。反过来,连接失败时也不能凭模型印象补出一个检查结果。

完整交付必须把这些责任落实:谁读取文档,谁执行检查,谁解释结果,谁决定是否写回;同时明确字段兼容、规则版本、运行环境和故障处理方式。协议调用成功,只证明这一次交互完成,不证明用户目标已经达成。

团队使用同一种宿主时,可以把相关组件打成插件。下面以 Claude Code 为例展示一个本地审查包的组织方式,其清单位于 .claude-plugin/plugin.json。[3]

doc-review-plugin/
├── .claude-plugin/
│   └── plugin.json
├── skills/
│   └── doc-review/
│       ├── SKILL.md
│       └── scripts/
│           └── checker.py
└── README.md

这个结构不需要为了“完整”而额外添加 MCP。它展示的是组件关系,而非可直接安装的完整发行包;实际交付还要提供清单内容、运行环境说明,并验证宿主能够发现技能、定位脚本和运行检查。

保持检查函数独立,还有一个实际好处:以后更换宿主或接入方式,可以替换外层适配,而不必把规则重新写一遍。需要复用的是业务能力,不必连同某个宿主的所有配置一起复制。

5. 误区四:把安装成功当作无限授权

设想审查插件的新版本增加了“上传全文生成建议”。名称仍是文档审查,但原本留在本地的数据现在将发送到外部服务。此时需要重新审视的是数据流和授权,而不只是版本兼容。

Anthropic 官方插件目录提醒用户在安装、更新和使用前确认信任,目录本身无法保证所含软件始终按预期工作或不会变化。[4] MCP 工具规范要求服务端进行输入验证和访问控制,并建议客户端提供确认机制;非可信服务端提供的工具注解不能直接作为可信事实。[5]

对于本文的审查流程,可以采用下面的责任分配:

用户指定文档与目标
        ↓
Skill 提供审查步骤
        ↓
模型提出工具调用请求
        ↓
宿主检查参数、操作范围与审批
        ↓
本地执行器或 MCP 服务端执行
        ↓
返回检查结果 → 生成审查报告

这是一种设计方案,不代表所有宿主已经实现相同控制。尤其要注意:宿主的检查不能替代服务端授权。访问远程业务资源时,服务端仍需根据可信身份核验权限,而不是相信模型传来的用户标识。

默认只读检查与写回、上传、发布应分成不同动作。把“不要上传”写进 Skill,表达的是工作要求;要把它变成不可绕过的限制,还需要执行代码、凭据范围与运行环境共同约束。

说明可以引导行为,但执行权限必须在真正执行动作的位置落实。

6. 从概念边界走向可验证的行为

概念划分是否有用,最终要看失败时能否定位责任。

对于固定规则,先测试函数。将下面的 test_checker.py 与 checker.py 放在同一目录,运行 python -m unittest -v test_checker.py:

import unittest
from checker import check_sections


class CheckSectionsTests(unittest.TestCase):
    def test_complete(self):
        result = check_sections('# Demo\n## 背景\n说明\n## 风险\n说明')
        self.assertTrue(result['passed'])
        self.assertEqual(result['missing'], [])

    def test_missing_risk(self):
        self.assertEqual(check_sections('## 背景')['missing'], ['风险'])

    def test_mention_is_not_heading(self):
        self.assertEqual(check_sections('提到了背景和风险')['missing'], ['背景', '风险'])

    def test_empty(self):
        self.assertFalse(check_sections('')['passed'])

    def test_invalid_type(self):
        with self.assertRaises(TypeError):
            check_sections(None)


if __name__ == '__main__':
    unittest.main()

这些测试固定了完整输入、缺失章节、正文提及、空文本和错误类型的行为,却不能证明模型选择正确,也不能证明插件权限正确。

因此,验收必须继续向外延伸:

  • 函数层:相同输入能否得到符合规则的结果?
  • 接入层:参数是否正确传递,错误是否被完整保留?
  • 宿主层:组件是否被发现和加载,脚本与依赖能否找到?
  • 任务层:最终报告是否准确,是否出现未经授权的写回或上传?

如果函数测试通过但报告遗漏了“风险”章节缺失,问题就不在检查算法,而在结果解释或任务验收。若宿主根本没有加载 Skill,反复修改提示词也解决不了组件加载问题。

Skill、工具、MCP 与插件的边界,正是这些不同故障的定位依据。把它们统一叫“插件”,会让本来可以分别测试的问题重新混在一起。

7. 三种架构,按实际约束选择

回到最初的需求,不同约束对应不同的组合方式。

轻量本地:先把一件事做完整

个人使用、规则简单、资料无需外传时,Skill 加检查函数,再配合受控的本地执行入口,已经可以承担任务。重点是稳定定位文件、说明运行依赖,并让检查结果得到正确解释。

这条路径需要使用者准备本地环境。遇到环境差异,先判断缺少的是依赖管理还是统一分发,不必直接改成远程服务。

服务接入:为多个调用方提供共同接口

多个宿主确实需要复用同一能力时,可以保留检查逻辑,通过 MCP 提供接口,再根据数据位置选择本地进程或远程服务。

投入重点转向协议适配、身份权限和故障处理。以后增加写回功能,还要考虑重复请求与超时后的状态确认,不能把只读查询和有副作用的修改当成同一种操作。

团队插件包:减少重复配置

团队采用明确的目标宿主时,可以将工作方法、脚本和必要配置组合交付。有外部服务就纳入连接配置,没有则不必添加。

验收范围也应覆盖生命周期:新环境能否安装,缺依赖是否有明确提示,升级是否改变权限需求,禁用后入口是否消失,卸载时哪些用户数据需要保留。插件的价值在于减少重复配置,并让更新可管理,而不在于多了一层目录。

这三种方案可以重叠,也没有固定的升级顺序。本地 Skill 加脚本并不是“不成熟的 MCP”,独立 MCP 服务也不是“尚未打包的插件”。它们承担的职责不同。

结语:先定义行为,再选择机制

设计 Agent 扩展时,可以依次问自己:

缺少可复用的工作方法吗?缺少确定的执行能力吗?需要共同的接入协议吗?需要统一的安装与维护单元吗?

回答这些问题,比先决定“所有能力都用 Skill”或“全部改成 MCP”更有价值。

Skill 让方法可复用,工具让动作可执行,MCP 让连接有共同约定,插件让组件可组合交付。真正可靠的系统,不是四种机制一个不少,而是每项需要承担的责任都有明确归属,结果也有对应的验证方式。

参考资料

  1. Agent Skills 格式规范
  2. MCP 架构概览
  3. Claude Code 插件概览
  4. Anthropic 官方插件目录
  5. MCP 工具规范

💬 评论

主题
字体
密度
语言