插件内部集成规范
本文面向 Mogick 产品内部集成人员,不是插件市场发布规范。
如需了解插件页面的使用方式,请参阅插件。
本文以 mogick-v26.7.17 为唯一实现基线,描述宿主当前真正读取、确认和投影的字段。当前交付方式是产品内部集成:普通插件以目录接受校验,产品连接器随应用放入内置插件目录。当前没有插件市场发布流程、插件压缩包安装能力,也没有面向第三方的自助权限授权流程。
适用范围
本规范覆盖两类包:
- 普通插件:通过普通 manifest contribution 提供 agent capability、文件渲染器、文件打开器、MCP 或 Skill 等能力。解析成功只表示 manifest 合法;非内置插件还必须由宿主写入 confirmed permission grant,经过兼容性、权限、运行状态和互斥选择后,才可能进入有效投影。虽然 parser 能读取
externalConnectors字段,但把同一目录作为普通本地插件安装,不会使它进入 26.7.17 的连接器自动探测,也不会生成受支持的 detected connector sidecar;当前连接器交付仅限下述产品内置可信路径。 - 产品内置连接器插件:声明
externalConnectors,随产品放入宿主内置插件目录,由内置 connector provider 自动枚举、加载和探测本机产品。只有source: builtin且trustLevel: host-bundled的记录属于这一信任域。
本文不定义公网分发、市场审核、签名发布、远程下载、压缩包安装或第三方自助授权。需要这些能力时,必须先补齐产品实现和单独的安全设计,不能把本文当作已有承诺。
插件包结构
宿主把插件包视为一个目录,根目录的 plugin.json 是唯一必需文件:
com.example.analysis/
├─ plugin.json # 必需;必须位于包根目录
├─ assets/ # 可选;图标等静态资源
├─ runtime/ # 可选;插件自己的运行时入口
└─ skills/ # 可选;随包提供的 Skillspackage.json、安装脚本、构建脚本和特定压缩格式都不是插件契约的一部分。26.7.17 的本地安装入口只接受目录;packageKind 不是 directory 时直接返回 plugin.archive-not-supported。产品内置连接器的物理位置是宿主打包的 plugins/builtin/<plugin-id>/,打包态应确保需要由外部进程读取的文件落在可访问的 unpacked 实体目录。
插件从声明到生效的机制
一个 contribution 会依次经过三层状态,不能把它们混为一谈:
- manifest 声明态:宿主读取包根目录的
plugin.json,执行mogick.plugin.v1解析。这里确认字段形状、枚举、ID 冲突和禁用脚本键,但provides中的 MCP 等载荷会在后续消费时再严格解析。 - 宿主 confirmed state:安装或内置目录合并后,宿主记录来源、信任级别、启停状态、健康状态、安装 sidecar 和
grantedPermissions。普通本地插件初始的grantedPermissions为空;产品集成必须由宿主侧的受控流程写入与审计 confirmed grant。清单自报权限不等于授权。 - 最终有效投影:宿主检查兼容性、confirmed 权限、运行时可用性、互斥组选择和 MCP 载荷,再把选中的 contribution 写入
effectiveContributions,投影到mcp-config、skill-resolver、文件处理注册表或工具注册表。未被选择或校验失败的声明不会成为当前运行能力。
内置可信插件是明确例外:只有同时满足 source === "builtin" 和 trustLevel === "host-bundled" 才跳过普通插件的兼容性与权限准入。连接器 provider 从内置目录枚举带有非空 externalConnectors 的包,当前只用数组第一项进行产品探测;因此一个产品连接器包应只声明一个主 connector contribution。
plugin.json 顶层字段
顶层字段表
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
schemaVersion | 是 | 固定为 mogick.plugin.v1 | 其他值以 manifest.unsupportedSchema 拒绝。 |
id | 是 | 非空字符串;内部规范要求稳定的反向域名 ID,如 com.example.analysis | parser 只检查非空,不替团队检查命名格式;本地安装还会拒绝宿主内置目录保留的 ID。 |
version | 是 | 非空字符串 | parser 当前不做 SemVer 校验;集成方仍应使用可比较的稳定版本。 |
displayName | 是 | 非空字符串 | 作为默认展示名。 |
publisher | 是 | 对象 | 按下表解析发布者字段。 |
hostCompatibility | 是 | 对象 | 可为空对象;非内置贡献在后续准入时使用其内容。 |
contributions | 是 | 对象 | 至少包含一个非空贡献数组,否则以 manifest.invalidField 拒绝。 |
description | 否 | 字符串 | 原样保留;当前不要求非空。 |
locales | 否 | locale 到 { displayName?, description? } 的对象映射 | 内部交付规范要求 locale 键以及至少一个文案字段在 trim 后非空。26.7.17 parser 会 trim locale 键,但文案主要按字符串真值判断:"" 会失败,纯空白字符串仍可能通过;空映射当前也可通过。不要依赖这些宽松行为。 |
icon | 否 | 包内 SVG、PNG、JPG、JPEG 或 WebP 相对路径 | 统一反斜杠后校验;绝对路径、盘符路径、空段、.、.. 或其他扩展名会被拒绝。 |
license | 否 | 字符串 | 原样保留,不验证许可证标识。 |
homepage | 否 | 字符串 | 原样保留,不做 URL 校验。 |
repository | 否 | 字符串 | 原样保留,不做 URL 校验。 |
packageIntegrity | 否 | 对象 | 解析算法和摘要声明;26.7.17 的目录安装器不重新计算或验证摘要。 |
runtime | 条件必选 | 对象 | 不包含 MCP 时可省略;普通 contribution 只要包含任意 MCP,就必须提供合法顶层 runtime,否则准入按 permission-denied 拒绝。stdio 使用 kind: subprocess;HTTP 的内部规范使用 { "kind": "none" }。 |
permissions | 否 | 权限声明数组 | 只接受下文列出的 kind;这是申请范围,不是 confirmed grant。 |
未识别的顶层键不会进入 parser 返回的 manifest;但任意层级一旦出现 scripts、install、postinstall、preinstall 或 commands,整份清单都会被视为安装脚本或宿主命令声明并拒绝。
publisher
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
publisher.id | 是 | 非空字符串 | 作为发布者稳定 ID 保留。 |
publisher.displayName | 是 | 非空字符串 | 作为发布者展示名保留。 |
publisher.verified | 否 | boolean | boolean 会保留;其他类型当前被当作未提供,而不是报错。不要依赖这个宽松行为。 |
publisher.contact | 否 | 字符串 | 非字符串会拒绝;当前不校验联系方式格式。 |
hostCompatibility
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
hostCompatibility.mogickAppDesktop | 否 | 字符串 | parser 只保留字符串;当前非内置 contribution 只要该值非空,就按 host-incompatible 处理,尚未执行版本范围匹配。 |
hostCompatibility.pluginApi | 否 | 字符串 | 与 mogickAppDesktop 相同:非空会使当前非内置 contribution 不兼容。 |
hostCompatibility.platforms | 否 | darwin-arm64、darwin-x64、linux-x64、win32-x64 的数组 | 未提供或空数组表示不限制;非内置 contribution 必须包含当前宿主平台。未知值会在解析时拒绝。 |
因此,普通插件在 26.7.17 中不要填写非空的 mogickAppDesktop 或 pluginApi;产品内置可信连接器可以携带产品版本说明,因为它走 host-bundled 信任路径,但仍应在打包测试中验证真实兼容性。
runtime 与 resourceLimits
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
runtime.kind | runtime 存在时是 | none、subprocess、renderer-isolate、wasm-worker | 未知值拒绝。包含任意 MCP 的普通 contribution 必须存在顶层 runtime;stdio 必须声明 subprocess,HTTP 的内部交付使用 none。 |
runtime.entry | 否 | 字符串 | 仅保留;manifest parser 当前不验证文件存在或是否位于包内。 |
runtime.protocol | 否 | json-rpc、stdio-json-rpc、http-local、custom-hosted | 未知值拒绝。 |
runtime.minHostApi | 否 | 字符串 | 仅保留;26.7.17 准入逻辑尚不比较该版本。 |
runtime.resourceLimits | 否 | 对象 | 非对象当前被当作未提供;对象会原样浅拷贝,parser 不逐项验证数值。 |
resourceLimits.maxMemoryMb | 否 | number | 类型声明为 number,但当前 parser 不做运行时校验或强制执行。 |
resourceLimits.startupTimeoutMs | 否 | number | 类型声明为 number,但当前 parser 不做运行时校验或强制执行。 |
resourceLimits.requestTimeoutMs | 否 | number | 类型声明为 number,但当前 parser 不做运行时校验或强制执行。 |
resourceLimits.maxConcurrentRequests | 否 | number | 类型声明为 number,但当前 parser 不做运行时校验或强制执行。 |
不要把未执行的 resourceLimits 当作安全沙箱。产品审核仍需对进程生命周期、资源消耗和故障恢复设置宿主侧限制。
packageIntegrity
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
packageIntegrity.algorithm | packageIntegrity 存在时是 | sha256 或 sha512 | 未知值拒绝。 |
packageIntegrity.digest | packageIntegrity 存在时是 | 非空字符串 | 作为包摘要声明保留。 |
packageIntegrity.files | 否 | 资源摘要对象数组 | 每项必须有非空 path、sha256/sha512 的 algorithm 和非空 digest;可选 sizeBytes 必须是大于等于 0 的 number。空数组当前可通过。 |
权限与 contribution 契约
权限声明
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
workspace.read | 否 | { kind, patterns?: string[] } | patterns 如存在必须全是非空字符串;空数组可通过。 |
workspace.write | 否 | { kind, patterns?: string[] } | 与 workspace.read 相同。 |
file.render | 否 | { kind, maxFileSizeMb?: number } | 非 number 会拒绝;当前不检查正负或强制限制。 |
network | 否 | { kind, hosts: string[] } | hosts 必须是至少一项的非空字符串数组。HTTP MCP 会自动把 network 加入所需权限集合。 |
externalProcess.launch | 否 | { kind, targets?: string[] } | targets 如存在必须全是非空字符串。stdio MCP 会自动要求此权限。 |
externalProduct.discover | 否 | { kind, productIds?: string[] } | productIds 如存在必须全是非空字符串。 |
mcp.server | 否 | { kind, serverIds: string[] } | serverIds 必须是至少一项的非空字符串数组;任何 MCP 声明都会自动要求此权限。 |
agent.tool | 否 | { kind, toolIds?: string[] } | toolIds 如存在必须全是非空字符串。 |
requiredPermissions、顶层 permissions 与宿主 grantedPermissions 是三件事:前者说明某个 contribution 的需求,第二项声明插件申请的范围,第三项才是宿主确认的授权。普通插件只有所需 kind 同时出现在声明和 confirmed grant 中才会通过;目标、host 或 server ID 等细粒度 scope 在 26.7.17 的 contribution admission 中尚未交叉比对,内部授权流程不能因此省略人工审查。
contribution 类型
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
contributions.agentCapabilities | 否 | contribution 对象数组 | 注册为 agent-capability;可通过 provides 声明 MCP、Skills 或 tools。 |
contributions.fileRenderers | 否 | contribution 对象数组 | 注册为 file-renderer;被选择时投影到文件渲染器注册表。 |
contributions.fileOpeners | 否 | contribution 对象数组 | 注册为 file-opener;被选择时投影到文件打开器注册表。 |
contributions.externalConnectors | 否 | contribution 对象数组 | parser 会注册为 external-connector;但 26.7.17 只有内置 connector provider 从宿主 plugins/builtin 枚举这类包、读取第一项并创建 builtin / host-bundled detected sidecar。普通本地安装即使可解析,也不会因此成为受支持的连接器交付。 |
contributions.exclusiveGroups | 否 | 互斥组声明数组 | 每项要求非空 id、owner 为 host 或 third-party,以及 { userSelectable: boolean } 的 selectionPolicy;组 ID 在该数组内必须唯一。宿主策略只采信内置 host-bundled 插件的声明。 |
前四类 contribution 的 id 在整个插件内必须唯一,不能只在各自数组内唯一。每项 id 必须非空;hostDefault 如存在必须是 boolean;exclusiveGroup 如存在必须是非空字符串。requiredPermissions 如存在,内部集成规则要求至少一项且每项必须是上表支持的非空 kind;26.7.17 parser 会校验元素,却不会单独拒绝空数组,因此审查时应拒绝空数组或直接省略字段。贡献专用的其他字段会保留给后续消费者。
contributions 至少要有一个非空数组。只写空对象、只写空数组或让所有数组都为空都会被拒绝。
普通 manifest contribution 的 provides.mcpServers
本节只描述普通的、非连接器 manifest contribution 消费路径;内置连接器走下一节的 provider 专用路径,字段和默认值不能混用。顶层 manifest parser 会保留 contribution 的 provides,真正的 MCP 字段校验发生在 contribution 已进入投影候选之后。provides.mcpServers 必须是数组,同一个 contribution 内的 MCP id 必须唯一。
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
id | 是 | 非空字符串 | 组成运行时全局 ID <plugin-id>/<server-id>。 |
name | 是 | 非空字符串 | 作为 MCP 展示名和配置名。 |
transport | 是 | stdio 或 http | 其他值拒绝。 |
isolation | 否 | shared 或 per-session | 默认 shared。 |
useProxy | 否 | boolean | 默认 true。 |
command | stdio 时是 | 非空字符串 | 解析模板后规范化为本地命令路径。 |
args | stdio 时否 | 字符串数组 | 每项展开模板;当前允许空字符串。 |
env | stdio 时否 | string 到 string 的映射 | 每个 value 展开模板。 |
url | http 时是 | 非空字符串 | 展开模板后作为 HTTP MCP 地址。 |
headers | http 时否 | string 到 string 的映射 | 每个 value 展开模板。 |
activation | 否;仅内置可信插件 | { provider: "product-runtime", productId, capability, mode: "headless-or-attach" } | 仅 source: builtin 且 trustLevel: host-bundled 可使用;第三方或普通本地插件会被拒绝并 fail closed。 |
普通 manifest MCP 只允许 ${pluginRoot} 与 ${userWorkspace} 两个模板变量。未知变量会使该 contribution 的 MCP 投影失败。只要 contribution 包含任意 MCP,准入逻辑就要求 manifest 存在顶层 runtime:stdio MCP 必须使用 runtime.kind: subprocess,并需要 externalProcess.launch 和 mcp.server;HTTP MCP 的产品内部规范使用 runtime: { "kind": "none" },并需要 network 和 mcp.server。缺失 runtime,或 stdio MCP 的 runtime 不是 subprocess,都会按 permission-denied 拒绝。权限既要在顶层声明,也要由宿主 confirmed grant。
内置连接器的 provides.mcpServers
连接器 MCP 不经过上一节的 plugin-manifest-mcp-entries 解析器。26.7.17 有两条连接器来源:静态 provider 读取 externalConnectors[0].provides.mcpServers;配置了 helper 时,connector-runtime-helper 读取 helper JSON 的 mcpServers。两者先写入 connector sidecar,只有 connector 为 detected、launchFileStatus 为 confirmed、插件已启用且 contribution 已投影时,才会进入 MCP runtime。
| 字段 | 产品内部要求 | 静态 provider 当前行为 | helper JSON 当前行为 |
|---|---|---|---|
id | trim 后非空,且同一连接器内唯一 | 只要求是 string;非 string 的整项被过滤,不检查空值或重复 | 与静态路径相同;只要求是 string,不检查空值或重复 |
name | trim 后非空 | 只要求是 string;否则整项被过滤 | 与静态路径相同 |
transport | stdio 或 http | 只有精确 http 保留为 HTTP,其他值(含缺失或未知值)都归一化为 stdio | 与静态路径相同 |
isolation | 显式填写 shared 或 per-session | 只有精确 shared 取 shared,其他值默认为 per-session | 只有精确 per-session 取 per-session,其他值默认为 shared |
command | stdio 必填且为已审核的非空路径 | string 会展开连接器模板并规范化;其他类型被丢弃 | string 会规范化;helper 必须直接输出已解析路径,不再展开模板 |
args | stdio 可选字符串数组 | 过滤非字符串项,并逐项展开连接器模板 | 过滤非字符串项,不展开模板 |
env | stdio 可选字符串映射 | 过滤非字符串 value,并展开连接器模板 | 过滤非字符串 value,不展开模板 |
endpoint | HTTP 必填且为非空地址 | string 会展开连接器模板;投影时映射为 MCP config 的 url | string 原样保留;投影时映射为 MCP config 的 url |
headers | 当前不得依赖 | 26.7.17 连接器路径不读取或投影该字段 | 26.7.17 helper 路径同样不读取或投影该字段 |
useProxy | 可选 boolean | 非 boolean 或省略时默认为 true | 与静态路径相同 |
activation | 仅在 helper 交付并通过宿主审核时使用受支持的 product-runtime 形状 | 静态 provider 不读取该字段 | 合法对象会保留;不合法对象被静默丢弃 |
连接器 provider 会构造新的 MCP 对象,表外字段不会原样穿透;许多类型错误会被过滤、默认化或静默丢弃,而不是像普通 manifest MCP 那样严格报错。因此,产品内部交付必须额外验证 MCP id 非空且唯一、HTTP 必须存在非空 endpoint、stdio 必须存在非空 command,并显式填写合法 transport 与 isolation。普通 MCP 使用的 url / headers 契约不能移植到连接器:连接器 HTTP 只消费 endpoint,当前不消费 headers。
provides.skills
Skill 声明至少包含:
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
id | 是 | 非空字符串 | 作为 Skill bundle 的稳定 ID。 |
path | 是 | 非空字符串 | 普通本地插件必须解析到插件根目录之内;路径不存在时不会进入详情能力。 |
普通插件详情投影当前只展开 ${pluginRoot}。绝对路径或相对路径最终逃逸出插件根目录、包含其他模板变量、或指向不存在目录的 Skill 声明都会被忽略。连接器的 Skill 路径属于另一条内置 provider 路径,可在发现产品后使用下文限定的 ${installRoot}。
普通插件完整示例
下面的包会通过 mogick.plugin.v1 manifest 解析;它声明一个 stdio MCP 和一个包内 Skills 目录:
{
"schemaVersion": "mogick.plugin.v1",
"id": "com.example.analysis",
"version": "1.0.0",
"displayName": "Example Analysis",
"publisher": {
"id": "example",
"displayName": "Example Team"
},
"hostCompatibility": {
"platforms": ["win32-x64"]
},
"runtime": {
"kind": "subprocess",
"entry": "runtime/example-mcp.exe",
"protocol": "stdio-json-rpc"
},
"permissions": [
{
"kind": "externalProcess.launch",
"targets": ["example-mcp"]
},
{
"kind": "mcp.server",
"serverIds": ["analysis"]
}
],
"contributions": {
"agentCapabilities": [
{
"id": "analysis",
"displayName": "Example Analysis",
"requiredPermissions": ["externalProcess.launch", "mcp.server"],
"exclusiveGroup": "com.example.analysis.engine",
"provides": {
"mcpServers": [
{
"id": "analysis",
"name": "example-analysis",
"transport": "stdio",
"isolation": "per-session",
"command": "${pluginRoot}/runtime/example-mcp.exe",
"args": ["--workspace", "${userWorkspace}"],
"useProxy": true
}
],
"skills": [
{
"id": "analysis-skills",
"path": "${pluginRoot}/skills"
}
]
}
}
]
}
}包能被解析不等于 MCP 已获授权。普通本地插件安装记录的 grantedPermissions 初始为空;产品集成时还需要宿主受控地写入 externalProcess.launch 和 mcp.server 的 confirmed grant,并保留授权时间与审计证据,之后 contribution 才可能进入 effective projection。26.7.17 没有提供给第三方的自助授权界面或流程。
产品内置连接器完整示例
下面的示例同时提供 connector helper 和静态发现回退。helper 的 command、args 与 connectorRuntime.env 只用可靠的 ${pluginRoot};静态发现成功后的 MCP 与 Skills 使用内置 ${installRoot}、${installRootBasename},也可使用 ${productVersion} 或由 context-root 成功解析出的动态变量:
{
"schemaVersion": "mogick.plugin.v1",
"id": "com.example.product-connector",
"version": "1.0.0",
"displayName": "Example Product Connector",
"publisher": {
"id": "example",
"displayName": "Example Team",
"verified": true
},
"hostCompatibility": {
"mogickAppDesktop": ">=26.7.17",
"platforms": ["win32-x64"]
},
"runtime": {
"kind": "none"
},
"permissions": [
{
"kind": "externalProduct.discover",
"productIds": ["example-product"]
},
{
"kind": "externalProcess.launch",
"targets": ["Example Product"]
},
{
"kind": "mcp.server",
"serverIds": ["example-product"]
}
],
"contributions": {
"externalConnectors": [
{
"id": "default",
"displayName": "Example Product",
"exclusiveGroup": "com.example.product.connector",
"requiredPermissions": [
"externalProduct.discover",
"externalProcess.launch",
"mcp.server"
],
"product": {
"productId": "example-product",
"displayName": "Example Product",
"envVar": "EXAMPLE_PRODUCT_HOME",
"exampleFolder": "Example Product 2026"
},
"connectorRuntime": {
"command": "${pluginRoot}/bin/example-connector-helper.exe",
"args": ["--plugin-root", "${pluginRoot}"],
"env": {
"EXAMPLE_CONNECTOR_PLUGIN_ROOT": "${pluginRoot}"
},
"timeoutMs": 10000
},
"discovery": [
{
"kind": "environment-variable",
"names": ["EXAMPLE_PRODUCT_HOME"]
},
{
"kind": "known-location",
"platform": "win32-x64",
"searchParents": ["%ProgramFiles%/Example"],
"installDirPattern": "Example Product *",
"requiredFiles": [
"bin/example-product.exe",
"bin/example-mcp.exe"
]
}
],
"provides": {
"mcpServers": [
{
"id": "example-product",
"name": "example-product-mcp",
"transport": "stdio",
"isolation": "per-session",
"command": "${installRoot}/bin/example-mcp.exe",
"args": [
"--install-root",
"${installRoot}",
"--install-label",
"${installRootBasename}"
],
"useProxy": true
}
],
"skills": [
{
"id": "example-product-skills",
"path": "${installRoot}/skills"
}
]
}
}
]
}
}连接器字段与发现顺序
下表的“必选”表示产品内部交付的强制规范,不表示 26.7.17 provider 已经对缺失、空字符串或过滤后的空数组逐项严格报错。“当前行为”列专门记录 provider 实际执行的校验和归一化;内部审核必须执行两者中更严格的一侧。
| 字段 | 必选 | 类型或允许值 | 当前行为 |
|---|---|---|---|
product.productId | 内部必填 | 非空字符串 | 内部交付必须提供稳定的非空产品 ID;26.7.17 provider 只验证值是 string,不验证非空或 trim。 |
product.displayName | 内部必填 | 非空字符串 | 内部交付必须提供非空展示名;26.7.17 provider 只验证值是 string,不验证非空或 trim。 |
product.envVar | 否 | 字符串 | 优先作为安装根环境变量;未提供时取第一个 environment-variable discovery 名称。 |
product.exampleFolder | 否 | 字符串 | 26.7.17 会读取该提示,但不使用它参与目录搜索。 |
connectorRuntime.command | connectorRuntime 存在时是 | 非空字符串 | 用 ${pluginRoot} 展开并启动 helper;命令不存在时回退静态发现,其他启动或输出错误记为 invalid。 |
connectorRuntime.args | 否 | 字符串数组 | 用 ${pluginRoot} 展开;非字符串项被过滤。 |
connectorRuntime.env | 否 | string 到 string 的映射 | 非字符串 value 被过滤;value 使用 helper 模板上下文展开,再覆盖到子进程环境。默认生产路径中可靠的内置变量只有 ${pluginRoot}。 |
connectorRuntime.timeoutMs | 否 | number | 默认 5000 ms;示例显式设为 10000 ms。 |
discovery[].kind | 是 | environment-variable、known-location 或 context-root | provider 先确定产品安装根;静态路径随后解析 context root,再渲染 MCP 与 Skill。 |
environment-variable.names | 内部必填 | 至少一个非空字符串的数组 | provider 只处理 names 为数组的项,再过滤非字符串和空字符串;过滤后为空时没有环境变量候选,不一定在读取阶段报错。 |
known-location.platform | 内部必填 | win32-x64 | 当前 Windows 自动发现必须显式填写。实现也兼容 windows,但公开的 manifest 平台枚举只包含 win32-x64;缺失或与当前平台不匹配时,该 known-location 项不会被选中。 |
known-location.searchParents | 内部必填 | 至少一个非空路径字符串的数组 | 内部交付必须提供标准目录探测根。26.7.17 缺失或非数组时会归一化为 [],数组内只过滤非字符串、不检查空字符串;读取阶段不一定报错,宿主测试选项还可覆盖该数组。路径支持 %NAME% 环境变量展开。 |
known-location.installDirPattern | 是 | 字符串 | 当前实现按目录名前缀匹配;末尾 * 被作为前缀通配提示处理。 |
known-location.requiredFiles | 内部必填 | 至少一个非空路径字符串的数组 | 内部交付与审核必须确认每项非空且真实存在。26.7.17 先检查原数组长度,再过滤非字符串;例如 [123] 会变成空列表,后续 every([]) 可能以真值通过,因此 provider 尚未严格封堵畸形值。 |
内部交付必须独立确认环境变量指向的目录和 known-location 候选包含全部非空 requiredFiles;不要仅凭 provider 返回 detected 判定安装根安全。26.7.17 会调用 validRoot 检查目录与归一化后的文件列表,但上述“先检查、后过滤”限制意味着该检查不是完整的安全边界。没有环境变量时,known-location 搜索只接受唯一有效候选;零个或多个候选都会保持 missing 并给出诊断。%ProgramFiles% 是 searchParents 专用的环境变量展开形式,不是 manifest MCP 模板变量。
context-root 动态模板变量
context-root 只在静态发现路径中运行。provider 先建立 { installRoot, productVersion, installRootBasename } 基础上下文,再逐项解析动态根;全部成功后,才把动态变量合并进静态连接器 MCP 的 command / args / env / endpoint 和 Skill path 模板上下文。helper 一旦返回结果(包括 missing 或 invalid),就不会处理 context-root,helper JSON 也不会再展开这些模板。
| 字段 | 产品内部要求 | 26.7.17 当前行为 |
|---|---|---|
context-root.variable | 匹配 [A-Za-z_][A-Za-z0-9_]*,同一 contribution 内唯一,且不得覆盖 installRoot、productVersion、installRootBasename | 不匹配正则的声明被跳过;不检查重复或保留名,后出现的动态值可以覆盖先前值乃至基础键,因此内部审核必须补上冲突检查。 |
context-root.envNames | 可选;每项 trim 后非空且不重复 | 按顺序读取宿主覆盖环境和进程环境;过滤非字符串与空字符串,但不 trim 名称。遇到第一个已设置的值后立即校验:若该目录无效,不再尝试后续环境名或 searchRoots。 |
context-root.searchRoots | 可选;至少提供一个 env name 或搜索根;每项为非空模板路径 | 过滤非字符串和空字符串,依次用基础上下文渲染;不能引用另一个动态 context root。第一个满足路径检查的目录胜出。 |
context-root.requiredPaths | 至少一个 trim 后非空的相对路径;逐项确认真实存在 | 原数组长度为 0 时整项声明被跳过;之后会过滤非字符串和空字符串,但未再次确认数组非空,因此畸形数组可能退化为 every([])。内部审核必须拒绝这种输入。 |
解析出的根必须是目录,并且每个 requiredPaths 项都存在(文件或目录均可)。任何已接受的 context-root 找不到有效根,整个静态连接器都会变为 invalid;只有所有动态根成功后,其变量才会加入 MCP / Skill 模板上下文。静态 provider 对未知模板变量严格失败:无论未知变量出现在 context-root 的 searchRoots、MCP 还是 Skill 中,渲染异常都会使连接器为 invalid。
connector helper runtime 与 JSON 输出
connectorRuntime.command 为空或没有可用 runtime 对象时,provider 直接使用静态发现;helper 启动命令返回 ENOENT 时也回退静态发现。除此之外,不存在“helper 失败后继续静态探测”:非零退出、超时、其他启动错误、无效 JSON 或 helper 返回的 invalid 都产生 invalid 结果;helper 返回 missing / unsupported 也按原状态结束,不再回退。
helper 的 command 与 args 以 ${pluginRoot} 展开;connectorRuntime.env 的 value 还可引用宿主显式传给 provider 的环境覆盖。26.7.17 helper 渲染器会把未知模板变量替换为空字符串,而不是报错;这与静态 provider 的严格失败不同。产品内部交付必须统一执行更严格审核:拒绝所有未声明变量,不得依赖“展开为空”或由此偶然触发的 ENOENT 回退。子进程环境由当前进程环境、宿主覆盖以及渲染后的 connectorRuntime.env 依次合并。
helper stdout 必须是单个 JSON 对象;当前读取以下字段:
| 字段 | 类型或允许值 | 当前行为 |
|---|---|---|
status | detected、missing、invalid、unsupported | 其他值或缺失归一化为 invalid。 |
productName、productVersion | 可选 string | 用于连接器和产品版本展示;非 string 丢弃。 |
installRoot、launchFile | string 或 null | string 会规范化为本地路径;其他值归一化为 null。 |
launchArgs | 可选字符串数组 | 非字符串项被过滤。 |
mcpServers | 可选数组 | 按上一节“内置连接器”表归一化;helper 必须输出已解析的 command / endpoint / path,不会再次展开 manifest 模板。 |
skills | 可选数组 | 只保留具有 string id 与 path 的项,路径会规范化;可选展示字段为 displayName、description、overview,source 只保留 skill-frontmatter / manifest,其他值默认为 runtime。 |
message | 可选 string | 作为失败或降级诊断;非 string 丢弃。 |
candidates | 可选字符串数组 | 用于展示多安装候选等诊断;非字符串项被过滤。 |
helper 的 mcpServers[].tools 还支持工具展示元数据:每项 name 必须 trim 后非空,可选 description 与对象形态的 inputSchema;source 可为 manifest、runtime、merged,否则按 runtime。这些展示字段不替代运行时 tools/list 探测,也不放宽 MCP 的 ID、transport、endpoint 或 command 审核。
这个示例只有在包被放入宿主内置插件目录、经产品打包,并被加载为 source: builtin、trustLevel: host-bundled 后,才会进入自动发现流程。把相同目录作为普通本地插件安装,不会把它提升为可信连接器,也不能获得自动授权。内置 provider 发现的 connector 不可卸载;启停状态和健康诊断仍会进入 confirmed state 与最终投影。
安全与信任边界
- 禁止安装脚本和隐式宿主命令:清单任意层级不得出现
scripts、install、postinstall、preinstall、commands。需要启动的子进程必须通过已审查的runtime、connectorRuntime或 MCPcommand显式声明,并与权限和信任域一致。 - 禁止第三方绑定产品运行时:
activation.provider: product-runtime只允许内置host-bundled插件;不可信来源会在解析或最终投影阶段 fail closed。 - 内置信任由产品目录赋予,不由 manifest 自报:
publisher.verified: true、产品 ID 或放置相似字段都不能把本地插件变为host-bundled。只有产品打包与宿主注册代码能建立该状态。 - 路径必须受控:普通插件的图标与 Skills 不得逃逸包根。连接器内部交付与审核必须独立确认
${installRoot}是预期产品目录,并逐项确认非空requiredFiles真实存在;26.7.17 provider 的过滤顺序尚未完全拒绝畸形列表,不能把其validRoot结果当作已封堵路径风险的安全保证。manifest parser 当前也没有覆盖所有运行时路径,因此集成审查必须补充真实文件与路径边界检查。 - 声明不等于执行隔离:
resourceLimits与packageIntegrity在 26.7.17 主要是声明数据,目录安装器没有完整执行这些限制或摘要验证。不要据此降低打包审计要求。 - 普通权限必须有宿主确认:清单申请、contribution 需求和 confirmed grant 缺一不可。当前没有第三方自助授权流程,任何绕过宿主确认的集成都不受支持。
内部集成流程
固定执行顺序如下:
确认能力类型 → 分配稳定 ID → 准备目录 → 编写并解析清单 → 审核运行时与权限 → 放入产品 Plugins 目录 → 打包启动验证 → 验证详情与有效能力 → 回归启停和故障行为
其中,“放入产品 Plugins 目录”只适用于要随产品交付的内置包。普通插件的集成验证可以从受控目录走本地安装路径,但因为当前没有第三方授权 UI,负责集成的宿主代码仍必须建立 confirmed grant;产品内置连接器必须进入 plugins/builtin,确认打包产物中的实体路径可被 helper 和 MCP 读取。
常见拒绝原因
| 原因 | 阶段 | 当前结果或处理 |
|---|---|---|
缺少根目录 plugin.json | 包校验 | PluginPackageValidationError:插件包缺少 plugin.json。 |
| schema 不支持 | manifest 解析 | manifest.unsupportedSchema。只接受 mogick.plugin.v1。 |
| 必选字段缺失或为空 | manifest 解析 | manifest.missingField;部分嵌套字段以 manifest.invalidField 返回。 |
| 图标路径非法 | manifest 解析 | 非包内相对路径或非 SVG/PNG/JPG/JPEG/WebP 扩展名以 manifest.invalidField 拒绝。 |
| 出现安装脚本键 | manifest 解析 | manifest.disallowedScript;检查是递归的。 |
contributions 为空 | manifest 解析 | manifest.invalidField。 |
| contribution ID 重复 | manifest 解析 | 前四类 contribution 跨数组重复时拒绝;exclusiveGroups 也在自身数组内查重。 |
| 未知权限 kind | manifest 解析 | manifest.invalidField。 |
平台不匹配,或普通插件填写非空 mogickAppDesktop / pluginApi | contribution 准入 | host-incompatible,不进入有效候选。 |
| 所需权限未声明或未 confirmed | contribution 准入 | permission-denied;普通本地插件初始 grant 为空。 |
普通 MCP 缺少顶层 runtime,或 stdio MCP 不是 runtime.kind: subprocess | contribution 准入 | permission-denied;HTTP 也必须提供合法 runtime,内部规范使用 kind: none。 |
| MCP 字段错误或同一 contribution 的 MCP ID 重复 | MCP 投影 | PluginManifestMcpEntryError,该 contribution 的 MCP 被拒绝并产生 plugin.manifest-mcp-invalid 诊断。 |
| MCP 使用未知模板变量 | MCP 投影 | 普通 manifest MCP 投影失败;连接器静态模板则把状态记为 invalid。 |
| 插件 ID 被宿主内置目录保留 | 本地安装 | plugin.reserved-id。本地包不能覆盖产品内置 ID。 |
| 提交压缩包 | 本地安装 | plugin.archive-not-supported;当前只支持目录。 |
集成检查清单
- [ ] 包根目录存在且只依赖一个必需文件:
plugin.json。 - [ ]
schemaVersion精确为mogick.plugin.v1,插件、contribution、MCP 和 Skill ID 稳定且无冲突。 - [ ] 普通插件 ID 使用团队控制的反向域名;没有占用宿主内置保留 ID。
- [ ] 图标和普通插件 Skills 均位于插件根目录内,运行时入口与实际打包路径一致。
- [ ] 清单任何层级都不含安装脚本键或隐式宿主命令。
- [ ]
contributions至少有一个非空数组;exclusiveGroup和requiredPermissions的值非空且受支持。 - [ ] 普通 stdio/HTTP MCP 字段完整并存在顶层 runtime:stdio 为
subprocess,HTTP 为内部规范的none;模板变量只使用${pluginRoot}/${userWorkspace}。 - [ ] 顶层声明权限覆盖 contribution 和 MCP 的全部需求;普通插件已有宿主 confirmed grant 与审计证据。
- [ ] 第三方或普通本地插件没有
product-runtimeactivation。 - [ ] 产品连接器只在内置目录中加载,并确认记录为
builtin/host-bundled;未用publisher.verified冒充信任。 - [ ]
environment-variable.names、known-location.platform、searchParents和requiredFiles均满足内部强制格式,且没有依赖 provider 对空值或畸形数组的宽松归一化。 - [ ]
context-root的 variable、envNames、searchRoots、requiredPaths 已严格审核;动态变量无重复、不覆盖基础变量,并在 MCP / Skill 投影前解析成功。 - [ ] 连接器 MCP 使用
endpoint而不是url,不依赖 headers;ID 非空且唯一,transport 与 isolation 显式合法。 - [ ] helper command / args / env 只使用已审核变量;helper JSON 的 status、路径、MCP、Skills 与诊断字段经过 schema 检查,并分别验证
ENOENT回退及其他错误 fail closed。 - [ ] 环境变量发现和 known-location 发现分别测试;零候选、多候选、缺文件和 helper 失败均有预期诊断。
- [ ] 详情页显示的 MCP/Skills 与
effectiveContributions、实际 tools/list 结果一致。 - [ ] 启用、禁用、健康降级、进程崩溃、重启和互斥选择行为完成回归。
- [ ] 使用目录安装或产品打包验证;没有假设市场发布、压缩包安装或第三方自助授权已经存在。