mcplane 0.4.0 → 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.
- package/CHANGELOG.md +27 -0
- package/README.md +108 -18
- package/dist/agent-plugins.d.ts +6 -0
- package/dist/agent-plugins.js +76 -0
- package/dist/cdp.d.ts +49 -0
- package/dist/cdp.js +253 -0
- package/dist/checks/ard.d.ts +67 -0
- package/dist/checks/ard.js +215 -0
- package/dist/checks/auth.js +2 -1
- package/dist/checks/chain.d.ts +45 -0
- package/dist/checks/chain.js +171 -0
- package/dist/checks/listing.js +23 -14
- package/dist/checks/oauth.d.ts +45 -0
- package/dist/checks/oauth.js +64 -0
- package/dist/checks/readiness.d.ts +58 -0
- package/dist/checks/readiness.js +230 -0
- package/dist/checks/tools.js +38 -7
- package/dist/checks/vercel.d.ts +27 -0
- package/dist/checks/vercel.js +153 -0
- package/dist/cli.js +54 -6
- package/dist/core.d.ts +1 -0
- package/dist/core.js +20 -4
- package/dist/demo.d.ts +60 -0
- package/dist/demo.js +573 -0
- package/dist/drift.js +10 -3
- package/dist/evals.d.ts +34 -0
- package/dist/evals.js +75 -0
- package/dist/extras.js +1 -1
- package/dist/images.d.ts +21 -0
- package/dist/images.js +99 -0
- package/dist/listings.d.ts +14 -0
- package/dist/listings.js +40 -1
- package/dist/mcp-client.d.ts +1 -5
- package/dist/mcp-client.js +8 -1
- package/dist/mcp-server.js +21 -11
- package/dist/packs.d.ts +48 -18
- package/dist/packs.js +380 -90
- package/dist/publish.js +13 -4
- package/dist/report.js +30 -13
- package/dist/skills.d.ts +21 -0
- package/dist/skills.js +180 -0
- package/dist/submissions.js +2 -1
- package/dist/types.d.ts +37 -4
- package/dist/types.js +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/video.d.ts +58 -0
- package/dist/video.js +190 -0
- package/dist/zip.d.ts +16 -2
- package/dist/zip.js +65 -5
- package/package.json +4 -1
- package/schema.json +86 -12
- package/schemas/agent-plugins-1.0.0/LICENSE +202 -0
- package/schemas/agent-plugins-1.0.0/mcp.schema.json +120 -0
- package/schemas/agent-plugins-1.0.0/plugin.schema.json +65 -0
package/dist/publish.js
CHANGED
|
@@ -233,15 +233,24 @@ function prepared(m, store) {
|
|
|
233
233
|
return { store, done: false, lines: [`smithery mcp publish ${m.server.url} -n @<your-org>/${m.name}`, 'Or at https://smithery.ai/new. If Smithery’s scan can’t get past your sign-in, serve /.well-known/mcp/server-card.json describing your tools.'] };
|
|
234
234
|
}
|
|
235
235
|
if (store === 'glama') {
|
|
236
|
+
const reg = registryName(m);
|
|
236
237
|
const owner = gh?.owner ?? '<your-github-user>';
|
|
238
|
+
const domainNs = !reg.startsWith('io.github.');
|
|
239
|
+
const domain = reg.split('/')[0].split('.').reverse().join('.');
|
|
237
240
|
return {
|
|
238
241
|
store,
|
|
239
242
|
done: false,
|
|
240
243
|
lines: [
|
|
241
|
-
`Hosted
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
244
|
+
`Hosted server: Glama imports it from the official registry as a connector, https://glama.ai/mcp/connectors/${reg}. Publish there first.`,
|
|
245
|
+
domainNs
|
|
246
|
+
? `To claim it: open that page, choose Claim ownership, sign in, and verify ${domain} with the token Glama shows, either as a TXT record at _glama-claim.${domain} or as https://${domain}/.well-known/glama.json ({"$schema":"https://glama.ai/mcp/schemas/connector.json","claim":"glama_claim_…"}). One verification covers every connector under the domain.`
|
|
247
|
+
: `To claim it: open that page, choose Claim ownership and "Claim with GitHub" as ${reg.split('/')[0].slice('io.github.'.length)}. An organisation also needs the Glama GitHub App installed.`,
|
|
248
|
+
...(gh
|
|
249
|
+
? [
|
|
250
|
+
`Server code on GitHub is a separate listing: add https://github.com/${gh.owner}/${gh.repo} at https://glama.ai/mcp/servers ("Add MCP Server") and claim it with glama.json at the repo root:`,
|
|
251
|
+
JSON.stringify({ $schema: 'https://glama.ai/mcp/schemas/server.json', maintainers: [owner] }, null, 2),
|
|
252
|
+
]
|
|
253
|
+
: []),
|
|
245
254
|
'Both awesome lists require the Glama badge on each entry, so do this first.',
|
|
246
255
|
],
|
|
247
256
|
};
|
package/dist/report.js
CHANGED
|
@@ -1,25 +1,41 @@
|
|
|
1
1
|
import { STORE_NAMES } from './types.js';
|
|
2
2
|
const tty = process.stdout.isTTY;
|
|
3
3
|
const c = (code, s) => (tty ? `\x1b[${code}m${s}\x1b[0m` : s);
|
|
4
|
-
const MARK = { pass: c('32', '✓'), warn: c('33', '!'), fail: c('31', '✗'), skip: c('90', '-') };
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
for (const x of r.checks) {
|
|
11
|
-
if (x.level === 'pass' && !opts.verbose)
|
|
4
|
+
const MARK = { pass: c('32', '✓'), warn: c('33', '!'), fail: c('31', '✗'), skip: c('90', '-'), info: c('36', 'i') };
|
|
5
|
+
/** Readiness for the next MCP revision gets its own section, so it never reads as a store requirement. */
|
|
6
|
+
const isReadiness = (x) => x.id.startsWith('readiness.');
|
|
7
|
+
function printChecks(checks, verbose, indent = '') {
|
|
8
|
+
for (const x of checks) {
|
|
9
|
+
if (x.level === 'pass' && !verbose)
|
|
12
10
|
continue;
|
|
13
11
|
const where = x.stores?.length ? c('90', ` [${x.stores.map((s) => STORE_NAMES[s]).join(', ')}]`) : '';
|
|
14
|
-
console.log(`${MARK[x.level]} ${x.title}${where}`);
|
|
12
|
+
console.log(`${indent}${MARK[x.level]} ${x.title}${where}`);
|
|
15
13
|
if (x.detail)
|
|
16
|
-
console.log(c('90',
|
|
14
|
+
console.log(c('90', `${indent} ${x.detail}`));
|
|
17
15
|
if (x.fix && x.level !== 'pass')
|
|
18
|
-
console.log(
|
|
16
|
+
console.log(`${indent} ${x.fix}`);
|
|
19
17
|
}
|
|
20
|
-
|
|
18
|
+
}
|
|
19
|
+
export function printPreflight(r, opts = {}) {
|
|
20
|
+
const fails = r.checks.filter((x) => x.level === 'fail');
|
|
21
|
+
const warns = r.checks.filter((x) => x.level === 'warn');
|
|
22
|
+
const notes = r.checks.filter((x) => x.level === 'info');
|
|
23
|
+
const main = r.checks.filter((x) => !isReadiness(x));
|
|
24
|
+
const ready = r.checks.filter(isReadiness);
|
|
25
|
+
console.log(c('1', `Preflight for ${r.stores.map((s) => STORE_NAMES[s]).join(', ')}`));
|
|
26
|
+
console.log('');
|
|
27
|
+
printChecks(main, opts.verbose);
|
|
28
|
+
const passed = main.filter((x) => x.level === 'pass').length;
|
|
21
29
|
if (!opts.verbose && passed)
|
|
22
30
|
console.log(c('32', `✓ ${passed} checks passed`) + c('90', ' (--verbose to list them)'));
|
|
31
|
+
if (ready.length) {
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(c('1', 'Readiness for MCP 2026-07-28') + c('90', ' (never blocking)'));
|
|
34
|
+
printChecks(ready, opts.verbose, ' ');
|
|
35
|
+
const ok = ready.filter((x) => x.level === 'pass').length;
|
|
36
|
+
if (!opts.verbose && ok)
|
|
37
|
+
console.log(` ${c('32', `✓ ${ok} ready`)}`);
|
|
38
|
+
}
|
|
23
39
|
if (r.reminders.length) {
|
|
24
40
|
console.log('');
|
|
25
41
|
console.log(c('1', 'Check these yourself'));
|
|
@@ -27,5 +43,6 @@ export function printPreflight(r, opts = {}) {
|
|
|
27
43
|
console.log(` • ${t}`);
|
|
28
44
|
}
|
|
29
45
|
console.log('');
|
|
30
|
-
|
|
46
|
+
const noteLine = notes.length ? `, ${notes.length} note${notes.length === 1 ? '' : 's'}` : '';
|
|
47
|
+
console.log(fails.length ? c('31', `${fails.length} blocking, ${warns.length} to review${noteLine}.`) : warns.length ? c('33', `No blockers, ${warns.length} to review${noteLine}.`) : c('32', `Ready to submit${noteLine ? ` (${notes.length} note${notes.length === 1 ? '' : 's'})` : ''}.`));
|
|
31
48
|
}
|
package/dist/skills.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ZipEntry } from './zip.js';
|
|
2
|
+
export type FrontMatter = {
|
|
3
|
+
data: Record<string, unknown>;
|
|
4
|
+
body: string;
|
|
5
|
+
} | {
|
|
6
|
+
error: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Reads SKILL.md front matter: the YAML a skill loader sees, for the subset skills use (plain,
|
|
10
|
+
* quoted and block scalars, nested maps and lists). A plain value holding ": " is an error, as in
|
|
11
|
+
* YAML itself, because strict loaders refuse the whole skill over it.
|
|
12
|
+
*/
|
|
13
|
+
export declare function frontMatter(raw: string): FrontMatter;
|
|
14
|
+
export interface SkillBundle {
|
|
15
|
+
files: ZipEntry[];
|
|
16
|
+
names: string[];
|
|
17
|
+
problems: string[];
|
|
18
|
+
todo: string[];
|
|
19
|
+
}
|
|
20
|
+
/** Reads, checks and lays out skills for skills/<folder>/ in a plugin package. */
|
|
21
|
+
export declare function bundleSkills(spec: string | string[] | undefined, dir: string, pluginName: string): Promise<SkillBundle>;
|
package/dist/skills.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Skills for plugin packages: each skill is a folder with a SKILL.md, bundled under
|
|
3
|
+
* skills/<folder>/ with its scripts and references, executable bits kept. Checked against the
|
|
4
|
+
* Agent Skills spec (agentskills.io) and the skill rules in OpenAI's submission errors.
|
|
5
|
+
*/
|
|
6
|
+
import { lstat, readFile, readdir } from 'node:fs/promises';
|
|
7
|
+
import { basename, join, relative, resolve, sep } from 'node:path';
|
|
8
|
+
/** Front matter fields the Agent Skills spec defines. */
|
|
9
|
+
const SPEC_FIELDS = new Set(['name', 'description', 'license', 'compatibility', 'metadata', 'allowed-tools']);
|
|
10
|
+
const NAME = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
11
|
+
const DESCRIPTION_MAX = 1024;
|
|
12
|
+
/** Noise and bulk that never belongs in a package. */
|
|
13
|
+
const SKIPPED = new Set(['.DS_Store', 'Thumbs.db', 'node_modules', '.git']);
|
|
14
|
+
function unquoteDouble(s) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(s.replace(/\\'/g, "'").replace(/\\\n/g, ''));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return s.slice(1, -1);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Reads SKILL.md front matter: the YAML a skill loader sees, for the subset skills use (plain,
|
|
24
|
+
* quoted and block scalars, nested maps and lists). A plain value holding ": " is an error, as in
|
|
25
|
+
* YAML itself, because strict loaders refuse the whole skill over it.
|
|
26
|
+
*/
|
|
27
|
+
export function frontMatter(raw) {
|
|
28
|
+
const lines = raw.replace(/^/, '').replace(/\r\n/g, '\n').split('\n');
|
|
29
|
+
if (lines[0].trimEnd() !== '---')
|
|
30
|
+
return { error: 'SKILL.md must start with front matter between --- lines' };
|
|
31
|
+
const close = lines.findIndex((l, i) => i > 0 && /^(---|\.\.\.)\s*$/.test(l));
|
|
32
|
+
if (close < 0)
|
|
33
|
+
return { error: 'the front matter has no closing --- line' };
|
|
34
|
+
const fm = lines.slice(1, close);
|
|
35
|
+
const data = {};
|
|
36
|
+
for (let i = 0; i < fm.length; i++) {
|
|
37
|
+
const line = fm[i];
|
|
38
|
+
if (!line.trim() || /^\s*#/.test(line))
|
|
39
|
+
continue;
|
|
40
|
+
const m = /^([A-Za-z0-9_-]+)\s*:(?:[ \t]+(.*))?$/.exec(line);
|
|
41
|
+
if (!m)
|
|
42
|
+
return { error: `front matter line ${i + 2} isn't "key: value": ${line.trim().slice(0, 60)}` };
|
|
43
|
+
const key = m[1];
|
|
44
|
+
const value = (m[2] ?? '').trim();
|
|
45
|
+
const more = [];
|
|
46
|
+
while (i + 1 < fm.length && (fm[i + 1].trim() === '' || /^\s/.test(fm[i + 1])))
|
|
47
|
+
more.push(fm[++i]);
|
|
48
|
+
while (more.length && !more[more.length - 1].trim())
|
|
49
|
+
more.pop();
|
|
50
|
+
if (/^[|>][+-]?\d*(\s+#.*)?$/.test(value)) {
|
|
51
|
+
const indent = Math.min(...more.filter((l) => l.trim()).map((l) => l.length - l.trimStart().length));
|
|
52
|
+
const body = more.map((l) => l.slice(Number.isFinite(indent) ? indent : 0));
|
|
53
|
+
data[key] = value.startsWith('|') ? body.join('\n') : body.reduce((s, l) => (!l.trim() ? `${s}\n` : s && !s.endsWith('\n') ? `${s} ${l}` : `${s}${l}`), '');
|
|
54
|
+
}
|
|
55
|
+
else if (!value && more.length) {
|
|
56
|
+
const items = more.filter((l) => l.trim());
|
|
57
|
+
data[key] = items.every((l) => /^\s*-\s/.test(l))
|
|
58
|
+
? items.map((l) => l.replace(/^\s*-\s+/, '').trim())
|
|
59
|
+
: Object.fromEntries(items.map((l) => /^\s*([^:]+):\s*(.*)$/.exec(l)).filter(Boolean).map((r) => [r[1].trim(), r[2].trim().replace(/^(["'])(.*)\1$/, '$2')]));
|
|
60
|
+
}
|
|
61
|
+
else if (value.startsWith('"')) {
|
|
62
|
+
data[key] = unquoteDouble([value, ...more.map((l) => l.trim())].join(' '));
|
|
63
|
+
}
|
|
64
|
+
else if (value.startsWith("'")) {
|
|
65
|
+
data[key] = [value, ...more.map((l) => l.trim())].join(' ').slice(1, -1).replace(/''/g, "'");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const text = [value, ...more.map((l) => l.trim())].join(' ').replace(/\s+#.*$/, '');
|
|
69
|
+
if (/:\s/.test(text))
|
|
70
|
+
return { error: `"${key}" holds ": " without quotes, which YAML reads as a nested key; quote the value` };
|
|
71
|
+
data[key] = text;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { data, body: lines.slice(close + 1).join('\n') };
|
|
75
|
+
}
|
|
76
|
+
async function walk(root, problems, skipped, at = root) {
|
|
77
|
+
const out = [];
|
|
78
|
+
for (const name of (await readdir(at)).sort()) {
|
|
79
|
+
const abs = join(at, name);
|
|
80
|
+
const inner = relative(root, abs).split(sep).join('/');
|
|
81
|
+
if (SKIPPED.has(name))
|
|
82
|
+
continue;
|
|
83
|
+
if (name.startsWith('.')) {
|
|
84
|
+
skipped.push(inner);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const st = await lstat(abs);
|
|
88
|
+
if (st.isSymbolicLink())
|
|
89
|
+
problems.push(`${basename(root)}/${inner} is a symbolic link; packages can't hold one (OpenAI ignores the skill). Copy the file in instead`);
|
|
90
|
+
else if (st.isDirectory())
|
|
91
|
+
out.push(...(await walk(root, problems, skipped, abs)));
|
|
92
|
+
else if (st.isFile())
|
|
93
|
+
out.push({ inner, abs, mode: st.mode & 0o111 ? 0o755 : 0o644 });
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/** Skill folders from the manifest setting: one folder of skills, or a list of skill folders. Null when the folder isn't there. */
|
|
98
|
+
async function skillDirs(spec, dir) {
|
|
99
|
+
if (Array.isArray(spec))
|
|
100
|
+
return spec.map((p) => resolve(dir, p));
|
|
101
|
+
const parent = resolve(dir, spec);
|
|
102
|
+
const names = await readdir(parent).catch(() => null);
|
|
103
|
+
if (!names)
|
|
104
|
+
return null;
|
|
105
|
+
const dirs = [];
|
|
106
|
+
for (const n of names.sort())
|
|
107
|
+
if (!n.startsWith('.') && !SKIPPED.has(n) && (await lstat(join(parent, n))).isDirectory())
|
|
108
|
+
dirs.push(join(parent, n));
|
|
109
|
+
return dirs;
|
|
110
|
+
}
|
|
111
|
+
/** Reads, checks and lays out skills for skills/<folder>/ in a plugin package. */
|
|
112
|
+
export async function bundleSkills(spec, dir, pluginName) {
|
|
113
|
+
const out = { files: [], names: [], problems: [], todo: [] };
|
|
114
|
+
if (spec === undefined)
|
|
115
|
+
return out;
|
|
116
|
+
const dirs = await skillDirs(spec, dir);
|
|
117
|
+
if (!dirs) {
|
|
118
|
+
out.problems.push(`chatgpt.skills: ${spec} isn't a folder (paths are relative to mcplane.json)`);
|
|
119
|
+
return out;
|
|
120
|
+
}
|
|
121
|
+
if (!dirs.length && typeof spec === 'string')
|
|
122
|
+
out.todo.push(`chatgpt.skills: ${spec} holds no skill folders, so the package has no skills.`);
|
|
123
|
+
const seen = new Map();
|
|
124
|
+
for (const root of dirs) {
|
|
125
|
+
const folder = basename(root);
|
|
126
|
+
const where = `skills/${folder}`;
|
|
127
|
+
const st = await lstat(root).catch(() => null);
|
|
128
|
+
if (!st?.isDirectory()) {
|
|
129
|
+
out.problems.push(`${where}: ${relative(dir, root) || root} isn't a folder`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const skipped = [];
|
|
133
|
+
const found = await walk(root, out.problems, skipped);
|
|
134
|
+
if (skipped.length)
|
|
135
|
+
out.todo.push(`Left out of ${where}: ${skipped.join(', ')} (hidden files never go in the package).`);
|
|
136
|
+
const md = found.find((f) => f.inner === 'SKILL.md');
|
|
137
|
+
if (!md) {
|
|
138
|
+
out.problems.push(`${where} has no SKILL.md`);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const raw = await readFile(md.abs, 'utf8');
|
|
142
|
+
const fm = frontMatter(raw);
|
|
143
|
+
if ('error' in fm) {
|
|
144
|
+
out.problems.push(`${where}/SKILL.md: ${fm.error}`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const { name, description } = fm.data;
|
|
148
|
+
if (typeof name !== 'string' || !name.trim())
|
|
149
|
+
out.problems.push(`${where}/SKILL.md: "name" is required`);
|
|
150
|
+
else {
|
|
151
|
+
if (!NAME.test(name) || name.length > 64)
|
|
152
|
+
out.problems.push(`${where}/SKILL.md: name "${name}" must be lowercase letters, digits and single hyphens, at most 64 characters`);
|
|
153
|
+
if (name !== folder)
|
|
154
|
+
out.problems.push(`${where}/SKILL.md: name "${name}" doesn't match its folder "${folder}"; the Agent Skills spec requires a match, and strict clients skip the skill`);
|
|
155
|
+
if (`${pluginName}:${name}`.length > 64)
|
|
156
|
+
out.problems.push(`${where}: "${pluginName}:${name}" is over 64 characters, OpenAI's limit for a skill's full name`);
|
|
157
|
+
if (seen.has(name))
|
|
158
|
+
out.problems.push(`${where}: the name "${name}" is also used by ${seen.get(name)}`);
|
|
159
|
+
seen.set(name, where);
|
|
160
|
+
out.names.push(name);
|
|
161
|
+
}
|
|
162
|
+
if (typeof description !== 'string' || !description.trim())
|
|
163
|
+
out.problems.push(`${where}/SKILL.md: "description" is required`);
|
|
164
|
+
else if (description.trim().length > DESCRIPTION_MAX)
|
|
165
|
+
out.problems.push(`${where}/SKILL.md: the description is ${description.trim().length} characters; the limit is ${DESCRIPTION_MAX}`);
|
|
166
|
+
if (!fm.body.trim())
|
|
167
|
+
out.problems.push(`${where}/SKILL.md has no instructions after the front matter`);
|
|
168
|
+
const extra = Object.keys(fm.data).filter((k) => !SPEC_FIELDS.has(k));
|
|
169
|
+
if (extra.length)
|
|
170
|
+
out.todo.push(`${where}/SKILL.md: ${extra.map((k) => `"${k}"`).join(', ')} ${extra.length === 1 ? "isn't" : "aren't"} in the Agent Skills spec. OpenAI ignores unknown fields, but strict clients may refuse the skill.`);
|
|
171
|
+
const own = new Set(found.map((f) => f.inner));
|
|
172
|
+
for (const ref of new Set([...raw.matchAll(/\bscripts\/[A-Za-z0-9._/-]*[A-Za-z0-9_-]/g)].map((r) => r[0]))) {
|
|
173
|
+
if (!own.has(ref))
|
|
174
|
+
out.problems.push(`${where}/SKILL.md refers to ${ref}, which isn't in the skill's folder. Installed on its own, the skill can't run it`);
|
|
175
|
+
}
|
|
176
|
+
for (const f of found)
|
|
177
|
+
out.files.push({ path: `${where}/${f.inner}`, data: await readFile(f.abs), mode: f.mode });
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
package/dist/submissions.js
CHANGED
|
@@ -17,6 +17,7 @@ const RT_SLUG = {
|
|
|
17
17
|
'claude-plugins': 'claude-plugins',
|
|
18
18
|
cursor: 'cursor',
|
|
19
19
|
muse: 'muse',
|
|
20
|
+
'vercel-connect': 'vercel-connect',
|
|
20
21
|
};
|
|
21
22
|
const TRACKED_FROM_GITHUB = ['grok', 'docker'];
|
|
22
23
|
export async function load(dir = process.cwd()) {
|
|
@@ -36,7 +37,7 @@ const today = () => new Date().toISOString().slice(0, 10);
|
|
|
36
37
|
function listingRef(m, store, appId) {
|
|
37
38
|
if (store === 'chatgpt')
|
|
38
39
|
return appId;
|
|
39
|
-
if (store === 'claude-connectors' || store === 'muse')
|
|
40
|
+
if (store === 'claude-connectors' || store === 'muse' || store === 'vercel-connect')
|
|
40
41
|
return m.server.url;
|
|
41
42
|
if (store === 'claude-plugins' || store === 'cursor')
|
|
42
43
|
return m.repository;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
/** info: optional or advisory. It never blocks and isn't counted as something to review. */
|
|
2
|
+
export type Level = 'pass' | 'warn' | 'fail' | 'skip' | 'info';
|
|
2
3
|
export interface Check {
|
|
3
4
|
id: string;
|
|
4
5
|
level: Level;
|
|
@@ -10,7 +11,7 @@ export interface Check {
|
|
|
10
11
|
/** The stores this matters for; empty means all. */
|
|
11
12
|
stores?: StoreId[];
|
|
12
13
|
}
|
|
13
|
-
export type StoreId = 'mcp-registry' | 'chatgpt' | 'claude-connectors' | 'claude-plugins' | 'cursor' | 'grok' | 'docker' | 'muse' | 'smithery' | 'glama' | 'cline' | 'lobehub' | 'awesome-mcp-servers' | 'awesome-remote-mcp-servers';
|
|
14
|
+
export type StoreId = 'mcp-registry' | 'chatgpt' | 'claude-connectors' | 'claude-plugins' | 'cursor' | 'grok' | 'docker' | 'muse' | 'smithery' | 'glama' | 'cline' | 'lobehub' | 'awesome-mcp-servers' | 'awesome-remote-mcp-servers' | 'vercel-connect';
|
|
14
15
|
export declare const STORE_NAMES: Record<StoreId, string>;
|
|
15
16
|
export interface Manifest {
|
|
16
17
|
$schema?: string;
|
|
@@ -23,6 +24,11 @@ export interface Manifest {
|
|
|
23
24
|
server: {
|
|
24
25
|
url: string;
|
|
25
26
|
auth?: 'none' | 'oauth';
|
|
27
|
+
/**
|
|
28
|
+
* Sign-in pages and identity-provider URLs your consent screen sends people to (e.g. https://accounts.example.com/sign-in).
|
|
29
|
+
* Preflight GETs them with the authorization endpoint to find bot challenges and 403s reviewers would hit.
|
|
30
|
+
*/
|
|
31
|
+
signIn?: string[];
|
|
26
32
|
};
|
|
27
33
|
repository?: string;
|
|
28
34
|
author?: {
|
|
@@ -36,6 +42,7 @@ export interface Manifest {
|
|
|
36
42
|
terms?: string;
|
|
37
43
|
docs?: string;
|
|
38
44
|
};
|
|
45
|
+
/** A direct image URL. The ChatGPT package also takes a local path, relative to mcplane.json. */
|
|
39
46
|
icon?: string;
|
|
40
47
|
prompts?: string[];
|
|
41
48
|
stores?: StoreId[];
|
|
@@ -54,7 +61,10 @@ export interface Manifest {
|
|
|
54
61
|
prompt: string;
|
|
55
62
|
}[];
|
|
56
63
|
};
|
|
57
|
-
/**
|
|
64
|
+
/**
|
|
65
|
+
* @deprecated OpenAI no longer asks for hint justifications. Still accepted: the ChatGPT pack lists them
|
|
66
|
+
* as optional notes for an appeal if review flags a hint.
|
|
67
|
+
*/
|
|
58
68
|
justifications?: Record<string, {
|
|
59
69
|
readOnly?: string;
|
|
60
70
|
openWorld?: string;
|
|
@@ -77,9 +87,15 @@ export interface Manifest {
|
|
|
77
87
|
/** #RRGGBB with 2:1 contrast against white; the dark one against #212121. */
|
|
78
88
|
brandColor?: string;
|
|
79
89
|
brandColorDark?: string;
|
|
80
|
-
/**
|
|
90
|
+
/** Image URLs or local paths (relative to mcplane.json), bundled into the package. logo and composerIcon default to "icon". */
|
|
91
|
+
logo?: string;
|
|
81
92
|
composerIcon?: string;
|
|
82
93
|
logoDark?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Agent Skills to bundle under skills/ in the package: a folder whose subfolders each hold a SKILL.md,
|
|
96
|
+
* or a list of skill folders. Paths are relative to mcplane.json.
|
|
97
|
+
*/
|
|
98
|
+
skills?: string | string[];
|
|
83
99
|
/** Reviewer-accessible video URL showing the main use cases (required for review). */
|
|
84
100
|
demoVideo?: string;
|
|
85
101
|
commerce?: boolean;
|
|
@@ -100,6 +116,23 @@ export interface Manifest {
|
|
|
100
116
|
keywords?: string[];
|
|
101
117
|
domains?: string[];
|
|
102
118
|
};
|
|
119
|
+
/** Vercel Connect's Submit a Service form fields with no shared equivalent. */
|
|
120
|
+
vercelConnect?: {
|
|
121
|
+
/** Base URL of a REST API your users also call, listed as a second target. */
|
|
122
|
+
apiBase?: string;
|
|
123
|
+
/** Default scopes for the OAuth method. Defaults to the discovery document's scopes_supported. */
|
|
124
|
+
scopes?: string[];
|
|
125
|
+
/** Offer an API key connection method: where users create a key, and the header it's sent in. */
|
|
126
|
+
apiKey?: {
|
|
127
|
+
docs?: string;
|
|
128
|
+
header?: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
/** The Claude Code plugin that ships this server, for "mcplane pack claude-eval". Read from the plugin repo when left out. */
|
|
132
|
+
claudePlugin?: {
|
|
133
|
+
name?: string;
|
|
134
|
+
server?: string;
|
|
135
|
+
};
|
|
103
136
|
/** Named workflows, run with "mcplane <lane>": each step is a command line without "mcplane". */
|
|
104
137
|
lanes?: Record<string, string[]>;
|
|
105
138
|
/** How reviewers get in, for servers behind sign-in. Never put the password here; say where it lives. */
|
package/dist/types.js
CHANGED
package/dist/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.5.0";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.5.0';
|
package/dist/video.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** One screencast frame: a JPEG relative to the work folder, and when it was shown (seconds). */
|
|
2
|
+
export interface Frame {
|
|
3
|
+
file: string;
|
|
4
|
+
ts: number;
|
|
5
|
+
}
|
|
6
|
+
export interface Scene {
|
|
7
|
+
caption: string;
|
|
8
|
+
frames: Frame[];
|
|
9
|
+
/** When recording stopped; the last frame is held until then. */
|
|
10
|
+
end: number;
|
|
11
|
+
}
|
|
12
|
+
/** ffmpeg's concat-demuxer list: each frame held until the next one, the last until `end`. */
|
|
13
|
+
export declare function concatList(frames: Frame[], end: number): string;
|
|
14
|
+
/** How long a scene runs once assembled. */
|
|
15
|
+
export declare function sceneSeconds(s: Scene): number;
|
|
16
|
+
/**
|
|
17
|
+
* Escapes a value for a filter option inside an ffmpeg filtergraph. Two levels:
|
|
18
|
+
* the option value (\ ' :) and then the filtergraph itself (\ ' [ ] , ;).
|
|
19
|
+
*/
|
|
20
|
+
export declare function ffEscape(value: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* A caption as at most `maxLines` lines of about `width` characters. Control
|
|
23
|
+
* characters become spaces (drawtext would draw them as boxes); anything that
|
|
24
|
+
* doesn't fit ends in an ellipsis.
|
|
25
|
+
*/
|
|
26
|
+
export declare function wrapCaption(text: string, width?: number, maxLines?: number): string[];
|
|
27
|
+
/** Width and height from a JPEG's start-of-frame marker. */
|
|
28
|
+
export declare function jpegSize(b: Uint8Array): {
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
} | null;
|
|
32
|
+
/** Even dimensions (H.264 needs them) within the maximum, keeping the aspect ratio. */
|
|
33
|
+
export declare function outputSize(width: number, height: number, maxWidth?: number, maxHeight?: number): {
|
|
34
|
+
width: number;
|
|
35
|
+
height: number;
|
|
36
|
+
};
|
|
37
|
+
export declare function findFfmpeg(path?: string): string | null;
|
|
38
|
+
/** Text colours that read on a background, given as 0xRRGGBB: light on dark, dark on light. */
|
|
39
|
+
export declare function inkFor(bg: string): {
|
|
40
|
+
text: string;
|
|
41
|
+
muted: string;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Builds the video in `work` (where the frames are) and writes it to `out`.
|
|
45
|
+
* Every part is encoded the same way so they join without re-encoding.
|
|
46
|
+
*/
|
|
47
|
+
export declare function buildVideo(o: {
|
|
48
|
+
scenes: Scene[];
|
|
49
|
+
title: string;
|
|
50
|
+
subtitle?: string;
|
|
51
|
+
out: string;
|
|
52
|
+
work: string;
|
|
53
|
+
ffmpeg: string;
|
|
54
|
+
size: {
|
|
55
|
+
width: number;
|
|
56
|
+
height: number;
|
|
57
|
+
};
|
|
58
|
+
}): Promise<number>;
|