mcplane 0.5.0 → 0.5.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1
4
+
5
+ New ChatGPT checks (warnings)
6
+ - `tools.destructive-option`: a tool marked non-destructive whose input schema offers a destructive option (`burn_after_read`, delete, overwrite, revoke, purge and the like), read from property names, descriptions and enum values one level deep. ChatGPT's scan flagged exactly that, and removing the option from the advertised schema cleared it. An expiry on a tool that creates something isn't flagged.
7
+ - `tools.money-crypto`: tools that move money or handle crypto wallets, signing or stablecoins. ChatGPT's submit step has you attest to neither. Prices, pricing pages and billing links aren't flagged.
8
+
9
+ Packs and drift
10
+ - ChatGPT: the tool scan's timing and spinner, what "Needs further review" means, unclear tool names, and that a rescan doesn't update a submission in review (cancel, reconnect, resubmit). `drift` says so for a snapshot still in review.
11
+ - Muse: Meta's developer portal at muse.ai/platform, its stages and three requirements, the 5 October intake questions, and per-tool Read/Write annotations drafted from your hints until the portal takes them.
12
+
3
13
  ## 0.5.0
4
14
 
5
15
  New command
package/README.md CHANGED
@@ -77,7 +77,7 @@ npx mcplane fleet --root ~/Projects
77
77
  | Claude connectors | **Prepares** every field of the directory form | Tool changes are live on deploy; listing edits (tool names included) are reviewed |
78
78
  | Claude plugins | **Prepares** and checks the plugin repo | The portal tracks your branch; drift tells you a new version is waiting for review |
79
79
  | Cursor | **Prepares** and checks the plugin repo | Pinned to the commit first added; drift flags when you've moved on |
80
- | Muse | **Prepares** the form | Flags changes |
80
+ | Muse | **Prepares** the form for Meta's developer portal, what its three requirements ask for, and per-tool Read/Write annotations to keep alongside the connector | Flags changes; Meta hasn't published an update process |
81
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 |
82
82
  | awesome-remote-mcp-servers | The three-line entry with your Glama connector badge and auth marker, ready for the PR | |
83
83
  | Cline, Glama, LobeHub, Smithery, Docker, awesome-mcp-servers | The prefilled issue, `glama.json`, CLI command or entry | |
