mcplane 0.5.0 → 0.6.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.
- package/CHANGELOG.md +28 -0
- package/README.md +7 -5
- package/dist/checks/readiness.js +1 -1
- package/dist/checks/server.js +32 -0
- package/dist/checks/tools.js +119 -8
- package/dist/core.js +1 -1
- package/dist/drift.js +7 -4
- package/dist/packs.d.ts +5 -0
- package/dist/packs.js +49 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0
|
|
4
|
+
|
|
5
|
+
New ChatGPT checks
|
|
6
|
+
- `server.widget-domain` (blocks): tools with a UI name resources (`_meta.ui.resourceUri` or `openai/outputTemplate`), and each one must set a widget domain, which OpenAI requires before you can submit a plugin with UI. Preflight now reads those resources; one that needs sign-in is skipped unless you pass `--token`. `openai/widgetDomain` and `ui.domain` both count.
|
|
7
|
+
- `tools.file-viewer-wording` (warning): a tool with a file entrypoint described as a command ("Opens an HTML file…"). The model then calls it with a file path and ChatGPT refuses the read ("MCP app cannot read resource outside its widget scope"), as Stacktree's viewer did on 6 October. Say the user opens the file and name the tool for content the model can read.
|
|
8
|
+
- `tools.sensitive-inputs`: an input called `password` that's really a passcode for something the tool makes gets the advice to call it `passcode`. ChatGPT's approval panel files `password` under "Account passwords".
|
|
9
|
+
|
|
10
|
+
ChatGPT pack
|
|
11
|
+
- Upload a new version from the plugin's own page (**Upload new version**, or **Upload plugin to make changes**). The Plugins page's upload area creates a second plugin even when the package name matches, which is how Stacktree ended up with two.
|
|
12
|
+
- Published tool changes don't arrive on cue: on 6 October a Rescan left a published plugin's tool table on its reviewed list, and a published plugin's MCP card has no Reconnect. Check the MCPs tab lists new tools before submitting test cases that use them.
|
|
13
|
+
- Don't fix a duplicate by deleting and uploading again: deleting can reserve the plugin's name (openai/codex#39508).
|
|
14
|
+
|
|
15
|
+
Fewer false alarms
|
|
16
|
+
- `tools.destructive-overwrite` skips verbs whose subject is a sibling tool ("update_site replaces a page in place" in another tool's description) and, like `tools.destructive-option`, expiry on a tool that creates. Stacktree's publish_html, which passed ChatGPT's scan, no longer warns.
|
|
17
|
+
|
|
18
|
+
README
|
|
19
|
+
- 2026-07-28 cache hints: a `tools/list` that differs by caller should be `cacheScope: "private"`.
|
|
20
|
+
|
|
21
|
+
## 0.5.1
|
|
22
|
+
|
|
23
|
+
New ChatGPT checks (warnings)
|
|
24
|
+
- `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.
|
|
25
|
+
- `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.
|
|
26
|
+
|
|
27
|
+
Packs and drift
|
|
28
|
+
- 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.
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
3
31
|
## 0.5.0
|
|
4
32
|
|
|
5
33
|
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 | |
|
|
@@ -89,10 +89,10 @@ npx mcplane fleet --root ~/Projects
|
|
|
89
89
|
|
|
90
90
|
Checks come from real rejections, most of them ours:
|
|
91
91
|
|
|
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.
|
|
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. Tools with a UI name resources that set a widget domain, which OpenAI requires before you can submit a plugin with UI.
|
|
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
|
|
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. File viewers say the user opens the file: described as a command, the model calls one with a path and ChatGPT refuses the read.
|
|
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.
|
|
@@ -125,6 +125,8 @@ Readiness for MCP 2026-07-28 (never blocking)
|
|
|
125
125
|
|
|
126
126
|
Servers behind sign-in need `--token` for the probes; the authorization checks run from the discovery documents either way.
|
|
127
127
|
|
|
128
|
+
One thing preflight can't see: 2026-07-28 results carry cache hints, and `tools/list` is one of the results that must. If your list differs by caller, for example tools you hide from ChatGPT connections, mark it `cacheScope: "private"`, or a shared cache can hand one client another's list.
|
|
129
|
+
|
|
128
130
|
## ARD manifest
|
|
129
131
|
|
|
130
132
|
`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.
|
|
@@ -197,9 +199,9 @@ Since DevDay (September 2026) ChatGPT takes a plugin ZIP. `mcplane pack chatgpt`
|
|
|
197
199
|
- **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
200
|
- The ZIP is reproducible: entries are dated 1980-01-01 and carry Unix modes, so the same files give the same bytes.
|
|
199
201
|
|
|
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
|
|
202
|
+
What stays with you: upload the ZIP, enter reviewer credentials and make the attestations. Bump `version` for every upload, and upload a new version from the plugin's own page: the Plugins page's upload area makes a second plugin. Tool changes don't need a new ZIP; OpenAI says its scans pick them up after automated checks, though not on cue (check the MCPs tab before you submit test cases that use new tools), and `drift` tracks extension metadata (sidebar, panels, file viewers) as part of each tool.
|
|
201
203
|
|
|
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
|
|
204
|
+
**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
205
|
|
|
204
206
|
## Demo video
|
|
205
207
|
|
package/dist/checks/readiness.js
CHANGED
|
@@ -106,7 +106,7 @@ export function initializeCheck(r, era) {
|
|
|
106
106
|
export function modernHeaderCheck(r) {
|
|
107
107
|
const title = 'Accepts the Mcp-Method and Mcp-Name headers';
|
|
108
108
|
return r && r.status < 300 && r.body?.result
|
|
109
|
-
? check('headers', 'pass', title, { detail: 'a 2026-07-28 tools/list with Mcp-Method works (Mcp-Name only goes on tools/call, resources/read and prompts/get, which
|
|
109
|
+
? check('headers', 'pass', title, { detail: 'a 2026-07-28 tools/list with Mcp-Method works (Mcp-Name only goes on tools/call, resources/read and prompts/get, which this probe doesn’t send)' })
|
|
110
110
|
: check('headers', 'warn', title, { detail: `a 2026-07-28 tools/list with Mcp-Method got ${said(r)}`, fix: 'Mcp-Method is required on every 2026-07-28 POST and must match the body’s method.' });
|
|
111
111
|
}
|
|
112
112
|
/** Legacy servers: the same tools/list with and without the new headers should get the same answer. */
|
package/dist/checks/server.js
CHANGED
|
@@ -60,6 +60,38 @@ export async function serverChecks(url, auth, token, extra = []) {
|
|
|
60
60
|
checks.push(tools.length
|
|
61
61
|
? { id: 'server.tools', level: 'pass', title: 'tools/list returns tools', detail: tools.map((t) => t.name).join(', ') }
|
|
62
62
|
: { id: 'server.tools', level: 'fail', title: 'tools/list returns tools', detail: `HTTP ${list?.status ?? 'error'}`, fix: 'Stores scan tools/list; with no tools there is nothing to list.' });
|
|
63
|
+
// Tools with UI name an MCP App resource (_meta.ui.resourceUri, or openai/outputTemplate). OpenAI requires a
|
|
64
|
+
// dedicated widget domain on each one when you submit a plugin with UI, unique per plugin. Set it as
|
|
65
|
+
// _meta["openai/widgetDomain"] on the resource: ui.domain also counts, but its format differs by host (Claude
|
|
66
|
+
// expects its own hash-derived domain there).
|
|
67
|
+
const uiUris = [...new Set(tools.flatMap((t) => {
|
|
68
|
+
const meta = (t._meta ?? {});
|
|
69
|
+
return [meta.ui?.resourceUri, meta['openai/outputTemplate']].filter((u) => typeof u === 'string');
|
|
70
|
+
}))];
|
|
71
|
+
if (uiUris.length) {
|
|
72
|
+
const missing = [];
|
|
73
|
+
const unread = [];
|
|
74
|
+
let denied = 0;
|
|
75
|
+
for (const uri of uiUris) {
|
|
76
|
+
const read = await client.request('resources/read', { uri }).catch(() => null);
|
|
77
|
+
if (read?.status === 401 || read?.status === 403) {
|
|
78
|
+
denied++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const content = read?.body?.result?.contents?.[0];
|
|
82
|
+
if (!content)
|
|
83
|
+
unread.push(uri);
|
|
84
|
+
else if (!content._meta?.['openai/widgetDomain'] && !content._meta?.ui?.domain)
|
|
85
|
+
missing.push(uri);
|
|
86
|
+
}
|
|
87
|
+
checks.push(denied === uiUris.length
|
|
88
|
+
? { id: 'server.widget-domain', level: 'skip', title: 'UI resources set a widget domain', detail: 'resources/read needs sign-in; run preflight with --token to check', stores: ['chatgpt'] }
|
|
89
|
+
: missing.length
|
|
90
|
+
? { id: 'server.widget-domain', level: 'fail', title: 'UI resources set a widget domain', detail: missing.join(', '), fix: 'Set _meta["openai/widgetDomain"] (for example "https://yourdomain.com") on each UI resource. OpenAI requires a dedicated, per-plugin widget domain to submit a plugin with UI.', stores: ['chatgpt'] }
|
|
91
|
+
: unread.length === uiUris.length
|
|
92
|
+
? { id: 'server.widget-domain', level: 'warn', title: 'UI resources set a widget domain', detail: `resources/read gave nothing for ${unread.join(', ')}`, fix: 'Tools name UI resources that resources/read does not return. ChatGPT reads them the same way to show the UI.', stores: ['chatgpt'] }
|
|
93
|
+
: { id: 'server.widget-domain', level: 'pass', title: 'UI resources set a widget domain', detail: uiUris.filter((u) => !unread.includes(u)).join(', '), stores: ['chatgpt'] });
|
|
94
|
+
}
|
|
63
95
|
const ping = await client.request('ping').catch(() => null);
|
|
64
96
|
checks.push({
|
|
65
97
|
id: 'server.ping',
|
package/dist/checks/tools.js
CHANGED
|
@@ -19,16 +19,58 @@ 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
|
-
/**
|
|
23
|
-
|
|
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
|
+
/**
|
|
38
|
+
* The words a pattern finds in text, skipping negated ones ("never overwrites") and ones whose subject is another
|
|
39
|
+
* tool: "(update_site replaces a page in place)" describes update_site, not the tool being checked.
|
|
40
|
+
*/
|
|
41
|
+
function mentions(text, re, others = []) {
|
|
24
42
|
const found = new Set();
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
const subject = others.length ? new RegExp(`\\b(${others.map((n) => n.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')})\\b(\\s+\\w+){0,2}\\s*$`, 'i') : null;
|
|
44
|
+
for (const m of text.matchAll(re)) {
|
|
45
|
+
const before = text.slice(Math.max(0, m.index - 40), m.index);
|
|
46
|
+
if (NEGATED.test(before) || subject?.test(before))
|
|
27
47
|
continue;
|
|
28
|
-
found.add(m[
|
|
48
|
+
found.add(m[0].toLowerCase().replace(/\s+/g, ' '));
|
|
29
49
|
}
|
|
30
50
|
return [...found];
|
|
31
51
|
}
|
|
52
|
+
/** The destructive words in a tool's name, title and description, given the names of its sibling tools. */
|
|
53
|
+
const destructiveWords = (text, others = []) => mentions(text, DESTRUCTIVE, others);
|
|
54
|
+
/** A name as words: set_password and setPassword both read "set password". */
|
|
55
|
+
const words = (name) => name.replace(/[_-]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2');
|
|
56
|
+
/** A tool's input properties and the ones a level down (an object's properties, an array's items), by path. */
|
|
57
|
+
function inputProps(t) {
|
|
58
|
+
return Object.entries(t.inputSchema?.properties ?? {}).flatMap(([k, v]) => {
|
|
59
|
+
const p = (v ?? {});
|
|
60
|
+
const kids = (p.properties ?? p.items?.properties ?? {});
|
|
61
|
+
return [[k, p], ...Object.entries(kids).map(([c, cv]) => [`${k}.${c}`, (cv ?? {})])];
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The destructive words in what a property says about itself: its name, string enum values ("overwrite") and
|
|
66
|
+
* description. "Replace" counts in names and values only: descriptions use it loosely ("until a refresh replaces them").
|
|
67
|
+
*/
|
|
68
|
+
function optionWords(path, p) {
|
|
69
|
+
const values = Array.isArray(p.enum) ? p.enum.filter((e) => typeof e === 'string').join(' ') : '';
|
|
70
|
+
const named = mentions(`${words(path.split('.').pop())} ${values}`, DESTRUCTIVE_OPTION);
|
|
71
|
+
const described = mentions(typeof p.description === 'string' ? p.description : '', DESTRUCTIVE_OPTION).filter((w) => !w.startsWith('replac'));
|
|
72
|
+
return [...new Set([...named, ...described])];
|
|
73
|
+
}
|
|
32
74
|
export function toolChecks(tools) {
|
|
33
75
|
if (!tools.length)
|
|
34
76
|
return [];
|
|
@@ -111,11 +153,15 @@ export function toolChecks(tools) {
|
|
|
111
153
|
}
|
|
112
154
|
// The same definition covers overwriting, revoking access and deleting; an undo doesn't change that.
|
|
113
155
|
// Conservative: explicit write tools whose own words say so, negated mentions ("never overwrites") excluded.
|
|
156
|
+
// Expiry follows the destructive-option rule below: on a tool that creates, it bounds what the call adds.
|
|
157
|
+
// Stacktree's publish_html ("plan-aware expiry", "update_site replaces a page in place") passed ChatGPT's scan.
|
|
114
158
|
const destroys = tools.flatMap((t) => {
|
|
115
159
|
if (t.annotations?.readOnlyHint !== false || t.annotations?.destructiveHint !== false)
|
|
116
160
|
return [];
|
|
117
|
-
const
|
|
118
|
-
|
|
161
|
+
const creates = CREATES.test(words(t.name));
|
|
162
|
+
const others = tools.filter((o) => o !== t).map((o) => o.name);
|
|
163
|
+
const found = destructiveWords(`${words(t.name)} ${t.title ?? ''} ${t.description ?? ''}`, others).filter((w) => !(creates && w.startsWith('expir')));
|
|
164
|
+
return found.length ? [`${t.name} (${found.join(', ')})`] : [];
|
|
119
165
|
});
|
|
120
166
|
if (destroys.length) {
|
|
121
167
|
checks.push({
|
|
@@ -127,6 +173,51 @@ export function toolChecks(tools) {
|
|
|
127
173
|
stores: ['chatgpt'],
|
|
128
174
|
});
|
|
129
175
|
}
|
|
176
|
+
// ChatGPT's scan reads the input schema too: it flagged Stacktree's publish_html, marked non-destructive, for
|
|
177
|
+
// offering burn_after_read, and a rescan cleared it once the option was gone from the advertised schema (Oct 2026).
|
|
178
|
+
// Conservative: property names, descriptions and enum values, one level deep. Expiry is the trade-off: it only
|
|
179
|
+
// counts on tools that change something that exists. On a tool that creates (a publish's lifetime, a share
|
|
180
|
+
// link's expires_in) it bounds what the call itself adds, and publish_html kept expires_in_hours through
|
|
181
|
+
// that rescan. An expiry on an update tool still counts: it can take a live page down.
|
|
182
|
+
const options = tools.flatMap((t) => {
|
|
183
|
+
if (t.annotations?.readOnlyHint !== false || t.annotations?.destructiveHint !== false)
|
|
184
|
+
return [];
|
|
185
|
+
const creates = CREATES.test(words(t.name));
|
|
186
|
+
return inputProps(t).flatMap(([path, p]) => {
|
|
187
|
+
const found = optionWords(path, p).filter((w) => !(creates && w.startsWith('expir')));
|
|
188
|
+
return found.length ? [`${t.name}.${path} (${found.join(', ')})`] : [];
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
if (options.length) {
|
|
192
|
+
checks.push({
|
|
193
|
+
id: 'tools.destructive-option',
|
|
194
|
+
level: 'warn',
|
|
195
|
+
title: 'destructiveHint covers the options a tool’s schema offers',
|
|
196
|
+
detail: `${options.join('; ')}: destructiveHint is false, but the schema offers an option that deletes, overwrites, revokes or expires`,
|
|
197
|
+
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.',
|
|
198
|
+
stores: ['chatgpt'],
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// Before submitting, ChatGPT has the developer attest that the app involves no money or crypto.
|
|
202
|
+
// Moving money is a write, so read-only tools are judged by what they're about (name, title, property names) and a
|
|
203
|
+
// passing mention in their description doesn't count ("inflation, GDP, crypto and more").
|
|
204
|
+
const money = tools.flatMap((t) => {
|
|
205
|
+
const about = `${words(t.name)} ${t.title ?? ''} ${t.annotations?.title ?? ''} ${inputProps(t).map(([path]) => words(path.split('.').pop())).join(' ')}`;
|
|
206
|
+
const readOnly = t.annotations?.readOnlyHint === true;
|
|
207
|
+
const text = readOnly ? about : `${about} ${t.description ?? ''}`;
|
|
208
|
+
const found = [...mentions(text, CRYPTO), ...(readOnly ? [] : mentions(text, MONEY))];
|
|
209
|
+
return found.length ? [`${t.name} (${found.join(', ')})`] : [];
|
|
210
|
+
});
|
|
211
|
+
if (money.length) {
|
|
212
|
+
checks.push({
|
|
213
|
+
id: 'tools.money-crypto',
|
|
214
|
+
level: 'warn',
|
|
215
|
+
title: 'Tools don’t move money or handle crypto',
|
|
216
|
+
detail: `${money.join('; ')}: the tool seems to move money or handle crypto`,
|
|
217
|
+
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.',
|
|
218
|
+
stores: ['chatgpt'],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
130
221
|
const long = tools.filter((t) => t.name.length > 64);
|
|
131
222
|
if (long.length)
|
|
132
223
|
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'] });
|
|
@@ -143,7 +234,10 @@ export function toolChecks(tools) {
|
|
|
143
234
|
.filter((k) => /(ssn|social_security|passport|national_id|tax_id|credit_card|card_number|cvv|iban|password|mfa|otp|2fa|diagnos|medical|health_record|biometric|fingerprint)/i.test(k))
|
|
144
235
|
.map((k) => `${t.name}.${k}`));
|
|
145
236
|
if (sensitive.length) {
|
|
146
|
-
|
|
237
|
+
const passcode = sensitive.some((s) => /\.password$/i.test(s))
|
|
238
|
+
? ' If a password input is really a passcode for a page or file the tool makes, name it passcode: ChatGPT’s approval panel files an argument called password under "Account passwords", which reads as a credential request. Accepting both names on the server keeps older clients working.'
|
|
239
|
+
: '';
|
|
240
|
+
checks.push({ id: 'tools.sensitive-inputs', level: 'fail', title: 'Tools don’t ask for sensitive data', detail: sensitive.join(', '), fix: `OpenAI flags inputs that request PHI, card data, SSNs, credentials, MFA codes, government IDs or biometrics unless strictly necessary. Remove them or justify them in the submission.${passcode}`, stores: ['chatgpt'] });
|
|
147
241
|
}
|
|
148
242
|
const upsell = tools.filter((t) => UPSELL.test(`${t.title ?? ''} ${t.annotations?.title ?? ''} ${t.description ?? ''}`));
|
|
149
243
|
checks.push(upsell.length
|
|
@@ -153,5 +247,22 @@ export function toolChecks(tools) {
|
|
|
153
247
|
const ext = tools.flatMap((t) => ((t._meta?.['openai/ui']?.entrypoints) ?? []).map((e) => `${t.name}: ${e.type}${e.extensions?.length ? ` (${e.extensions.join(', ')})` : ''}`));
|
|
154
248
|
if (ext.length)
|
|
155
249
|
checks.push({ id: 'tools.extensions', level: 'pass', title: 'ChatGPT extensions declared', detail: ext.join('; '), stores: ['chatgpt'] });
|
|
250
|
+
// A file viewer opens when the user opens a file, and ChatGPT passes it an opaque resource URI. Described as a command
|
|
251
|
+
// ("Opens an HTML file in…"), the model calls it itself with a file path, and ChatGPT refuses the read ("MCP app cannot
|
|
252
|
+
// read resource outside its widget scope"). Seen with Stacktree's open_html_file in ChatGPT desktop, 6 Oct 2026.
|
|
253
|
+
const fileViewers = tools.filter((t) => ((t._meta?.['openai/ui']?.entrypoints) ?? []).some((e) => e.type === 'file'));
|
|
254
|
+
if (fileViewers.length) {
|
|
255
|
+
const invites = fileViewers.filter((t) => !/(user opens|when (the |a )?user opens|path does not work|not a (file )?path|opaque)/i.test(t.description ?? ''));
|
|
256
|
+
checks.push(invites.length
|
|
257
|
+
? {
|
|
258
|
+
id: 'tools.file-viewer-wording',
|
|
259
|
+
level: 'warn',
|
|
260
|
+
title: 'File viewers say the user opens the file',
|
|
261
|
+
detail: invites.map((t) => t.name).join(', '),
|
|
262
|
+
fix: 'Say that ChatGPT calls this when the user opens a file, that a file path does not work here, and which tool the model should use instead for a file it can read (Stacktree points at publish_html). Otherwise the model calls the viewer with a path and the user sees ChatGPT refuse the read.',
|
|
263
|
+
stores: ['chatgpt'],
|
|
264
|
+
}
|
|
265
|
+
: { id: 'tools.file-viewer-wording', level: 'pass', title: 'File viewers say the user opens the file', stores: ['chatgpt'] });
|
|
266
|
+
}
|
|
156
267
|
return checks;
|
|
157
268
|
}
|
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
|
|
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:
|
|
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.
|
|
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
|
@@ -289,12 +289,16 @@ export async function chatgptPack(m, opts = {}) {
|
|
|
289
289
|
const steps = `# ChatGPT submission: ${m.title} ${m.version ?? ''}
|
|
290
290
|
|
|
291
291
|
## 1. Upload the package
|
|
292
|
-
platform.openai.com/plugins → **Upload new or existing plugin** → choose your verified developer identity → upload \`${zipName}\`.
|
|
292
|
+
- **First version:** platform.openai.com/plugins → **Upload new or existing plugin** → choose your verified developer identity → upload \`${zipName}\`.
|
|
293
|
+
- **A new version of a plugin you already have:** open that plugin's page and use **Upload new version** (or **Upload plugin to make changes**). Uploading from the Plugins page instead creates a second plugin, even when the package name matches.
|
|
293
294
|
It fills the listing, icons, test cases, demo video, release notes and translations${skills.names.length ? `, and adds ${skills.names.length === 1 ? 'the skill' : `${skills.names.length} skills:`} ${skills.names.join(', ')}` : ''}. To change any of them later, edit mcplane.json, bump "version" and run \`mcplane pack chatgpt\` again.${skills.names.length ? '\nSkills are scanned too, which can take up to 2 hours. If one is flagged, take it out of chatgpt.skills and upload again; skills can come back in a later version.' : ''}
|
|
294
295
|
|
|
295
296
|
## 2. Resolve findings
|
|
296
297
|
Open **Metadata & Skills** and **MCPs**, wait for the checks, fix anything listed (Copy issues is handy), and upload again if the package changes.
|
|
297
298
|
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.
|
|
299
|
+
- The tool scan takes about 4 to 6 minutes, and the page's spinner never finishes: reload to see the result.
|
|
300
|
+
- "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.
|
|
301
|
+
- 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
302
|
|
|
299
303
|
## 3. What only you can enter
|
|
300
304
|
- **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 +309,9 @@ Hints need no justification: OpenAI's automated review checks readOnlyHint, dest
|
|
|
305
309
|
## Traps
|
|
306
310
|
- One review can be active per plugin. To replace a package in review, cancel the review first.
|
|
307
311
|
- "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
|
|
312
|
+
- Tool changes don't need a new package: OpenAI says its scans pick them up once you're published, after automated checks. Listing changes do. Neither happens on cue: on 6 Oct 2026 a Rescan on a published plugin left its tools table at the reviewed list ("last checked yesterday") and the scanner never connected, and a published plugin's MCP card has no Reconnect. If a new version's test cases use tools added since the last review, wait until the MCPs tab lists them before you submit.
|
|
313
|
+
- Don't fix a duplicate plugin by deleting it and uploading again: deleting can reserve the plugin's name for an unknown time (openai/codex#39508).
|
|
314
|
+
- 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
315
|
- 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
316
|
|
|
311
317
|
When it's in: \`mcplane submitted chatgpt --version ${m.version ?? '1.0.0'} --app-id <asdk_app_… from the URL>\`
|
|
@@ -637,8 +643,48 @@ export async function packFor(m, name, opts = {}) {
|
|
|
637
643
|
return claudePack(m, opts);
|
|
638
644
|
if (name === 'vercel-connect')
|
|
639
645
|
return vercelConnectPack(m);
|
|
646
|
+
if (name === 'muse')
|
|
647
|
+
return musePack(m, opts);
|
|
640
648
|
return simplePack(m, name);
|
|
641
649
|
}
|
|
650
|
+
/**
|
|
651
|
+
* Muse: the values for Meta's developer portal (muse.ai/platform, live since 2 Oct 2026), what each
|
|
652
|
+
* submission's three requirements ask for, and per-tool annotations to keep until the portal takes them.
|
|
653
|
+
*/
|
|
654
|
+
export async function musePack(m, opts = {}) {
|
|
655
|
+
m = forStore(m, 'muse');
|
|
656
|
+
const listed = await toolsFor(m, opts);
|
|
657
|
+
const oauth = m.server.auth === 'oauth';
|
|
658
|
+
const todo = listed.tools ? [] : [withoutTools(listed.why, 'the per-tool annotations')];
|
|
659
|
+
const annotations = listed.tools
|
|
660
|
+
? listed.tools.map((t) => `- ${t.name}: ${t.annotations?.readOnlyHint === true ? 'Read' : 'Write'}`).join('\n') || 'No tools listed.'
|
|
661
|
+
: 'Unknown without the tool list (pass --token or --tools).';
|
|
662
|
+
const md = `# Muse connectors: ${m.title}
|
|
663
|
+
|
|
664
|
+
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.
|
|
665
|
+
|
|
666
|
+
- MCP server URL: ${m.server.url}
|
|
667
|
+
- Auth: ${oauth ? 'OAuth (PKCE)' : 'None'}
|
|
668
|
+
- Name: ${m.title}
|
|
669
|
+
- Description: ${m.oneLiner ?? m.description ?? 'MISSING'}
|
|
670
|
+
- Privacy: ${m.links?.privacy ?? 'MISSING'}
|
|
671
|
+
|
|
672
|
+
## After you submit
|
|
673
|
+
Each submission shows its stage, Submitted then In review, and three requirements:
|
|
674
|
+
- **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.
|
|
675
|
+
- **Questionnaire:** your company details and where data is stored.
|
|
676
|
+
- **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.'}
|
|
677
|
+
|
|
678
|
+
On 5 October Meta emailed developers asking them to "complete the additional intake questions", so check the portal for anything still open.
|
|
679
|
+
|
|
680
|
+
## Tool annotations
|
|
681
|
+
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:
|
|
682
|
+
${annotations}
|
|
683
|
+
|
|
684
|
+
Meta hasn't published an update process yet. When it's in: \`mcplane submitted muse\`.
|
|
685
|
+
`;
|
|
686
|
+
return { files: [{ path: 'muse.md', content: md }], problems: [], todo };
|
|
687
|
+
}
|
|
642
688
|
export async function simplePack(m, store) {
|
|
643
689
|
m = forStore(m, store);
|
|
644
690
|
const problems = [];
|
|
@@ -649,7 +695,7 @@ export async function simplePack(m, store) {
|
|
|
649
695
|
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
696
|
}
|
|
651
697
|
else if (store === 'muse') {
|
|
652
|
-
|
|
698
|
+
return musePack(m);
|
|
653
699
|
}
|
|
654
700
|
else {
|
|
655
701
|
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.
|
|
1
|
+
export declare const VERSION = "0.6.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.6.0';
|
package/package.json
CHANGED