插件市场
这页写给管理员和插件维护人:怎么把一个插件放进企业市场,让可见范围内的员工用 /plugin install <name>@company 装上。
企业市场是 sid-code 的一个插件来源,地址由员工的 backend.url 推出,请求带设备凭据。客户端怎么校验、解包、加载插件,写在插件 · 企业插件市场,这页不重复。两页的分工是:这页管「怎么上架」,那页管「装下去之后客户端怎么处理」。
整个流程:
登记插件 ──▶ 上传 tar.gz ──▶ 核对组件清单 ──▶ 写理由发布 ──▶(需要时)下架
(名称、可见范围) (落为草稿) (Hooks / MCP) (员工可见) (不可撤销)版本状态只能单向走:
草稿 draft ──发布──▶ 已发布 published ──下架──▶ 已下架 yanked
│ ▲
└──────────────── 下架(不要的草稿)──────────┘没有「撤回到草稿」,也没有「删除版本」。不要的草稿也只能下架,版本号随之作废。要修一个已发布的版本,升版本号重新上传。
发布前必读
发布之后,插件里的 Hooks 会在员工机器上执行命令,MCP 会被连接,客户端安装时只展示组件清单,不再弹信任确认。上架审核是你的职责。
所以发布前,必须在版本行展开的组件清单里逐条看过 Hooks 和 MCP:挂了哪些 hook 事件,MCP 连的是哪个地址、跑的是什么命令。
联动:配了之后员工那边会怎样
| 你在管理台做了什么 | sid-code 什么时候知道 | 拉取失败时 | 员工看到什么 |
|---|---|---|---|
| 登记插件、上传草稿 | 员工看不到。目录只包含已发布版本 | —— | 什么都没有。直接按名字下载草稿也是 404 |
| 发布一个版本 | 员工下次 /plugin market、安装或检查更新时拉目录。目录带 ETag,没变时服务端回 304 | 网络错、5xx、目录格式不对时用上次的本地缓存(fail-static);401 / 403 不用缓存,直接报错 | /plugin market 列表里出现这个插件;已装旧版本的员工收到可更新提示 |
| 下架一个版本 | 同上,下架的版本从目录里消失 | 同上 | 已经装了的员工照常可用;再去下载这个版本得到 410「已下架,请刷新目录后安装最新版本」 |
| 改可见范围(编辑) | 同上 | 同上 | 移出范围的员工在目录里看不到它,下载 404;已装的照常可用 |
几个细节:
- 鉴权:目录是
GET /api/v1/ctl/marketplace/index,制品是GET /api/v1/ctl/marketplace/artifacts/{name}/{version},都必须带设备凭据。没登录的员工看不到企业市场。 - 下载是 fail-closed:sha256 和目录里登记的不一致、包结构不合规、
plugin.json的名字或版本和目录对不上,客户端一律拒绝安装,插件目录不留残留。客户端比对的是自己算出来的值和目录里的值,不信响应头。 - 服务端也校验存储:存储里的文件和登记的 sha256 对不上时,制品接口返回 500,不下发。
- 隐私级别不影响市场通道。
出处:packages/cli/src/plugin/market.ts;agent-backend: backend/app/modules/marketplace/router/serve.py、service/catalog.py。
插件包规范
服务端上传时整包校验,任何一条不过就是 422,不入库。detail.errors 里逐条列出原因。
| 规则 | 说明 |
|---|---|
| 格式 | .tar.gz |
plugin.json 位置 | 包根目录(允许 ./ 前缀)。不要在外面再套一层目录 |
name | slug:小写字母、数字、-、_,以字母或数字开头,最长 64。必须和登记的名称一致 |
version | semver,例如 1.2.0。客户端按它判断有没有更新 |
description | 必填 |
| 组件路径 | commands skills agents hooks mcpServers(写成文件路径时)必须是包内相对路径,而且确实存在 |
| 禁止的条目 | ..、绝对路径、反斜杠、盘符、软链接、硬链接、设备文件等非普通文件、重复条目 |
| 压缩后大小 | ≤ 20 MiB(超了 413) |
| 解压后合计 | ≤ 100 MiB |
| 条目数 | ≤ 2000 |
| 单个配置文件 | plugin.json、hooks 文件、MCP 配置文件各 ≤ 1 MiB |
本地插件允许组件路径写绝对路径,市场插件不允许:否则一个上架包就能让员工机器加载任意位置的文件。
最小可用的 plugin.json
下面是后端仓库自带的模板插件 feishu-docs 的写法(地址换成了示例域名)。它带一个技能和一个远程 MCP:
{
"name": "feishu-docs",
"version": "1.0.1",
"description": "读取飞书云文档:以员工本人的飞书权限按链接或 token 读取正文并给出摘要",
"author": "acme",
"license": "MIT",
"mcpServers": {
"feishu": {
"transport": "http",
"url": "https://agent.example.com/traj/api/v1/ctl/feishu/mcp",
"auth": "sid-backend"
}
}
}目录结构:
feishu-docs/
├── plugin.json
└── skills/
└── read-doc/
└── SKILL.mdskills/、commands/、agents/、hooks.json 是默认位置,不在 plugin.json 里声明也会被识别。这个 MCP 的含义见飞书文档授权。
打包
在插件目录里打包,让 plugin.json 落在包根:
cd feishu-docs
COPYFILE_DISABLE=1 tar -czf ../feishu-docs-1.0.1.tar.gz .COPYFILE_DISABLE=1 是给 macOS 用的,避免系统 tar 往包里塞 ._* 文件。
系统 tar 会写入修改时间和属主,同一份源码两次打出来的 sha256 不一样。想要可复现的包,用后端仓库里的 scripts/plugin_publish.py pack <name>:条目排序、时间和属主归零,同一份源码总是同一个 sha256。
在管理台里操作
页面在左侧菜单「市场」。卡片标题「插件市场」,下方说明写着「流程:登记插件 → 上传 tar.gz(草稿)→ 核对组件清单 → 写理由发布。」,和页首的流程图是同一件事。
列表的列:名称 / 类型 / 描述 / 可见范围 / 最新发布 / 草稿 / 下载 / 维护人 / 更新时间 / 操作。操作列有「版本」「编辑」「审计」。
1. 登记插件
- 点右上角「登记插件」。
- 按字段说明填写。
- 确定,提示「已登记,接下来上传插件包」。
登记只是在目录里占一个名字,员工看不到。
2. 上传草稿
- 在那一行点「版本」,打开抽屉。
- 「上传新版本」区域拖入或选择
.tar.gz。 - 点「上传为草稿」,提示「已上传 1.0.1(草稿),确认组件清单后再发布」。
版本号从包里的 plugin.json 读,不在表单里填。
3. 核对组件清单
抽屉的「版本」表里,每个版本展开后是组件清单:Skills / Commands / Agents / Hooks / MCP。
- Hooks 列出挂了哪些 hook 事件,用橙色标出;
- MCP 每个 server 一行,格式是
名称 · 类型 · url 或命令 · auth=…。
重点看两件事:hook 挂在哪些事件上、内容是什么;MCP 的地址是不是你认识的、auth=sid-backend 的地址是不是本企业后端。
4. 写理由发布
- 在版本行点「发布」。
- 弹窗提示:「发布后,可见范围内的员工就能安装。请先核对组件清单里的 Hooks 与 MCP 地址:hooks 会在员工机器上执行命令,客户端不会再弹信任确认。」
- 填「理由(必填,进审计)」,确定。
只有草稿能发布。发布前服务端会再读一次存储里的制品并核对 sha256,不一致返回 409「制品缺失或与登记的 sha256 不一致,不能发布」。
5. 下架(需要时)
- 在版本行点「下架」。
- 弹窗提示:「下架后不再出现在目录里、不能再下载;已经装了的员工照常可用。下架不可撤销。」
- 填理由,确定。
下架不会卸载员工机器上已装的版本。要让员工换掉有问题的版本,发布一个更高的版本号。
写入规则
| 情况 | 结果 |
|---|---|
| 登记的名称已存在 | 409「插件 'xxx' 已存在」 |
| 登记的名称不是 slug | 422 |
| 可见组织或团队不存在 | 404 |
| 上传时插件还没登记 | 404「插件 'xxx' 不存在」 |
包里 plugin.json 的 name 和登记的不一致 | 422 |
| 同一个版本号已经上传过(无论草稿、已发布还是已下架) | 409「…已存在。版本不可覆盖,请升版本号」 |
| 包超过 20 MiB | 413 |
| 包不合规 | 422,detail.errors 逐条列原因 |
| 发布一个不是草稿的版本 | 409「…当前是 published,只有 draft 能发布」 |
| 下架一个已下架的版本 | 409「…已经下架」 |
| 理由为空 | 422 |
字段说明
登记弹窗:
| 字段 | 取值 | 校验规则 | 员工端效果 |
|---|---|---|---|
| 名称 | 例如 feishu-docs | 必填;小写字母、数字、-、_,以字母或数字开头,≤64。登记后不能改 | 安装命令里的 <name>,也是员工本地安装记录里的身份。界面提示:「必须与插件包里 plugin.json 的 name 一致。登记后不能改:它是员工本地安装记录里的身份。」 |
| 类型 | 插件 / 技能 / MCP | 必选 | 只是目录里的分类标签,包结构的要求都一样 |
| 目录描述 | 文本,≤1024 | 选填 | 目录里显示的描述。留空则用 plugin.json 的 description |
| 维护人 | 文本,≤128 | 选填 | 目录里显示,方便员工知道找谁 |
| 可见组织 | 组织 ID,例如 acme | 必选,组织必须存在 | 只有这个组织的设备能看到和下载 |
| 可见团队 | 团队 ID,例如 platform | 选填,团队必须存在 | 「留空 = 整个组织可见;填了只对该组织内这个团队可见」 |
除名称外,其他字段都能用「编辑」改。
看不见和草稿都是 404
设备不在可见范围、插件不存在、版本还是草稿,制品接口统一返回 404,不做区分。这样拿着设备凭据也没法探测公司登记了哪些插件。只有已下架返回 410。
飞书登录的设备默认不在任何团队。插件只对某个团队可见时,先在「设备」页用「调整归属」把设备分进团队,见组织与团队。
员工端
完整说明见插件 · 企业插件市场。和管理员相关的要点:
| 命令 | 作用 |
|---|---|
/plugin market [关键词] | 浏览企业市场。需要配好 backend.url 并登录 |
/plugin install <name>@company | 安装:下载、sha256 校验、安全解包 |
/plugin update [name] | 按目录更新。新包校验不过时保留旧版本 |
/plugin uninstall <name> | 卸载 |
脚本和 CI 里用 sid-code plugin <子命令>。
自动更新
企业市场默认只提示有更新,不自动升级。官方市场 sid-code 默认自动升级。
要让企业市场自动升级,在员工的用户级 settings 或本地 managed-settings.json 里写:
{
"pluginMarketplaceAutoUpdate": { "company": true }
}- 项目级
.sid-code/settings.json里写这一项不生效,会被过滤掉。否则一个仓库就能把需要确认的升级变成静默升级。 - 管理台的远程策略不接受这个字段(写进去会 422)。要统一下发,用本地
managed-settings.json,见企业 policy 与安全边界。
打开之后仍有两条硬边界:
- 只升级已经装了的插件,从不替员工新装;
- 新版本的 Hooks 或 MCP 和已装版本不同(或者无法判断)时不静默升级,改为提示员工手动
/plugin update。
自动更新在启动时后台进行,失败只记日志,不打扰员工。
出处:packages/cli/src/plugin/auto-update.ts、market.ts、validate.ts。
CI 上传凭据
适用场景:插件源码放在私有仓库里,合入后由 CI 自动打包上传成草稿,管理员在管理台核对组件清单后再发布。
发布不交给 CI。凭据只能做一件事:给本组织已登记的插件上传草稿。
签发
「市场」页下方的「CI 上传凭据」标签页,点「签发凭据」。
弹窗「签发 CI 上传凭据」:
字段 说明 名称 字母或数字开头,可含 ._-,≤64。会出现在审计的操作人里(ci:<名称>#<id>),建议写仓库名,例如ci-acme-plugins组织 这把凭据只能给这个组织的插件上传 有效期(天) 1–365,默认 90。「到期不续期,需重新签发。」 签发理由 必填 确定后弹出「凭据已签发」,形如
mkt_...。明文只显示这一次,库里只存哈希。存进 CI 的 secret,再点「我已保存」。
权限
| 调用 | 结果 |
|---|---|
POST /api/v1/marketplace/items/{name}/versions,插件属于本组织 | 允许,落为草稿 |
| 同上,插件属于别的组织 | 403「该凭据只能上传组织 'acme' 的插件」 |
| 发布、下架、登记、改可见范围、签发凭据、读审计等其他管理接口 | 403「CI 上传凭据只能上传草稿;发布 / 下架请到管理台操作」 |
| 凭据无效、已过期、已吊销 | 401 |
被 403 拒绝的调用会在「凭据审计」里记一行「越权被拒」。
吊销与到期
- 列表每行「吊销」,填吊销原因。立即生效且不可恢复,下一次使用即 401。需要时重新签发一把。
- 没有滑动续期。天天在用也会按期过期,到期后重新签发。
- 状态列显示「有效」「已吊销」「已过期」。
GitHub Actions 示例
上传是一个 multipart 请求,文件字段名是 file:
name: publish-plugin
on:
push:
branches: [main]
paths: ["plugins/feishu-docs/**"]
jobs:
upload:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Pack
run: |
VERSION=$(jq -r .version plugins/feishu-docs/plugin.json)
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
tar -czf "feishu-docs-$VERSION.tar.gz" -C plugins/feishu-docs .
- name: Upload as draft
env:
MARKET_UPLOAD_TOKEN: ${{ secrets.MARKET_UPLOAD_TOKEN }}
run: |
curl --fail-with-body -sS -X POST \
-H "Authorization: Bearer $MARKET_UPLOAD_TOKEN" \
-F "file=@feishu-docs-$VERSION.tar.gz;type=application/gzip" \
https://agent.example.com/traj/api/v1/marketplace/items/feishu-docs/versions返回 201 和版本信息就是成功。409 表示这个版本号已经传过,忘了升版本号;422 看返回体里的逐条原因。
后端仓库里的 scripts/plugin_publish.py upload <name> 发的是同一个请求(凭据从环境变量 MARKET_UPLOAD_TOKEN 读,地址用 --base-url 或 MARKET_BASE_URL 传,写到 /api/v1 为止),打包是可复现的。
上传完成后,到管理台「市场」页对应插件的「版本」抽屉里核对组件清单,再写理由发布。
下载统计与审计
都在「市场」页下方的标签页里:
| 位置 | 内容 |
|---|---|
| 「谁装了什么(近 30 天)」 | 按插件 × 人统计制品下载:插件 / 人 / 设备数 / 下载次数 / 最近一次。人取自设备凭据(强归因),注册码设备显示「未登录设备」 |
| 「变更审计」 | 登记、改元数据、上传、发布、下架。每行有时间、插件、版本、动作、理由、操作人。可按插件筛选 |
| 「CI 上传凭据」下的「凭据审计」 | 签发、吊销、上传草稿、越权被拒 |
| 版本抽屉里的「下载记录(最近 100 条)」 | 这个插件每一次下载:时间、版本、人、设备 |
| 列表行的「审计」 | 只看这个插件的变更审计 |
下载不等于装成功
统计的是制品下载次数。客户端校验失败会重新下载,所以次数只多不少。员工本地安装成功的记录走事件通道(plugin_installed),见审计。
和「限制插件来源」的关系
企业市场本身不禁止员工从别处装插件。要强制员工只能用企业市场,在策略里打开「限制插件来源」(strictKnownMarketplaces),名单里写上企业市场的 index 地址:
https://agent.example.com/traj/api/v1/ctl/marketplace/index打开后本地目录安装和 --plugin-dir 一律被拒。名单留空表示除内置插件外禁一切插件。细节见策略 · 限制插件来源。
能力边界(如实说)
1. 审核靠人。 服务端只校验包的结构和路径,不审查 hook 命令和 MCP 地址是否安全。发布前核对组件清单是整条治理链的核心。
2. 下架不会卸载。 已经装了的员工照常可用。要替换有问题的版本,发布更高的版本号;员工要执行 /plugin update,或者打开了自动更新且新版本的 Hooks / MCP 没变化。
3. 下架和发布都不可撤销。 版本不能删、不能覆盖、不能退回草稿。
4. 新版本不会推到员工机器上。 发布只是让它出现在目录里。员工没装过的插件,自动更新也不会替他装。
5. 本地篡改挡不住有心人。 客户端加载市场插件前会复核目录指纹,装好后被改过的插件不加载。但有本机写权限、又存心伪造安装记录的人绕得过去,见插件 · 锁定后只认市场来源。
常见问题
上传报 422「包根目录没有 plugin.json」
打包时多套了一层目录。进到插件目录里再 tar -czf ../x.tar.gz .,或者用 -C <插件目录> .。
上传报 409,可是我改了内容
版本号没变。同一个版本号只能上传一次,草稿也一样。把 plugin.json 的 version 升一级再传。
员工说 /plugin market 里看不到刚发布的插件
按顺序查:
- 版本状态是不是「已发布」,草稿员工看不到;
- 可见范围:员工设备所在的组织和团队对不对。飞书登录的设备默认不在任何团队;
- 员工是否登录。
sid-code auth status看一下,401 时客户端不会回退到缓存。
发布了新版本,员工那边没自动升级
企业市场默认只提示不自动升级。打开了 pluginMarketplaceAutoUpdate 也一样:新版本的 Hooks 或 MCP 有变化时,只提示员工手动 /plugin update。
CI 上传报 403
看返回的提示。「只能上传草稿」说明调了上传以外的接口;「只能上传组织 'xxx' 的插件」说明插件登记的可见组织和凭据的组织不一致。
相关
- 插件 · 企业插件市场 —— 客户端怎么校验、解包、加载
- 飞书文档授权 —— 通过市场分发远程 MCP 的完整例子
- 策略 · 限制插件来源 —— 强制员工只用企业市场
- 组织与团队 —— 按团队控制可见范围
- 审计 —— 员工端
plugin_installed事件