mcplane 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +108 -18
  3. package/dist/agent-plugins.d.ts +6 -0
  4. package/dist/agent-plugins.js +76 -0
  5. package/dist/cdp.d.ts +49 -0
  6. package/dist/cdp.js +253 -0
  7. package/dist/checks/ard.d.ts +67 -0
  8. package/dist/checks/ard.js +215 -0
  9. package/dist/checks/auth.js +2 -1
  10. package/dist/checks/chain.d.ts +45 -0
  11. package/dist/checks/chain.js +171 -0
  12. package/dist/checks/listing.js +23 -14
  13. package/dist/checks/oauth.d.ts +45 -0
  14. package/dist/checks/oauth.js +64 -0
  15. package/dist/checks/readiness.d.ts +58 -0
  16. package/dist/checks/readiness.js +230 -0
  17. package/dist/checks/tools.js +38 -7
  18. package/dist/checks/vercel.d.ts +27 -0
  19. package/dist/checks/vercel.js +153 -0
  20. package/dist/cli.js +54 -6
  21. package/dist/core.d.ts +1 -0
  22. package/dist/core.js +20 -4
  23. package/dist/demo.d.ts +60 -0
  24. package/dist/demo.js +573 -0
  25. package/dist/drift.js +10 -3
  26. package/dist/evals.d.ts +34 -0
  27. package/dist/evals.js +75 -0
  28. package/dist/extras.js +1 -1
  29. package/dist/images.d.ts +21 -0
  30. package/dist/images.js +99 -0
  31. package/dist/listings.d.ts +14 -0
  32. package/dist/listings.js +39 -0
  33. package/dist/mcp-client.d.ts +1 -5
  34. package/dist/mcp-client.js +8 -1
  35. package/dist/mcp-server.js +21 -11
  36. package/dist/packs.d.ts +48 -18
  37. package/dist/packs.js +380 -90
  38. package/dist/report.js +30 -13
  39. package/dist/skills.d.ts +21 -0
  40. package/dist/skills.js +180 -0
  41. package/dist/submissions.js +2 -1
  42. package/dist/types.d.ts +37 -4
  43. package/dist/types.js +1 -0
  44. package/dist/version.d.ts +1 -1
  45. package/dist/version.js +1 -1
  46. package/dist/video.d.ts +58 -0
  47. package/dist/video.js +190 -0
  48. package/dist/zip.d.ts +16 -2
  49. package/dist/zip.js +65 -5
  50. package/package.json +4 -1
  51. package/schema.json +86 -12
  52. package/schemas/agent-plugins-1.0.0/LICENSE +202 -0
  53. package/schemas/agent-plugins-1.0.0/mcp.schema.json +120 -0
  54. package/schemas/agent-plugins-1.0.0/plugin.schema.json +65 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ ## 0.5.0
