From 29b970ebb4b5e80bd1871b959fa025efb4c5d742 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 21 Oct 2026 14:38:06 -0700 Subject: [PATCH 1/3] feat(codex): warn that format "legacy" is deprecated The legacy .codex-plugin layout only serves Codex releases older than 0.147.0, the first release that installs Agent Plugins packages and reads their mcp.json (0.146.0 read the root plugin.json only). Until now no doc said how long the option would last, and the ADR's "across v0.146 and v0.147" did not say which release a user needs. A build that sets format: "legacy" on the codex target now prints a warning that names the field, says it is removed in 0.13, and says how to move off it. The README, ADR 0001, the schema comment and the authoring skill state the same policy, and the version claim carries a dated citation to the Codex release notes. --- README.md | 8 +++--- ...0001-codex-emits-agent-plugins-packages.md | 12 ++++++--- skills/authoring-pluginpack-config/SKILL.md | 3 ++- src/schema.ts | 4 ++- src/targets/codex.ts | 27 +++++++++++++++---- tests/conformance.test.ts | 9 +++++++ 6 files changed, 50 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 626eded..bf28585 100644 --- a/README.md +++ b/README.md @@ -275,8 +275,10 @@ Pluginpack emits packages in two ways: `extensions["com.openai"]` automatically, so existing manifest config needs no changes. MCP config is held to the stricter Agent Plugins rules (see [MCP Directory](#mcp-directory)). Set `format: "legacy"` on the target to keep the older - `.codex-plugin/plugin.json` layout for Codex builds without Agent Plugins - support, which landed across v0.146 and v0.147 (see + `.codex-plugin/plugin.json` layout for Codex releases older than 0.147.0, + the first release that installs Agent Plugins packages. `format: "legacy"` + is deprecated in 0.12 and will be removed in 0.13; a build that uses it + prints a warning (see [ADR 0001](./docs/adr/0001-codex-emits-agent-plugins-packages.md)). - **`agent-plugins`** emits portable packages. On its own it has no client profile, so it ships `skills`, `scripts`, `assets`, static files, and `mcp`. @@ -607,7 +609,7 @@ rest of the artifact. | Field | Type | Required | Meaning | | ------------------ | ------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `outDir` | string | yes | Output directory for this target, relative to the config root. | -| `format` | `"agent-plugins"` \| `"legacy"` | no | `codex` only. `"legacy"` keeps the `.codex-plugin/plugin.json` layout; default `"agent-plugins"`. | +| `format` | `"agent-plugins"` \| `"legacy"` | no | `codex` only. `"legacy"` keeps the `.codex-plugin/plugin.json` layout; deprecated in 0.12, removed in 0.13. Default `"agent-plugins"`. | | `plugins` | record | yes | Emitted plugins, keyed by emitted plugin name (see **`targets..plugins.`**). | | `marketplaceDir` | string (safe relative) | no | Override the marketplace dir (defaults: `.cursor-plugin` / `.claude-plugin`). | | `pluginRoot` | string (safe relative) | no | Override the plugin root dir (`claude`; defaults to `plugins`). | diff --git a/docs/adr/0001-codex-emits-agent-plugins-packages.md b/docs/adr/0001-codex-emits-agent-plugins-packages.md index 2db8fed..8978580 100644 --- a/docs/adr/0001-codex-emits-agent-plugins-packages.md +++ b/docs/adr/0001-codex-emits-agent-plugins-packages.md @@ -7,9 +7,15 @@ status: accepted OpenAI's packaging docs make the Agent Plugins root `plugin.json` (OpenAI settings under `extensions.com.openai`) the preferred format and keep `.codex-plugin/plugin.json` only as a fallback. From 0.12.0 the `codex` target -therefore emits Agent Plugins packages by default. A `format: "legacy"` option -keeps the old layout for one release window, for users on Codex builds that -predate Agent Plugins support (it landed across v0.146 and v0.147). +therefore emits Agent Plugins packages by default. + +A `format: "legacy"` option keeps the old layout for users on Codex releases +older than 0.147.0. Codex 0.146.0 reads an Agent Plugins `plugin.json`, but +0.147.0 is the first release that installs Agent Plugins packages and reads +their `mcp.json` +([release notes](https://github.com/openai/codex/releases/tag/rust-v0.147.0)). +`format: "legacy"` is deprecated in 0.12 and will be removed in 0.13. A build +that uses it prints a deprecation warning. We deliberately do not emit both layouts in one build. A dual layout would need two MCP files in different shapes (`mcp.json` and `.mcp.json`) and a diff --git a/skills/authoring-pluginpack-config/SKILL.md b/skills/authoring-pluginpack-config/SKILL.md index 90641a1..7852624 100644 --- a/skills/authoring-pluginpack-config/SKILL.md +++ b/skills/authoring-pluginpack-config/SKILL.md @@ -93,7 +93,8 @@ so author it once. Don't add a per-target copy just to change (root `plugin.json` + `mcp.json`). Codex-only manifest fields such as `interface` move under `extensions["com.openai"]` automatically. Set `format: "legacy"` on the codex target only to keep the old -`.codex-plugin/plugin.json` layout. The `agent-plugins` target emits +`.codex-plugin/plugin.json` layout for Codex releases older than 0.147.0. +`format: "legacy"` is deprecated in 0.12 and will be removed in 0.13. The `agent-plugins` target emits portable packages. Without `clients` it writes no marketplace and can't include `agents`, `commands`, `rules`, or `hooks`. With `clients: { codex: {}, copilot: {} }` it builds each package once and writes each client's marketplace. Listing diff --git a/src/schema.ts b/src/schema.ts index f6acb7d..e39377b 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -162,7 +162,9 @@ export const CLIENT_ONLY_CONTENT_KINDS = [ const targetSchema = z.object({ outDir: z.string().min(1), // codex only: "agent-plugins" (default) emits Agent Plugins packages; - // "legacy" emits the .codex-plugin layout. See docs/adr/0001. + // "legacy" emits the .codex-plugin layout for Codex releases older than + // 0.147.0. "legacy" is deprecated in 0.12 and will be removed in 0.13. + // See docs/adr/0001. format: z.enum(["agent-plugins", "legacy"]).optional(), marketplaceDir: safeRelativePath.optional(), pluginRoot: safeRelativePath.optional(), diff --git a/src/targets/codex.ts b/src/targets/codex.ts index ddba33a..134f633 100644 --- a/src/targets/codex.ts +++ b/src/targets/codex.ts @@ -300,6 +300,13 @@ export const codexMarketplace = { documentationUrl: "https://developers.openai.com/codex/plugins/build", verifiedAt: "2026-10-02", }, + { + claim: + "Codex 0.146.0 reads Agent Plugins manifests (openai/codex#35105); 0.147.0 is the first release that installs Agent Plugins packages and parses their mcp.json (openai/codex#36544, #36796), so format: legacy serves releases older than 0.147.0", + documentationUrl: + "https://github.com/openai/codex/releases/tag/rust-v0.147.0", + verifiedAt: "2026-10-21", + }, { claim: 'legacy .codex-plugin/plugin.json requires only "name"; version/description/author etc. are optional', @@ -336,9 +343,10 @@ const openaiProfile = { namespace: "com.openai" }; /** * The pre-Agent Plugins Codex layout (`format: "legacy"`): * `.codex-plugin/plugin.json` pointing at `skills/`, `hooks/`, and - * `.mcp.json`. Kept for Codex builds that predate Agent Plugins support - * (v0.146–v0.147) — see - * docs/adr/0001-codex-emits-agent-plugins-packages.md. + * `.mcp.json`. Kept for Codex releases older than 0.147.0, the first release + * that installs Agent Plugins packages and reads their `mcp.json` (0.146.0 + * read the root `plugin.json` only). Deprecated in 0.12, removed in 0.13 — + * see docs/adr/0001-codex-emits-agent-plugins-packages.md. */ const codexLegacy: PluginTargetDefinition = { ...codexMarketplace, @@ -380,6 +388,10 @@ const codexLegacy: PluginTargetDefinition = { mcpDialect: "verbatim", }; +/** Printed once per build that uses `format: "legacy"`. */ +const LEGACY_FORMAT_DEPRECATION = + 'warning: targets.codex.format: "legacy" is deprecated and will be removed in pluginpack 0.13. It only serves Codex releases older than 0.147.0. Remove "format" from targets.codex to emit Agent Plugins packages.'; + /** * OpenAI Codex / ChatGPT target. Emits Agent Plugins packages by default — * root `plugin.json` with OpenAI settings under `extensions.com.openai`, and @@ -389,8 +401,13 @@ const codexLegacy: PluginTargetDefinition = { export const codex: PluginTargetDefinition = { ...codexMarketplace, - forConfig: (targetConfig) => - targetConfig.format === "legacy" ? codexLegacy : codex, + forConfig: (targetConfig) => { + if (targetConfig.format !== "legacy") { + return codex; + } + console.warn(LEGACY_FORMAT_DEPRECATION); + return codexLegacy; + }, buildPluginManifest: buildPackageManifest, finalizeManifest: (manifest, pluginName) => diff --git a/tests/conformance.test.ts b/tests/conformance.test.ts index cf608f5..8e3f708 100644 --- a/tests/conformance.test.ts +++ b/tests/conformance.test.ts @@ -346,6 +346,7 @@ describe("emitted output conforms to external target schemas", () => { it("codex emits the documented Codex plugin marketplace layout", async () => { const result = await runBin("build", "--target", "codex"); expect(result.exitCode, String(result.stderr)).toBe(0); + expect(String(result.stderr)).not.toContain("deprecated"); const marketplace = readJson( project.baseDir, @@ -402,6 +403,14 @@ describe("emitted output conforms to external target schemas", () => { }); const result = await runBin("build", "--target", "codex"); expect(result.exitCode, String(result.stderr)).toBe(0); + // Deprecated in 0.12, removed in 0.13: every build says so once, and + // names the config field and the way off it. + const deprecations = String(result.stderr) + .split("\n") + .filter((line) => line.includes('format: "legacy"')); + expect(deprecations).toEqual([ + 'warning: targets.codex.format: "legacy" is deprecated and will be removed in pluginpack 0.13. It only serves Codex releases older than 0.147.0. Remove "format" from targets.codex to emit Agent Plugins packages.', + ]); const plugin = readJson( project.baseDir, From 0b14624a636ce69c6cc5f35de6677a782553a1ab Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 21 Oct 2026 14:38:07 -0700 Subject: [PATCH 2/3] docs: add the 0.12 migration guide 0.12 changes generated output for four targets, and the README's upgrade note covered only the codex layout. A user upgrading from 0.11 could find that a cursor build now fails on ${CLAUDE_PLUGIN_DATA} or ${PLUGIN_DATA} and writes no target at all, or that claude, cursor and copilot MCP commands and transport labels were rewritten, with nothing explaining why. MIGRATING_TO_0.12.md covers the codex layout change and format: "legacy", the per-target MCP rewrites with real 0.11.1 and 0.12 output, the cursor plugin-data failure and its two fixes, the codex to agent-plugins handoff, and the frontmatter parser change (validate now rejects ---json blocks and tab indentation, and leading-zero numbers read as decimal). The README upgrade section names each change and links the guide. The guide ships in the npm package, and the pack check requires it. --- MIGRATING_TO_0.12.md | 197 +++++++++++++++++++++++++++++++ README.md | 38 ++++-- package.json | 1 + scripts/verify-pack-contents.mjs | 1 + 4 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 MIGRATING_TO_0.12.md diff --git a/MIGRATING_TO_0.12.md b/MIGRATING_TO_0.12.md new file mode 100644 index 0000000..be403b7 --- /dev/null +++ b/MIGRATING_TO_0.12.md @@ -0,0 +1,197 @@ +# Migrating pluginpack 0.11 to 0.12 + +Pluginpack 0.12 reads the same config as 0.11, but it changes generated output +for four targets and makes `validate` stricter about frontmatter. Read this +guide before you publish output built with 0.12. + +| Change | Who is affected | What to do | +| ---------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- | +| `codex` emits Agent Plugins packages | every `codex` target | Publish the new layout, or set `format: "legacy"` until 0.13 | +| MCP config is rendered per target | `claude`, `cursor` and `copilot` targets that ship MCP servers | Check the rewritten `.mcp.json` files | +| `cursor` fails on `${CLAUDE_PLUGIN_DATA}`/`${PLUGIN_DATA}` | `cursor` targets whose MCP config uses a plugin-data variable | Give `cursor` its own MCP config, or exclude `mcp` for `cursor` | +| A new frontmatter parser | sources with JSON frontmatter, tabs, or leading-zero numbers | Convert the frontmatter to YAML that uses spaces and quoted strings | + +## 1. Record the 0.11 output + +Before you upgrade, build with 0.11: + +```bash +npm ci +npx pluginpack build +``` + +Copy each target's `outDir` to a directory outside the project. Step 5 +compares the 0.12 output against this copy. If you publish output to a +separate repository, its checkout works as the copy. + +Then upgrade pluginpack: + +```bash +npm install --save-dev @gleanwork/pluginpack@^0.12.0 +``` + +## 2. The codex layout + +The `codex` target now writes an [Agent Plugins](https://agent-plugins.org/specification) +package for each plugin. OpenAI's +[packaging docs](https://developers.openai.com/codex/plugins/build) prefer +this format, and keep `.codex-plugin/plugin.json` only as a fallback. + +| 0.11 | 0.12 | +| ---------------------------------------------- | ----------------------------------------------------------------- | +| `plugins//.codex-plugin/plugin.json` | `plugins//plugin.json`, with `$schema` | +| `plugins//.mcp.json`, copied as authored | `plugins//mcp.json`, with `$schema` and a `type` per server | +| `.agents/plugins/marketplace.json` | `.agents/plugins/marketplace.json` (unchanged) | + +The first 0.12 build removes the old `.codex-plugin/plugin.json` and `.mcp.json`, +because pluginpack manages them. Manifest fields such as `interface`, `apps` +and `hooks` move under `extensions["com.openai"]` automatically, so the config +needs no changes. + +Agent Plugins clients skip a skill or MCP server that breaks the specification +without telling the user. Pluginpack reports these cases instead: + +- `validate` rejects a skill name that isn't lowercase letters, digits and + hyphens, such as `skill_name`. Rename the skill directory and its `name`. +- `build` rejects MCP config that 0.11 copied as authored. For example: + + ```txt + Target "codex" plugin "acme": MCP server "local" "cwd" is "."; Agent Plugins requires a ./ path or a path under ${PLUGIN_ROOT} or ${PLUGIN_DATA} that stays inside it (omit "cwd" to use the plugin root). + Target "codex" plugin "acme": MCP server "local" "env.TOKEN" uses ${API_TOKEN}; Agent Plugins expands only ${PLUGIN_ROOT} and ${PLUGIN_DATA} and leaves anything else literal. + ``` + + Change the config to follow the rules in the README's + [MCP Directory](./README.md#mcp-directory) section. + +### Keep the old layout with `format: "legacy"` + +Set `format: "legacy"` on the `codex` target if your users run Codex releases +older than 0.147.0. That is the first Codex release that installs Agent +Plugins packages and reads their `mcp.json`. The legacy output is identical to +0.11.1 output. + +```ts +targets: { + codex: { outDir: "dist/codex", format: "legacy", plugins: { ... } }, +}, +``` + +`format: "legacy"` is deprecated in 0.12 and will be removed in 0.13. Every +build that uses it prints this warning: + +```txt +warning: targets.codex.format: "legacy" is deprecated and will be removed in pluginpack 0.13. It only serves Codex releases older than 0.147.0. Remove "format" from targets.codex to emit Agent Plugins packages. +``` + +### Move from `codex` to `agent-plugins` + +The `agent-plugins` target with `clients.codex` writes the same marketplace +file as the `codex` target. To switch, follow these steps in order: + +1. Run `pluginpack clean --target codex`. +2. Remove the `codex` target from the config. +3. Add the `agent-plugins` target with `clients.codex`, and run + `pluginpack build`. + +`clean` works only on a configured target. If you remove the target first, +`clean --target codex` fails with `Target "codex" is not configured.`, and +`.pluginpack/codex.json` stays behind. + +## 3. MCP config for claude, cursor and copilot + +Pluginpack 0.11 copied `mcp/config.json` into each target as authored. +Pluginpack 0.12 renders it into each client's MCP dialect. The README's +[MCP Directory](./README.md#mcp-directory) table lists every rule. These are +the ones that change 0.11 output. Variables are rewritten in `command`, +`args`, `env` and `cwd`. + +| Authored | `claude` | `cursor` | `copilot` | +| ------------------------------------- | --------------------------- | --------------------------- | -------------------- | +| `command` or `cwd` starting with `./` | `${CLAUDE_PLUGIN_ROOT}/...` | `${CURSOR_PLUGIN_ROOT}/...` | `${PLUGIN_ROOT}/...` | +| `${PLUGIN_ROOT}` | `${CLAUDE_PLUGIN_ROOT}` | `${CURSOR_PLUGIN_ROOT}` | unchanged | +| `${CURSOR_PLUGIN_ROOT}` | `${CLAUDE_PLUGIN_ROOT}` | unchanged | `${PLUGIN_ROOT}` | +| `${PLUGIN_DATA}` | `${CLAUDE_PLUGIN_DATA}` | build error | unchanged | +| `${CLAUDE_PLUGIN_DATA}` | unchanged | build error | unchanged | +| `"type": "streamable-http"` | `"type": "http"` | `type` removed | `"type": "http"` | +| `"type": "stdio"` or `"type": "http"` | unchanged | `type` removed | unchanged | + +`${CLAUDE_PLUGIN_ROOT}` is unchanged for all three targets, because Cursor and +Copilot also expand it. + +For example, take this `mcp/config.json`: + +```json +{ + "mcpServers": { + "local": { + "type": "stdio", + "command": "./bin/server", + "args": ["--verbose"] + }, + "remote": { + "type": "streamable-http", + "url": "https://mcp.example.com/mcp" + } + } +} +``` + +Pluginpack 0.11.1 wrote it unchanged to all three targets. Pluginpack 0.12 +writes these `mcpServers`: + +```txt +claude: {"local":{"type":"stdio","command":"${CLAUDE_PLUGIN_ROOT}/bin/server","args":["--verbose"]},"remote":{"type":"http","url":"https://mcp.example.com/mcp"}} +cursor: {"local":{"command":"${CURSOR_PLUGIN_ROOT}/bin/server","args":["--verbose"]},"remote":{"url":"https://mcp.example.com/mcp"}} +copilot: {"local":{"type":"stdio","command":"${PLUGIN_ROOT}/bin/server","args":["--verbose"]},"remote":{"type":"http","url":"https://mcp.example.com/mcp"}} +``` + +If you kept a per-target `mcp/config.json` override only to change variable +names, you can delete it. + +### Cursor and plugin-data variables + +Cursor has no plugin-data variable, so it never expanded `${CLAUDE_PLUGIN_DATA}` +or `${PLUGIN_DATA}`. Pluginpack 0.11.1 shipped these variables to Cursor +anyway. Pluginpack 0.12 stops the build: + +```txt +Target "cursor" plugin "acme": MCP server "local" uses ${CLAUDE_PLUGIN_DATA}, but Cursor has no plugin-data variable. +``` + +This error stops the whole build. A plain `pluginpack build` writes no output +for any target until you fix it. Choose one fix: + +- Give Cursor its own MCP config without the variable. Set + `overrides: "overrides/cursor/"` on the `cursor` plugin and add + `overrides/cursor//mcp/config.json`. +- Ship no MCP servers to Cursor. Add `exclude: ["mcp"]` to the `cursor` plugin. + +## 4. Frontmatter parsing + +Pluginpack 0.11 read skill, agent, command and rule frontmatter with +`gray-matter`, which uses js-yaml 3 (YAML 1.1). Pluginpack 0.12 reads only +YAML frontmatter, with js-yaml 4 (YAML 1.2). Only `validate` reads +frontmatter, so `build` output doesn't change. Three inputs behave +differently: + +- 0.11.1 accepted a `---json` frontmatter block. 0.12 fails with + `Frontmatter language "json" is not supported; use YAML frontmatter.` + Rewrite the block as YAML. +- 0.11.1 accepted a tab-indented line, and read it as a separate top-level + field. 0.12 fails with `tab characters must not be used in indentation`. + Indent with spaces. +- 0.11.1 read an unquoted `017` as the octal number 15. 0.12 reads it as 17. + Quote a value such as `"017"` if it must stay a string. + +## 5. Check the result + +Build once, then validate and diff each configured target: + +```bash +npx pluginpack build +npx pluginpack validate --target +npx pluginpack diff --target --against +``` + +The diff lists every managed file that 0.12 adds, changes or removes. Check +each one against this guide before you publish. diff --git a/README.md b/README.md index bf28585..d7e58c8 100644 --- a/README.md +++ b/README.md @@ -353,14 +353,36 @@ valid plugin name, `mcp.json` servers, and skill names that follow the [Agent Skills specification](https://agentskills.io/specification) and match their directory. Conforming clients skip a skill that fails these checks. -**Upgrading from 0.11:** the `codex` target's output layout changes. Run -`pluginpack build`, and either publish the new layout or set -`format: "legacy"`. If `validate` reports skill names such as -`skill_name`, rename them before switching, because Agent Plugins clients -skip them. The build can also fail on MCP config that 0.11 copied as -authored, such as `cwd: "."` or a `${API_TOKEN}` placeholder. Change it to -follow the Agent Plugins rules in [MCP Directory](#mcp-directory), or set -`format: "legacy"` to keep writing `.mcp.json` as authored. +## Upgrading from 0.11 + +0.12 changes generated output for `codex`, `claude`, `cursor` and `copilot`. +[`MIGRATING_TO_0.12.md`](./MIGRATING_TO_0.12.md) has the full steps and +before/after output. + +- The `codex` target's output layout changes. Run `pluginpack build`, and + either publish the new layout or set `format: "legacy"` until 0.13. If + `validate` reports skill names such as `skill_name`, rename them before + switching, because Agent Plugins clients skip them. The build can also fail + on MCP config that 0.11 copied as authored, such as `cwd: "."` or a + `${API_TOKEN}` placeholder. Change it to follow the Agent Plugins rules in + [MCP Directory](#mcp-directory), or set `format: "legacy"` to keep writing + `.mcp.json` as authored. +- A `cursor` build fails when the shared MCP config uses + `${CLAUDE_PLUGIN_DATA}` or `${PLUGIN_DATA}`, which 0.11 shipped to Cursor + unexpanded: + `MCP server "local" uses ${CLAUDE_PLUGIN_DATA}, but Cursor has no plugin-data variable.` + The error stops every target, so nothing is written. Give `cursor` its own + `mcp/config.json` through `overrides`, or add `"mcp"` to its `exclude`. +- `claude`, `cursor` and `copilot` no longer copy MCP config as authored. A + `./bin/server` command or `./` `cwd` becomes `${CLAUDE_PLUGIN_ROOT}/bin/server` + for `claude`, `${CURSOR_PLUGIN_ROOT}/bin/server` for `cursor` and + `${PLUGIN_ROOT}/bin/server` for `copilot`. Other root and data variables + are renamed to each client's own. `"type": "streamable-http"` becomes + `"type": "http"` for `claude` and `copilot`, and `cursor` drops `type` + for stdio and HTTP servers. +- `validate` rejects `---json` frontmatter and tab-indented YAML, which 0.11 + accepted. An unquoted number with a leading zero, such as `017`, now reads + as 17 instead of the octal 15. ## Legacy 0.10 Source Composition diff --git a/package.json b/package.json index 683e624..3bbe19f 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "!dist/**/*.map", "CHANGELOG.md", "MIGRATING_TO_0.11.md", + "MIGRATING_TO_0.12.md", "LICENSE", "README.md" ], diff --git a/scripts/verify-pack-contents.mjs b/scripts/verify-pack-contents.mjs index dea1302..da6ff7d 100755 --- a/scripts/verify-pack-contents.mjs +++ b/scripts/verify-pack-contents.mjs @@ -11,6 +11,7 @@ const REQUIRED_FILES = [ "dist/index.d.ts", "package.json", "README.md", + "MIGRATING_TO_0.12.md", "LICENSE", ]; From a63687d35d9683278900b12d9a260eac55e683c6 Mon Sep 17 00:00:00 2001 From: Steve Calvert Date: Wed, 21 Oct 2026 16:22:07 -0700 Subject: [PATCH 3/3] docs: cover every 0.12 behavior change in the migration guide The 0.12 release adds new build and config errors, output changes, warnings and API type changes beyond the codex, MCP and frontmatter changes the guide covered. Each one was checked against the integrated 0.12 build and against 0.11.1. The guide is now ordered by what a user has to do: fix the errors that stop a build which passed on 0.11.1, review the changed output, read the new warnings, then update API callers. Each entry says what changed, who hits it and the fix. It also explains that the version 2 managed manifest is one-way for 0.11, and corrects the MCP table, since a claude build now rejects cwd instead of rewriting it. The README upgrade section now points to the guide's structure and the manifest change, and drops the claim that claude rewrites cwd. --- MIGRATING_TO_0.12.md | 396 ++++++++++++++++++++++++++++++++++--------- README.md | 21 ++- 2 files changed, 333 insertions(+), 84 deletions(-) diff --git a/MIGRATING_TO_0.12.md b/MIGRATING_TO_0.12.md index be403b7..df50600 100644 --- a/MIGRATING_TO_0.12.md +++ b/MIGRATING_TO_0.12.md @@ -1,19 +1,23 @@ # Migrating pluginpack 0.11 to 0.12 -Pluginpack 0.12 reads the same config as 0.11, but it changes generated output -for four targets and makes `validate` stricter about frontmatter. Read this -guide before you publish output built with 0.12. +Pluginpack 0.12 reads the same config fields as 0.11. Some configs and sources +that 0.11 accepted now fail, and several targets write different output. Work +through this guide in order: -| Change | Who is affected | What to do | -| ---------------------------------------------------------- | -------------------------------------------------------------- | ------------------------------------------------------------------- | -| `codex` emits Agent Plugins packages | every `codex` target | Publish the new layout, or set `format: "legacy"` until 0.13 | -| MCP config is rendered per target | `claude`, `cursor` and `copilot` targets that ship MCP servers | Check the rewritten `.mcp.json` files | -| `cursor` fails on `${CLAUDE_PLUGIN_DATA}`/`${PLUGIN_DATA}` | `cursor` targets whose MCP config uses a plugin-data variable | Give `cursor` its own MCP config, or exclude `mcp` for `cursor` | -| A new frontmatter parser | sources with JSON frontmatter, tabs, or leading-zero numbers | Convert the frontmatter to YAML that uses spaces and quoted strings | +1. [Before you upgrade](#before-you-upgrade): record the 0.11 output. +2. [Fix build and config errors](#1-fix-build-and-config-errors): each one + stops a build that passed with 0.11.1. +3. [Review changed output](#2-review-changed-output): files that 0.12 writes + differently. +4. [Read the new warnings](#3-read-the-new-warnings): messages that don't stop + the build. +5. [Update code that uses the API](#4-update-code-that-uses-the-api): only if + you import `@gleanwork/pluginpack` from code. +6. [Check the result](#5-check-the-result). -## 1. Record the 0.11 output +## Before you upgrade -Before you upgrade, build with 0.11: +Build with 0.11: ```bash npm ci @@ -30,7 +34,211 @@ Then upgrade pluginpack: npm install --save-dev @gleanwork/pluginpack@^0.12.0 ``` -## 2. The codex layout +The first 0.12 build rewrites each managed manifest +(`.pluginpack/.json`) as version 2, which records a sha256 hash for +every file. Pluginpack 0.11 cannot read version 2 and fails with +`Invalid managed manifest: `. Upgrade every machine and CI job that +builds the same output at the same time. To go back to 0.11, delete the +`.pluginpack/` directory in each `outDir` first. + +On a 0.11 output, the first 0.12 `build` and `prune` work without `--force`, +and `clean` works too. This holds for the README layout, a `codex` target at +`outDir: "."`, legacy 0.10 sources and the `pluginpack init` layout. + +## 1. Fix build and config errors + +Each error below stops a build that passed with 0.11.1. A config error stops +every command. A build error in one target stops the whole build, so +`pluginpack build` writes no output for any target until you fix it. The most +common errors come first. + +### codex MCP config that breaks Agent Plugins rules + +The `codex` target now writes Agent Plugins packages (see +[The codex layout](#the-codex-layout)), so its MCP config must follow the +Agent Plugins rules. Pluginpack 0.11 copied the config as authored. Agent +Plugins clients skip a server that breaks the rules without telling the user, +so 0.12 stops the build instead: + +```txt +Target "codex" plugin "acme": MCP server "local" "cwd" is "."; Agent Plugins requires a ./ path or a path under ${PLUGIN_ROOT} or ${PLUGIN_DATA} that stays inside it (omit "cwd" to use the plugin root). +Target "codex" plugin "acme": MCP server "local" "env.TOKEN" uses ${API_TOKEN}; Agent Plugins expands only ${PLUGIN_ROOT} and ${PLUGIN_DATA} and leaves anything else literal. +``` + +A `command` must also be a single token. A value that holds arguments, such as +`"./bin/server --verbose"`, fails because clients run the whole value as one +file name. Move the arguments into `args`. + +This affects `codex` and `agent-plugins` targets that ship MCP servers. Change +the config to follow the README's [MCP Directory](./README.md#mcp-directory) +rules. You can also give `codex` its own `mcp/config.json` through +`overrides`, or set `format: "legacy"` on the `codex` target until 0.13. + +### Plugin-data variables on cursor + +Cursor has no plugin-data variable, so it never expanded `${CLAUDE_PLUGIN_DATA}` +or `${PLUGIN_DATA}`. Pluginpack 0.11.1 shipped these variables to Cursor +anyway. Pluginpack 0.12 stops the build: + +```txt +Target "cursor" plugin "acme": MCP server "local" uses ${CLAUDE_PLUGIN_DATA}, but Cursor has no plugin-data variable. +``` + +Choose one fix: + +- Give Cursor its own MCP config without the variable. Set + `overrides: "overrides/cursor/"` on the `cursor` plugin and add + `overrides/cursor//mcp/config.json`. +- Ship no MCP servers to Cursor. Add `exclude: ["mcp"]` to the `cursor` plugin. + +### `cwd` on a claude MCP server + +The `claude` target fails when a stdio MCP server sets `cwd`. Claude Code +ignores `cwd` for plugin MCP servers and starts them in the user's working +directory, so the server that 0.11 shipped never ran where you expected. Claude +Code expands `${CLAUDE_PLUGIN_ROOT}` only in `command`, `args` and `env`. + +Remove `cwd`, and write paths in `args` as `${CLAUDE_PLUGIN_ROOT}/...`, for +example `"args": ["${CLAUDE_PLUGIN_ROOT}/server/index.js"]`. If other targets +need `cwd`, keep it in the shared config and add a claude-only +`overrides/claude//mcp/config.json` without it. + +### Frontmatter that YAML 1.2 rejects + +Pluginpack 0.11 read skill, agent, command and rule frontmatter with +`gray-matter`, which uses js-yaml 3 (YAML 1.1). Pluginpack 0.12 reads only +YAML frontmatter, with js-yaml 4 (YAML 1.2). `validate` reports these errors. +`build` output doesn't change, but `build` prints the same problems as warnings +for `codex` and `agent-plugins` skills. Three inputs behave differently: + +- 0.11.1 accepted a `---json` frontmatter block. 0.12 fails with + `Frontmatter language "json" is not supported; use YAML frontmatter.` + Rewrite the block as YAML. +- 0.11.1 accepted a tab-indented line, and read it as a separate top-level + field. 0.12 fails with `tab characters must not be used in indentation`. + Indent with spaces. +- 0.11.1 read an unquoted `017` as the octal number 15. 0.12 reads it as 17. + Quote a value such as `"017"` if it must stay a string. + +### Skill names on codex and agent-plugins + +`validate` rejects a `codex` or `agent-plugins` skill whose name isn't +lowercase letters, digits and hyphens, such as `skill_name`. Agent Plugins +clients skip that skill. Rename the skill directory and its `name`. + +### MCP command files missing from a package + +`validate` fails for `codex` and `agent-plugins` when a `./` command names a +file that the package doesn't ship: + +```txt +error: acme: mcp.json server "a" "command" is "./bin/missing", but the package has no file "bin/missing" — the client can't start the server. +``` + +Ship the file by listing it in `mcp/pluginpack.json` `files`, or fix the +command. Other targets only warn (see +[the new warnings](#3-read-the-new-warnings)). + +### A custom `marketplaceDir` on claude or cursor + +Claude Code and Cursor read their marketplace only from `.claude-plugin/` and +`.cursor-plugin/`. A `claude` or `cursor` target with any other +`marketplaceDir` now fails at config load. Remove `marketplaceDir`, or set it +to `.claude-plugin` or `.cursor-plugin`. To move the generated repository, use +`outDir`. + +### Two files at one output path + +A target now fails when two plugins, or two files, write the same output path. +Pluginpack 0.11 kept the last write. Paths that differ only by case or by +Unicode normalization count as the same path, so `plugins/Acme` and +`plugins/acme` collide: + +```txt +Target "claude": plugin "acme" manifest and plugin "beta" manifest collide at "plugins/acme/.claude-plugin/plugin.json". Change the plugin path or remove the conflicting file in pluginpack.config.ts. +``` + +Give each plugin its own `path`, or remove the conflicting file. + +### Two targets in one directory + +Two targets fail when their `outDir`s are the same directory and they write +the same file. Pluginpack now compares real paths, so `out` and `OUT` on a +case-insensitive filesystem, or a symlink to another `outDir`, count as the +same directory. The error starts with +`Targets write overlapping output paths; give them distinct outDirs:`. Give +each target its own `outDir`, and replace a symlinked output path with a +regular directory. + +### An `outDir` inside a source + +An `outDir` that resolves inside a source, `overrides`, `partials`, +`repositoryFiles` or `rootFiles` path now fails at config load, even when it +gets there through a symlink. A build there overwrites source files, and prune +or clean would delete them. Point each `outDir` at a directory outside every +source path. + +### Path fields outside the project + +The plugin `source`, `overrides` and `path` fields, and `source.partials`, +must be relative paths inside the project. Pluginpack 0.11 accepted a +URL-shaped value such as `https://../../x` and read it as `../x`. It also +accepted a `source.partials` directory outside the project, such as +`../partials`. Both now fail at config load: + +```txt +targets.claude.plugins.acme.source: must be a safe relative path (no absolute paths or ".." segments) +``` + +Move the directory into the project, and use a plain relative path. + +### A `rootFiles` entry that copies a file onto itself + +A `rootFiles` entry whose source and destination are the same file now fails, +for example `"README.md": "README.md"` with `outDir: "."`. Pluginpack would +otherwise manage the authored file and later delete it. Map the entry to a +different destination, or remove it if the file already lives in the output +repository. + +### Output left by a target you removed + +`build` and `prune` now refuse when an `outDir` holds the manifest of a target +that the config no longer builds there. This happens when you switch from +`codex` to `agent-plugins` with `clients.codex` at the same `outDir`: + +```txt +Found managed output from a target this config does not build there: + /path/to/out/.pluginpack/codex.json (codex) +No configured target prunes the files these manifests list, so they would stay in the output. Run `pluginpack clean --target codex` to delete them first. To keep the files unmanaged instead, delete the manifest. +``` + +Run `pluginpack clean --target ` to delete the old files. `clean` can now +remove a target that the config no longer lists. To keep the files as +unmanaged files instead, delete the manifest. + +### Partials that aren't valid UTF-8 + +A file that holds a `{{> name}}` tag, or a partial it includes, must be valid +UTF-8. Pluginpack 0.11 replaced the invalid bytes with U+FFFD and shipped the +result. Pluginpack 0.12 stops the build. Re-save the file as UTF-8, or remove +the partial tags from it. + +### Partial tags built from neighboring text + +A tag that substitution would assemble from separate pieces now fails instead +of shipping as a live `{{> name}}` tag. Examples are `{{{> auth}}}`, and a +partial that ends in `{` placed before `{> auth}}`. Separate the pieces, or +write `\{{> auth}}` to emit the tag on purpose. + +### Dry runs run the same checks + +`build --dry-run`, `prune --dry-run` and `clean --dry-run` now run the same +safety checks as the real command. A dry run that passed with 0.11 fails with +any of the errors above. + +## 2. Review changed output + +### The codex layout The `codex` target now writes an [Agent Plugins](https://agent-plugins.org/specification) package for each plugin. OpenAI's @@ -46,29 +254,16 @@ this format, and keep `.codex-plugin/plugin.json` only as a fallback. The first 0.12 build removes the old `.codex-plugin/plugin.json` and `.mcp.json`, because pluginpack manages them. Manifest fields such as `interface`, `apps` and `hooks` move under `extensions["com.openai"]` automatically, so the config -needs no changes. - -Agent Plugins clients skip a skill or MCP server that breaks the specification -without telling the user. Pluginpack reports these cases instead: - -- `validate` rejects a skill name that isn't lowercase letters, digits and - hyphens, such as `skill_name`. Rename the skill directory and its `name`. -- `build` rejects MCP config that 0.11 copied as authored. For example: - - ```txt - Target "codex" plugin "acme": MCP server "local" "cwd" is "."; Agent Plugins requires a ./ path or a path under ${PLUGIN_ROOT} or ${PLUGIN_DATA} that stays inside it (omit "cwd" to use the plugin root). - Target "codex" plugin "acme": MCP server "local" "env.TOKEN" uses ${API_TOKEN}; Agent Plugins expands only ${PLUGIN_ROOT} and ${PLUGIN_DATA} and leaves anything else literal. - ``` +needs no changes. A per-server `extensions["com.openai"]` object in the MCP +config, which Codex reads for MCP auth, ships in `codex` output. The +`agent-plugins` target still rejects it. - Change the config to follow the rules in the README's - [MCP Directory](./README.md#mcp-directory) section. - -### Keep the old layout with `format: "legacy"` +#### Keep the old layout with `format: "legacy"` Set `format: "legacy"` on the `codex` target if your users run Codex releases older than 0.147.0. That is the first Codex release that installs Agent -Plugins packages and reads their `mcp.json`. The legacy output is identical to -0.11.1 output. +Plugins packages and reads their `mcp.json`. The legacy layout is the 0.11.1 +layout. The other changes in this section still apply to it. ```ts targets: { @@ -83,21 +278,22 @@ build that uses it prints this warning: warning: targets.codex.format: "legacy" is deprecated and will be removed in pluginpack 0.13. It only serves Codex releases older than 0.147.0. Remove "format" from targets.codex to emit Agent Plugins packages. ``` -### Move from `codex` to `agent-plugins` +#### Move from `codex` to `agent-plugins` The `agent-plugins` target with `clients.codex` writes the same marketplace -file as the `codex` target. To switch, follow these steps in order: +file as the `codex` target. The `agent-plugins` target doesn't ship Codex +hooks. To switch, follow these steps in order: 1. Run `pluginpack clean --target codex`. -2. Remove the `codex` target from the config. -3. Add the `agent-plugins` target with `clients.codex`, and run - `pluginpack build`. +2. Replace the `codex` target in the config with the `agent-plugins` target + and `clients.codex`. +3. Run `pluginpack build`. -`clean` works only on a configured target. If you remove the target first, -`clean --target codex` fails with `Target "codex" is not configured.`, and -`.pluginpack/codex.json` stays behind. +If you change the config first and the new target uses the same `outDir`, +`build` refuses until you run `pluginpack clean --target codex` (see +[Output left by a target you removed](#output-left-by-a-target-you-removed)). -## 3. MCP config for claude, cursor and copilot +### MCP config for claude, cursor and copilot Pluginpack 0.11 copied `mcp/config.json` into each target as authored. Pluginpack 0.12 renders it into each client's MCP dialect. The README's @@ -107,7 +303,8 @@ the ones that change 0.11 output. Variables are rewritten in `command`, | Authored | `claude` | `cursor` | `copilot` | | ------------------------------------- | --------------------------- | --------------------------- | -------------------- | -| `command` or `cwd` starting with `./` | `${CLAUDE_PLUGIN_ROOT}/...` | `${CURSOR_PLUGIN_ROOT}/...` | `${PLUGIN_ROOT}/...` | +| `command` starting with `./` | `${CLAUDE_PLUGIN_ROOT}/...` | `${CURSOR_PLUGIN_ROOT}/...` | `${PLUGIN_ROOT}/...` | +| `cwd` starting with `./` | build error | `${CURSOR_PLUGIN_ROOT}/...` | `${PLUGIN_ROOT}/...` | | `${PLUGIN_ROOT}` | `${CLAUDE_PLUGIN_ROOT}` | `${CURSOR_PLUGIN_ROOT}` | unchanged | | `${CURSOR_PLUGIN_ROOT}` | `${CLAUDE_PLUGIN_ROOT}` | unchanged | `${PLUGIN_ROOT}` | | `${PLUGIN_DATA}` | `${CLAUDE_PLUGIN_DATA}` | build error | unchanged | @@ -116,7 +313,9 @@ the ones that change 0.11 output. Variables are rewritten in `command`, | `"type": "stdio"` or `"type": "http"` | unchanged | `type` removed | unchanged | `${CLAUDE_PLUGIN_ROOT}` is unchanged for all three targets, because Cursor and -Copilot also expand it. +Copilot also expand it. The build errors are described in +[`cwd` on a claude MCP server](#cwd-on-a-claude-mcp-server) and +[Plugin-data variables on cursor](#plugin-data-variables-on-cursor). For example, take this `mcp/config.json`: @@ -148,40 +347,85 @@ copilot: {"local":{"type":"stdio","command":"${PLUGIN_ROOT}/bin/server","args":[ If you kept a per-target `mcp/config.json` override only to change variable names, you can delete it. -### Cursor and plugin-data variables - -Cursor has no plugin-data variable, so it never expanded `${CLAUDE_PLUGIN_DATA}` -or `${PLUGIN_DATA}`. Pluginpack 0.11.1 shipped these variables to Cursor -anyway. Pluginpack 0.12 stops the build: - -```txt -Target "cursor" plugin "acme": MCP server "local" uses ${CLAUDE_PLUGIN_DATA}, but Cursor has no plugin-data variable. -``` - -This error stops the whole build. A plain `pluginpack build` writes no output -for any target until you fix it. Choose one fix: - -- Give Cursor its own MCP config without the variable. Set - `overrides: "overrides/cursor/"` on the `cursor` plugin and add - `overrides/cursor//mcp/config.json`. -- Ship no MCP servers to Cursor. Add `exclude: ["mcp"]` to the `cursor` plugin. - -## 4. Frontmatter parsing - -Pluginpack 0.11 read skill, agent, command and rule frontmatter with -`gray-matter`, which uses js-yaml 3 (YAML 1.1). Pluginpack 0.12 reads only -YAML frontmatter, with js-yaml 4 (YAML 1.2). Only `validate` reads -frontmatter, so `build` output doesn't change. Three inputs behave -differently: - -- 0.11.1 accepted a `---json` frontmatter block. 0.12 fails with - `Frontmatter language "json" is not supported; use YAML frontmatter.` - Rewrite the block as YAML. -- 0.11.1 accepted a tab-indented line, and read it as a separate top-level - field. 0.12 fails with `tab characters must not be used in indentation`. - Indent with spaces. -- 0.11.1 read an unquoted `017` as the octal number 15. 0.12 reads it as 17. - Quote a value such as `"017"` if it must stay a string. +### Hooks pointers + +The `copilot` target and the `codex` target with `format: "legacy"` now write +a `hooks` field in `plugin.json` only when the plugin ships +`hooks/hooks.json`. Pluginpack 0.11 also wrote the field for a `hooks/` +directory without `hooks/hooks.json`, and `validate` then failed on the +missing file. No action is needed. To ship hooks, add `hooks/hooks.json`. + +### Partial line endings and BOMs + +An inlined partial now takes the line endings of the file that includes it, +and loses a leading byte order mark. Pluginpack 0.11 inserted the partial's +bytes unchanged, so a CRLF file could get LF lines and a BOM in the middle. +No action is needed. + +### `install-info` commands + +`pluginpack install-info --target claude` now prints the shell commands +`claude plugin marketplace add ` and `claude plugin install @` +first. The interactive `/plugin` commands moved to the note. +`--target antigravity` now prints `agy plugin install https://github.com//`, +and the note keeps the clone-and-install form. Update any docs that copied the +0.11 output. + +### `pluginpack init` + +`pluginpack init` now scaffolds all six targets. It also refuses to run when +any file it would create already exists. Pluginpack 0.11 scaffolded four +targets and overwrote an existing `shared/example` file. + +## 3. Read the new warnings + +These messages print during `build` but don't stop it. Check each one before +you publish. + +- Unknown config and manifest keys. A key that pluginpack doesn't know, such + as a misspelled `outdir`, prints + `Unknown config key "targets.claude.outdir" in was ignored.` The + same check covers `plugin.pluginpack.json` and `mcp/pluginpack.json`. The + key is still ignored, as in 0.11. Fix the spelling or remove the key. + Passthrough objects such as `manifest` and `entry` don't warn. +- An MCP file that the plugin doesn't ship. A `./` or root-variable `command`, + or an `args` entry that starts with `${PLUGIN_ROOT}/`, + `${CLAUDE_PLUGIN_ROOT}/` or `${CURSOR_PLUGIN_ROOT}/`, names a file that the + plugin doesn't ship. Add the file to `mcp/pluginpack.json` `files`, or fix + the path. +- A `codex` entry, or an `agent-plugins` `clients.codex` entry, without + `policy.installation`, `policy.authentication` or `category`. Set them in + the plugin's `entry`. +- A `codex` or `agent-plugins` skill with an invalid name or unparseable + frontmatter. `validate` fails on the same skill, as described in + [Skill names](#skill-names-on-codex-and-agent-plugins). +- Hooks dropped by `agent-plugins`. A plugin with a `hooks/` directory prints + `"shared//hooks" is omitted.` Keep the `codex` target if you need + hooks, or add `"hooks"` to the plugin's `exclude`. +- A managed file edited by hand. `build`, `prune` and `clean` warn before they + overwrite or delete a managed file whose content no longer matches its + recorded hash, and then go ahead. Move the edit into the source, or recover + it from version control. +- `format: "legacy"` on `codex`. See + [Keep the old layout](#keep-the-old-layout-with-format-legacy). + +One warning is gone. `validate` no longer warns about an escaped `\{{> name}}` +tag in a file type that gets partial substitution. + +## 4. Update code that uses the API + +Skip this section if you only run the CLI. + +- `PluginpackConfig` is now the type of the config you write, and the new + `ResolvedPluginpackConfig` is the parsed config. `ResolvedProject.config`, + as returned by `loadConfig`, has the `ResolvedPluginpackConfig` type. The + two types have the same shape today, so existing code compiles. Use + `ResolvedPluginpackConfig` for parsed config. +- `FileValue`, the type of each value in `Artifact.files`, is now + `string | Uint8Array` instead of `string | Buffer`. Runtime values are still + Buffers, but a call such as `value.toString("utf8")` no longer type-checks. + Decode with `Buffer.from(value).toString("utf8")` or `new TextDecoder()`. +- The published type declarations no longer import `zod`. No action is needed. ## 5. Check the result diff --git a/README.md b/README.md index d7e58c8..4c63684 100644 --- a/README.md +++ b/README.md @@ -356,8 +356,12 @@ their directory. Conforming clients skip a skill that fails these checks. ## Upgrading from 0.11 0.12 changes generated output for `codex`, `claude`, `cursor` and `copilot`. -[`MIGRATING_TO_0.12.md`](./MIGRATING_TO_0.12.md) has the full steps and -before/after output. +Some configs and sources that 0.11 accepted now fail at config load, build or +`validate`. [`MIGRATING_TO_0.12.md`](./MIGRATING_TO_0.12.md) lists each error +with its fix, then the changed output, the new warnings and the API type +changes. The first 0.12 build rewrites each `.pluginpack/.json` in a +format 0.11 can't read, so upgrade every machine that builds the same output +together. - The `codex` target's output layout changes. Run `pluginpack build`, and either publish the new layout or set `format: "legacy"` until 0.13. If @@ -374,12 +378,13 @@ before/after output. The error stops every target, so nothing is written. Give `cursor` its own `mcp/config.json` through `overrides`, or add `"mcp"` to its `exclude`. - `claude`, `cursor` and `copilot` no longer copy MCP config as authored. A - `./bin/server` command or `./` `cwd` becomes `${CLAUDE_PLUGIN_ROOT}/bin/server` - for `claude`, `${CURSOR_PLUGIN_ROOT}/bin/server` for `cursor` and - `${PLUGIN_ROOT}/bin/server` for `copilot`. Other root and data variables - are renamed to each client's own. `"type": "streamable-http"` becomes - `"type": "http"` for `claude` and `copilot`, and `cursor` drops `type` - for stdio and HTTP servers. + `./bin/server` command becomes `${CLAUDE_PLUGIN_ROOT}/bin/server` for + `claude`, `${CURSOR_PLUGIN_ROOT}/bin/server` for `cursor` and + `${PLUGIN_ROOT}/bin/server` for `copilot`. A `claude` build fails on an MCP + server with `cwd`, because Claude Code ignores it. Other root and data + variables are renamed to each client's own. `"type": "streamable-http"` + becomes `"type": "http"` for `claude` and `copilot`, and `cursor` drops + `type` for stdio and HTTP servers. - `validate` rejects `---json` frontmatter and tab-indented YAML, which 0.11 accepted. An unquoted number with a leading zero, such as `017`, now reads as 17 instead of the octal 15.