Plug-in internal integration specifications
This article is intended for Mogick product internal integrators and is not a plug-in market release specification.
To learn how to use the plug-in page, see Plugins.
This article uses mogick-v26.7.17 as the only implementation baseline and describes the fields that the host currently actually reads, confirms and projects. The current delivery method is internal product integration: ordinary plug-ins are verified in the directory, and product connectors are placed in the built-in plug-in directory along with the application. Currently, there is no plug-in market release process, plug-in compressed package installation capability, and no self-service permission authorization process for third parties.
Scope of application
This specification covers two types of packages:
- Ordinary plug-in: Provide capabilities such as agent capability, file renderer, file opener, MCP or Skill through ordinary manifest contribution. Successful parsing only means that the manifest is legal; non-built-in plug-ins must also be written by the host with a confirmed permission grant. Only after compatibility, permissions, running status, and mutual exclusion selections can they enter effective projection. Although the parser can read the
externalConnectorsfield, installing the same directory as a normal local plugin will not bring it into 26.7.17's connector auto-detection, nor will it generate a supported detected connector sidecar; current connector delivery is limited to the product built-in trusted paths listed below. - Product built-in connector plug-in: declare
externalConnectors, put it into the host's built-in plug-in directory with the product, and the built-in connector provider will automatically enumerate, load and detect the native product. Only records withsource: builtinandtrustLevel: host-bundledbelong to this trust domain.
This article does not define public network distribution, market review, signature release, remote download, compressed package installation or third-party self-service authorization. When these capabilities are needed, product implementation and separate security design must be completed first, and this article cannot be regarded as a commitment.
Plug-in package structure
The host treats the plug-in package as a directory, and the plugin.json in the root directory is the only required file:
com.example.analysis/
├─ plugin.json # Required; must be located in the package root directory
├─ assets/ # Optional; static resources such as icons
├─ runtime/ # Optional; plug-in’s own runtime entry
└─ skills/ # Optional; Skills provided with the packagepackage.json, installation scripts, build scripts and specific compression formats are not part of the plugin contract. The local installation entry of 26.7.17 only accepts directories; when packageKind is not directory, it directly returns plugin.archive-not-supported. The physical location of the product's built-in connector is plugins/builtin/<plugin-id>/ packaged by the host. The packaging state should ensure that files that need to be read by external processes fall into the accessible unpacked entity directory.
The mechanism of plug-in from declaration to taking effect
A contribution will pass through three levels of states in sequence, and they cannot be confused:
- manifest declaration state: The host reads
plugin.jsonin the package root directory and executesmogick.plugin.v1parsing. Field shapes, enumerations, ID conflicts and disabled script keys are confirmed here, but payloads such as MCP inprovideswill be strictly parsed during subsequent consumption. - Host confirmed state: After installation or built-in directory merging, the host records the source, trust level, start and stop status, health status, installation sidecar and
grantedPermissions. The initialgrantedPermissionsof ordinary local plug-ins is empty; product integration must be written and audited by the controlled process on the host side to confirm the grant. List self-reported permissions are not equivalent to authorization. - Final effective projection: The host checks compatibility, confirmed permissions, runtime availability, exclusive group selection and MCP payload, then writes the selected contribution to
effectiveContributionsand projects it tomcp-config,skill-resolver, file processing registry or tool registry. Claims that are not selected or fail validation will not become current capabilities.
Built-in trusted plug-ins are a clear exception: only source === "builtin" and trustLevel === "host-bundled" are met to skip the compatibility and permission admission of ordinary plug-ins. The connector provider enumerates packages with non-empty externalConnectors from the built-in directory, currently only using the first item of the array for product detection; therefore a product connector package should declare only one main connector contribution.
plugin.json Top-level fields
Top-level field table
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
schemaVersion | Yes | Fixed to mogick.plugin.v1 | Other values are rejected with manifest.unsupportedSchema. |
id | Yes | A non-empty string; internal specifications require stable reverse domain name IDs, such as com.example.analysis | The parser only checks for non-empty, and does not check the naming format for the team; local installations will also reject IDs reserved by the host's built-in directory. |
version | Yes | Non-empty string | parser currently does not do SemVer verification; integrators should still use a comparable stable version. |
displayName | is | a non-empty string | as the default display name. |
publisher | is | an object | Parses the publisher fields as per the table. |
hostCompatibility | is | an object | A nullable object; non-built-in contributions use its contents on subsequent admissions. |
contributions | is an | object | containing at least one non-empty contribution array, otherwise rejected with manifest.invalidField. |
description | No | String | Leave as is; non-empty is not currently required. |
locales | No | Object mapping of locale to { displayName?, description? } | Internal delivery specification requires that the locale key and at least one copy field be non-null after trim. 26.7.17 The parser will trim the locale key, but the text is mainly judged by the true value of the string: "" will fail, and pure blank strings may still pass; empty mappings can currently also pass. Don't rely on these lax behaviors. |
icon | No | Relative paths to SVG, PNG, JPG, JPEG or WebP in the package | Uniform verification after backslash; absolute paths, drive letter paths, empty segments, ., .. or other extensions will be rejected. |
license | No | String | Leave as is, do not verify the license ID. |
homepage | No | String | Leave as is without URL verification. |
repository | No | String | Leave as is without URL verification. |
packageIntegrity | No | Object | Parsing algorithm and digest declaration; the catalog installer of 26.7.17 does not recompute or validate digests. |
runtime | Required condition | Object | Can be omitted if it does not contain MCP; ordinary contribution must provide a legal top-level runtime as long as it contains any MCP, otherwise access will be denied according to permission-denied. stdio uses kind: subprocess; HTTP's internal specification uses { "kind": "none" }. |
permissions | No | Permission declaration array | Only accept the kind listed below; this is the application scope, not the confirmed grant. |
Unrecognized top-level keys will not enter the manifest returned by the parser; but once scripts, install, postinstall, preinstall or commands appear at any level, the entire manifest will be treated as an installation script or host command declaration and rejected.
publisher
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
publisher.id | Yes | Non-empty string | Reserved as the publisher's stable ID. |
publisher.displayName | Yes | Non-empty string | Reserved as the publisher display name. |
publisher.verified | No | boolean | boolean will be retained; other types are currently treated as not provided, rather than reporting an error. Don't rely on this lax behavior. |
publisher.contact | No | String | Non-string will be rejected; contact format is not currently verified. |
hostCompatibility
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
hostCompatibility.mogickAppDesktop | No | String | parser Holds only strings; currently not a built-in contribution As long as the value is non-null, it is treated as host-incompatible, version range matching has not yet been performed. |
hostCompatibility.pluginApi | No | String | Same as mogickAppDesktop: non-empty makes the current non-builtin contribution incompatible. |
hostCompatibility.platforms | No | Array of darwin-arm64, darwin-x64, linux-x64, win32-x64 | Not provided or an empty array means no limit; non-built-in contributions must contain the current host platform. Unknown values are rejected during parsing. |
Therefore, ordinary plug-ins should not fill in non-empty mogickAppDesktop or pluginApi in 26.7.17; the product's built-in trusted connector can carry the product version description because it follows the host-bundled trust path, but the true compatibility should still be verified in the packaging test.
runtime and resourceLimits
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
runtime.kind | runtime when present is | none, subprocess, renderer-isolate, wasm-worker | Unknown value rejected. A normal contribution containing any MCP must have a top-level runtime; stdio must declare subprocess, the internal delivery of HTTP uses none. |
runtime.entry | No | String | Reserved only; the manifest parser does not currently verify that the file exists or is located within the package. |
runtime.protocol | No | json-rpc, stdio-json-rpc, http-local, custom-hosted | Unknown value rejected. |
runtime.minHostApi | No | String | Reserved only; 26.7.17 admission logic does not compare this version yet. |
runtime.resourceLimits | No | Objects | Non-objects are currently treated as unprovided; objects are shallow copied as-is, and the parser does not validate values on an item-by-item basis. |
resourceLimits.maxMemoryMb | No | number | The type is declared as number, but the current parser does not do runtime verification or enforcement. |
resourceLimits.startupTimeoutMs | No | number | The type is declared as number, but the current parser does not do runtime verification or enforcement. |
resourceLimits.requestTimeoutMs | No | number | The type is declared as number, but the current parser does not do runtime verification or enforcement. |
resourceLimits.maxConcurrentRequests | No | number | The type is declared as number, but the current parser does not do runtime verification or enforcement. |
Don't treat unenforced resourceLimits as a security sandbox. Product review still requires setting host-side limits on process lifecycle, resource consumption, and failure recovery.
packageIntegrity
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
packageIntegrity.algorithm | packageIntegrity when present is | sha256 or sha512 | Unknown value rejected. |
packageIntegrity.digest | packageIntegrity, when present, is | a non-empty string | reserved as a package digest declaration. |
packageIntegrity.files | No | Array of resource digest objects | Each must have a non-empty path, an algorithm of sha256/sha512, and a non-empty digest; optional sizeBytes must be a number greater than or equal to 0. Empty arrays are currently passed. |
Permission and contribution contract
Permission Statement
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
workspace.read | No | { kind, patterns?: string[] } | patterns, if present, must all be non-empty strings; empty arrays are passed. |
workspace.write | No | { kind, patterns?: string[] } | Same as workspace.read. |
file.render | No | { kind, maxFileSizeMb?: number } | Anything other than number will be rejected; there is currently no check for positive, negative or enforced limits. |
network | No | { kind, hosts: string[] } | hosts must be a non-empty string array with at least one item. HTTP MCP will automatically add network to the required permission set. |
externalProcess.launch | No | { kind, targets?: string[] } | targets, if present, must all be non-empty strings. stdio MCP automatically requires this permission. |
externalProduct.discover | No | { kind, productIds?: string[] } | productIds must be a non-empty string if present. |
mcp.server | No | { kind, serverIds: string[] } | serverIds must be a non-empty string array of at least one item; any MCP assertion automatically requires this permission. |
agent.tool | No | { kind, toolIds?: string[] } | toolIds, if present, must all be non-empty strings. |
requiredPermissions, top-level permissions and host grantedPermissions are three things: the former describes the requirements for a certain contribution, the second declares the scope of plug-in application, and the third is the authorization confirmed by the host. Ordinary plug-ins will only pass if the required kind appears in both the declaration and the confirmed grant; fine-grained scopes such as target, host or server ID have not yet been cross-checked in the contribution admission of 26.7.17, and the internal authorization process cannot therefore omit manual review.
contribution type
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
contributions.agentCapabilities | No | Array of contribution objects | Registered as agent-capabilities; MCP, Skills or tools can be declared via provides. |
contributions.fileRenderers | No | Array of contribution objects | Registered as file-renderer; projected into the file renderer registry when selected. |
contributions.fileOpeners | No | Array of contribution objects | Registered as file-opener; projected into the file opener registry when selected. |
contributions.externalConnectors | No | array of contribution objects | parser will be registered as external-connector; but 26.7.17 only built-in connector provider Enumerates such packages from host plugins/builtin, reads first item and creates builtin / host-bundled detected sidecar. A normal local installation, even if resolvable, does not therefore become a supported connector delivery. |
contributions.exclusiveGroups | No | Array of mutually exclusive group declarations | Each requires a non-empty id, a owner of host or third-party, and a selectionPolicy of { userSelectable: boolean }; the group ID must be unique within the array. The hosting policy only honors declarations from the built-in host-bundled plugin. |
The id of the first four types of contributions must be unique within the entire plugin, not just within their respective arrays. Each id must be non-empty; hostDefault, if present, must be a boolean; exclusiveGroup, if present, must be a non-empty string. requiredPermissions If present, the internal integration rules require at least one item and each item must be a non-empty kind supported by the above table; 26.7.17 parser will verify the elements, but will not reject empty arrays individually, so empty arrays should be rejected or fields should be omitted directly during review. Other fields specific to the contribution are retained for subsequent consumers. |
contributions must have at least one non-empty array. Writing just an empty object, just an empty array, or making all arrays empty will be rejected.
provides.mcpServers of normal manifest contribution
This section only describes the common, non-connector manifest contribution consumption path; built-in connectors follow the provider-specific path in the next section, and fields and default values cannot be mixed. The top-level manifest parser will retain the provides of the contribution, and the actual MCP field verification occurs after the contribution has entered the projection candidate. provides.mcpServers must be an array, and the MCP id within the same contribution must be unique.
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
id | is | a non-empty string | that forms the runtime global ID <plugin-id>/<server-id>. |
name | is | a non-empty string | used as the MCP display name and configuration name. |
transport | Yes | stdio or http | Other values rejected. |
isolation | No | shared or per-session | Default shared. |
useProxy | No | boolean | Default true. |
command | stdio is a | non-empty string | normalized to the local command path after parsing the template. |
args | stdio | String array | Each item expansion template; currently allows empty strings. |
env | stdio | string to string mapping | Expand template for each value. |
url | http is a | non-empty string | used as the HTTP MCP address after expanding the template. |
headers | http | string to string mapping | Expand template for each value. |
activation | No; only built-in trusted plugins | { provider: "product-runtime", productId, capability, mode: "headless-or-attach" } | Only source: builtin and trustLevel: host-bundled can be used; third-party or ordinary local plugins will be rejected and fail closed. |
Ordinary manifest MCP only allows two template variables, ${pluginRoot} and ${userWorkspace}. Unknown variables will cause the MCP projection of this contribution to fail. As long as the contribution contains any MCP, the admission logic requires the presence of a top-level runtime in the manifest: stdio MCP must use runtime.kind: subprocess and requires externalProcess.launch and mcp.server; the product internal specification for HTTP MCP uses runtime: { "kind": "none" } and requires network and mcp.server. If runtime is missing, or the runtime of stdio MCP is not subprocess, it will be rejected with permission-denied. Permissions must be declared at the top level and confirmed by the host. |
provides.mcpServers for built-in connectors
Connector MCP does not go through the plugin-manifest-mcp-entries resolver from the previous section. 26.7.17 There are two connector sources: the static provider reads externalConnectors[0].provides.mcpServers; when the helper is configured, connector-runtime-helper reads the mcpServers of the helper JSON. The two are first written into the connector sidecar. Only when the connector is detected, launchFileStatus is confirmed, the plug-in is enabled and the contribution has been projected, the MCP runtime will be entered.
| Fields | Internal Product Requirements | Static provider current behavior | helper JSON current behavior |
|---|---|---|---|
id | Non-empty after trim, and unique within the same connector | Only required to be a string; whole items that are not strings are filtered, and no null values or duplicates are checked | Same as static paths; only required to be a string, no null values or duplicates are checked |
name | Non-empty after trim | Only required to be a string; otherwise the entire item is filtered | Same as static path |
transport | stdio or http | Only exact http is retained as HTTP, other values (including missing or unknown values) are normalized to stdio | Same as static paths |
isolation | Explicitly fill in shared or per-session | Only precise shared takes shared, other values default to per-session | Only precise per-session takes per-session, other values default to shared |
command | stdio required and audited non-empty path | string will expand the connector template and normalize; other types are discarded | string will be normalized; helper must directly output the parsed path and no longer expand the template |
args | stdio optional string array | Filter non-string items, and expand the connector template item by item | Filter non-string items, do not expand the template |
env | stdio optional string map | Filter non-string values, and expand the connector template | Filter non-string values, do not expand the template |
endpoint | HTTP required and non-empty address | string will expand the connector template; mapped to the url of MCP config during projection | string is left as is; mapped to url of MCP config during projection |
headers | Must not currently be relied upon | 26.7.17 Connector paths do not read or project this field | 26.7.17 Helper paths also do not read or project this field |
useProxy | Optional boolean | Defaults to true if not boolean or omitted | Same as static path |
activation | Only use supported product-runtime shapes if the helper is delivered and approved by the host | Static provider does not read this field | Legal objects are retained; illegal objects are silently discarded |
The connector provider will construct a new MCP object, and fields outside the table will not be penetrated as they are; many type errors will be filtered, defaulted, or silently discarded, instead of strictly reporting errors like ordinary manifest MCP. Therefore, product internal delivery must additionally verify that the MCP id is non-empty and unique, HTTP must have a non-empty endpoint, stdio must have a non-empty command, and the legal transport and isolation must be explicitly filled in. The url / headers contract used by normal MCP is not portable to connectors: connector HTTP only consumes endpoint, not currently headers.
provides.skills
Skill declaration contains at least:
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
id | is | a non-empty string | as the stable ID of the Skill bundle. |
path | Yes | Non-empty string | Ordinary local plug-ins must be resolved to the plug-in root directory; if the path does not exist, the detailed capabilities will not be entered. |
Normal plugin detail projection currently only expands ${pluginRoot}. Skill declarations that end up escaping the plugin root, contain other template variables, or point to non-existing directories are ignored. The connector's Skill path belongs to another built-in provider path, which can be used after product discovery using ${installRoot} qualified below.
Complete example of common plug-in
The following package will be parsed via the mogick.plugin.v1 manifest; it declares a stdio MCP and an in-package Skills directory:
{
"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"
}
]
}
}
]
}
}The fact that the packet can be parsed does not mean that the MCP is authorized. The grantedPermissions of ordinary local plug-in installation records is initially empty; during product integration, the host also needs to write the confirmed grant of externalProcess.launch and mcp.server in a controlled manner, and retain the authorization time and audit evidence, and then the contribution can enter the effective projection. 26.7.17 There is no self-service authorization interface or process provided to third parties.
Complete example of built-in product connector
The following example provides both a connector helper and a static discovery fallback. The helper's commands, args and connectorRuntime.env only use the reliable ${pluginRoot}; MCP and Skills after successful static discovery use the built-in ${installRoot}, ${installRootBasename}, or you can use ${productVersion} or dynamic variables successfully parsed by 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"
}
]
}
}
]
}
}Connector fields and discovery order
"Required" in the following table indicates mandatory specifications for product internal delivery, and does not mean that the 26.7.17 provider has strictly reported errors item by item for missing, empty strings or filtered empty arrays. The "Current Behavior" column is dedicated to recording the actual checksum normalization performed by the provider; internal audits must perform the stricter side of the two.
| Field | Required | Type or allowed values | Current behavior |
|---|---|---|---|
product.productId | Internally required | Non-empty string | Internal delivery must provide a stable non-empty product ID; 26.7.17 provider only validates that the value is a string, not non-empty or trim. |
product.displayName | Internally required | Non-empty string | Internal delivery must provide a non-empty display name; 26.7.17 provider only validates that the value is a string, not non-empty or trim. |
product.envVar | No | String | Preferred as the installation root environment variable; if not provided, the first environment-variable discovery name will be used. |
product.exampleFolder | No | String | 26.7.17 This hint is read but is not used to participate in directory searches. |
connectorRuntime.command | When connectorRuntime exists, it is | a non-empty string | Use ${pluginRoot} to expand and start the helper; when the command does not exist, fall back to static discovery, and other startup or output errors are recorded as invalid. |
connectorRuntime.args | No | String array | Expand with ${pluginRoot}; non-string items are filtered. |
connectorRuntime.env | No | string to string mapping | Non-string values are filtered; value is expanded using the helper template context and then overwritten to the child process environment. The only reliable built-in variable in the default production path is ${pluginRoot}. |
connectorRuntime.timeoutMs | No | number | Default 5000 ms; example explicitly set to 10000 ms. |
discovery[].kind | Yes | environment-variable, known-location or context-root | provider first determines the product installation root; the static path then resolves the context root, and then renders the MCP and Skill. |
environment-variable.names | Internally required | At least one non-empty string array | The provider only processes names as items in the array, and then filters non-strings and empty strings; if it is empty after filtering, there are no environment variable candidates, and an error is not necessarily reported in the reading phase. |
known-location.platform | Internally required | win32-x64 | Currently Windows auto-discovery must be filled in explicitly. The implementation is also compatible with windows, but the public manifest platform enumeration only contains win32-x64; when missing or does not match the current platform, the known-location item will not be selected. |
known-location.searchParents | Internally required | An array of at least one non-empty path string | Internal deliveries must provide a standard directory probe root. 26.7.17 If it is missing or not an array, it will be normalized to []. Only non-strings will be filtered in the array, and empty strings will not be checked. An error will not necessarily be reported during the reading phase. The host test option can also cover the array. Path supports %NAME% environment variable expansion. |
known-location.installDirPattern | Yes | String | The current implementation is based on directory name prefix matching; the trailing * is treated as a prefix wildcard hint. |
known-location.requiredFiles | Internally required | An array of at least one non-empty path string | Internal delivery and review must confirm that each item is non-empty and actually exists. 26.7.17 First check the length of the original array, and then filter non-strings; for example, [123] will become an empty list, and subsequent every([]) may pass as a true value, so the provider has not strictly blocked malformed values. |
Internal delivery must independently confirm that the directory pointed to by the environment variable and the known-location candidate contain all non-empty requiredFiles; do not rely solely on the provider returning detected to determine that the installation root is safe. 26.7.17 will call validRoot to check the directory and normalized file list, but the above "check first, filter later" restriction means that this check is not a complete security boundary. Without environment variables, a known-location search only accepts the only valid candidate; zero or more candidates will remain missing and give a diagnostic. %ProgramFiles% is a searchParents-specific environment variable expansion, not a manifest MCP template variable.
context-root dynamic template variables
context-root only runs in static discovery paths. The provider first establishes the { installRoot, productVersion, installRootBasename } basic context, and then parses the dynamic roots item by item; only after all are successful, the dynamic variables are merged into the command / args / env / endpoint and Skill path template context of the static connector MCP. Once the helper returns a result (including missing or invalid), the context-root will not be processed and the helper JSON will no longer expand these templates.
| Fields | Product Internal Requirements | 26.7.17 Current Behavior |
|---|---|---|
context-root.variable | Matches [A-Za-z_][A-Za-z0-9_]*, unique within the same contribution, and must not overwrite installRoot, productVersion, installRootBasename | Statements that do not match regular rules are skipped; duplicates or reserved names are not checked, and the dynamic value that appears later can overwrite the previous value or even the basic key, so the internal audit must supplement the conflict check. |
context-root.envNames | Optional; each trim is non-empty and non-repeating | Read the host coverage environment and process environment in order; filter non-strings and empty strings, but do not trim names. Verify immediately after encountering the first set value: if the directory is invalid, subsequent environment names or searchRoots will not be tried. |
context-root.searchRoots | Optional; provide at least one env name or search root; each item is a non-empty template path | Filter non-strings and empty strings, which are rendered with the base context in turn; cannot reference another dynamic context root. The first directory that satisfies the path check wins. |
context-root.requiredPaths | At least one non-empty relative path after trim; confirm the real existence item by item | When the original array length is 0, the entire declaration is skipped; non-strings and empty strings will be filtered afterwards, but the array is not confirmed to be non-empty again, so the malformed array may degenerate into every([]). Internal review must reject such input. |
The resolved root must be a directory and each requiredPaths entry must be present (either file or directory). If any accepted context-root cannot find a valid root, the entire static linker becomes invalid; only after all dynamic roots succeed will its variables be added to the MCP/Skill template context. Static providers fail strictly on unknown template variables: regardless of whether the unknown variable appears in the context-root's searchRoots, MCP or Skill, a rendering exception will make the connector invalid.
connector helper runtime and JSON output
When connectorRuntime.command is empty or there is no runtime object available, the provider directly uses static discovery; when the helper startup command returns ENOENT, it also falls back to static discovery. In addition, there is no "continue static detection after helper failure": non-zero exit, timeout, other startup errors, invalid JSON or invalid returned by the helper all produce invalid results; the helper returns missing / unsupported and ends in the original state without any rollback. The command and args of the helper are expanded with ${pluginRoot}; the value of connectorRuntime.env can also refer to the environment override explicitly passed to the provider by the host. 26.7.17 The helper renderer will replace unknown template variables with empty strings instead of reporting errors; this is different from the strict failure of static providers. Internal product delivery must uniformly implement stricter review: reject all undeclared variables, and must not rely on "expand to null" or the ENOENT fallback accidentally triggered by this. The child process environment is sequentially merged by the current process environment, host override and rendered connectorRuntime.env.
Helper stdout must be a single JSON object; currently the following fields are read:
| Field | Type or allowed values | Current behavior |
|---|---|---|
status | detected, missing, invalid, unsupported | Other values or missing values are normalized to invalid. |
productName, productVersion | Optional string | Used for connector and product version display; non-string discarded. |
installRoot, launchFile | string or null | string is normalized to the local path; other values are normalized to null. |
launchArgs | Optional array of strings | Non-string items to be filtered. |
mcpServers | Optional array | Normalized according to the "Built-in Connector" table in the previous section; the helper must output the parsed command / endpoint / path and will not expand the manifest template again. |
skills | Optional array | Only retain items with string id and path, the path will be normalized; optional display fields are displayName, description, overview, source only retains skill-frontmatter / manifest, other values default to runtime. |
message | Optional string | Used as a failure or degradation diagnostic; non-string discarded. |
candidates | Optional string array | Used to display diagnostics such as multiple installation candidates; non-string items are filtered. |
The helper's mcpServers[].tools also supports tool display metadata: each name must be trimmed and not empty, optional description and inputSchema in object form; source can be manifest, runtime, merged, otherwise press runtime. These display fields do not replace runtime tools/list detection, nor do they relax MCP's ID, transport, endpoint, or command auditing. This example will only enter the automatic discovery process after the package is placed in the host's built-in plug-in directory, packaged by the product, and loaded as source: builtin, trustLevel: host-bundled. Installing the same directory as a normal local plug-in will not promote it to a trusted connector and will not obtain automatic authorization. The connector discovered by the built-in provider cannot be uninstalled; the start-stop state and health diagnosis will still enter the confirmed state and final projection.
Security and Trust Boundary
- Installation scripts and implicit host commands are prohibited:
scripts,install,postinstall,preinstall,commandsmust not appear at any level of the list. Child processes that need to be started must be explicitly declared via an auditedruntime,connectorRuntimeor MCPcommandand consistent with permissions and trust domains. - Disable third-party bundled product runtime:
activation.provider: product-runtimeonly allows built-inhost-bundledplug-ins; untrusted sources will fail closed during the parsing or final projection phase. - Built-in trust is given by the product catalog, not self-reported by the manifest:
publisher.verified: true, product ID, or placing similar fields cannot turn a local plugin intohost-bundled. Only product packaging and host registration code can establish this status. - Path must be controlled: Icons and Skills of ordinary plug-ins must not escape the package root. The internal delivery and review of the connector must independently confirm that
${installRoot}is the expected product directory, and confirm item by item that the non-emptyrequiredFilesactually exists; the filtering order of 26.7.17 provider has not completely rejected the malformed list, and itsvalidRootresult cannot be regarded as a safety guarantee for the risk of blocked paths. The manifest parser also does not currently cover all runtime paths, so integration review must be supplemented by real file and path boundary checks. - Declaration does not equal enforcement of isolation:
resourceLimitsandpackageIntegrityin 26.7.17 were primarily declaration data, and the directory installer did not fully enforce these limits or digest validation. Do not lower your packaging audit requirements based on this. - Ordinary permissions must be confirmed by the host: list application, contribution requirement and confirmed grant are indispensable. There is currently no third-party self-service authorization process, and any integration that bypasses host confirmation is not supported.
Internal integration process
The fixed execution sequence is as follows:
Confirm capability type → Assign stable ID → Prepare directory → Write and parse manifest → Review runtime and permissions → Place in product Plugins directory → Package startup verification → Verify details and effective capabilities → Regression start-stop and fault behavior
Among them, "Put into the product Plugins directory" only applies to built-in packages that are to be delivered with the product. Integration verification of ordinary plug-ins can take the local installation path from the controlled directory, but because there is currently no third-party authorization UI, the host code responsible for integration must still establish a confirmed grant; the product's built-in connector must enter plugins/builtin to confirm that the entity path in the packaged product can be read by the helper and MCP.
Common rejection reasons
| Reason | Phase | Current Result or Processing |
|---|---|---|
Missing root directory plugin.json | Package validation | PluginPackageValidationError: The plug-in package is missing plugin.json. |
| schema not supported | manifest parsing | manifest.unsupportedSchema. Only accepts mogick.plugin.v1. |
| Required fields are missing or empty | Manifest parsing | manifest.missingField; some nested fields are returned as manifest.invalidField. |
| Invalid icon path | Manifest parsing | Non-package relative paths or non-SVG/PNG/JPG/JPEG/WebP extensions are rejected with manifest.invalidField. |
| Installation script key appears | manifest parsing | manifest.disallowedScript; check is recursive. |
contributions is empty | manifest parsing | manifest.invalidField. |
| Duplicate contribution ID | Manifest parsing | The first four categories of contributions are rejected when repeated across arrays; exclusiveGroups also checks for duplication within its own array. |
| Unknown permission kind | manifest parsing | manifest.invalidField. |
The platform does not match, or the common plug-in fills in non-empty mogickAppDesktop / pluginApi | contribution admission | host-incompatible, does not enter the valid candidate. |
| The required permission is not declared or confirmed | contribution permission | permission-denied; the initial grant for ordinary local plug-ins is empty. |
Normal MCP lacks top-level runtime, or stdio MCP is not runtime.kind: subprocess | contribution permission | permission-denied; HTTP must also provide a legal runtime, and the internal specification uses kind: none. |
| MCP field error or duplicate MCP ID for the same contribution | MCP projections | PluginManifestMcpEntryError, the contribution's MCP is rejected with the plugin.manifest-mcp-invalid diagnostic. |
| MCP uses unknown template variable | MCP projection | Normal manifest MCP projection fails; connector static template records status as invalid. |
| The plugin ID is reserved by the host's built-in directory | Local installation | plugin.reserved-id. Local packages cannot override product built-in IDs. |
| Submit compressed package | Local installation | plugin.archive-not-supported; currently only supports directories. |
Integration Checklist
- [ ] The package root directory exists and depends on only one required file:
plugin.json. - [ ]
schemaVersionis exactlymogick.plugin.v1, plugins, contributions, MCPs and Skill IDs are stable and conflict-free. - [ ] The ordinary plug-in ID uses the reverse domain name controlled by the team; it does not occupy the host's built-in reserved ID.
- [ ] Icons and common plug-in Skills are located in the plug-in root directory, and the runtime entry is consistent with the actual packaging path.
- [ ] Manifest does not contain install script keys or implicit host commands at any level.
- [ ]
contributionshas at least one non-empty array; the values ofexclusiveGroupandrequiredPermissionsare non-empty and supported. - [ ] Ordinary stdio/HTTP MCP fields are complete and exist in the top-level runtime: stdio is
subprocess, HTTP is internally specifiednone; template variables only use${pluginRoot}/${userWorkspace}. - [ ] Top-level declaration permissions cover all requirements of contribution and MCP; ordinary plug-ins already have host confirmed grant and audit evidence.
- [ ] There is no
product-runtimeactivation for third-party or generic native plugins. - [ ] Product connectors are only loaded in built directories with verification records as
builtin/host-bundled; nopublisher.verifiedis used to impersonate trust. - [ ]
environment-variable.names,known-location.platform,searchParentsandrequiredFilesall satisfy internal forced formatting and do not rely on the provider's relaxed normalization of null or malformed arrays. - [ ] The variables, envNames, searchRoots, and requiredPaths of
context-roothave been strictly reviewed; dynamic variables have no duplication, do not overwrite basic variables, and are parsed successfully before MCP/Skill projection. - [ ] Connector MCP uses
endpointinstead ofurland does not rely on headers; the ID is non-empty and unique, and transport and isolation are explicitly legal. - [ ] helper command / args / env only uses audited variables; the status, path, MCP, Skills and diagnostic fields of the helper JSON are schema checked and
ENOENTfallback and other errors fail closed are verified respectively. - [ ] Environment variable discovery and known-location discovery are tested separately; zero candidates, multiple candidates, missing files and helper failures all have expected diagnoses.
- [ ] The MCP/Skills displayed on the details page are consistent with the results of
effectiveContributionsand actual tools/list. - [ ] Enable, disable, health degradation, process crash, restart and mutually exclusive selection behavior complete regression.
- [ ] Use catalog installation or product packaging verification; no assumption is made that market releases, tarball installations, or third-party self-service authorizations already exist.