4
+
5
+ New command
6
+ - `mcplane demo` (experimental): records the developer-mode demo video ChatGPT's review asks for, from your positive tests, by driving the ChatGPT desktop app on a Mac. It asks before sending any prompt, hides the sidebar, and writes the video to `.mcplane/packs/chatgpt/demo.mp4`. It finds its way by ChatGPT's button labels, so a ChatGPT release can break it.
7
+
8
+ New stores and checks
9
+ - Vercel Connect: preflight against its Required, Recommended and Optional tiers, from your discovery documents alone; `pack vercel-connect`; its directory in `listings`; drift rules. Manifests without `stores` now get these checks too, so an OAuth server with a Required gap exits 1 where it didn't before.
10
+ - Readiness for MCP 2026-07-28, in its own preflight section and never blocking: `server/discover`, `initialize` negotiated down, the `Mcp-Method` and `Mcp-Name` headers and CORS for them, issuer, CIMD, RFC 9207 `iss`, MCP Events.
11
+ - ARD: preflight reads `/.well-known/ard.json` (and the older `ai-catalog.json`) and validates it against ARD v0.91 and Lighthouse 13.5; `pack ard` writes a manifest both accept.
12
+ - `pack claude-eval`: your review tests as a `claude plugin eval` suite.
13
+ - Sign-in chain: plain GETs from the authorization endpoint to the sign-in page and the identity-provider hosts it loads, flagging bot challenges (`cf-mitigated: challenge`) and 403s. Nothing signs in and no client is registered. `server.signIn` or `--sign-in` names pages to check when the authorization server wants a registered client first.
14
+ - A new check level, `info`: a note that never blocks and isn't counted as something to review. Code that maps levels one by one needs to handle it.
15
+
16
+ OpenAI's annotation rules
17
+ - No more drafted hint justifications or 200-character warnings: OpenAI no longer asks for them. Any in `mcplane.json` become optional appeal notes.
18
+ - A new warning when a write that overwrites, revokes or deletes says `destructiveHint: false`.
19
+ - A ChatGPT hint change is a note in `drift`, so `drift --ci` no longer fails on it.
20
+
21
+ ChatGPT plugin ZIP
22
+ - Bundles Agent Skills from `chatgpt.skills`, checking names against folders, description length, front matter and scripts a skill runs but doesn't ship.
23
+ - Checks `plugin.json` and `mcp.json` against the Agent Plugins 1.0.0 JSON Schemas, and the root description against 1,024 characters (`plugin_description_too_long`, also in preflight).
24
+ - Icons can be local files (`icon`, `chatgpt.logo`, `composerIcon`, `logoDark`), and sizes are read from PNG, JPEG, WebP and SVG bytes, in preflight too.
25
+ - Builds behind sign-in without `--token`, and says what it couldn't check; `--tools <file>` takes a saved `tools/list` result.
26
+ - Entries are dated 1980-01-01 and keep Unix modes, so scripts stay executable and the same files give the same ZIP.
27
+ - The 10 KB icon warning is gone: the ZIP takes icons up to 5 MiB, and only developer mode's own upload is capped at 10 KB. An icon over 5 MiB now blocks.
package/README.md CHANGED
@@ -17,9 +17,10 @@ No terminal handy? Paste your URL at [reviewtimes.fyi/check](https://reviewtimes
17
17
  ✗ Every tool has a human-readable title [Claude connectors, Claude plugins]
18
18
  search_docs, fetch_page
19
19
  Add "title" (or annotations.title) to each tool. Anthropic rejects connectors without them.
20
- ! A small icon is available for ChatGPT's dialogs [ChatGPT]
21
- 28 KB
22
- ChatGPT's icon upload in developer mode caps at 10 KB. Export an 8-bit PNG.
20
+ ! Reviewers can reach your sign-in page
21
+ https://accounts.example.com/sign-in: Cloudflare challenge (cf-mitigated: challenge)
22
+ A bot challenge or a 403 on the sign-in chain leaves the OAuth window blank for reviewers, who sign
23
+ in from data-centre networks. Exempt the authorization endpoint, consent page and sign-in hosts …
23
24
  ✓ 15 checks passed (--verbose to list them)
24
25
 
25
26
  2 blocking, 1 to review.
@@ -27,11 +28,11 @@ No terminal handy? Paste your URL at [reviewtimes.fyi/check](https://reviewtimes
27
28
 
28
29
  ## Why
29
30
 
30
- An MCP server now has a dozen places to be listed: ChatGPT, Claude, Cursor, Grok, Muse, the official MCP Registry, Docker, Smithery and the directories that read from them. Each has its own form, its own rules and its own idea of what an update is. Most rejections are for the same few mechanical things, and each one costs you days in the queue. With one app that's annoying. With seventeen you stop shipping.
31
+ An MCP server now has a dozen places to be listed: ChatGPT, Claude, Cursor, Grok, Muse, Vercel Connect, the official MCP Registry, Docker, Smithery and the directories that read from them. Each has its own form, its own rules and its own idea of what an update is. Most rejections are for the same few mechanical things, and each one costs you days in the queue. With one app that's annoying. With seventeen you stop shipping.
31
32
 
32
33
  iOS had the same problem in 2014. fastlane fixed it by putting your metadata in the repo, checking it before Apple could reject it, and turning every release into one command. mcplane does that for agent-era stores.
33
34
 
34
- An agent with a browser could fill in a form. It can't tell you that your DNS will make ChatGPT's reviewer time out, that a hint needs a justification you haven't written, or which of your seventeen listings went stale on Tuesday. That's the part mcplane does.
35
+ An agent with a browser could fill in a form. It can't tell you that your DNS will make ChatGPT's reviewer time out, that ChatGPT counts overwriting a page as destructive, or which of your seventeen listings went stale on Tuesday. That's the part mcplane does.
35
36
 
36
37
  ## Use it from your agent
37
38
 
@@ -58,7 +59,7 @@ npx mcplane fleet --root ~/Projects
58
59
  ✓ Review Times preflight clean · listed on mcp-registry, glama, chatgpt
59
60
  ✗ Invoice Bot 2 blocking · waiting on claude-connectors · listed on mcp-registry
60
61
  ✗ Weather Pro 1 store update needed · listed on mcp-registry, claude-connectors · 1 listing fix
61
- chatgpt: Hints changed: send_alert
62
+ chatgpt: Listing text changed (description)
62
63
  claude-connectors: listing doesn't show: forecast_hourly (ask the review team to resync)
63
64
 
64
65
  3 projects, 2 need attention.
@@ -72,11 +73,12 @@ npx mcplane fleet --root ~/Projects
72
73
  |---|---|---|
73
74
  | Official MCP Registry | **Publishes**: writes `server.json`, runs `mcp-publisher`, signs in with GitHub OIDC in Actions. Glama and PulseMCP import from it; GitHub's MCP gallery (VS Code) syncs it after a one-time manual onboarding | Versions can't change: bump, or a prerelease like `1.2.0-1` for listing-only edits. mcplane checks what's already published |
74
75
  | Grok plugins | **Publishes**: opens the pull request to `xai-org/plugin-marketplace`, validated with xAI's own scripts | Opens a pin-bump PR when xAI's daily bump lags |
75
- | ChatGPT | **Builds the plugin ZIP** the portal uploads since DevDay: `plugin.json` (Agent Plugins format) with listing, icons, 5+3 tests, demo video, release notes and translations, plus `mcp.json`. Checked against OpenAI's submission rules | Tool changes roll out after OpenAI's scans. Listing changes need a new ZIP and review, and drift says when |
76
+ | ChatGPT | **Builds the plugin ZIP** the portal uploads since DevDay: `plugin.json` (Agent Plugins format) with listing, icons, 5+3 tests, demo video, release notes and translations, plus `mcp.json` and your Agent Skills. Checked against the Agent Plugins 1.0.0 schema and OpenAI's submission rules | Tool changes, hints included, roll out after OpenAI's scans. Listing changes need a new ZIP and review, and drift says when |
76
77
  | Claude connectors | **Prepares** every field of the directory form | Tool changes are live on deploy; listing edits (tool names included) are reviewed |
77
78
  | Claude plugins | **Prepares** and checks the plugin repo | The portal tracks your branch; drift tells you a new version is waiting for review |
78
79
  | Cursor | **Prepares** and checks the plugin repo | Pinned to the commit first added; drift flags when you've moved on |
79
80
  | Muse | **Prepares** the form | Flags changes |
81
+ | Vercel Connect | **Checks** your OAuth discovery documents against Vercel's Required, Recommended and Optional tiers, and **prepares** Submit a Service: every field, the OAuth method as Connect will discover it, and the token test each OAuth method needs | Listing edits go back through review (Edit and Resubmit); tools aren't listed, so they can change freely |
80
82
  | awesome-remote-mcp-servers | The three-line entry with your Glama connector badge and auth marker, ready for the PR | |
81
83
  | Cline, Glama, LobeHub, Smithery, Docker, awesome-mcp-servers | The prefilled issue, `glama.json`, CLI command or entry | |
82
84
  | mcp.so, MCP Market, mcpservers.org, cursor.directory | `mcplane pack directories`: one sheet with every value to paste | |
@@ -89,14 +91,66 @@ Checks come from real rejections, most of them ours:
89
91
 
90
92
  - **Server**: `initialize`, `tools/list` and `ping` work. An OAuth server that initializes without a token also lists its tools without one (the half-way state leaves a new ChatGPT connector stuck on "no actions available"). Unknown methods return `-32601` (OpenAI's tool scan probes `server/discover` and gives up on a crash). The URL works with a trailing slash. CORS preflight answers. Automated clients aren't blocked by a bot filter. A TLS 1.2 client connects. DNS doesn't advertise Encrypted ClientHello, which review proxies reset without a trace in your logs.
91
93
  - **OAuth**: unauthenticated calls get `401` with `resource_metadata`. Protected-resource and authorization-server metadata resolve, and PKCE S256 is supported. With `--register`, Dynamic Client Registration is tested, including the `cursor://` and other native redirect schemes that locked out every desktop client for us.
92
- - **Tools**: every tool has a title and explicit `readOnlyHint`, `destructiveHint` and `openWorldHint`, and the hints are consistent. Tools declare `outputSchema`. Descriptions don't instruct the model. Writes aren't authorised by a token passed through the chat (Anthropic rejected exactly that). Hints follow OpenAI's current definitions: `openWorldHint` for anything that reaches the public internet (read-only web search included), `destructiveHint` for messages that can't be unsent. No catch-all request tools, no tool names over 64 characters, no sensitive inputs, no upgrade or pricing copy.
93
- - **Listing**: the icon is a square PNG of at least 512px behind a direct link, and `/favicon.ico` resolves. The privacy policy covers collection, retention and user controls. Support is a web page. Name, subtitle, one-liner, description and starter prompts fit each store's limits. Every test case uses tools the live server actually has. The ChatGPT domain challenge is served.
94
+ - **Sign-in chain**: plain GETs, the way a reviewer's browser goes: the authorization endpoint, the sign-in page it sends people to, and the identity-provider hosts that page loads. A bot challenge (`cf-mitigated: challenge`) or a 403 anywhere on it is flagged: every MCP check can pass while reviewers never reach your sign-in page. Nothing signs in and no client is registered, so an authorization server that wants a registered client stops the walk at its error page: name your sign-in pages in `server.signIn` (or `--sign-in <url>`) and they're checked too. A pass from your own network proves less than one from a cloud VM or CI.
95
+ - **Tools**: every tool has a title and explicit `readOnlyHint`, `destructiveHint` and `openWorldHint`, and the hints are consistent. Tools declare `outputSchema`. Descriptions don't instruct the model. Writes aren't authorised by a token passed through the chat (Anthropic rejected exactly that). Hints follow OpenAI's current definitions: `openWorldHint` for public or open-ended destinations (read-only web search included; connectivity alone doesn't decide it), `destructiveHint` for messages that can't be unsent and for overwriting, revoking access or deleting. No catch-all request tools, no tool names over 64 characters, no sensitive inputs, no upgrade or pricing copy.
96
+ - **Listing**: the icon is square and at least 512px behind a direct link (PNG, JPEG, WebP or SVG, measured from the bytes), and `/favicon.ico` resolves. The privacy policy covers collection, retention and user controls. Support is a web page. Name, subtitle, one-liner, description and starter prompts fit each store's limits, including the 1,024 characters the ChatGPT package allows for its root description. Every test case uses tools the live server actually has. The ChatGPT domain challenge is served.
94
97
  - **Plugin repo**: public, on GitHub, with `.claude-plugin/plugin.json`, and no root `SKILL.md` shadowing the plugin's skills. Grok plugins come from an organisation.
98
+ - **Vercel Connect**: what its [For Service Providers](https://vercel.com/docs/connect/providers) page asks for, read from your discovery documents alone (nothing is registered). Required items block: metadata served as JSON (at the path-suffixed form when the issuer has a path), `token_endpoint`, `grant_types_supported`, and OAuth and OpenID Connect documents that agree. Recommended items warn: DCR or CIMD so Connect can create the client, a `token_endpoint_auth_methods_supported` it can use, PKCE S256, refresh tokens. Optional ones are notes: protected-resource metadata, `revocation_endpoint`, `scopes_supported`. What needs a real token (`expires_in`, refresh rotation, the `https://connect.vercel.com/callback` redirect) is on the list of things to check yourself.
99
+ - **ARD**: whether your domain publishes an [Agentic Resource Discovery](https://agenticresourcediscovery.org/spec/) manifest at `/.well-known/ard.json`, whether it's valid, whether it lists this server, and whether Lighthouse 13.5 (which still reads the older `/.well-known/ai-catalog.json`) accepts it.
95
100
 
96
- `--json` for machines; exit code 1 when something blocks.
101
+ `--json` for machines; exit code 1 when something blocks. A check at level `info` is a note: it never blocks and isn't counted as something to review.
97
102
 
98
103
  Preflight also lists what no script can see and that has still cost real submissions. For example: a reviewer account a stranger can actually use (not your Google login, email already confirmed, no 2FA), and a safe target for write tools so reviewers don't post to a real timeline.
99
104
 
105
+ ## Readiness for MCP 2026-07-28
106
+
107
+ The [2026-07-28 revision](https://modelcontextprotocol.io/specification/2026-07-28/changelog) drops the `initialize` handshake for per-request metadata and `server/discover`, requires `Mcp-Method` and `Mcp-Name` headers, and deprecates Dynamic Client Registration for Client ID Metadata Documents. Preflight reports how your server meets it in its own section. These checks never block: a warning means a 2026-07-28 client would fail against you today, a note means something deprecated or optional.
108
+
109
+ ```
110
+ Readiness for MCP 2026-07-28 (never blocking)
111
+ i Speaks MCP 2026-07-28
112
+ legacy only: server/discover got HTTP 200, JSON-RPC -32601 Method not found: server/discover
113
+ ! An initialize that offers 2026-07-28 is negotiated down
114
+ initialize answered with "2026-07-28"
115
+ 2026-07-28 has no initialize handshake, so answering one with it tells the client you speak something you don't. …
116
+ i CORS allows the Mcp-Method and Mcp-Name headers
117
+ Access-Control-Allow-Headers: content-type, mcp-protocol-version, mcp-session-id, authorization
118
+ ```
119
+
120
+ - **Era**: a 2026-07-28 `server/discover` gets a `DiscoverResult` (modern), a JSON-RPC error or a 4xx (legacy, and clients that speak both fall back to `initialize`), or a crash (a warning).
121
+ - **Negotiation**: an `initialize` offering `2026-07-28` is answered with a version you implement, not echoed back.
122
+ - **Headers**: requests carrying `Mcp-Method` and `Mcp-Name` work, and your CORS preflight allows them.
123
+ - **Authorization**: the metadata's `issuer` matches the URL it was fetched from (2026-07-28 clients must refuse it otherwise), `client_id_metadata_document_supported`, and RFC 9207 `authorization_response_iss_parameter_supported`.
124
+ - **MCP Events**: whether `events/list` answers. Events is a [draft](https://github.com/modelcontextprotocol/experimental-ext-triggers-events) from the Triggers and Events Working Group, not part of 2026-07-28, so this is only ever a note.
125
+
126
+ Servers behind sign-in need `--token` for the probes; the authorization checks run from the discovery documents either way.
127
+
128
+ ## ARD manifest
129
+
130
+ `mcplane pack ard` writes `.mcplane/packs/ard/ard.json`: one entry for your server (`application/mcp-server-card+json`, pointing at your server card when you serve one), with representative queries from your prompts and tests and capabilities from your tool names. It validates against [ARD v0.91](https://agenticresourcediscovery.org/spec/) and against the older schema [Lighthouse 13.5](https://github.com/GoogleChrome/lighthouse/blob/v13.5.0/core/audits/agentic/ard-schema.js) audits. Serve it at `/.well-known/ard.json`, and either at `/.well-known/ai-catalog.json` too or from an `Agentmap:` line in robots.txt, which Lighthouse reads first. ARD is a proposal, so treat it as cheap insurance rather than a requirement.
131
+
132
+ ## Plugin evals
133
+
134
+ If a Claude Code plugin ships your server, `mcplane pack claude-eval` turns your review tests into a suite for [`claude plugin eval`](https://code.claude.com/docs/en/plugin-evals), which scores the plugin against a no-plugin baseline. Each positive test becomes a case graded on the reply, with the tools it names as indicators; each negative test a case that must not call your server. Tools answer from mocks built from your live `tools/list`, so a run needs no network or sign-in and writes nothing. The plugin and server names come from the plugin repo (or `claudePlugin` in mcplane.json).
135
+
136
+ Copy `.mcplane/packs/claude-eval/evals/` into the plugin repo, then gate pull requests on it:
137
+
138
+ ```yaml
139
+ # .github/workflows/plugin-eval.yml, in the plugin repo
140
+ on: [pull_request]
141
+ jobs:
142
+ eval:
143
+ runs-on: ubuntu-latest
144
+ steps:
145
+ - uses: actions/checkout@v4
146
+ - run: npm install -g @anthropic-ai/claude-code
147
+ - run: claude plugin eval . --trust-plugin --json results.json --threshold 0.8 --model claude-sonnet-5 --judge-model claude-haiku-4-5 --no-publish --max-cost-usd 5
148
+ env:
149
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
150
+ ```
151
+
152
+ It exits 1 when a case scores under the threshold or a case file doesn't load. Every run and every llm grader is a model call on your account.
153
+
100
154
  ## Where you're listed
101
155
 
102
156
  ```sh
@@ -119,27 +173,56 @@ A form that returned 200 is a claim; the public record is the result. `listings`
119
173
  - Cursor and Grok.
120
174
  - Glama's registry import.
121
175
  - Both awesome lists and Docker's catalog.
176
+ - Vercel Connect's directory, from the agent-readable copy of [vercel.com/connect/browse](https://vercel.com/connect/browse) (it has no feed; if that copy stops parsing, mcplane says so and sends you to the page).
122
177
 
123
178
  Coverage decays quietly, so run it on a schedule (`--ci` exits 1 on a mismatch). `fleet` runs it for every project.
124
179
 
125
180
  ## ChatGPT's plugin ZIP
126
181
 
127
- Since DevDay (September 2026) ChatGPT takes a plugin ZIP. `mcplane pack chatgpt` builds it from `mcplane.json`: `plugin.json` in the Agent Plugins format, with listing, icons, the 5 positive and 3 negative test cases, demo video, release notes and translations, plus `mcp.json`. It checks the package against OpenAI's submission rules first: name and description lengths, category, the four https URLs, at most 3 starter prompts, square icons of 48 to 4096 px, brand-colour contrast.
182
+ Since DevDay (September 2026) ChatGPT takes a plugin ZIP. `mcplane pack chatgpt` builds it from `mcplane.json`: `plugin.json` in the Agent Plugins format, with listing, icons, the 5 positive and 3 negative test cases, demo video, release notes and translations, plus `mcp.json` and any Agent Skills you bundle. It checks the package first: `plugin.json` and `mcp.json` against the [Agent Plugins 1.0.0](https://agent-plugins.org) JSON Schemas, then OpenAI's submission rules: name and description lengths (1,024 characters for the root description), category, the four https URLs, at most 3 starter prompts, square icons of 48 to 4096 px in any of PNG, JPEG, WebP or SVG, brand-colour contrast.
128
183
 
129
184
  ```json
130
185
  "chatgpt": {
131
186
  "demoVideo": "https://example.com/demo.mp4",
132
187
  "releaseNotes": "Adds hourly forecasts.",
133
188
  "capabilities": ["Search forecasts", "Compare cities"],
134
- "brandColor": "#2357C6"
189
+ "brandColor": "#2357C6",
190
+ "logo": "assets/logo.png",
191
+ "skills": "skills"
135
192
  }
136
193
  ```
137
194
 
138
- What stays with you: upload the ZIP, enter reviewer credentials and the per-tool hint justifications (drafted in `chatgpt.md`), and make the attestations. Bump `version` for every upload. Tool changes don't need a new ZIP; OpenAI's scans pick them up, and `drift` tracks extension metadata (sidebar, panels, file viewers) as part of each tool.
195
+ - **Icons** can be URLs or local files (relative to `mcplane.json`), so the package builds offline. `logo` defaults to `icon`, which stays a URL for the other stores.
196
+ - **Skills**: `skills` is a folder of skill folders, or a list of them, each with a `SKILL.md`. They go in the ZIP under `skills/` with their scripts, executable bits kept. mcplane checks what clients and OpenAI refuse: a `name` that doesn't match its folder, a description over 1,024 characters, front matter YAML can't read, a script the `SKILL.md` runs but the folder doesn't hold. Hidden files stay out. Skills are scanned at upload; `"skills": []` ships the server alone.
197
+ - **Behind sign-in**: the ZIP doesn't need your tool list, so `pack chatgpt` builds without `--token` and says what it couldn't check. `--tools <file>` takes a saved `tools/list` result instead.
198
+ - The ZIP is reproducible: entries are dated 1980-01-01 and carry Unix modes, so the same files give the same bytes.
199
+
200
+ What stays with you: upload the ZIP, enter reviewer credentials and make the attestations. Bump `version` for every upload. Tool changes don't need a new ZIP; OpenAI's scans pick them up, and `drift` tracks extension metadata (sidebar, panels, file viewers) as part of each tool.
201
+
202
+ **Annotation rules.** OpenAI's [plugin guidelines](https://developers.openai.com/plugins/plugin-guidelines) now ask only for explicit `readOnlyHint`, `destructiveHint` and `openWorldHint` booleans on every tool; its automated review checks them, and you appeal if it flags one you believe is right. Justifications are no longer required, so the pack no longer drafts them: any you wrote in `mcplane.json` become optional appeal notes. Writes count as destructive unless they only add (overwriting, cancelling, revoking access and irreversible sends are all destructive, and an undo button doesn't change that), and preflight warns when a write that overwrites, revokes or deletes says `destructiveHint: false`. `openWorldHint` is about where a tool reaches, not whether it calls an API. A hint change is a note in `drift`, not an action: OpenAI re-checks annotations itself, so `drift --ci` doesn't fail on one.
203
+
204
+ ## Demo video
205
+
206
+ ChatGPT's review asks for a video recorded in developer mode that shows the main use cases and tools, and it goes stale whenever your tools change. `mcplane demo` records it from your positive tests, the way fastlane's `snapshot` takes screenshots:
207
+
208
+ ```sh
209
+ mcplane demo --only 1,2,3
210
+ ```
211
+
212
+ On a Mac with the ChatGPT desktop app, it opens ChatGPT with a local debugging port and hides the sidebar. For each test it opens a new chat, @mentions your plugin (the developer-mode one, when two share the name), types the prompt, waits for the answer and opens the summary of tool calls. ffmpeg joins the scenes behind a title card, with each test's scenario as its caption, into `.mcplane/packs/chatgpt/demo.mp4`. Upload it where a reviewer can open it without signing in and set `chatgpt.demoVideo` to its URL.
213
+
214
+ It drives ChatGPT with your account, so:
215
+ - it lists the prompts and asks before it starts (`--yes` skips that), and marks tests that call tools which write;
216
+ - it records only while a test runs, with the sidebar (your chats and name) hidden;
217
+ - tool approvals are yours to click, unless you pass `--approve`, which clicks "Allow once" (never "Always allow");
218
+ - it quits ChatGPT afterwards so the debugging port doesn't stay open (`--keep-open` leaves it running);
219
+ - the chats stay in your history, and answers can draw on your ChatGPT memory, so watch the video before you share it.
220
+
221
+ `--plugin` names the plugin as ChatGPT shows it, `--work` records in Work mode and `--out` writes somewhere else. It needs ffmpeg (`brew install ffmpeg`). It finds its way around ChatGPT by the labels on buttons, so a ChatGPT release can break it; they're all in one table at the top of `src/demo.ts`.
139
222
 
140
223
  ## Keeping listings current
141
224
 
142
- Every store calls your live server, so fixes reach users on their own. What doesn't: listing text, tool names in Claude's listing, ChatGPT's hint justifications, registry versions and plugins pinned to a commit. Each store has different rules for these. When you record a submission, mcplane snapshots what the store is reviewing. `mcplane drift` compares your live server and `mcplane.json` against that snapshot and tells you what each store needs.
225
+ Every store calls your live server, so fixes reach users on their own. What doesn't: listing text, tool names in Claude's listing, registry versions and plugins pinned to a commit. Each store has different rules for these. When you record a submission, mcplane snapshots what the store is reviewing. `mcplane drift` compares your live server and `mcplane.json` against that snapshot and tells you what each store needs.
143
226
 
144
227
  ```sh
145
228
  mcplane submitted chatgpt --version 1.2.0 --app-id asdk_app_…
@@ -219,8 +302,8 @@ For a reverse-DNS name, set an `MCP_PRIVATE_KEY` secret and mcplane signs in wit
219
302
 
220
303
  ```
221
304
  init --url <mcp url> Create mcplane.json from your live server
222
- preflight [--store <id>] Check against every store's rejection causes (--url, --json, --verbose, --register, --token)
223
- pack <store> Write a submission pack to .mcplane/packs (or "pack directories")
305
+ preflight [--store <id>] Check against every store's rejection causes (--url, --json, --verbose, --register, --token, --sign-in)
306
+ pack <store> Write a submission pack to .mcplane/packs (or "pack directories", "pack ard", "pack claude-eval"; --tools)
224
307
  publish <store> [--yes] Publish where the store allows it; dry run without --yes
225
308
  submitted <store> Record a submission (--date, --kind, --version, --app-id, --private)
226
309
  decided <store> <outcome> approved | rejected | withdrawn
@@ -229,13 +312,14 @@ drift [--ci] What each store needs since it last saw your serve
229
312
  baseline <store> Mark an existing live listing as the drift baseline
230
313
  listings [--ci] Where you're listed, and whether what went live matches
231
314
  try Install links for every client, for you and your testers
315
+ demo Record ChatGPT's review video from your positive tests (macOS; --only, --approve, --out)
232
316
  fleet [--root <dir>] Every project at a glance (--quick, --json, --ci)
233
317
  fleet run <command...> Run a command in every project
234
318
  lanes / <lane> List lanes / run one
235
319
  mcp Run as an MCP server over stdio
236
320
  ```
237
321
 
238
- Servers behind sign-in: pass `--token` or set `MCPLANE_TOKEN` so tool checks can run.
322
+ Servers behind sign-in: pass `--token` or set `MCPLANE_TOKEN` so tool checks can run. Packs also take `--tools <file>`, a saved `tools/list` result.
239
323
 
240
324
  ## Use the checks in your own code
241
325
 
@@ -264,19 +348,25 @@ Each store can have its own listing text, the way fastlane keeps metadata per la
264
348
 
265
349
  Packs, checks, `drift` and `listings` all use the right text for each store.
266
350
 
351
+ Store fields with no shared equivalent have their own blocks: `chatgpt` for the plugin ZIP (including its `logo` and `skills`), `grok` for the marketplace entry, and `vercelConnect` for a REST API target (`apiBase`), default OAuth `scopes` and an `apiKey` connection method. For OAuth servers, `server.signIn` lists the sign-in pages your consent screen uses, for the sign-in chain check.
352
+
267
353
  ## fastlane, mapped
268
354
 
269
355
  | fastlane | mcplane |
270
356
  |---|---|
271
357
  | `precheck` | `preflight` |
272
- | `deliver` + metadata in the repo | `mcplane.json`, `pack`, `publish` |
358
+ | `deliver` + metadata in the repo | `mcplane.json`, `pack`, `publish`; for ChatGPT, one reproducible ZIP with your skills |
273
359
  | `download_metadata` | `listings`: reads what went live back and diffs it |
274
360
  | `pilot` (TestFlight testers) | `try`: install links for every client |
361
+ | `snapshot` (screenshots on every device) | `demo`: the review video, recorded in ChatGPT from your tests |
275
362
  | Fastfile lanes | `lanes` |
276
363
  | `fastlane init` | `init`, and the **onboard** prompt that writes the listing for you |
277
364
  | CI-first | `--ci` exit codes, JSON output, GitHub Action |
365
+ | `scan` (tests in CI) | `pack claude-eval`: your review tests as a `claude plugin eval` suite, scored against a no-plugin baseline |
278
366
  | New: agent-driven | the whole thing is an MCP server |
279
367
  | New: drift | knows what each store needs when your server changes |
368
+ | New: readiness | how your server meets the next MCP revision (2026-07-28), before clients require it |
369
+ | New: discovery | `pack ard`: the ARD manifest agents and Lighthouse look for |
280
370
 
281
371
  ## Contributing
282
372
 
@@ -0,0 +1,6 @@
1
+ export declare const PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
2
+ export declare const MCP_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
3
+ /** OpenAI's limit for plugin.json's root description (plugin_description_too_long). */
4
+ export declare const DESCRIPTION_MAX = 1024;
5
+ /** Every way plugin.json and mcp.json break the Agent Plugins 1.0.0 schemas; empty when both pass. */
6
+ export declare function schemaProblems(plugin: unknown, mcp: unknown): Promise<string[]>;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Agent Plugins 1.0.0, the open package format ChatGPT's portal takes: plugin.json and mcp.json
3
+ * checked against the official JSON Schemas.
4
+ *
5
+ * schemas/agent-plugins-1.0.0/ holds unmodified copies of https://agent-plugins.org/schemas/1.0.0/
6
+ * (github.com/agentplugins/agent-plugins-spec), Apache License 2.0; the licence is in the same folder.
7
+ */
8
+ import { readFile } from 'node:fs/promises';
9
+ import { Ajv2020 } from 'ajv/dist/2020.js';
10
+ export const PLUGIN_SCHEMA = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json';
11
+ export const MCP_SCHEMA = 'https://agent-plugins.org/schemas/1.0.0/mcp.schema.json';
12
+ /** OpenAI's limit for plugin.json's root description (plugin_description_too_long). */
13
+ export const DESCRIPTION_MAX = 1024;
14
+ /** The MCP server forms, by the "type" that selects each one. */
15
+ const SERVER_FORMS = { stdio: 'stdioServer', 'streamable-http': 'streamableHttpServer', sse: 'sseServer' };
16
+ let compiled;
17
+ function validators() {
18
+ compiled ??= (async () => {
19
+ const load = async (file) => JSON.parse(await readFile(new URL(`../schemas/agent-plugins-1.0.0/${file}`, import.meta.url), 'utf8'));
20
+ const [plugin, mcp] = await Promise.all([load('plugin.schema.json'), load('mcp.schema.json')]);
21
+ const ajv = new Ajv2020({ allErrors: true, strict: true, logger: false });
22
+ const v = { plugin: ajv.compile(plugin), mcp: ajv.compile(mcp) };
23
+ const forms = Object.fromEntries(Object.entries(SERVER_FORMS).map(([type, def]) => [type, ajv.getSchema(`${MCP_SCHEMA}#/$defs/${def}`)]));
24
+ return { ...v, forms };
25
+ })();
26
+ return compiled;
27
+ }
28
+ /** One readable line per schema error, e.g. `plugin.json /author: "phone" isn't allowed by the Agent Plugins schema`. */
29
+ function describe(file, e) {
30
+ const at = `${file}${e.instancePath ? ` ${e.instancePath}` : ''}`;
31
+ const p = e.params;
32
+ switch (e.keyword) {
33
+ case 'required':
34
+ return `${at}: "${p.missingProperty}" is required by the Agent Plugins schema`;
35
+ case 'additionalProperties':
36
+ return `${at}: "${p.additionalProperty}" isn't allowed by the Agent Plugins schema`;
37
+ case 'const':
38
+ return `${at} must be ${JSON.stringify(p.allowedValue)}`;
39
+ case 'pattern':
40
+ return e.instancePath === '/name' ? `${at} must be lowercase letters, digits, dots and hyphens, with no "--" or ".." (Agent Plugins schema)` : `${at} must match ${p.pattern}`;
41
+ default:
42
+ return `${at} ${e.message ?? 'is invalid'} (Agent Plugins schema)`;
43
+ }
44
+ }
45
+ /**
46
+ * Ajv reports every branch of a oneOf. An MCP server entry is checked again against the one form its
47
+ * "type" names, so a missing url reads as one problem rather than three.
48
+ */
49
+ function readable(file, errors, doc, forms) {
50
+ const oneOfs = errors.filter((e) => e.keyword === 'oneOf').map((e) => e.instancePath);
51
+ const out = [];
52
+ for (const path of oneOfs) {
53
+ const entry = path.split('/').slice(1).reduce((v, k) => v?.[k.replace(/~1/g, '/').replace(/~0/g, '~')], doc);
54
+ const form = typeof entry?.type === 'string' ? forms[entry.type] : undefined;
55
+ if (form && !form(entry))
56
+ out.push(...(form.errors ?? []).map((e) => describe(file, { ...e, instancePath: path + e.instancePath })));
57
+ else
58
+ out.push(`${file} ${path}: "type" must be stdio, streamable-http or sse (Agent Plugins schema)`);
59
+ }
60
+ for (const e of errors) {
61
+ if (e.keyword === 'oneOf' || oneOfs.some((p) => e.instancePath.startsWith(p)))
62
+ continue;
63
+ out.push(describe(file, e));
64
+ }
65
+ return [...new Set(out)];
66
+ }
67
+ /** Every way plugin.json and mcp.json break the Agent Plugins 1.0.0 schemas; empty when both pass. */
68
+ export async function schemaProblems(plugin, mcp) {
69
+ const v = await validators();
70
+ const out = [];
71
+ if (!v.plugin(plugin))
72
+ out.push(...readable('plugin.json', v.plugin.errors ?? [], plugin, v.forms));
73
+ if (!v.mcp(mcp))
74
+ out.push(...readable('mcp.json', v.mcp.errors ?? [], mcp, v.forms));
75
+ return out;
76
+ }
package/dist/cdp.d.ts ADDED
@@ -0,0 +1,49 @@
1
+ export interface Target {
2
+ id: string;
3
+ type: string;
4
+ title: string;
5
+ url: string;
6
+ webSocketDebuggerUrl?: string;
7
+ }
8
+ /** The debugging port's target list. Plain http with no keep-alive, so nothing holds the process open afterwards. */
9
+ export declare function listTargets(port: number, timeoutMs?: number): Promise<Target[]>;
10
+ /**
11
+ * The app's main window: the page target at exactly `url` (ChatGPT desktop: app://-/index.html).
12
+ * Its other windows (app://-/index.html?initialRoute=…), MCP App iframes, webviews and
13
+ * workers are separate targets and never match.
14
+ */
15
+ export declare function pickMainWindow(targets: Target[], url: string): Target | undefined;
16
+ /** One client-to-server frame: final, masked as the RFC requires of clients. */
17
+ export declare function encodeFrame(payload: Buffer, opcode?: number, mask?: Buffer): Buffer;
18
+ export interface WsFrame {
19
+ fin: boolean;
20
+ opcode: number;
21
+ payload: Buffer;
22
+ }
23
+ /** Takes every complete frame off the front of `buf`; `rest` is the partial frame still arriving. */
24
+ export declare function decodeFrames(buf: Buffer): {
25
+ frames: WsFrame[];
26
+ rest: Buffer;
27
+ };
28
+ export declare class Cdp {
29
+ private socket;
30
+ private next;
31
+ private pending;
32
+ private listeners;
33
+ private closed;
34
+ private buf;
35
+ private parts;
36
+ private constructor();
37
+ /** Opens a DevTools WebSocket URL (ws://127.0.0.1:<port>/devtools/page/<id>). */
38
+ static connect(url: string, timeoutMs?: number): Promise<Cdp>;
39
+ send<T = any>(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
40
+ /** Runs an expression in the page and returns its JSON value. */
41
+ evaluate<T>(expression: string): Promise<T>;
42
+ /** Subscribes to an event; returns the unsubscribe function. */
43
+ on(event: string, fn: (params: any) => void): () => void;
44
+ /** Sends a close frame and drops the socket without waiting for an answer. */
45
+ close(): void;
46
+ private ended;
47
+ private data;
48
+ private receive;
49
+ }