工具、Skill、MCP 与插件:怎样扩展能力
按动作、方法、接入协议与宿主扩展点区分能力,理解它们如何组合。
工具、Skill、MCP 与插件:怎样扩展能力
想让智能体更会检查代码,可能需要更准确的扫描工具,也可能需要一套检查流程,或者接入现有分析服务。这几种需求不在同一层,用同一个"安装插件"来描述会隐藏关键区别。
上一篇讲会话与记忆,解决的是"跨轮次留下什么"。这一篇接着讲能力扩展,解决的是"这一轮能做更多什么"。
先问缺少的是动作、方法,还是接入方式,再选择工具、Skill、MCP 或具体宿主的扩展机制。
四者各自补什么
这是职责上的比较,具体产品的名字与支持范围会不同。插件没有一个跨所有宿主通用的运行模型,也不应默认互相兼容。
四者的关系可以理解成一句话:工具提供动作,Skill 规定动作怎么用,MCP 规定动作怎么从外部来,扩展规定动作怎么嵌进这个宿主。
缺什么,该补哪一层
上面那张表是静态比较。真正用起来需要一个判断顺序。按下面的顺序问,通常在第二步或第三步就能停。
四步的顺序有讲究。先确认动作有没有,缺动作时不要用 Skill 绕过去。 Skill 里的"运行检查脚本"只是一条任务说明,要真正运行,仍需要可用工具、正确路径和执行权限。把 Skill 复制进目录,不会自动创建操作系统能力。
第二步和第三步的分界是"能力在哪儿"。如果这件事在你自己的机器上就能做完,一个本地工具就够;如果它背后是一个已经在跑的独立服务,而且别的宿主也需要用同一套,那才值得走 MCP。
第四步是"宿主内部的事"。加一个斜杠命令、给工具调用加一道确认、往上下文里注入内容,这些都不属于前三层——它们要写代码进宿主。
四层各长什么样
抽象的分层容易停在口头。把这四层各自最小的一个实例放在一起,差别会具体很多。
工具是宿主里注册的一个函数,带名称、描述和参数 schema:
import { Type } from "typebox";
pi.registerTool({
name: "greet",
label: "Greet",
description: "Greet someone by name",
parameters: Type.Object({ name: Type.String({ description: "Name to greet" }) }),
async execute(toolCallId, params, signal, onUpdate, ctx) {
return { content: [{ type: "text", text: `Hello ${params.name}` }], details: {} };
},
});Skill 是一个目录,里面放一份 SKILL.md:
---
name: pdf-tools
description: Extract text and tables from PDF files. Use when reading, converting, or inspecting PDFs.
---
# PDF tools
Read `references/formats.md` before converting a document. Run scripts relative to this skill directory.MCP 是一份服务器配置,声明用哪个可执行文件连上去:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
}
}
}扩展是一个 TypeScript 模块,默认导出工厂函数:
import { type ExtensionAPI, isToolCallEventType } from "@earendil-works/pi-coding-agent";
export default function (pi: ExtensionAPI) {
pi.on("tool_call", async (event, ctx) => {
if (isToolCallEventType("bash", event) && event.input.command.includes("rm -rf")) {
const ok = await ctx.ui.confirm("Dangerous!", "Allow rm -rf?");
if (!ok) return { block: true, reason: "Blocked by user" };
}
});
}四份代码的差别,正是四层的差别:工具是一个函数,Skill 是一份文档,MCP 是一个外部进程的连接方式,扩展是一段在宿主里运行的代码。
只有第四种会执行你自己的代码,这决定了它的风险等级和排查方式——前三种出问题,多半是配置或依赖;第四种出问题,先看它自己。
工具提供动作,不负责保证所有决策
工具通常有名称、描述、参数约束与结果。模型可以选择并构造调用,但宿主仍要验证并执行。说明"只读文件"不能替代实际只读实现,参数 Schema 合法也不能保证路径有权限。
一个工具存在于注册表,不代表本轮已向模型开放;模型能看到,也不代表它真的选择过。最终判断应回到调用与结果证据,而不是安装成功提示。
这三个"不代表"对应三种不同环节:
第 2 条最容易踩坑。工具装好了、界面里也显示了,但宿主这一轮没有把它写进请求——这时候模型的"不用这个工具"是正确行为,排查方向应该是请求组装,不是工具实现。
Skill 提供方法与支持材料
Skill 可以把检查步骤、参考资料和模板放在一起,让任务方法被复用。宿主可能先提供名称与描述,需要时再展开正文;具体发现与调用规则应按相应产品核对。
Pi 的行为是可以逐字确认的。启动时,Pi 扫描配置的 Skill 位置,只把每个 Skill 的 name、description 和路径写进系统提示,不写正文。系统提示里对应的那一段长这样:
The following skills provide specialized instructions for specific tasks.
Use the read tool to load a skill's file when the task matches its description.
Resolve relative paths against the skill's directory (parent of SKILL.md). Use absolute paths in tool calls.
<available_skills>
<skill>
<name>pdf-tools</name>
<description>Extract text and tables from PDF files. Use when reading, converting, or inspecting PDFs.</description>
<location>/Users/jeffery/.pi/agent/skills/pdf-tools/SKILL.md</location>
</skill>
</available_skills>任务匹配时,模型才去读 SKILL.md 并按里面的说明执行。这条设计的目的很明确:把详细指引挡在上下文之外,直到真的需要。
有两个细节会直接影响使用体验。一是 disable-model-invocation: true:设了这个字段的 Skill 完全不进系统提示,只能通过显式命令 /skill:name 调用。它的用途是把长流程的命令行操作留在人工触发这一侧。二是模型可能匹配不到相关 Skill——文档里直说了"A model might fail to load a relevant skill",所以需要强制时用 /skill:name,参数会作为一条用户请求追加到加载后的指令后面:
/skill:pdf-tools extract report.pdfMCP 连接宿主与服务
MCP 可以把工具、资源等能力通过协议提供给 AI 应用。规范的架构页把三方角色定义得很清楚,可以直接引用:
MCP Host: The AI application that coordinates and manages one or multiple MCP clients
MCP Client: A component that maintains a connection to an MCP server and obtains context from an MCP server for the MCP host to use
MCP Server: A program that provides context to MCP clients
关系是:Host 为每个 Server 建一个 Client,每个 Client 各自维持一条连接。Server 可以在本机,也可以在远端——本地 Server 通常走 stdio 传输,远端 Server 通常走 Streamable HTTP 传输。
本版规范只有两种 transport。 传输层页把标准绑定列得很清楚:stdio 用标准输入输出流传换行分隔的消息;Streamable HTTP 用 HTTP POST 发送每条消息,回复要么是一个 JSON 对象,要么是一个请求级 SSE 流。旧版的 HTTP+SSE 传输已经不是其中之一:
The HTTP+SSE transport from protocol version 2024-11-05 has been deprecated since protocol version 2025-03-26 and is classified as Deprecated under the feature lifecycle policy (SEP-2596).
所以"SSE 是第三种 transport"这个说法要改。SSE 在本版里是 Streamable HTTP 的可选流式能力,不是独立的一层。同理,文档站路径里的 2026-07-28 是规范修订版的日期式版本标识——它标记的是这一版规范的内容,不是发布日期。
这里有一个容易踩的坑:规范当前修订版,和某个客户端实际在说的版本,是两件事。 Pi 自带的 MCP 客户端在 packages/mcp/src/protocol/types.ts 里把最新版本写死为:
export const LATEST_PROTOCOL_VERSION = "2025-11-25";
export const SUPPORTED_PROTOCOL_VERSIONS = [
LATEST_PROTOCOL_VERSION, "2025-06-18", "2025-03-26", "2024-11-05"
] as const;连接时它走的还是带 initialize 握手的老流程:先发 initialize 请求带上自己的协议版本和 capabilities,收到服务端回传的版本后校验,不在支持列表里就直接报错,然后补发 notifications/initialized。
所以读 MCP 文档时要分清三件事:规范现在写了什么、你的客户端实现到了哪一版、对端服务端支持哪一版。把某个版本的消息字段当成永远不变的通用格式,是最常见的误用。
本版规范定义三种核心原语:
MCP defines three core primitives that servers can expose
每种原语都有对应的发现方法(*/list)、获取方法(*/get),部分还有执行方法(tools/call)。客户端先 tools/list 拿到工具列表,再调用其中某一个。
规范不定权限模型。 这一点容易被误解。规范的授权页把范围写得很窄:
The Model Context Protocol provides authorization capabilities at the transport level
也就是说,认证是传输层的事——HTTP 传输用 bearer token、API key 和自定义请求头,规范推荐用 OAuth 取令牌。capabilities 字段描述的是"支持哪些原语、要不要接收变更通知",也就是双方能处理哪些操作,不是"允许对系统做什么"。
同一版规范里还有几项已废弃。Sampling(让 Server 反过来请求模型生成内容)、Logging(Server 向 Client 发日志)以及 Roots(Client 告知 Server 关注哪些目录)都已标记为 deprecated,官方给的迁移方向是:Sampling 改为直接对接模型 Provider API,Logging 改为写 stderr 或用 OpenTelemetry,Roots 改为通过工具参数、资源 URI 或服务端配置传递目录。
Roots 尤其值得留意。规范自己的说明写得很直白:"while roots communicate intended boundaries, they do not enforce security restrictions."它是一个范围约定,不是隔离机制。
MCP 也不决定模型一定调用哪个工具,不替应用管理全部上下文,更不意味着服务因此拥有桌面上的任意权限。Server 能做到什么,取决于它被允许以什么身份运行——这是宿主的权限问题,权限与提示注入那一篇会展开。
在 Pi 里接一个 MCP 服务器
Pi 内置了对 MCP 的支持。配置一个本地 stdio 服务器,检查连接,然后启动 Pi:
pi mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem .
pi mcp list远端服务器用 --url:
pi mcp add docs --url https://example.com/mcp --bearer-token-env-var DOCS_TOKENpi mcp list 会连接每一个启用的服务器,打印它的状态、工具和错误。如果某个配置项无效或有服务器没连上,命令以状态码 1 退出——这让它可以直接用在脚本里做健康检查。想看当前接了哪些服务器,这是最直接的一条命令。
配置放在两个位置:用户级的 ~/.pi/agent/mcp.json 对所有项目生效,项目级的 .pi/mcp.json 只在项目被信任之后才读取。同名的项目配置会覆盖用户级配置。文件格式和别的 MCP 客户端一致:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
},
"docs": {
"url": "https://example.com/mcp",
"headers": { "Authorization": "Bearer ${DOCS_TOKEN}" },
"description": "Search and read the product documentation"
}
}
}pi mcp remove <server> 移除一个服务器,已存的 OAuth 凭据会保留。
工具暴露的名字是 mcp__<服务器名>__<工具名>。 这个命名有实际用途:它让 --tools 可以按前缀过滤。--tools 默认不会移除 MCP 工具,但只要有一条以 mcp__ 开头,就会开始过滤:
pi --tools read,codemode,'mcp__radius__*'这条命令只保留 radius 这个服务器的工具。想一次禁用全部 MCP,用 --no-mcp——它不连接任何服务器,也就没有 MCP 工具和 /mcp 命令。
暴露方式本身有四种,默认值也值得知道:
toolExposure 可以按单个工具覆盖服务器的暴露方式,键是精确的工具名或带 * 的模式,精确名优先于模式、模式之间第一个匹配优先。
在会话内部,/mcp 可以查看连接状态、登录登出、重连、改暴露方式、启停服务器。pi mcp add、pi mcp remove、pi mcp list、pi mcp login、pi mcp logout 这些命令不需要开会话就能跑,Agent 自己也可以通过 bash 调用它们——但要注意,shell 层的 pi mcp 命令不加载扩展,只能看到文件里配置的服务器。
扩展负责接入宿主自己的扩展点
在支持扩展的宿主中,可以通过代码注册自定义工具,增加命令、监听事件或展示结果。它运行在哪里、能访问什么、如何重载与释放资源,都取决于具体宿主。
扩展和宿主同进程运行,可能具有相同操作系统权限;"这是个插件"并不意味着它被沙箱隔离。Pi 官方扩展文档在讲扩展位置时给了明确的安全声明:
Security: Extensions run with your full system permissions and can execute arbitrary code. Only install from sources you trust.
这句话不是套话,是可以被验证的机制。扩展是宿主进程内加载的 TypeScript 模块,能看到提示词、工具调用、文件、凭据和会话历史。Pi 的安全文档把边界说得更具体:Pi 以启动它的账户权限读写文件、改变和执行文件,并且不会在每次工具调用前都请求批准;扩展、包管理器、语言服务器和其他子进程都以同样的权限运行,除非有操作系统或虚拟化边界把限制住。
安装前需要考虑代码来源和实际权限,不能只看展示名称。Pi 对应的做法是项目信任机制:.pi/extensions、.pi/settings.json、.pi/mcp.json、.pi/skills 这些项目级资源,都要先给出信任决定才会加载。但信任机制管的是"启动时加载什么",管不了"运行起来能访问什么"——文档自己写明了这一点。
名字叫"插件"的,未必是插件
生态里叫 plugin 的东西可能实为扩展、Skill 包、MCP 服务器集合,或者只是一个安装脚本。看包里的清单比看名字可靠。
判断一个包到底提供了什么,看它自己的资源声明。Pi 的包用 package.json 里的 pi 字段显式列出内容:
{
"name": "my-pi-package",
"keywords": ["pi-package"],
"pi": {
"extensions": ["./src/extension.ts"],
"skills": ["./resources/skills"],
"prompts": ["./resources/prompts/*.md"],
"themes": ["./resources/themes/*.json"]
}
}有 extensions 意味着它会执行代码;有 skills 意味着它提供的是方法说明和材料;两者都有的包,风险等级完全不同。这一层展开是另一个专栏的事,这里只留一条可用的判断依据。
组合比替代更常见
同一个检查功能可以由扩展注册工具,工具调用 MCP 服务,Skill 再指导模型按合适步骤使用。也可以没有 MCP,由宿主直接提供一个本地工具。
两种结构都可能合理。若只是读取一个教学文件,直接工具已经足够;已有稳定外部服务需要复用时,标准接入才可能更有价值。不要为了凑齐名词增加每一层,也不要把它们误当成四种互相排斥的 Agent 类型。
组合起来之后,判断仍然要落在证据上。工具被调用了吗,结果回填了吗,模型是基于哪一层的信息给出的结论——这三件事各自可以在事件流和会话文件里查到。第 6 篇讲的排查顺序在这里同样适用。
小结
四层能力解决四类缺口:缺动作用工具,缺方法用 Skill,缺外部服务接入用 MCP,缺宿主集成用扩展。
MCP 这一层有几件事值得记住:本版只有 stdio 和 Streamable HTTP 两种 transport,SSE 是后者的可选流式能力;三种核心原语是 Tools、Resources、Prompts;规范只管传输层认证,不定义"允许对系统做什么"的权限模型;Sampling、Logging、Roots 已废弃。
装上扩展之前,先看它是不是要在你的宿主进程里执行代码。扩展和宿主同进程运行,权限相同,也没有默认沙箱。
下一篇会接着这个思路,介绍工作流与协作:什么时候用固定流程,什么时候让模型自己决定下一步。