@@ -92,7 +92,7 @@ Checks come from real rejections, most of them ours:
92
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.
93
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.
94
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.
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, for overwriting, revoking access or deleting, and for destructive options in the input schema (ChatGPT's scan flagged a non-destructive publish tool for offering `burn_after_read`). No catch-all request tools, no tool names over 64 characters, no sensitive inputs, no upgrade or pricing copy. Tools that move money or handle crypto wallets are flagged for ChatGPT, whose submit step has you attest to neither.
96
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.
97
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
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.
@@ -199,7 +199,7 @@ Since DevDay (September 2026) ChatGPT takes a plugin ZIP. `mcplane pack chatgpt`
199
199
 
200
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
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.
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`, or offers an option that does. To keep such an option for other clients, stop advertising it in the schema you serve ChatGPT. `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
203
 
204
204
  ## Demo video
205
205
 
@@ -19,16 +19,53 @@ const BEARER_INPUT = /^(token|edit_token|update_token|secret|access_key|api_key|
19
19
  /** OpenAI's destructive effects ("deletion, overwriting, cancellation, access revocation") in the words a tool uses about itself. */
20
20
  const DESTRUCTIVE = /\b(overwrit\w*|replac\w*|revo[kc]\w*|delet\w*|cancel\w*|archiv\w*|expir\w*|passwords?|gat(?:e|es|ed|ing))\b/gi;
21
21
  const NEGATED = /\b(never|not|no|without|nothing|refuses?|cannot|can['’]t|doesn['’]t|don['’]t|won['’]t)\b(\s+\w+){0,2}\s*$/i;
22
- /** The destructive words in a tool's name, title and description, skipping negated ones ("never overwrites"). */
23
- function destructiveWords(text) {
22
+ /**
23
+ * Destructive options in an input schema. No "-ed" forms: they describe a state (include_deleted, "the deleted
24
+ * page's id"), not what the option does. Passcodes and gates stay out: a passcode on a page the call creates
25
+ * revokes nothing.
26
+ */
27
+ const DESTRUCTIVE_OPTION = /\b(burn[\s_-]+after[\s_-]+read\w*|self[\s_-]?destruct(?:s|ing)?|delet(?:e|es|ing|ion)|destroy(?:s|ing)?|overwrit(?:e|es|ing)|replac(?:e|es|ing|ement)|revok(?:e|es|ing)|revocation|purg(?:e|es|ing)|wip(?:e|es|ing)|eras(?:e|es|ing|ure)|expir(?:e|es|ing|y|ation))\b/gi;
28
+ /** Tools that make something new: a creating verb anywhere in the name (api_create_key), or first when it's also a noun (update_post isn't one). */
29
+ const CREATES = /\b(create|add|new|publish|upload|mint|generate|insert)\b|^(post|share|invite|issue|schedule|make)\b/i;
30
+ /** Crypto: wallets, chains, signing schemes and stablecoins. */
31
+ const CRYPTO = /\b(crypto(?:currenc(?:y|ies))?|wallets?|on[\s-]?chain|blockchains?|web3|eip[\s-]?(?:191|712)|personal[\s_-]?sign|eth[\s_-]sign|sign[\s_-]?typed[\s_-]?data|usdc|usdt|stablecoins?|bitcoin|ethereum|solana|x402|nfts?|seed phrases?)\b/gi;
32
+ /**
33
+ * Moving money, as a verb with what it moves. Like UPSELL, phrases only: prices, pricing pages and billing
34
+ * links aren't money movement, and neither is a purchase history or a hotel's check-out date.
35
+ */
36
+ const MONEY = /\b(transfer(?:s|ring)? (?:the |your )?(?:funds|money|payments?)|send(?:s|ing)? (?:a |the |your )?(?:money|funds|payments?)|(?:make|makes|making|complete|completes|process|processes|processing|submit|submits|execute|executes) (?:a |the )?(?:payments?|purchases?)|pay(?:s|ing)? (?:an? |the |your )?(?:invoices?|bills?)|charg(?:e|es|ing) (?:a |the |their |your )?(?:cards?|customers?)|refund(?:s|ing)? (?:an? |the |their |your )?(?:customers?|orders?|payments?|charges?)|issu(?:e|es|ing) (?:a )?refunds?|(?:withdraw|deposit)(?:s|ing)? (?:funds|money)|(?:purchas(?:e|es|ing)|buy(?:s|ing)?) (?:an?|the|this|it)|checkouts?(?![\s_-]*(?:date|time|day)))\b/gi;
37
+ /** The words a pattern finds in text, skipping negated ones ("never overwrites"). */
38
+ function mentions(text, re) {
24
39
  const found = new Set();
25
- for (const m of text.matchAll(DESTRUCTIVE)) {
40
+ for (const m of text.matchAll(re)) {
26
41
  if (NEGATED.test(text.slice(Math.max(0, m.index - 40), m.index)))
27
42
  continue;
28
- found.add(m[1].toLowerCase());
43
+ found.add(m[0].toLowerCase().replace(/\s+/g, ' '));
29
44
  }
30
45
  return [...found];
31
46
  }
47
+ /** The destructive words in a tool's name, title and description. */
48
+ const destructiveWords = (text) => mentions(text, DESTRUCTIVE);
49
+ /** A name as words: set_password and setPassword both read "set password". */
50
+ const words = (name) => name.replace(/[_-]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2');
51
+ /** A tool's input properties and the ones a level down (an object's properties, an array's items), by path. */
52
+ function inputProps(t) {
53
+ return Object.entries(t.inputSchema?.properties ?? {}).flatMap(([k, v]) => {
54
+ const p = (v ?? {});
55
+ const kids = (p.properties ?? p.items?.properties ?? {});
56
+ return [[k, p], ...Object.entries(kids).map(([c, cv]) => [`${k}.${c}`, (cv ?? {})])];
57
+ });
58
+ }
59
+ /**
60
+ * The destructive words in what a property says about itself: its name, string enum values ("overwrite") and
61
+ * description. "Replace" counts in names and values only: descriptions use it loosely ("until a refresh replaces them").
62
+ */
63
+ function optionWords(path, p) {
64
+ const values = Array.isArray(p.enum) ? p.enum.filter((e) => typeof e === 'string').join(' ') : '';
65
+ const named = mentions(`${words(path.split('.').pop())} ${values}`, DESTRUCTIVE_OPTION);
66
+ const described = mentions(typeof p.description === 'string' ? p.description : '', DESTRUCTIVE_OPTION).filter((w) => !w.startsWith('replac'));
67
+ return [...new Set([...named, ...described])];
68
+ }
32
69
  export function toolChecks(tools) {
33
70
  if (!tools.length)
34
71
  return [];
@@ -114,8 +151,8 @@ export function toolChecks(tools) {
114
151
  const destroys = tools.flatMap((t) => {
115
152
  if (t.annotations?.readOnlyHint !== false || t.annotations?.destructiveHint !== false)
116
153
  return [];
117
- const words = destructiveWords(`${t.name.replace(/[_-]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2')} ${t.title ?? ''} ${t.description ?? ''}`);
118
- return words.length ? [`${t.name} (${words.join(', ')})`] : [];
154
+ const found = destructiveWords(`${words(t.name)} ${t.title ?? ''} ${t.description ?? ''}`);
155
+ return found.length ? [`${t.name} (${found.join(', ')})`] : [];
119
156
  });
120
157
  if (destroys.length) {
121
158
  checks.push({
@@ -127,6 +164,51 @@ export function toolChecks(tools) {
127
164
  stores: ['chatgpt'],
128
165
  });
129
166
  }
167
+ // ChatGPT's scan reads the input schema too: it flagged Stacktree's publish_html, marked non-destructive, for
168
+ // offering burn_after_read, and a rescan cleared it once the option was gone from the advertised schema (Oct 2026).
169
+ // Conservative: property names, descriptions and enum values, one level deep. Expiry is the trade-off: it only
170
+ // counts on tools that change something that exists. On a tool that creates (a publish's lifetime, a share
171
+ // link's expires_in) it bounds what the call itself adds, and publish_html kept expires_in_hours through
172
+ // that rescan. An expiry on an update tool still counts: it can take a live page down.
173
+ const options = tools.flatMap((t) => {
174
+ if (t.annotations?.readOnlyHint !== false || t.annotations?.destructiveHint !== false)
175
+ return [];
176
+ const creates = CREATES.test(words(t.name));
177
+ return inputProps(t).flatMap(([path, p]) => {
178
+ const found = optionWords(path, p).filter((w) => !(creates && w.startsWith('expir')));
179
+ return found.length ? [`${t.name}.${path} (${found.join(', ')})`] : [];
180
+ });
181
+ });
182
+ if (options.length) {
183
+ checks.push({
184
+ id: 'tools.destructive-option',
185
+ level: 'warn',
186
+ title: 'destructiveHint covers the options a tool’s schema offers',
187
+ detail: `${options.join('; ')}: destructiveHint is false, but the schema offers an option that deletes, overwrites, revokes or expires`,
188
+ fix: 'Set destructiveHint to true, or stop advertising the option in the schema you serve ChatGPT; your server can keep honouring it for older clients that still send it. ChatGPT’s scan flagged a publish tool marked non-destructive whose schema offered burn_after_read, and removing the option from the advertised schema cleared the flag on rescan.',
189
+ stores: ['chatgpt'],
190
+ });
191
+ }
192
+ // Before submitting, ChatGPT has the developer attest that the app involves no money or crypto.
193
+ // Moving money is a write, so read-only tools are judged by what they're about (name, title, property names) and a
194
+ // passing mention in their description doesn't count ("inflation, GDP, crypto and more").
195
+ const money = tools.flatMap((t) => {
196
+ const about = `${words(t.name)} ${t.title ?? ''} ${t.annotations?.title ?? ''} ${inputProps(t).map(([path]) => words(path.split('.').pop())).join(' ')}`;
197
+ const readOnly = t.annotations?.readOnlyHint === true;
198
+ const text = readOnly ? about : `${about} ${t.description ?? ''}`;
199
+ const found = [...mentions(text, CRYPTO), ...(readOnly ? [] : mentions(text, MONEY))];
200
+ return found.length ? [`${t.name} (${found.join(', ')})`] : [];
201
+ });
202
+ if (money.length) {
203
+ checks.push({
204
+ id: 'tools.money-crypto',
205
+ level: 'warn',
206
+ title: 'Tools don’t move money or handle crypto',
207
+ detail: `${money.join('; ')}: the tool seems to move money or handle crypto`,
208
+ fix: 'ChatGPT’s final submit step has you tick an attestation that the app involves no money or crypto. Remove the tool, or hide it from ChatGPT connections only: leave it out of their tools/list and refuse calls to it. Stacktree serves a per-client tool profile, and a client counts as ChatGPT when every OAuth redirect URI it registered is on chatgpt.com or openai.com, or its client name is "ChatGPT". "Every", not "any": some clients register a claude.ai callback beside a chatgpt.com one.',
209
+ stores: ['chatgpt'],
210
+ });
211
+ }
130
212
  const long = tools.filter((t) => t.name.length > 64);
131
213
  if (long.length)
132
214
  checks.push({ id: 'tools.name-length', level: 'fail', title: 'Tool names are 64 characters or fewer', detail: long.map((t) => t.name).join(', '), fix: 'Anthropic’s review criteria cap tool names at 64 characters.', stores: ['claude-connectors', 'claude-plugins'] });
package/dist/core.js CHANGED
@@ -26,7 +26,7 @@ function reminders(m, stores) {
26
26
  r.push('ChatGPT needs a demo video recorded in developer mode that shows the tools your test cases use. Re-record it whenever tools change ("mcplane demo" records it on a Mac with ChatGPT desktop).');
27
27
  r.push('After "Submit for Review" the ChatGPT portal shows nothing for about 20 seconds. Confirm the status on the plugins list, not the button.');
28
28
  r.push('OpenAI no longer asks for hint justifications: its automated review checks each tool’s hints. If it flags one you believe is right, appeal with an explanation.');
29
- r.push('ChatGPT allows one version in review at a time. To change a submission, Cancel Review and resubmit the same draft.');
29
+ r.push('ChatGPT allows one version in review at a time, and a rescan doesn’t update the tools it was submitted with. To change a submission, Cancel Review (back to Draft), reconnect if tools changed so ChatGPT rediscovers them, and resubmit.');
30
30
  }
31
31
  if (stores.includes('claude-connectors') || stores.includes('claude-plugins')) {
32
32
  if (stores.includes('claude-connectors'))
package/dist/drift.js CHANGED
@@ -162,16 +162,19 @@ export async function drift(m, stores, opts = {}, dir = process.cwd()) {
162
162
  const since = `since ${snap.state === 'live' ? 'the live version' : 'your submission'}${snap.version ? ` (v${snap.version})` : ''} on ${snap.takenOn}`;
163
163
  if (store === 'chatgpt') {
164
164
  // OpenAI rescans published servers: removals apply at once, new and changed tools after automated checks.
165
- // Listing text is part of the version and needs a new one.
165
+ // A version in review keeps the tool snapshot it was submitted with. Listing text is part of the version and needs a new one.
166
166
  const hintChanges = d.changed.filter((c) => c.fields.some((f) => f.includes('Hint')));
167
+ const inReview = snap.state === 'submitted';
167
168
  if (toolChange)
168
169
  items.push({
169
170
  store,
170
171
  level: 'info',
171
172
  what: `Tools changed ${since}: ${summary}`,
172
- todo: `No resubmission needed for tools: OpenAI rescans your server, drops removed tools at once and switches new or changed ones over after they pass automated checks (the old definition stays live until then).${d.added.length || d.removed.length ? ' Update the test cases and demo video in your next version.' : ''}`,
173
+ todo: inReview
174
+ ? 'A rescan doesn’t update a submission already in review: it keeps the tools it was submitted with. For review to see these changes, cancel the review (back to Draft), reconnect so ChatGPT rediscovers the tools, then resubmit. The scan takes about 4 to 6 minutes; reload the page to see the result.'
175
+ : `No resubmission needed for tools: OpenAI rescans your server, drops removed tools at once and switches new or changed ones over after they pass automated checks (the old definition stays live until then).${d.added.length || d.removed.length ? ' Update the test cases and demo video in your next version.' : ''}`,
173
176
  });
174
- if (hintChanges.length)
177
+ if (hintChanges.length && !inReview)
175
178
  items.push({ store, level: 'info', what: `Hints changed: ${hintChanges.map((c) => c.tool).join(', ')}`, todo: 'Nothing to resubmit: OpenAI re-checks changed annotations automatically and no longer asks for justifications. If its review flags a value you believe is right, appeal with an explanation.' });
176
179
  if (listingChange.length)
177
180
  items.push({ store, level: 'action', what: `Listing text changed (${listingChange.join(', ')})`, todo: 'Listing changes need a new version, review and publication: "mcplane pack chatgpt", import it into a new version, submit.' });
@@ -198,7 +201,7 @@ export async function drift(m, stores, opts = {}, dir = process.cwd()) {
198
201
  items.push({ store, level: 'info', what: `Grok serves ${pinned.slice(0, 7)}; your repo is at ${head.slice(0, 7)}`, todo: 'xAI bumps pins daily. If it’s still behind tomorrow, "mcplane publish grok" opens a pin-bump pull request.' });
199
202
  }
200
203
  if (store === 'muse' && (toolChange || listingChange.length)) {
201
- items.push({ store, level: 'info', what: `Changed ${since}: ${[summary, listingChange.length && `listing ${listingChange.join(', ')}`].filter(Boolean).join('; ')}`, todo: 'Meta hasn’t published an update process yet. Update the listing in muse.ai/platform if it shows old details.' });
204
+ items.push({ store, level: 'info', what: `Changed ${since}: ${[summary, listingChange.length && `listing ${listingChange.join(', ')}`].filter(Boolean).join('; ')}`, todo: 'Meta hasn’t published an update process yet. Open the submission in muse.ai/platform, which shows its stage (Submitted, then In review) and three requirements, and update anything that shows old details. If tools changed, update the Read, Write and Sensitive annotations you keep alongside the connector too.' });
202
205
  }
203
206
  if (store === 'mcp-registry' && (toolChange || listingChange.length)) {
204
207
  items.push({ store, level: 'action', what: `Changed ${since}`, todo: 'Bump the version and run "mcplane publish mcp-registry".' });
package/dist/packs.d.ts CHANGED
@@ -60,6 +60,11 @@ export declare function claudeEvalPack(m: Manifest, opts?: PackOptions): Promise
60
60
  /** Every pack by name: a store with a form, or one of the extras. */
61
61
  export declare const PACKS: readonly ["chatgpt", "claude-connectors", "vercel-connect", "cursor", "muse", "directories", "ard", "claude-eval"];
62
62
  export declare function packFor(m: Manifest, name: string, opts?: PackOptions): Promise<Pack>;
63
+ /**
64
+ * Muse: the values for Meta's developer portal (muse.ai/platform, live since 2 Oct 2026), what each
65
+ * submission's three requirements ask for, and per-tool annotations to keep until the portal takes them.
66
+ */
67
+ export declare function musePack(m: Manifest, opts?: PackOptions): Promise<Pack>;
63
68
  export declare function simplePack(m: Manifest, store: StoreId): Promise<Pack>;
64
69
  /** The long tail: directories that only take a web form. One sheet with every value to paste. */
65
70
  export declare function directoriesPack(m: Manifest): Pack;
package/dist/packs.js CHANGED
@@ -295,6 +295,9 @@ It fills the listing, icons, test cases, demo video, release notes and translati
295
295
  ## 2. Resolve findings
296
296
  Open **Metadata & Skills** and **MCPs**, wait for the checks, fix anything listed (Copy issues is handy), and upload again if the package changes.
297
297
  Domain verification: serve the token it gives you as plain text at ${new URL(m.server.url).origin}/.well-known/openai-apps-challenge, and keep it there.
298
+ - The tool scan takes about 4 to 6 minutes, and the page's spinner never finishes: reload to see the result.
299
+ - "Needs further review" sends a tool to human review. It isn't an error, and the automated judge reshuffles which tools land there between scans.
300
+ - Name tools by what they do. The scan flags names it can't understand: Stacktree's set_agentation, named after a product, was flagged "unclear name".
298
301
 
299
302
  ## 3. What only you can enter
300
303
  - **Review details:** reviewer credentials${m.server.auth === 'oauth' ? ` (${m.reviewerAccess ?? 'an email-and-password account with no 2FA, SMS or email confirmation, seeded for every test case'})` : ' (none needed: no sign-in)'}. Credentials never go in the ZIP.
@@ -305,7 +308,8 @@ Hints need no justification: OpenAI's automated review checks readOnlyHint, dest
305
308
  ## Traps
306
309
  - One review can be active per plugin. To replace a package in review, cancel the review first.
307
310
  - "Submit for review" can show nothing for a while; confirm the status on the Plugins page.
308
- - Tool changes don't need a new package: OpenAI's scans pick them up. Listing changes do.
311
+ - Tool changes don't need a new package: OpenAI's scans pick them up once you're published. Listing changes do.
312
+ - A rescan doesn't update a submission already in review: it keeps the tools it was submitted with. To change them, cancel the review (back to Draft), reconnect so ChatGPT rediscovers the tools, then resubmit.
309
313
  - The package takes icons up to 5 MiB, but developer mode's own icon upload (when you add the server by hand to record the demo) takes 10 KB at most.
310
314
 
311
315
  When it's in: \`mcplane submitted chatgpt --version ${m.version ?? '1.0.0'} --app-id <asdk_app_… from the URL>\`
@@ -637,8 +641,48 @@ export async function packFor(m, name, opts = {}) {
637
641
  return claudePack(m, opts);
638
642
  if (name === 'vercel-connect')
639
643
  return vercelConnectPack(m);
644
+ if (name === 'muse')
645
+ return musePack(m, opts);
640
646
  return simplePack(m, name);
641
647
  }
648
+ /**
649
+ * Muse: the values for Meta's developer portal (muse.ai/platform, live since 2 Oct 2026), what each
650
+ * submission's three requirements ask for, and per-tool annotations to keep until the portal takes them.
651
+ */
652
+ export async function musePack(m, opts = {}) {
653
+ m = forStore(m, 'muse');
654
+ const listed = await toolsFor(m, opts);
655
+ const oauth = m.server.auth === 'oauth';
656
+ const todo = listed.tools ? [] : [withoutTools(listed.why, 'the per-tool annotations')];
657
+ const annotations = listed.tools
658
+ ? listed.tools.map((t) => `- ${t.name}: ${t.annotations?.readOnlyHint === true ? 'Read' : 'Write'}`).join('\n') || 'No tools listed.'
659
+ : 'Unknown without the tool list (pass --token or --tools).';
660
+ const md = `# Muse connectors: ${m.title}
661
+
662
+ Submit at muse.ai/platform, Meta's developer portal (launched 2 October 2026), choosing "Existing MCP". Meta's Jake Levine mentioned about 3,000 connector submissions when he announced it on X, so expect a queue.
663
+
664
+ - MCP server URL: ${m.server.url}
665
+ - Auth: ${oauth ? 'OAuth (PKCE)' : 'None'}
666
+ - Name: ${m.title}
667
+ - Description: ${m.oneLiner ?? m.description ?? 'MISSING'}
668
+ - Privacy: ${m.links?.privacy ?? 'MISSING'}
669
+
670
+ ## After you submit
671
+ Each submission shows its stage, Submitted then In review, and three requirements:
672
+ - **Business verification:** a code sent by email. Each "Verify email" click sends a new code and invalidates the last one, so click once and don't leave the code box until the email arrives.
673
+ - **Questionnaire:** your company details and where data is stored.
674
+ - **Integration credentials:** ${oauth ? 'a test API key or similar, so Meta can reach the tools behind your sign-in.' : 'none required: for an authless MCP, the portal says so.'}
675
+
676
+ On 5 October Meta emailed developers asking them to "complete the additional intake questions", so check the portal for anything still open.
677
+
678
+ ## Tool annotations
679
+ Meta's docs ask for a Read, Write or Sensitive annotation on each tool. The portal's field for them is marked as coming soon, so document them alongside the connector for now. Drafted from your readOnlyHint values; Sensitive is your call against Meta's docs:
680
+ ${annotations}
681
+
682
+ Meta hasn't published an update process yet. When it's in: \`mcplane submitted muse\`.
683
+ `;
684
+ return { files: [{ path: 'muse.md', content: md }], problems: [], todo };
685
+ }
642
686
  export async function simplePack(m, store) {
643
687
  m = forStore(m, store);
644
688
  const problems = [];
@@ -649,7 +693,7 @@ export async function simplePack(m, store) {
649
693
  md = `# Cursor Marketplace: ${m.title}\n\nSubmit at cursor.com/marketplace/publish.\n\n- Repository: ${m.repository ?? 'MISSING'} (public, with a Cursor plugin manifest)\n- Name: ${m.title}\n- Description: ${m.oneLiner ?? m.description ?? 'MISSING'}\n- Website: ${m.links?.website ?? ''}\n\nEvery plugin is reviewed by hand. When it's in: \`mcplane submitted cursor\`. Review Times closes the report itself when your plugin appears in the marketplace.\n`;
650
694
  }
651
695
  else if (store === 'muse') {
652
- md = `# Muse connectors: ${m.title}\n\nSubmit at muse.ai/platform, choosing "Existing MCP".\n\n- MCP server URL: ${m.server.url}\n- Auth: ${m.server.auth === 'oauth' ? 'OAuth (PKCE)' : 'None'}\n- Name: ${m.title}\n- Description: ${m.oneLiner ?? m.description ?? 'MISSING'}\n- Privacy: ${m.links?.privacy ?? 'MISSING'}\n\nMeta onboards in waves and gives no status yet. When it's in: \`mcplane submitted muse\`.\n`;
696
+ return musePack(m);
653
697
  }
654
698
  else {
655
699
  throw new Error(`No pack for ${store} yet. Packs: ${PACKS.join(', ')}.`);
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.0";
1
+ export declare const VERSION = "0.5.1";
package/dist/version.js CHANGED
@@ -1 +1 @@
1
- export const VERSION = '0.5.0';
1
+ export const VERSION = '0.5.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcplane",
3
- "version": "0.5.0",
3
+ "version": "0.5.1",
4
4
  "description": "fastlane for MCP servers: preflight against every store's rejection causes, publish where you can, prepare the rest, track every review.",
5
5
  "type": "module",
6
6
  "bin": {