OpenCode 扩展与集成:Plugins、MCP、权限与快照
Posted on Mon 05 October 2026 in AI 编程 • Sucre Liu
权限:有序规则 + 最后匹配者胜
OpenCode 的权限不是一堆开关,而是有序规则列表,每条三个字段:
{
"permissions": [
{ "action": "shell", "resource": "*", "effect": "ask" },
{ "action": "shell", "resource": "git status *", "effect": "allow" },
{ "action": "shell", "resource": "git diff *", "effect": "allow" },
{ "action": "shell", "resource": "git push *", "effect": "deny" }
]
}
规则是"最后匹配者胜",所以宽规则在前、例外在后。写成反过来,deny 会被后面的 allow 盖掉。
匹配是整值通配:* 匹配任意字符(含 /),? 匹配单字符,其余按字面量。一个以 * 结尾的 shell 规则也会匹配"无参数"形式——git status * 同时命中 git status 和 git status --short。
可用的 action 和它们的 resource:
| action | resource |
|---|---|
read |
路径 |
edit |
edit/write/patch 的目标路径 |
glob |
请求的 glob 模式 |
grep |
请求的正则(不是搜索路径) |
shell |
扫描器产出的命令串(复合命令可能产生多条) |
subagent |
目标 agent ID |
skill |
skill ID |
webfetch / websearch |
URL / 查询 |
external_directory |
外部目录边界 |
<server>_<tool> |
MCP 工具 |
一次操作可能检查多个资源(比如一个补丁改多个文件)。判定规则:任一 deny 就 deny;否则任一 ask 就 ask;否则 allow。
默认策略与"为什么它动了我的 .env"
每个 agent(包括自定义的)都从这套基础策略起步:
[
{ "action": "*", "resource": "*", "effect": "allow" },
{ "action": "external_directory", "resource": "*", "effect": "ask" },
{ "action": "read", "resource": "*.env", "effect": "ask" },
{ "action": "read", "resource": "*.env.*", "effect": "ask" },
{ "action": "read", "resource": "*.env.example", "effect": "allow" }
]
默认"全放",但对外部目录和.env 读取会问。内置 agent 在此之上各有追加:Build 允许提问;Plan 允许提问、除 ~/.opencode/plan 外拒绝编辑;General 拒绝提问和派生子代理;Explore 除读取/glob/grep/网页外基本全拒。
这也解释了那个经典困惑:为什么它读 .env 时有时不问你?如果你之前选过 "Allow always",那条批准会被存成项目级的持久 allow 规则。而保存的批准永远压不过你配置的 deny——这是有意的。
三条硬经验
- shell 是最高危的 action。 它以宿主用户的文件系统、进程和网络权限运行,且"从命令文本推断目录"只是尽力而为。官方建议也很直接:用窄的 shell 允许清单,别指望写一条模式去识别所有危险命令。
- deny 优先用在这些地方:
read命中*.env*、read/edit命中密钥目录、shell命中git push *、subagent命中不想要的子代理、skill命中内部技能。 - 需要"任何配置都不能开"的规则,用 policy(
experimental.policies),它能把allow/ask强制变成deny,且全局与 Console 托管策略压过项目配置——这正是组织阻止某个仓库自行放开命令的机制。
批准交互里,客户端可以回复 once(只批准本次)、always(批准并为项目保存建议的模式)、reject(拒绝并拒绝该会话其余待批准的请求)。保存的模式因工具而异:shell 存命令前缀,skill/subagent 存 ID,有的工具建议 *。定期清理过宽的已保存批准,它们不会自己过期。
MCP:连接外部能力
命令行添加最省心:
opencode mcp add context7 --url https://mcp.context7.com/mcp
opencode mcp add context7 --global --url https://mcp.context7.com/mcp
opencode mcp list
V2 的结构是 mcp.servers,服务器名不直接挂在 mcp 下:
{
"mcp": {
"servers": {
"my-server": { "type": "local", "command": ["npx", "-y", "example-mcp-server"] },
"context7": { "type": "remote", "url": "https://mcp.context7.com/mcp" }
}
}
}
要点:
- 远程默认启用 OAuth(PKCE、刷新令牌、支持时做动态客户端注册)。需要登录就跑
/mcps选它授权,或opencode mcp auth <name>;换账号用mcp logout。 - 只有服务器只用 API key/自定义头时才设
oauth: false,且密钥用环境变量替换{env:NAME},别写进文件。 - 关闭但保留配置用
disabled: true,没有enabled字段。 - 工具名会规范化为
<server>_<tool>(非字母数字下划线连字符转_),server 名要起得短且规范化后仍唯一。 - 想隐藏而非断开某 server 的工具,用权限:
{ "action": "context7_*", "resource": "*", "effect": "deny" }。 - 超时有
startup(默认 30s)、catalog(30s)、execution(12h),可按 server 覆盖。 codemode默认true,把工具按 server 分组暴露;需要让工具留在 provider 原生工具列表时设false。
MCP 会消耗模型上下文,所以只加你需要的 server——这点和"插件越多越强"的直觉相反。
插件:能力大,但要有边界
插件从包或本地目录加载:
{
"plugins": [
"opencode-acme-plugin@1.2.0",
"@acme/opencode-plugin",
"./plugins/local",
{ "package": "@acme/opencode-plugin", "options": { "strict": true } }
]
}
几个机制:
- 本地发现:每个
.opencode/plugins/里的.ts/.js文件和直接的插件包目录会被加载。注意:项目根opencode.json旁边的plugins/目录不会被自动发现,要么显式配置,要么放进.opencode/。 - 控制:条目按顺序处理,用
-前缀禁用、*匹配全部、.*匹配前缀;后面的 ID 可以重新启用。 - 有两个内置插件忽略删除:
opencode.config.policy和opencode.provider.opencode——保证仓库无法关掉策略执行。 - CLI 管理包插件:
plugin add/list/check/update/remove,支持 npm 版本/标签/范围,以及 Git 规格(含私有仓库、子目录选择器)。 - 配置目录下的改动会热加载;未监视的本地依赖可能要
opencode service restart。
快照:能撤销,但不等于备份
/undo 会回滚最近的对话和文件改动:它把回滚暂存起来,恢复受影响文件,并把被你删掉的那条提示放回输入框让你改:
/undo
# 改写提示后重新提交,才真正提交这次回滚
/redo # 提交前可取消
它也提供快捷键(<leader>u 撤销、<leader>r 取消),或选中较早消息选 Revert。
范围是关键,别高估:
| 内容 | 是否捕获 |
|---|---|
| 活动目录内已跟踪文件 | 是 |
| 未忽略的未跟踪文件(单个 ≤ 2 MiB) | 是 |
| 被 gitignore 的文件 | 否 |
| 单个 > 2 MiB 的未跟踪文件 | 否 |
| 活动目录之外的文件 | 否 |
| git 元数据(提交、分支)、数据库、进程 | 否 |
而且快照需要 git 仓库;不在仓库里,/undo 只能回滚对话,不能恢复文件。存储用的是数据目录下独立的内部 git 对象库,不会创建提交或移动分支。
官方对安全的表述值得原样记住:快照是便利功能,不是事务,也不是备份。 shell 可以改服务、进程、网络、数据库、git 状态、被忽略的产物或活动目录外的文件,恢复快照不会逆转这些。另外,恢复会覆盖受影响路径的当前内容——所以在脏工作区上用 /undo 前,先提交或备份。
小结
- 权限是有序规则、"最后匹配者胜";宽规则在前,例外在后;任一 deny 即 deny。
- 默认对
.env和外部目录会问;保存的 "always" 是项目级持久 allow,但压不过 deny。 - MCP 只加需要的;远程默认 OAuth;密钥走
{env:...}。 - 内置策略插件无法被仓库移除,这是有意的防线。
- 快照能救"最近的文件改动",救不了远端副作用——重要工作先提交。
下一篇收尾:HTTP API、客户端与 SDK、上下文压缩,以及一套排障流程。