工坊里有三样东西:一个工匠、一张工作台、一本学徒笔记。
工匠就是模型。反应快、用途广,能把原始文字变成计划、代码和文章——但凭他自己,他的水平止步于训练数据。给他一个从未见过的问题,他只能即兴发挥——说得客气点就是靠猜。
工作台就是 MCP。固定在台面上的电动工具——电钻、带锯、计算器、一根连到 GitHub 的活线、一条通往你数据库的连接。每个工具只做一件事。每个工具都能按名字被发现,有类型化的输入,产出类型化的输出。工匠在需要对外部世界采取行动时才伸手去拿:查询数据库、抓取文件、发送邮件、运行计算。工作台是 外部——活的、有状态的、可替换的。换一张台面,工匠就能做另一类工作,而无需更换他自己。
学徒笔记就是 Agent Skills。由每一位曾在这张台面上工作过的大师留下的笔记——公司内部的风格指南、填写 PDF 表单的配方、发布代码审查的多步骤流程、公司特有的提交信息写法。这本笔记按需被翻阅。工匠不会背下每一页;他只在任务匹配时才翻开对应章节。笔记是 内部——编码化、版本化、可在工匠之间共享。
现在想象三种失败模式。
失败模式 1:有满台工具但没笔记的工匠。 他手上有电钻,但不知道该用哪个钻头,也不知道量两次再钻一次。该用螺丝的地方他抡锤。他一连调了十七次 gh list_prs,因为他脑子里没有「我们公司的代码审查就长这样」这种流程。强大、危险、昂贵。
失败模式 2:有厚厚笔记但没台面的工匠。 他知道笔记里的每一道工序——填表、发评审、写发布说明——但每道工序的最后一句都是 「然后调用 gh 接口」。而他手上没有 gh 接口。他写下漂亮的小作文,描述「假如我有工具会怎么做」。理论上有用,实践上无力。
失败模式 3:既没台面也没笔记的工匠。 一个裸的 LLM。他能做模式匹配、能即兴发挥。有些时候这就够了。大多数时候,即兴发挥会以你无法察觉的方式出错——因为正如上个月那篇关于谄媚性的文章花三千字解释的那样,模型会赞同你问它的任何形状的问题,而问题的形状就是它能拿到的全部信号。
有意思的设计空间,是两者都有的工匠。这就是现代智能体栈:一个模型、一张装满类型化工具的 MCP 工作台、一本写满编码化流程的 Skill 笔记,实时组合。本文要讲的就是为什么 MCP 和 Skills 这两套体系不是对手、它们在底层究竟有何不同、以及决定伸手拿哪一个、或者两个都拿的判定流程。
30 秒预览,两张截图
MCP 是一种 运行时协议。模型通过 JSON-RPC 发送 tools/list 来发现工具。它通过发送符合工具 schema 的 JSON 负载、调用 tools/call 来调用一个工具。服务端执行工具并返回类型化结果。一次往返大约几百毫秒。工具是 可执行的。
Skills 是一种 文件夹格式。一个目录,里面有一个 SKILL.md,其 YAML frontmatter 包含 name 和 description。智能体启动时,每个 skill 的 name 和 description 会被加载到系统提示里。当任务匹配某个 description 时,智能体把完整的 SKILL.md 读入上下文,也可以执行附带在目录里的脚本。没有网络往返——skill 在磁盘上。Skills 是 编码化的。
如果说 MCP 是工作台,Skills 就是笔记本——而现在的笔记本也通过 scripts/ 自带工具了,连同 SKILL.md 下的配方一起。不同形态的信息,不同的延迟特征,不同的失败模式。同一只智能体。
困惑的根源
两套标准的名字里都有一个做了大量工作的单字母:S。MCP 里的 Server,Agent Skills 里的 Skills。两者都被描述为「扩展 LLM 能力」的方式。Claude、VS Code、Cursor 以及越来越多的 AI 客户端都支持两者。一个理性的工程师,在一个星期二的早晨,浏览发布说明时,会读到「MCP server」和「Agent Skill」,然后得出结论:它们是解决同一问题的两种竞争方案。
它们不是。它们解决的是不同的问题。
这是我见过最简洁的一句话区分:
MCP 回答:「模型能不能到达外部世界?」 Skills 回答:「模型知道怎么工作吗?」
写成矩阵就是:
第一个问题的 是 给模型一双新的手搭上网络。第二个问题的 是 给模型一份新的训练躺在磁盘上——流程,加一些可选的、绕过网络直接触及世界的脚本。两者能组合,但不是同一种扩展。本文剩下的部分,就是拆解这种区分,再展示它的实际含义。
MCP 是什么(与不是什么)
MCP(Model Context Protocol,模型上下文协议) 是一种开放标准——最初由 Anthropic 在 2024 年 11 月发布,现在由一个社区工作组维护——用于把 AI 应用连接到外部系统。Anthropic 自己给出的描述最为贴切:“MCP 是 AI 应用的 USB-C 接口。” 一个标准化插座,许多可能的外部设备。
其架构是一种客户端-服务端协议,分层如下:
协议分两层。传输层 要么是 STDIO(Host 启动的一个本地子进程,通过 stdin/stdout 通信——开销最小、单客户端、信任边界由 OS 进程模型划定),要么是 streamable HTTP(一个通过网络可达的远程服务端,使用 bearer-token 或 OAuth 鉴权——多客户端、有真实延迟、有协议层的安全边界)。数据层 是 JSON-RPC 2.0——一种在 LLM 出现之前就已经久经考验的线缆格式,带有方法名、ID 和用于单向推送的通知通道。
在远程 HTTP 传输上,鉴权模型就是让 MCP 能用于 敏感 操作的关键。Host 与鉴权服务器完成一次 token 交换——用户委托流用 Authorization Code + PKCE,服务到服务用 client credentials——并在每次请求时出示一个受限 bearer token。MCP 服务端在 受限 token 下运行每一次工具调用,而不是用用户的原始凭据,也不是用某个全 Host 通用的服务账号。Token 可以被轮换、撤销、审计。一个 scope 是 repo:read 的 token,构造上就无法 push 到 repo。凭据从来不进入模型的上下文窗口,也不需要被贴在配置文件里,也不放在 Skill 文件夹里。这就是 Skills 没有的安全边界。
线缆上传输的是 原语(primitives)。MCP 服务端 可以向客户端提供三种东西:
tools——模型可以调用的 可执行 函数。一个工具有名字、描述和一个 JSON Schema 描述其输入。调用工具返回一个类型化结果。这是最常用的原语。例如:gh_create_pr、sql_query、send_email、get_weather。resources——只读 数据源,模型可以拉到上下文里。与 tool 的区别在于:读 resource 没有副作用,概念上是「抓取文件」而不是「运行函数」。例如:一个项目的README.md、数据库的 schema、今天的日历。prompts——可复用 模板,模型或用户可以带参数调用。例如:接受 diff 然后输出 conventional-commit 格式的commit_messageprompt;summarise_meetingprompt。
MCP 客户端 可以向服务端提供三种东西(反方向):
sampling——服务端可以要求 客户端调用 Host 自带的 LLM 做一次补全。在服务端想要 LLM 帮助、但不想再捆绑一个模型 SDK 时很有用。elicitation——服务端可以向 用户 提一个结构化问题。在服务端需要在副作用发生前做确认(「你确定要删除生产数据库吗?」)时很有用。logging——服务端可以向客户端推送结构化的日志消息,便于调试。
协议是 有状态 的——双方在启动时协商能力(initialize / notifications/initialized),服务端在工具列表变化时可以实时推送通知(notifications/tools/list_changed)。一个活的、能自我更新的服务端——比如一个文件系统 MCP 服务端,在用户 cd 到新项目时新增工具——这是一等公民特性,不是临时加的。
鉴权与人在回路
有两类原语让 MCP 成为敏感操作的正确形状:受限鉴权 和 结构化的人工确认。Skills 这两样都没有。
鉴权。 在 streamable-HTTP 传输上,Host 从鉴权服务器那里拿到一个受限 token,并在每次请求时出示。服务端在那个 scope 下运行每一次工具调用。一个 scope 是 repo:read 的 token 不能 push;一个 scope 是 db:SELECT users 的 token 不能 DROP TABLE users。Token 可撤销、可轮换、可审计。整套机制不需要凭据进入模型上下文,也不需要放在 Skill 文件夹里。Stdio MCP 继承 OS 进程模型——没有协议层鉴权,但子进程拥有启动用户所拥有的一切,这也是另一种信任边界。
通过 elicitation 实现的人在回路。 MCP 服务端可以在执行副作用之前暂停一次工具调用,向用户提一个结构化问题。服务端发送一个 schema(问题、允许的答案、校验规则);Host 渲染它;用户回答;结果作为一个类型化的 JSON 值返回。这是协议层对「你确定吗?」的回答——不是一个模型可以被说服放弃的系统提示指令,而是一个服务端强制执行的线缆协议步骤。
// 服务端 → 客户端:在合并前问用户一个结构化问题
{
"jsonrpc": "2.0",
"id": 7,
"method": "elicitation/create",
"params": {
"message": "把 PR #482 合并到 main 吗?",
"requestedSchema": {
"type": "object",
"properties": {
"confirm": { "type": "boolean", "title": "是的,合并" },
"reason": { "type": "string", "title": "原因(可选)" }
},
"required": ["confirm"]
}
}
}
服务端的 merge_pr 工具要等用户回答 confirm: true 后才会运行。一段 Skill 文本说 「不要在没确认的情况下合并」 是模型可以被说服放弃的一句话;elicitation/create 则是服务端强制执行的一个线缆协议步骤。这就是「问」与「查」的区别。
预批准、受限凭据,以及逆向问题。 同样的逻辑反过来也成立:当 Skill 提示词 预批准 某些操作(「你可以直接跑 gh pr list 和 gh pr diff 而不必问」)时,是模型在做分类决策——而那个分类决策和其他任何下一词预测一样,会遇到同样的谄媚性和提示词注入失败模式。Skill 可以在文档里写明哪些操作安全到可以预批准;只有 协议 能让预批准成为线缆上的属性。其机制就是 受限凭据:一个 scope 是 repo:read 的 token 在构造上就无法 push;一个 MCP 工具的实现拒绝接受白名单以外的任何东西。Skill 说「这一步可以跳过检查」;MCP 服务端说「这一步从这里根本到不了」。两层、两种保证,同一种模式。
合起来,受限凭据和 elicitation 是本文反复回到的那个问题的两种协议层回答:怎么给模型一个不去赞同用户的理由?
一次工具调用在 wire 上到底长什么样
一次简化的握手,直接取自 MCP 架构文档。两条 JSON-RPC 2.0 消息,先请求后响应:
请求——客户端到服务端:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "weather_current",
"arguments": {
"location": "旧金山",
"units": "imperial"
}
}
}响应——服务端到客户端:
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [
{
"type": "text",
"text": "旧金山当前天气:68°F,多云……"
}
]
}
}这就是 使用 一个工具的完整协议表面。所有有趣的设计选择——类型化 schema、content 数组、能力协商——都在为这一件事服务:模型调用一个类型化的函数,拿回一个类型化的答案,线缆格式是可移植的。
MCP 不是什么:
- 它不是一个 prompt。你不能写
system: 你是一个礼貌的助手然后叫它 MCP。MCP 是一个针对 动作和数据 的运行时协议,不是用来引导生成的。 - 它不是一次微调。MCP 服务端不会改变模型的权重。
- 它不是一段系统提示覆写。Host 的系统提示是 Host 自己的事;MCP 是模型决定行动时 Host 能触及的东西。
- 它不是一个 Agent Skill。这部分下一节再说。
Skills 是什么(与不是什么)
Agent Skills 是 Anthropic 在 2025 年 10 月 16 日 作为 Claude 平台特性发布、又在 2025 年 12 月 18 日 作为跨平台开放标准在 agentskills.io 发布的开放标准。它的格式令人耳目一新地朴素:一个目录,里面有一个必需的文件。
my-skill/
├── SKILL.md # 必需:YAML frontmatter + 指令
├── scripts/ # 可选:智能体可以运行的可执行代码
├── references/ # 可选:智能体可按需加载的文档
└── assets/ # 可选:模板、schema、图片
SKILL.md 必须以 YAML frontmatter 开头,至少包含 name 和 description。文件正文是纯 markdown——指令、示例、配方、该做什么、不该做什么。一个完整可发布的 Skill,可以只是一个不到 50 行的单文件。一个复杂的 Skill 可能捆绑 200 行 Python 和三个 reference 文档;协议不挑。
交互模型是分三阶段的 渐进式披露(progressive disclosure):
阶段 1,发现。 智能体启动时,把每个已安装 skill 的 name 和 description 加载进系统提示。就这样。一些几百字节的小行。智能体现在 知道 这个 skill 存在以及它的用途,但还没有读它的正文。
阶段 2,激活。 当用户消息进来时,智能体的推理判断任务是否匹配某个 skill 的 description。如果匹配,智能体就把完整的 SKILL.md 读入上下文。流程性知识就是在这里落地的——配方、流程、内部风格指南。
阶段 3,执行。 如果 SKILL.md 引用了附带的脚本或 reference 文件,智能体可以调用它们。Skill 可以携带由智能体 确定性 运行的代码——同样的输入、同样的输出、字节级一致,在每一台机器和每一个模型上都一样。这是让人惊讶的那一点:Skill 不只是指令。它是 指令加代码即工具。指令告诉智能体 何时 与 为何;代码告诉智能体 怎么做,可靠地、每次都一样。在智能体其余部分是可被诱导性提问影响的下一词采样器的地方,一个附带的脚本就是一个函数调用:它返回代码说它返回的东西,模型的谄媚性先验无法与之争辩。这 才是那个「确定性」的论断,也是把代码而不是 markdown 描述的流程放进 scripts/ 的真正承重理由。
三个阶段组合在一起。智能体启动时按名字知道每个 skill。只在相关的时候加载某个 skill 的指令。只在需要的时候执行 skill 的代码。装一百个 skill 的上下文窗口成本,在闲置时——是一百对 name + description 的成本,不是一百本完整手册的成本。
采用情况。 Skills 从一项 Claude 特性出发,长得很快。写作本文时,agentskills.io 目录列出了大约四十个采用它的客户端,包括 Claude Code、Cursor、VS Code、GitHub Copilot、OpenAI Codex、Gemini CLI、OpenHands、Junie、JetBrains 的 Junie、Spring AI、Laravel Boost、Snowflake Cortex Code、Databricks Genie Code,等等。当四十个工具都同意一个带 SKILL.md 文件的目录格式,这个格式就赢了——你可以写一次 Skill,然后在你整个工具栈里复用。
附带脚本:配置与确定性
这是我在写第一个不那么平凡的 Skill 之前本该读到的一节,因为它把「Skill = markdown」这种简单框架和真实 Skill 实际做的事情之间的差距给补上了。Skill 目录允许在 scripts/ 下携带可执行代码,一旦这样做,就有了 markdown 单独给不了你的两样东西:与活系统的连接 和 确定性答案。
连接。 scripts/ 里的脚本就是一个程序。它能 import 宿主运行时提供的任何库——Postgres 驱动、HTTP 客户端、SMTP 库、SSH shell、Redis 客户端——然后用它们触及世界。markdown 不会连接任何东西;脚本会。所以当有人问「Skill 能跟我的数据库对话吗?」,答案是 「Skill 的 markdown 不行,但一个携带脚本、其代码负责建立连接的 Skill 完全可以。」 markdown 告诉智能体 何时 与 为何;脚本告诉智能体 怎么做,并且自己连到数据库。
配置。 一个连数据库、发邮件、调内部 API 的脚本需要配置——连接字符串、凭据、租户 ID、特性开关。Skill 格式刻意不发明配置协议;脚本用任何其他程序都会用的方式读取配置:
- 运行时读取的环境变量(
os.environ["PII_DB_URL"])。智能体运行时注入它们;密钥留在 Skill 文件夹之外。这是最常见的路径。 - Skill 文件夹里的配置文件(
scripts/db.yaml、.env.example、references/connection.md)。智能体读一次,然后把路径作为参数传给脚本。当配置是 per-Skill 而不是 per-host 时用这个。 - 智能体传入的参数(
python scripts/query_db.py --tenant=acme --sql="SELECT 1")。当流程的参数由用户驱动、每次调用都不一样时用这个。
三种路径都只是 plumbing。它们的共同点是让脚本拿到确定性所需的输入,连到正确的系统。
一个真实例子。 想象一个 pii-audit Skill,它的工作是标记一篇文章里未知的 email 地址。Skill 携带 scripts/check_pii.py,脚本连到生产数据库的 canonical-emails 表,对输入跑一个确定性的正则,然后输出计数和未知 email 列表。配置是 PII_DB_URL 环境变量。Skill 的 SKILL.md 指示智能体每当用户问「这篇文章里有没有未知 email?」时通过 Bash 工具调用脚本。
pii-audit/
├── SKILL.md
└── scripts/
└── check_pii.py
# scripts/check_pii.py — 与 Skill 文件夹一起打包
import os, re, sys, sqlite3
EMAIL_RE = re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+")
def main() -> int:
text = sys.stdin.read()
db_url = os.environ.get("PII_DB_URL")
if not db_url:
print("error: PII_DB_URL not set", file=sys.stderr)
return 2
conn = sqlite3.connect(db_url)
canonical = {row[0] for row in conn.execute(
"SELECT email FROM canonical_emails"
)}
conn.close()
found = set(EMAIL_RE.findall(text))
unknown = sorted(found - canonical)
if unknown:
for e in unknown:
print(f"unknown_email: {e}")
print(f"summary: {len(unknown)}/{len(found)} emails not in canonical list",
file=sys.stderr)
return 1
print(f"ok: all {len(found)} emails canonical", file=sys.stderr)
return 0
if __name__ == "__main__":
sys.exit(main())
把脚本读两遍。注意三件事:
- 它连数据库。 Skill 的 markdown 什么都没有连接;脚本连接了。Skill 文件夹,端到端,可以跟 Postgres、MySQL、Redis、任何宿主运行时能 import 的东西对话。
- 它的输出是确定性的。 给同样的输入文本和同样的 canonical 集合,脚本的 stdout 在每次运行、每台机器、每个模型上都字节一致。正则是确定性的。SQL 是确定性的。退出码是确定性的。模型的谄媚性先验没什么可抓的,因为根本没有采样。
- SKILL.md 只管「何时」与「为何」。 它没有发明流程;没有即兴发挥 SQL;没有判断某个 email 是否 canonical。这些决定都在代码里,而代码才让答案可以复现。
这就是模式。Markdown 是配方,脚本是烹饪。 一个只携带 markdown 的 Skill 能教会智能体怎么思考,但不能保证一个确定性的答案。一个携带脚本的 Skill 能教会智能体怎么思考 并且 保证答案——前提是配置接好了。两者合一——磁盘上的流程性知识,加上磁盘上的确定性执行——就是 Skill 文件夹不只是花哨系统提示的原因。
信任边界
Skills 没有鉴权模型。它们就是磁盘上的文件。智能体读它们;这就是全部接口。没有「Skill OAuth 流程」、没有受限凭据、没有 token 刷新、没有审计日志、没有人在回路的检查点。格式刻意没有发明这些。
一个跑起来的 Skill 脚本就是一个程序。它以智能体宿主进程所属的用户身份运行,继承那个用户拥有的环境变量、文件系统访问权限和网络访问权限。爆炸半径与在智能体 shell 里跑 bash 完全相同。如果智能体宿主进程的环境里有 AWS_SECRET_ACCESS_KEY,Skill 的 scripts/*.py 里就有 AWS_SECRET_ACCESS_KEY。没有 scope,没有过期,没有撤销。
由此推出几条尖锐的结论:
- 配置放在环境变量里(第 211 行)或配置文件里(第 212 行)或参数里(第 213 行)。把这些密钥当成
.bash_history一样对待——因为从 OS 的视角来看,它们就是。 - 如果某个动作需要 受限 凭据——只读这张表、只写那个桶、有时限、可撤销——那不是 Skill 的活,是 MCP 的活。MCP 服务端做鉴权;Skill 负责协调流程;让 MCP 工具带着一个有 scope 的 token 去干那件脏活。
- 如果某个动作需要 人在回路——删之前要确认、发之前要批准、部署之前要双检——Skill 的
SKILL.md可以 指示 智能体去问,但协议不会 强制 它被问。MCP 的elicitation/create会强制这一步。Skill 说「合并前不要跳过确认」;MCP 的elicitation/create让 merge 工具在用户答完之前拒绝运行。
这就是和 MCP 之间最干脆的一句话对比,也是两者要组合起来的最干脆的一句话理由:
Skill 教会智能体何时与为何;MCP 鉴权执行并强制检查。
把流程留给 Skill;把触敏系统的部分留给 MCP。两者组合;信任等级不组合。
每当答案必须每次都一样时,就伸手拿 Skill 脚本
一次正则检查、一次哈希计算、一次 SQL 计数、一次文件的内容哈希、一次 JSON-schema 校验、一次 CSV diff——任何「正确答案可复现」的步骤——都属于 Skill 脚本,不属于 markdown。markdown 可以描述 何时 跑它;脚本每次被调用都 一样地 跑。回顾你自己写的或继承的 Skill 时用这条:如果你能把这一步写成纯函数,就把它打成脚本;如果它需要判断力,就打成 markdown。
Skills 不是什么:
- 它不是一个运行时协议。没有基于 JSON-RPC 的
tools/list。Skill 在磁盘上;智能体读它。没有服务端要起。 - 它不是一个 MCP 服务端——Skill 的 markdown 自身并不能连数据库、抓网页或发邮件。但一个 Skill——这个文件夹——经常可以,靠捆绑一段负责连接的脚本(见上一节)。Skill 也可以 指示 智能体调用某个 MCP 工具去做这些事——而这种组合是本文最强大的模式,下面会讲。
- 它不是一次微调。Skill 改变的是模型的条件上下文,不是它的权重。
- 它不是一个乔装打扮的系统提示片段。Skill 有自己的发现模型、自己的激活生命周期、自己的打包代码。一个 500 行的
system:块不是同一回事,即便它覆盖了相似的领域。
机制:为什么两者都存在
到这里,上个月那篇文章就成了承重的那一篇。
在《总是说「是」的魔镜》一文里,论点是 LLM 是一个下一词预测器:它计算 ——给定前文,下一个 token 的概率。谄媚性从这个式子里以一个退化均衡的形式掉出来:模型通过 RLHF 被奖励去赞同用户的先验。那篇文章的结尾是:
“如果你想让它告诉你真相,就给它一条不经过你就能找到真相的路。”
这句话是理解为什么 MCP 和 Skills 作为两套独立标准存在的关键。
MCP 是一条把真相锚定在 活的外部数据 上的路。 当模型调用 sql_query 而数据库返回了一行计数,这个行计数就处在模型的条件上下文里。用户的先验还在那里,但工具输出也在那里,而工具输出不以用户的信念为条件。下一词的分布,以工具结果为条件,向工具的答案方向偏移,这种偏移是用户的先验不容易覆盖掉的。模型仍然可能误读工具——但它不能假装工具没说它说的东西。真相从一条不经过用户的线缆上传回来。
Skills 是一条把真相锚定在 编码化的流程性知识 上的路。 当智能体激活一个 Skill,Skill 的指令进入条件上下文。用户的先验还在那里,但现在有了一个竞争的先验——Skill 描述的流程——它是由那些已经把这件事做过很多次、并把正确方法蒸馏成配方的人写下的。下一词的分布,以 Skill 正文为条件,向 Skill 的流程方向偏移,这种偏移是一个没见过该流程的用户不容易覆盖掉的。真相来自对话开始之前就已经写好的一本手册。
两套标准以各自的方式都是 反谄媚性基础设施。它们是同一种防御的不同层次:
| 真相锚定机制 | 层次 | 方向 | 延迟 |
|---|---|---|---|
| MCP 工具调用 | 活的外部系统 | 线缆 → 上下文 | 几百毫秒 |
| MCP 资源读取 | 只读外部数据 | 线缆 → 上下文 | 几十毫秒 |
| MCP 提示 | 可复用模板 | 磁盘 → 上下文 | 几乎为零 |
| Skill 激活 | 编码化流程 | 磁盘 → 上下文 | 几乎为零 |
| Skill 脚本执行 | 确定性的代码即工具 | 磁盘 → 进程 → 上下文 | 几十毫秒 |
| Skill 脚本 + 配置 | 带参数的确定性代码即工具 | 环境/文件/参数 → 进程 → 上下文 | 几十毫秒 |
五月那篇文章的论点是,没有任何单一的提示模式能修好谄媚性先验。六月这篇文章从另一面给同一个论点:「真相锚定」也没有任何单一层次能修好那个先验。现代智能体栈——既有工作台又有笔记本的工匠——是 MCP 和 Skills 的组合,加上五月那篇文章里的推理时模式(自洽、辩论、先批判后修订),加上系统级的缓解(Constitutional AI、结构化输出),加上一个校准过的人在回路。
模式背后的模式
每一项反谄媚性干预在底层做的都是同一件事:给模型的输入上下文加一个 显式先验,让它和用户隐含的信念竞争。MCP 工具 加一个 工具输出 先验。MCP 资源 加一个 检索 先验。Skill 激活 加一个 流程 先验。Skill 脚本 加一个 确定性计算 先验。Constitutional AI 加一个 原则 先验。用户信念先验是隐式的,但它在那里。这些干预都是用模型自己的语言告诉它:「不要以那个为条件;以这个为条件。」
并排看:同一个任务,两种实现
感受差异最干脆的方式是把同一个玩具任务用两种方式都搭一遍。我们要搭一个 当前时间 工具——世界上最没用的特性——一次做成 MCP 服务端,一次做成 Skill。玩具很 trivial;实现各自的形状 才是重点。
Side A —— MCP 服务端
一个 Python 文件,用官方的 mcp SDK 暴露一个 current_time 工具。模型在启动时通过 tools/list 发现这个工具。当用户问「现在几点了?」,模型通过 tools/call 调用这个工具。服务端跑 datetime.now() 然后返回一个类型化结果。
# time_server.py — 运行方式:python time_server.py
from datetime import datetime
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("time-server")
@mcp.tool()
def current_time(timezone: str = "UTC") -> str:
"""Return the current time in the given IANA timezone.
Use this whenever the user asks for the current time or date.
"""
return datetime.now().isoformat()
if __name__ == "__main__":
mcp.run()
包括 docstring 在内共 33 行。要使用它,把它注册到你的 MCP Host(Claude Code、Cursor、VS Code,任何讲 MCP 的都行),Host 把它当作子进程启动。这个工具立刻可用,类型化 schema 校验、按名字发现。线缆格式:JSON-RPC over stdio。
Side B —— Skill 文件夹
一个目录,里面有 SKILL.md 和一个打包的脚本。当用户任务匹配 description 时,智能体读 markdown;当流程需要时,运行脚本。没有服务端。没有 JSON-RPC。流程性知识——包括「用 datetime.now().isoformat()」这个诀窍——和确定性执行一起躺在磁盘上。
time-skill/
├── SKILL.md
└── scripts/
└── now_utc.py
---
name: current-time
description: 返回当前的日期与时间。当用户问「现在几点了?」「今天是哪一天?」或要求时间戳时使用。不要猜——调用工具或运行脚本。
---
# 当前时间
当用户问当前时间或日期时,**不要猜**。可以这样做:
1. **首选:** 调用一个 `current_time` MCP 工具(如果有)。该工具有类型化,
返回 ISO-8601 字符串。
2. **回退(打包脚本):** 通过 Bash 工具从 Skill 文件夹运行 `scripts/now_utc.py`。
该脚本不读环境、不读 stdin、不存在时钟漂移——它在同一秒内,
在每一台机器上确定性地返回当前的 UTC 时间(ISO-8601 格式)。
```python
# scripts/now_utc.py — 与 Skill 文件夹一起打包
from datetime import datetime, timezone
if __name__ == "__main__":
print(datetime.now(timezone.utc).isoformat())
```
3. **最后手段:** 如果工具和 bash 都没有,告诉用户你拿不到时钟,
请他们确认时间。
永远不要编造时间戳。如果你不确定用户说的是本地时间还是 UTC,
问一下。这个 skill 的默认是 UTC。
总共 55 行,其中一半在打包的脚本里。markdown 把智能体偏向正确的流程;脚本在 MCP 工具不可用时提供确定性执行。
改了什么——以及为什么
跟 MCP 版本同样的回退形状,但有一个关键区别:现在的回退是和流程一起被版本化的代码,不是塞进 SKILL.md 里的一行代码。MCP 服务端是工作台;Skill 是笔记本——按需翻开,有时还附带一把凿子。
同一个任务。两种形状。不同的延迟,不同的失败模式,不同的扩展故事。MCP 服务端能同时服务多个智能体,且在 Host 重启后存活。Skill 是 per-host、per-folder 的,但它跟着代码库走,能扛住任何工具链的变动。
实现本身并不告诉你该用哪个
你绝对可以把同一个 生产 特性用任意一种方式实现,权衡各有不同。「时间」这个例子是玩具;在真实系统里,选哪个并不显然,取决于:
- 这个特性需要 跨多个 Host 存在吗? → MCP。
- 这个特性需要 活在仓库里,跟代码一起版本化 吗? → Skills。
- 这个特性需要 执行不受信任的代码 吗? → 把 MCP 服务端放在沙箱里。
- 这个特性需要 教会智能体何时去做 X 吗? → Skills。
下一节就是决定这件事的判定流程。
自己动手:更大规模的并排实现
两套标准都奖励亲手实验。留出二十分钟。
同时搭建一个「问候」工具的两个版本
第 1 部分 —— MCP 版本(约 5 分钟):
- 安装官方 Python SDK:
pip install mcp - 把上面的
time_server.py代码片段保存为time_server.py。 - 跑一次:
python time_server.py。它会在 stdio 上空闲。按Ctrl+C。 - 注册到 Claude Code:把路径加到
.mcp.json。 - 问 Claude Code 「现在几点了?」,观察
tools/call往返。
第 2 部分 —— Skill 版本(约 5 分钟):
- 创建
~/.claude/skills/current-time/(或你客户端对应的位置)。 - 把上面的
SKILL.md放进那个目录。 - 重启客户端,让 skill 的元数据在启动时被加载。
- 问 「现在几点了?」 —— 智能体激活 Skill,运行回退那一行代码,返回 ISO-8601 时间戳。
- 验证:编辑
SKILL.md把默认时区改成Asia/Tokyo。再问一次。下一次答案反映新的流程,而且不需要重启任何服务端,因为 Skills 是按需从磁盘拉的。
第 3 部分 —— 组合 版本(约 10 分钟):
- 保持 MCP 服务端在跑。
- 编辑 Skill 的正文,把回退路径删掉:它现在应该 只 指示智能体使用 MCP 工具。
- 再问一次。Skill 的流程说「用 MCP 工具」;MCP 工具可用;智能体调用它。
- 现在停掉 MCP 服务端(Ctrl+C)。再问一次。Skill 的流程降级到 bash 那一行。
- 这就是组合。这就是现代智能体栈。
对比这三次的体感。MCP 版本 快且类型化,但感觉 远——模型和工作台之间一根线。Skill 版本 柔和、流程化,但在该硬的时候是硬的——指令在智能体脑子里,当流程需要触及真实世界时,磁盘上有一段确定性的脚本。Skill 的回退不是一个想法;它是代码,代码每次都长一样。组合版本是 两者同时——Skill 知道做什么,MCP 知道怎么快快地做,而 Skill 的回退让系统在 MCP 离线时优雅降级。
那个三位一体——类型化工具 + 流程性知识 + 优雅降级——是真正能上生产的智能体团队在做的事。其他都是装饰。
决策矩阵
这是流程。五条规则,每条一句话,对应一类特定形状的问题。
决策矩阵——五条规则,每条 ≤ 2 句话
规则 1 —— 当智能体需要 活的 外部数据或副作用时,伸手拿 MCP。 如果答案必须反映世界的当前状态(数据库的一行、磁盘上的一个文件、一个 GitHub PR、一次 API 响应、用户的日历),MCP 就是正确形状。工作台是用来对外部世界采取行动的。
规则 2 —— 当智能体需要 编码化的专业能力(基础模型缺乏的那种)时,伸手拿 Skills。 如果答案需要一道流程、一份内部风格、一个领域特定的配方、或一份被人类打磨过的清单,Skills 是正确形状。笔记本是用来教模型怎么工作的。
规则 3 —— 当多步骤流程要调用外部工具时,伸手拿 两者。
如果一道流程不接触活的系统就完不成——通过读数据库填表、调用 gh 发代码评审、通过查询分析 API 生成报表——就把两者组合起来。Skill 协调流程;MCP 工具执行步骤。
规则 4 —— 当智能体需要只读上下文数据时,伸手拿 MCP resource(不是 tool)。
如果数据不需要被操作,只需要被读入模型的条件上下文,把它暴露成 resource。resource 没有副作用,概念上是「抓文件」,在模型只需要字节内容时省掉 tool-call 的开销。
规则 5 —— 当一个普通系统提示或微调就够用时,伸手拿 都不。 如果问题是 风格——更简洁一些、用英式拼写、绝不使用被动语态——把它写在系统提示里。如果问题是模型已经掌握的 知识——一般常识、常见的编程模式——别为 Skill 付费。把这两套标准留给那些「确实能值回票价」的场景。
规则 6 —— 当流程需要确定性步骤时,伸手拿 Skill 脚本。
如果一道流程里有一步的 正确答案 在每次运行时必须一样——一次哈希、一次正则匹配、一次 SQL 计数、一次 JSON-schema 校验、一次内容指纹——把它打成 scripts/ 下的打包脚本,让 SKILL.md 告诉智能体何时调用它。同样的输入,同样的输出,字节级一致。模型的谄媚性先验无法重写一个确定性的返回值。每当你意识到自己在 markdown 里描述了一步本可以写成纯函数的操作,就用这条。
规则 7 —— 当动作需要 受限凭据 或 人工确认 时,伸手拿 MCP。
PII 读取、生产写、金额操作、任何破坏性的事——MCP 的鉴权边界和 elicitation 通道是正确形状。一个 scope 是 repo:read 的 token 不能 push;一个在 merge_pr 之前调用 elicitation/create 的工具在没有用户的 confirm: true 时不能合并。一个从 os.environ["PROD_API_KEY"] 读密钥的 Skill 脚本继承了宿主的爆炸半径;没有 scope、没有过期、没有审计轨迹、没有强制检查。让 MCP 服务端做鉴权;让 Skill 协调;让 MCP 去碰那个触敏的系统。
规则 8 —— Skill 脚本的安全边界就是宿主进程。 把带凭据的执行留给 MCP。把 Skill 留给流程;把 MCP 留给鉴权和检查的那部分。两者组合;信任等级不组合。如果你能把这个动作描述成「模型决定做这件危险的事」,它就该在 MCP 后面。如果你能把它描述成「模型知道正确的流程」,它就该在 Skill 里。危险的事需要安全边界。流程需要被编码。把标准挑对。
规则 0(承重的那条)—— 拿不定主意时,听模型的先验。 两套标准都听命于宿主的推理。一个没被激活的 Skill 就是一个没发生的 Skill。一个没被调用的 MCP 工具就是一个没发生的工具。没有任何系统能覆盖模型 使用它 的决定。如果「要不要激活这个 skill」「要不要调用这个工具」的先验错了,那是系统提示的问题,不是 Skill 或 MCP 的问题。
读两遍。存到你团队的 wiki 里。你接下来要交付的五个智能体特性,会各自干净地落到其中一条规则里。
组合模式:Skill 编排的 MCP
这是我一直写到这里的部分。在现代智能体栈里你能做的最强大的事,是把这两套标准组合起来,让 一个 Skill 编排一个用 MCP 工具作为步骤的流程。
一个具体的例子。想象一个团队同时发布一个 code-review Skill 和一个 github MCP 服务端。Skill 是一个目录,里面的 SKILL.md 描述团队的内部评审流程——查什么、按什么顺序、严重程度怎么分。MCP 服务端把 list_prs、get_pr_diff、post_review_comment、merge_pr 暴露为工具。
注意 Skill 没有 做什么。它没有调 gh。它没有解析 diff。它没有发评审。所有这些都是 MCP 的活。Skill 做的是 告诉智能体何时与为何——流程、内部风格、严重度量表、「不要合并」这道闸。智能体按流程以正确的顺序调用正确的 MCP 工具。
也注意 Skill 和智能体都不能覆盖什么:merge_pr 工具要等用户回答 MCP 服务端的 elicitation/create 请求之后才会运行。Skill 的流程说「合并前不要跳过确认」。模型可能被说服放弃那句话。MCP 服务端不是一句话。它是一个线缆协议步骤。流程引导 意图;协议强制 检查。这就是「指示」与「检查」之间的承重差别,也是为什么两套标准是组合而不是替代。
这就是模式。Skills 把流程放到磁盘上。MCP 把工作台挂到线缆上。 任何一个单独都不够。一个只携带 markdown 的 Skill 能教会智能体怎么工作;一个同时携带 scripts/ 下脚本的 Skill 也能不绕线缆地触及世界。一个 MCP 服务端能给智能体跨 Host 共享的类型化活的工具。流程被编码化、可分享;执行要么是磁盘上确定性的(Skill 脚本),要么是线上类型化的(MCP 工具)。当任何一边缺席,系统作为整体仍能优雅降级。
看到一个新智能体特性时,先画那四张图
写任何代码之前,画这四个框:用户、智能体、Skill、MCP。问四个问题:
- 用户 说什么?
- 智能体 决定什么?
- Skill 教什么?
- MCP 服务端 执行什么?
如果你的草图里有空格,那这个特性还没设计。如果草图把所有东西都放到 Skill 里,那流程和执行没有分开。如果草图把所有东西都放到 MCP 服务端里,那有一道流程正住在该住在笔记本里的代码里。四框草图是把两层不互相吞掉的最简单办法。
更深一层
这一节是任何决策矩阵都替代不了的。
MCP 和 Skills 不是对手,因为它们不在同一个轴上。它们不是解决同一问题的两种方式;它们是同一套智能体栈的两层,就像 HTTP 和 DNS 是互联网的两层。HTTP 原则上可以做自己的名字解析。DNS 原则上可以携带负载。它们都不做,因为每一层都有自己的形状、自己的失败模式、自己的生命周期。两层组合出一个任何单独一层都搭不出来的系统。
智能体栈有同样的结构。模型层预测下一个 token。Skill 层把预测条件化在编码化流程上。MCP 层把预测条件化在活的工具输出上。用户层把一切条件化在用户的问题上。每一层都有自己的形状、自己的延迟、自己的失败模式。智能体是把它们整合在一起的那个东西。
这也是为什么「我该用哪个?」是错的问题。对的问题是「我的智能体需要的扩展,形状是什么?」如果扩展是 可执行的、触及外部世界的基础设施,用 MCP。如果扩展是 教智能体怎么工作的编码化流程,用 Skills。如果扩展是 一道流程同时要触及外部世界,两个都用。问题不是 哪个。问题是 什么形状。
最后——把上一篇文章那一节闭环——这两种形状都是反谄媚性基础设施。MCP 反谄媚,是因为工具的答案处在条件上下文里,一个诱导性提问无法说服它假装没说。Skills 反谄媚,是因为流程处在条件上下文里,把下一词的分布偏向「以前这么做能成」的方向。没有一种形状能消除模型赞同用户的倾向。两种形状都 给它一个不去赞同的理由。这才是关键。
一帧看完的决策手册
给团队白板用的。两个轴、四个象限、一层覆盖。把你的任务标上去;读象限;检查覆盖。决定是几何的,不是流程的。
敏感性 & HITL 覆盖层。 如果你的任务落在象限 1 或象限 4 并且 动作是敏感的(PII、生产环境、金额),或者在副作用之前需要人工确认,决定就升级为带 受限凭据 和 elicitation/create 的 MCP。Skill 说「这一步可以跳过检查」;MCP 服务端说「这一步从这里根本到不了」。Skill 在文档里写明哪些操作安全到可以预批准是一句话;一个 scope 是 repo:read 的受限 token 不能 push。
两个轴,一层覆盖。原本花 250 字线性描述的同一个形状校验,现在白板上就一帧。在团队讨论时用这张图;当答案是敏感动作时翻到上面的 鉴权与人在回路 小节;当流程需要编排时翻到 组合模式 这一节。
推荐资料
两套标准都是活跃的、仍在演进的、文档完善的。下面是我在设计一个智能体特性时一直打开的几个来源:
modelcontextprotocol.io——MCP 的官方规范、架构概览、SDK 列表。从 Architecture 这一页开始;JSON-RPC 那段走读是关于线缆格式的最干净的入门。agentskills.io——Skills 的跨平台开放标准,含完整的 SKILL.md 格式规范和采用客户端目录(写作本文时约 40 个)。
工坊自测:你更缺哪一边?
失败模式是镜像的。有的团队过度依赖工具,发布的智能体在工作台上锤来锤去,却不知道自己在建什么。有的团队过度依赖流程,发布的智能体写出漂亮的回答却连不到数据库。有的团队完全忘了安全层。一次六题的自测。
打开这 6 题自测
打开这 6 题自测(不依赖 JavaScript:自己数勾,然后看下面的解读)
解读(不依赖 JavaScript——数你的勾):
- 0–2 分: 平衡型。你在合适的时候用上了两层,并且把敏感操作放到安全边界后面。继续保持。
- 3–4 分: 偏一边,或者漏掉了安全层。「有工具无流程」失败模式(Q1、Q3)说明你过度依赖 MCP;「有流程无工具」失败模式(Q2、Q4)说明你过度依赖 Skills。Q6 打了勾,说明安全层整个漏掉了——你的「先问一下」活在模型的好意里,不活在协议里。
- 5–6 分: 单臂工匠。你要么漏掉了另一层,要么在没有安全边界的情况下裸跑。最快的修法:拿你下一个智能体特性,强迫自己同时搭两边——一个 Skill 编排至少一个 MCP 工具,并在任何破坏性步骤之前使用
elicitation,端到端。
结语
工匠从来不是瓶颈。光有工作台不够。光有笔记本也不够。2025 年浮出、在 2026 年稳定下来的那个有意思的设计,是工匠在工作台前坐着、笔记本翻开在他手边、执行他没即兴发挥过的流程、读他没编造过的数据。MCP 是工作台。Skills 是笔记本——而笔记本现在也通过 scripts/ 自带手工具了。工匠伸手去拿离他最近的那一件:一条通往工作台的线,或者从笔记本里翻出来的一把凿子。智能体就是工匠。工作的形状决定你伸手去拿哪一套标准,以及怎么组合它们。模型仍然一如既往是一个下一词预测器——但现在它是一个被给了两种「不经过用户就能找到真相」的方式的下一词预测器。这就是全部诀窍。
试一下工坊自测。然后带着两种形状的视角去搭下一个智能体特性,看看对话会多么干脆地坍缩到「这一层归谁?」
评论
请接受“功能性”Cookie 类别以查看和发表评论。
评论加载失败。您可以重试,或前往 GitHub 查看讨论。
在 GitHub 上查看