mcplane 0.2.0 → 0.3.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/README.md +13 -0
- package/dist/checks/auth.d.ts +9 -0
- package/dist/checks/listing.d.ts +4 -0
- package/dist/checks/server.d.ts +8 -0
- package/dist/checks/server.js +3 -20
- package/dist/checks/tls.d.ts +3 -0
- package/dist/checks/tls.js +17 -0
- package/dist/checks/tools.d.ts +2 -0
- package/dist/checks/tools.js +5 -5
- package/dist/cli.d.ts +2 -0
- package/dist/core.d.ts +18 -0
- package/dist/core.js +52 -0
- package/dist/draft.d.ts +5 -0
- package/dist/draft.js +55 -0
- package/dist/drift.d.ts +57 -0
- package/dist/extras.d.ts +13 -0
- package/dist/fleet.d.ts +40 -0
- package/dist/listings.d.ts +13 -0
- package/dist/manifest.d.ts +10 -0
- package/dist/manifest.js +2 -53
- package/dist/mcp-client.d.ts +24 -0
- package/dist/mcp-server.d.ts +1 -0
- package/dist/packs.d.ts +32 -0
- package/dist/preflight.d.ts +9 -0
- package/dist/preflight.js +5 -43
- package/dist/publish.d.ts +52 -0
- package/dist/report.d.ts +4 -0
- package/dist/submissions.d.ts +33 -0
- package/dist/types.d.ts +97 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -1
- package/package.json +10 -2
package/README.md
CHANGED
|
@@ -7,6 +7,8 @@ npx mcplane init --url https://mcp.example.com/mcp
|
|
|
7
7
|
npx mcplane preflight
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
+
No terminal handy? Paste your URL at [reviewtimes.fyi/check](https://reviewtimes.fyi/check).
|
|
11
|
+
|
|
10
12
|
```
|
|
11
13
|
✗ DNS does not advertise Encrypted ClientHello [ChatGPT]
|
|
12
14
|
example.com has ech= in its HTTPS record
|
|
@@ -220,6 +222,17 @@ mcp Run as an MCP server over stdio
|
|
|
220
222
|
|
|
221
223
|
Servers behind sign-in: pass `--token` or set `MCPLANE_TOKEN` so tool checks can run.
|
|
222
224
|
|
|
225
|
+
## Use the checks in your own code
|
|
226
|
+
|
|
227
|
+
`mcplane/core` runs anywhere with `fetch` (Workers, Deno, Bun, Node):
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
import { draftManifest, preflight } from 'mcplane/core';
|
|
231
|
+
const report = await preflight(await draftManifest('https://mcp.example.com/mcp'));
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
It's what powers [reviewtimes.fyi/check](https://reviewtimes.fyi/check). The TLS 1.2 handshake needs a raw socket, so only the CLI runs that one.
|
|
235
|
+
|
|
223
236
|
## mcplane.json
|
|
224
237
|
|
|
225
238
|
One file describes your server and its listings. [`schema.json`](schema.json) gives editors completion and validation; `mcplane init` adds the `$schema` line. [`examples/mcplane.json`](examples/mcplane.json) is a complete one, from [Review Times](https://reviewtimes.fyi)' own ChatGPT and Claude submissions.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth checks, from Stacktree's ChatGPT rounds. Read-only by default:
|
|
3
|
+
* registering a client writes to your authorization server, so that only
|
|
4
|
+
* happens with --register.
|
|
5
|
+
*/
|
|
6
|
+
import type { Check } from '../types.js';
|
|
7
|
+
export declare function authChecks(url: string, opts?: {
|
|
8
|
+
register?: boolean;
|
|
9
|
+
}): Promise<Check[]>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Check, Manifest, StoreId, Tool } from '../types.js';
|
|
2
|
+
export declare function listingChecks(m: Manifest, tools: Tool[], stores: StoreId[]): Promise<Check[]>;
|
|
3
|
+
/** Rules for plugin repositories (Claude plugins, Cursor, Grok). */
|
|
4
|
+
export declare function repoChecks(m: Manifest, stores: StoreId[]): Promise<Check[]>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Check, Tool } from '../types.js';
|
|
2
|
+
/** Connects the way a store's scanner does and returns the tool list for the other checks. */
|
|
3
|
+
/** A check that needs a raw socket, which only some runtimes have. Node passes the TLS 1.2 check in. */
|
|
4
|
+
export type SocketCheck = (url: string) => Promise<Check>;
|
|
5
|
+
export declare function serverChecks(url: string, auth: 'none' | 'oauth', token?: string, extra?: SocketCheck[]): Promise<{
|
|
6
|
+
checks: Check[];
|
|
7
|
+
tools: Tool[];
|
|
8
|
+
}>;
|
package/dist/checks/server.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { connect } from 'node:tls';
|
|
2
1
|
import { McpClient } from '../mcp-client.js';
|
|
3
2
|
const BROWSER_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36';
|
|
4
|
-
|
|
5
|
-
export async function serverChecks(url, auth, token) {
|
|
3
|
+
export async function serverChecks(url, auth, token, extra = []) {
|
|
6
4
|
const checks = [];
|
|
7
5
|
const bearer = token ? { authorization: `Bearer ${token}` } : {};
|
|
8
6
|
const client = new McpClient(url, bearer);
|
|
@@ -128,26 +126,11 @@ export async function serverChecks(url, auth, token) {
|
|
|
128
126
|
fix: 'A firewall or bot rule is returning 403 to non-browser clients. Store scanners and reviewers are automated; allow them on the MCP path.',
|
|
129
127
|
});
|
|
130
128
|
}
|
|
131
|
-
|
|
129
|
+
for (const c of extra)
|
|
130
|
+
checks.push(await c(url));
|
|
132
131
|
checks.push(await echCheck(url));
|
|
133
132
|
return { checks, tools };
|
|
134
133
|
}
|
|
135
|
-
/** Some review networks only speak TLS 1.2. */
|
|
136
|
-
function tls12Check(url) {
|
|
137
|
-
const host = new URL(url).hostname;
|
|
138
|
-
return new Promise((resolve) => {
|
|
139
|
-
const sock = connect({ host, port: 443, servername: host, maxVersion: 'TLSv1.2', timeout: 8000 }, () => {
|
|
140
|
-
sock.end();
|
|
141
|
-
resolve({ id: 'server.tls12', level: 'pass', title: 'A TLS 1.2 client can connect' });
|
|
142
|
-
});
|
|
143
|
-
const bad = (why) => resolve({ id: 'server.tls12', level: 'warn', title: 'A TLS 1.2 client can connect', detail: why, fix: 'Allow TLS 1.2; some corporate and review proxies cannot do TLS 1.3.' });
|
|
144
|
-
sock.on('error', (e) => bad(e.message));
|
|
145
|
-
sock.on('timeout', () => {
|
|
146
|
-
sock.destroy();
|
|
147
|
-
bad('timed out');
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
134
|
/** Encrypted ClientHello in DNS makes TLS-inspecting proxies reset the connection. */
|
|
152
135
|
async function echCheck(url) {
|
|
153
136
|
const host = new URL(url).hostname;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { connect } from 'node:tls';
|
|
2
|
+
/** Some review networks only speak TLS 1.2. */
|
|
3
|
+
export function tls12Check(url) {
|
|
4
|
+
const host = new URL(url).hostname;
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
const sock = connect({ host, port: 443, servername: host, maxVersion: 'TLSv1.2', timeout: 8000 }, () => {
|
|
7
|
+
sock.end();
|
|
8
|
+
resolve({ id: 'server.tls12', level: 'pass', title: 'A TLS 1.2 client can connect' });
|
|
9
|
+
});
|
|
10
|
+
const bad = (why) => resolve({ id: 'server.tls12', level: 'warn', title: 'A TLS 1.2 client can connect', detail: why, fix: 'Allow TLS 1.2; some corporate and review proxies cannot do TLS 1.3.' });
|
|
11
|
+
sock.on('error', (e) => bad(e.message));
|
|
12
|
+
sock.on('timeout', () => {
|
|
13
|
+
sock.destroy();
|
|
14
|
+
bad('timed out');
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
}
|
package/dist/checks/tools.js
CHANGED
|
@@ -12,17 +12,17 @@ const INSTRUCTION = [
|
|
|
12
12
|
/\b(call|use) (this|it) only\b/i,
|
|
13
13
|
];
|
|
14
14
|
/** Plan and pricing copy. OpenAI prohibits in-app upsells, "including freemium upsells". */
|
|
15
|
-
|
|
15
|
+
// Plan and tier language only: product prices ("live pricing", "premium cars") are not upsells.
|
|
16
|
+
const UPSELL = /\b(upgrade (to|your|now)|pro plan|premium (plan|tier|account|subscription|version)|paid (plan|tier|account)|free trial|subscribe (to|now)|unlock (more|premium|pro|unlimited|all))\b/i;
|
|
16
17
|
/** Inputs that suggest a write authorised only by a secret passed through the chat. */
|
|
17
18
|
const BEARER_INPUT = /^(token|edit_token|update_token|secret|access_key|api_key|password)$/i;
|
|
18
19
|
export function toolChecks(tools) {
|
|
19
20
|
if (!tools.length)
|
|
20
21
|
return [];
|
|
21
22
|
const checks = [];
|
|
22
|
-
const names = new Set(tools.map((t) => t.name));
|
|
23
23
|
const noTitle = tools.filter((t) => !t.title && !t.annotations?.title);
|
|
24
24
|
checks.push(noTitle.length
|
|
25
|
-
? { id: 'tools.title', level: '
|
|
25
|
+
? { id: 'tools.title', level: 'warn', title: 'Every tool has a human-readable title', detail: noTitle.map((t) => t.name).join(', '), fix: 'Add "title" (or annotations.title) to each tool. Clients show the raw name without one, and Anthropic’s reviewers ask for them on new submissions.', stores: ['claude-connectors', 'claude-plugins'] }
|
|
26
26
|
: { id: 'tools.title', level: 'pass', title: 'Every tool has a human-readable title' });
|
|
27
27
|
const missing = tools.flatMap((t) => HINTS.filter((h) => typeof t.annotations?.[h] !== 'boolean').map((h) => `${t.name}.${h}`));
|
|
28
28
|
checks.push(missing.length
|
|
@@ -46,8 +46,8 @@ export function toolChecks(tools) {
|
|
|
46
46
|
const instructing = tools.flatMap((t) => {
|
|
47
47
|
const text = `${t.description ?? ''}`;
|
|
48
48
|
const hits = INSTRUCTION.filter((re) => re.test(text)).map((re) => text.match(re)[0]);
|
|
49
|
-
|
|
50
|
-
return hits.length
|
|
49
|
+
// Naming a sibling tool ("call get_session first") is common in approved listings, so only explicit instructions count.
|
|
50
|
+
return hits.length ? [`${t.name}: ${hits.map((h) => `"${h}"`).join(', ')}`] : [];
|
|
51
51
|
});
|
|
52
52
|
checks.push(instructing.length
|
|
53
53
|
? {
|
package/dist/cli.d.ts
ADDED
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SocketCheck } from './checks/server.js';
|
|
2
|
+
import type { Check, Manifest, StoreId } from './types.js';
|
|
3
|
+
export interface PreflightResult {
|
|
4
|
+
checks: Check[];
|
|
5
|
+
reminders: string[];
|
|
6
|
+
stores: StoreId[];
|
|
7
|
+
}
|
|
8
|
+
/** Runtime-neutral: fetch only. Node adds socket checks (TLS 1.2) through extraChecks; see preflight-node. */
|
|
9
|
+
export declare function preflight(m: Manifest, opts?: {
|
|
10
|
+
stores?: StoreId[];
|
|
11
|
+
register?: boolean;
|
|
12
|
+
token?: string;
|
|
13
|
+
extraChecks?: SocketCheck[];
|
|
14
|
+
}): Promise<PreflightResult>;
|
|
15
|
+
export { draftManifest, storesOf, SCHEMA_URL } from './draft.js';
|
|
16
|
+
export { toolChecks } from './checks/tools.js';
|
|
17
|
+
export { STORE_NAMES } from './types.js';
|
|
18
|
+
export type { Check, Level, Manifest, StoreId, Tool } from './types.js';
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mcplane's checks for any runtime with fetch (Workers, Deno, browsers' servers).
|
|
3
|
+
* Import from "mcplane/core".
|
|
4
|
+
*/
|
|
5
|
+
import { authChecks } from './checks/auth.js';
|
|
6
|
+
import { listingChecks, repoChecks } from './checks/listing.js';
|
|
7
|
+
import { serverChecks } from './checks/server.js';
|
|
8
|
+
import { toolChecks } from './checks/tools.js';
|
|
9
|
+
import { storesOf } from './draft.js';
|
|
10
|
+
/** Things no script can see, which have still cost real submissions. */
|
|
11
|
+
function reminders(m, stores) {
|
|
12
|
+
const r = [];
|
|
13
|
+
if (m.server.auth === 'oauth') {
|
|
14
|
+
r.push('Give reviewers a demo account a stranger can use: email and password (not your Google or SSO login), email already confirmed, no 2FA or SMS, seeded with data for every test case. Re-check the seed right before submitting.');
|
|
15
|
+
r.push('Sign in through your OAuth flow once as a brand-new user. A blank post-sign-in redirect stranded a ChatGPT reviewer between registration and consent.');
|
|
16
|
+
}
|
|
17
|
+
if (stores.some((s) => s === 'chatgpt' || s === 'claude-connectors')) {
|
|
18
|
+
r.push('If a tool writes somewhere public (posts, sends, publishes), give reviewers a safe target: a sandbox account or a "playground" option that validates and then discards. Reviewers test writes; they shouldn’t post to a real timeline.');
|
|
19
|
+
}
|
|
20
|
+
if (stores.includes('chatgpt')) {
|
|
21
|
+
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.');
|
|
22
|
+
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.');
|
|
23
|
+
r.push('Tool-annotation justifications are cut at 200 characters without warning.');
|
|
24
|
+
r.push('ChatGPT allows one version in review at a time. To change a submission, Cancel Review and resubmit the same draft.');
|
|
25
|
+
}
|
|
26
|
+
if (stores.includes('claude-connectors') || stores.includes('claude-plugins')) {
|
|
27
|
+
if (stores.includes('claude-connectors'))
|
|
28
|
+
r.push('Claude connectors are submitted from an organisation’s admin settings (claude.ai/directory/manage), so you need admin rights in a paid org. The tool list is synced from your server when you submit; it is not an editable field.');
|
|
29
|
+
if (stores.includes('claude-plugins'))
|
|
30
|
+
r.push('Run "claude plugin validate --strict" in the plugin repo; the portal validates every commit on the branch it tracks.');
|
|
31
|
+
r.push('Claude: "Passed review" is not the same as listed. Check claude.ai/directory for your listing after approval; with the new portal you choose when to publish.');
|
|
32
|
+
}
|
|
33
|
+
return r;
|
|
34
|
+
}
|
|
35
|
+
/** Runtime-neutral: fetch only. Node adds socket checks (TLS 1.2) through extraChecks; see preflight-node. */
|
|
36
|
+
export async function preflight(m, opts = {}) {
|
|
37
|
+
const stores = opts.stores?.length ? opts.stores : storesOf(m);
|
|
38
|
+
const auth = m.server.auth ?? 'none';
|
|
39
|
+
const server = await serverChecks(m.server.url, auth, opts.token, opts.extraChecks);
|
|
40
|
+
const [authC, listing, repo] = await Promise.all([
|
|
41
|
+
auth === 'oauth' ? authChecks(m.server.url, { register: opts.register }) : Promise.resolve([]),
|
|
42
|
+
listingChecks(m, server.tools, stores),
|
|
43
|
+
repoChecks(m, stores),
|
|
44
|
+
]);
|
|
45
|
+
const all = [...server.checks, ...authC, ...toolChecks(server.tools), ...listing, ...repo];
|
|
46
|
+
// Keep checks that apply to every store or to one we're submitting to.
|
|
47
|
+
const checks = all.filter((c) => !c.stores?.length || c.stores.some((s) => stores.includes(s)));
|
|
48
|
+
return { checks, reminders: reminders(m, stores), stores };
|
|
49
|
+
}
|
|
50
|
+
export { draftManifest, storesOf, SCHEMA_URL } from './draft.js';
|
|
51
|
+
export { toolChecks } from './checks/tools.js';
|
|
52
|
+
export { STORE_NAMES } from './types.js';
|
package/dist/draft.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Manifest, type StoreId } from './types.js';
|
|
2
|
+
export declare const SCHEMA_URL = "https://unpkg.com/mcplane/schema.json";
|
|
3
|
+
export declare function storesOf(m: Manifest): StoreId[];
|
|
4
|
+
/** A starting manifest from what the live server and its domain reveal. */
|
|
5
|
+
export declare function draftManifest(url: string): Promise<Manifest>;
|
package/dist/draft.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Runtime-neutral pieces of the manifest: fetch only, no filesystem. */
|
|
2
|
+
import { McpClient } from './mcp-client.js';
|
|
3
|
+
import { STORE_NAMES } from './types.js';
|
|
4
|
+
export const SCHEMA_URL = 'https://unpkg.com/mcplane/schema.json';
|
|
5
|
+
export function storesOf(m) {
|
|
6
|
+
return m.stores?.length ? m.stores : Object.keys(STORE_NAMES);
|
|
7
|
+
}
|
|
8
|
+
/** A starting manifest from what the live server and its domain reveal. */
|
|
9
|
+
export async function draftManifest(url) {
|
|
10
|
+
const client = new McpClient(url);
|
|
11
|
+
const init = await client.initialize().catch(() => null);
|
|
12
|
+
const auth = init?.status === 401 ? 'oauth' : 'none';
|
|
13
|
+
const info = init?.body?.result?.serverInfo ?? {};
|
|
14
|
+
const host = new URL(url).hostname;
|
|
15
|
+
const labels = host.split('.');
|
|
16
|
+
// api.example.com and mcp.example.com usually keep their pages on example.com.
|
|
17
|
+
const origins = [`https://${host}`, ...(labels.length > 2 ? [`https://${labels.slice(1).join('.')}`] : [])];
|
|
18
|
+
const origin = origins[origins.length - 1];
|
|
19
|
+
const found = async (p) => {
|
|
20
|
+
for (const o of origins) {
|
|
21
|
+
const r = await fetch(o + p, { method: 'GET', redirect: 'follow' }).catch(() => null);
|
|
22
|
+
if (r?.ok && !(r.headers.get('content-type') ?? '').includes('json'))
|
|
23
|
+
return o + p;
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
};
|
|
27
|
+
const first = async (...paths) => {
|
|
28
|
+
for (const p of paths) {
|
|
29
|
+
const hit = await found(p);
|
|
30
|
+
if (hit)
|
|
31
|
+
return hit;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
};
|
|
35
|
+
const [privacy, support, terms, docs, icon] = await Promise.all([
|
|
36
|
+
first('/privacy', '/privacy-policy', '/legal/privacy'),
|
|
37
|
+
first('/support', '/contact', '/help'),
|
|
38
|
+
first('/terms', '/terms-of-service', '/legal/terms'),
|
|
39
|
+
first('/llms.txt', '/docs'),
|
|
40
|
+
first('/icon-512.png', '/icon.png', '/apple-touch-icon.png'),
|
|
41
|
+
]);
|
|
42
|
+
return {
|
|
43
|
+
$schema: SCHEMA_URL,
|
|
44
|
+
name: String(info.name ?? new URL(url).hostname.split('.')[0]),
|
|
45
|
+
title: String(info.title ?? info.name ?? ''),
|
|
46
|
+
subtitle: '',
|
|
47
|
+
oneLiner: '',
|
|
48
|
+
description: '',
|
|
49
|
+
server: { url, auth },
|
|
50
|
+
author: { name: '', url: origin },
|
|
51
|
+
links: { website: origin, support, privacy, terms, docs },
|
|
52
|
+
icon,
|
|
53
|
+
stores: ['mcp-registry', 'chatgpt', 'claude-connectors', 'cursor', 'muse'],
|
|
54
|
+
};
|
|
55
|
+
}
|
package/dist/drift.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { type Manifest, type StoreId, type Tool } from './types.js';
|
|
2
|
+
interface ToolShape {
|
|
3
|
+
name: string;
|
|
4
|
+
title?: string;
|
|
5
|
+
description?: string;
|
|
6
|
+
hints: {
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
destructive?: boolean;
|
|
9
|
+
openWorld?: boolean;
|
|
10
|
+
};
|
|
11
|
+
input: string;
|
|
12
|
+
output: string;
|
|
13
|
+
}
|
|
14
|
+
export interface Snapshot {
|
|
15
|
+
store: StoreId;
|
|
16
|
+
takenOn: string;
|
|
17
|
+
version?: string;
|
|
18
|
+
state: 'submitted' | 'live';
|
|
19
|
+
tools: ToolShape[];
|
|
20
|
+
listing: Record<string, string | undefined>;
|
|
21
|
+
repoSha?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DriftItem {
|
|
24
|
+
store: StoreId;
|
|
25
|
+
level: 'action' | 'info';
|
|
26
|
+
what: string;
|
|
27
|
+
todo: string;
|
|
28
|
+
}
|
|
29
|
+
export declare function liveTools(m: Manifest, token?: string): Promise<Tool[]>;
|
|
30
|
+
/** HEAD of the plugin repository on GitHub, when there is one. */
|
|
31
|
+
export declare function repoHead(repository?: string): Promise<string | undefined>;
|
|
32
|
+
export declare function takeSnapshot(m: Manifest, store: StoreId, opts: {
|
|
33
|
+
version?: string;
|
|
34
|
+
state: Snapshot['state'];
|
|
35
|
+
token?: string;
|
|
36
|
+
}, dir?: string): Promise<Snapshot>;
|
|
37
|
+
export declare function markLive(store: StoreId, dir?: string): Promise<void>;
|
|
38
|
+
/** What changed between a snapshot and now, as plain descriptions. */
|
|
39
|
+
export declare function diffTools(before: ToolShape[], now: ToolShape[]): {
|
|
40
|
+
added: string[];
|
|
41
|
+
removed: string[];
|
|
42
|
+
changed: {
|
|
43
|
+
tool: string;
|
|
44
|
+
fields: string[];
|
|
45
|
+
}[];
|
|
46
|
+
};
|
|
47
|
+
export declare function drift(m: Manifest, stores: StoreId[], opts?: {
|
|
48
|
+
token?: string;
|
|
49
|
+
}, dir?: string): Promise<{
|
|
50
|
+
items: DriftItem[];
|
|
51
|
+
missing: StoreId[];
|
|
52
|
+
}>;
|
|
53
|
+
export declare function printDrift(r: {
|
|
54
|
+
items: DriftItem[];
|
|
55
|
+
missing: StoreId[];
|
|
56
|
+
}): void;
|
|
57
|
+
export {};
|
package/dist/extras.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Manifest } from './types.js';
|
|
2
|
+
export declare const DEFAULT_LANES: Record<string, string[]>;
|
|
3
|
+
export declare function lanes(m: Manifest): Record<string, string[]>;
|
|
4
|
+
/**
|
|
5
|
+
* Runs a lane step by step. A step that fails stops the lane unless it starts
|
|
6
|
+
* with "?" (optional). "handoff:" steps are what only a person can do: policy
|
|
7
|
+
* statements, the final submit. They're collected and shown at the end.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runLane(name: string, steps: string[], passthrough: string[]): number;
|
|
10
|
+
export declare function tryLinks(m: Manifest): {
|
|
11
|
+
client: string;
|
|
12
|
+
how: string;
|
|
13
|
+
}[];
|
package/dist/fleet.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Manifest, StoreId } from './types.js';
|
|
2
|
+
export interface Project {
|
|
3
|
+
dir: string;
|
|
4
|
+
path: string;
|
|
5
|
+
manifest?: Manifest;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
/** Folders under root that hold an mcplane.json, depth-first, a few levels down. */
|
|
9
|
+
export declare function findProjects(root?: string, depth?: number): Promise<Project[]>;
|
|
10
|
+
export interface FleetRow {
|
|
11
|
+
project: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
url?: string;
|
|
14
|
+
blocking?: number;
|
|
15
|
+
toReview?: number;
|
|
16
|
+
driftActions?: {
|
|
17
|
+
store: StoreId;
|
|
18
|
+
what: string;
|
|
19
|
+
}[];
|
|
20
|
+
noSnapshot?: StoreId[];
|
|
21
|
+
waiting: {
|
|
22
|
+
store: StoreId;
|
|
23
|
+
since: string;
|
|
24
|
+
}[];
|
|
25
|
+
live: StoreId[];
|
|
26
|
+
listedOn?: StoreId[];
|
|
27
|
+
listingIssues?: {
|
|
28
|
+
store: StoreId;
|
|
29
|
+
issue: string;
|
|
30
|
+
}[];
|
|
31
|
+
error?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Preflight, drift, listings and submissions for every project under root. Checks run four projects at a time. */
|
|
34
|
+
export declare function fleet(root?: string, opts?: {
|
|
35
|
+
checks?: boolean;
|
|
36
|
+
token?: string;
|
|
37
|
+
}): Promise<FleetRow[]>;
|
|
38
|
+
export declare function printFleet(rows: FleetRow[]): void;
|
|
39
|
+
/** Runs one mcplane command in every project, one after another. */
|
|
40
|
+
export declare function fleetRun(root: string, args: string[]): Promise<number>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Manifest, type StoreId } from './types.js';
|
|
2
|
+
export interface Listing {
|
|
3
|
+
store: StoreId;
|
|
4
|
+
state: 'listed' | 'missing' | 'unknown';
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Differences between the live record and your server or mcplane.json. */
|
|
7
|
+
issues: string[];
|
|
8
|
+
note?: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function printListings(rows: Listing[]): void;
|
|
11
|
+
export declare function listings(m: Manifest, stores: StoreId[], opts?: {
|
|
12
|
+
token?: string;
|
|
13
|
+
}): Promise<Listing[]>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Manifest } from './types.js';
|
|
2
|
+
export { draftManifest, storesOf } from './draft.js';
|
|
3
|
+
export declare const MANIFEST_FILE = "mcplane.json";
|
|
4
|
+
export { SCHEMA_URL } from './draft.js';
|
|
5
|
+
export declare function loadManifest(dir?: string): Promise<Manifest>;
|
|
6
|
+
/**
|
|
7
|
+
* Creates .mcplane with its own .gitignore: snapshots are committed (CI reads
|
|
8
|
+
* them for drift), while submissions (private Review Times tokens) and packs stay local.
|
|
9
|
+
*/
|
|
10
|
+
export declare function ensureLocalDir(dir?: string): Promise<string>;
|
package/dist/manifest.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { McpClient } from './mcp-client.js';
|
|
4
3
|
import { STORE_NAMES } from './types.js';
|
|
4
|
+
export { draftManifest, storesOf } from './draft.js';
|
|
5
5
|
export const MANIFEST_FILE = 'mcplane.json';
|
|
6
|
-
export
|
|
6
|
+
export { SCHEMA_URL } from './draft.js';
|
|
7
7
|
export async function loadManifest(dir = process.cwd()) {
|
|
8
8
|
const path = resolve(dir, MANIFEST_FILE);
|
|
9
9
|
let raw;
|
|
@@ -44,54 +44,3 @@ export async function ensureLocalDir(dir = process.cwd()) {
|
|
|
44
44
|
await writeFile(resolve(d, '.gitignore'), 'submissions.json\npacks/\n', { flag: 'wx' }).catch(() => null);
|
|
45
45
|
return d;
|
|
46
46
|
}
|
|
47
|
-
export function storesOf(m) {
|
|
48
|
-
return m.stores?.length ? m.stores : Object.keys(STORE_NAMES);
|
|
49
|
-
}
|
|
50
|
-
/** A starting manifest from what the live server and its domain reveal. */
|
|
51
|
-
export async function draftManifest(url) {
|
|
52
|
-
const client = new McpClient(url);
|
|
53
|
-
const init = await client.initialize().catch(() => null);
|
|
54
|
-
const auth = init?.status === 401 ? 'oauth' : 'none';
|
|
55
|
-
const info = init?.body?.result?.serverInfo ?? {};
|
|
56
|
-
const host = new URL(url).hostname;
|
|
57
|
-
const labels = host.split('.');
|
|
58
|
-
// api.example.com and mcp.example.com usually keep their pages on example.com.
|
|
59
|
-
const origins = [`https://${host}`, ...(labels.length > 2 ? [`https://${labels.slice(1).join('.')}`] : [])];
|
|
60
|
-
const origin = origins[origins.length - 1];
|
|
61
|
-
const found = async (p) => {
|
|
62
|
-
for (const o of origins) {
|
|
63
|
-
const r = await fetch(o + p, { method: 'GET', redirect: 'follow' }).catch(() => null);
|
|
64
|
-
if (r?.ok && !(r.headers.get('content-type') ?? '').includes('json'))
|
|
65
|
-
return o + p;
|
|
66
|
-
}
|
|
67
|
-
return undefined;
|
|
68
|
-
};
|
|
69
|
-
const first = async (...paths) => {
|
|
70
|
-
for (const p of paths) {
|
|
71
|
-
const hit = await found(p);
|
|
72
|
-
if (hit)
|
|
73
|
-
return hit;
|
|
74
|
-
}
|
|
75
|
-
return undefined;
|
|
76
|
-
};
|
|
77
|
-
const [privacy, support, terms, docs, icon] = await Promise.all([
|
|
78
|
-
first('/privacy', '/privacy-policy', '/legal/privacy'),
|
|
79
|
-
first('/support', '/contact', '/help'),
|
|
80
|
-
first('/terms', '/terms-of-service', '/legal/terms'),
|
|
81
|
-
first('/llms.txt', '/docs'),
|
|
82
|
-
first('/icon-512.png', '/icon.png', '/apple-touch-icon.png'),
|
|
83
|
-
]);
|
|
84
|
-
return {
|
|
85
|
-
$schema: SCHEMA_URL,
|
|
86
|
-
name: String(info.name ?? new URL(url).hostname.split('.')[0]),
|
|
87
|
-
title: String(info.title ?? info.name ?? ''),
|
|
88
|
-
subtitle: '',
|
|
89
|
-
oneLiner: '',
|
|
90
|
-
description: '',
|
|
91
|
-
server: { url, auth },
|
|
92
|
-
author: { name: '', url: origin },
|
|
93
|
-
links: { website: origin, support, privacy, terms, docs },
|
|
94
|
-
icon,
|
|
95
|
-
stores: ['mcp-registry', 'chatgpt', 'claude-connectors', 'cursor', 'muse'],
|
|
96
|
-
};
|
|
97
|
-
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Just enough of a streamable-HTTP MCP client to inspect a server the way a
|
|
3
|
+
* store's scanner does: plain POSTs, JSON or SSE replies, a session id if the
|
|
4
|
+
* server hands one out.
|
|
5
|
+
*/
|
|
6
|
+
export interface RpcReply {
|
|
7
|
+
status: number;
|
|
8
|
+
contentType: string;
|
|
9
|
+
body: any;
|
|
10
|
+
headers: Headers;
|
|
11
|
+
}
|
|
12
|
+
export declare class McpClient {
|
|
13
|
+
readonly url: string;
|
|
14
|
+
private extraHeaders;
|
|
15
|
+
private timeoutMs;
|
|
16
|
+
private session;
|
|
17
|
+
private id;
|
|
18
|
+
constructor(url: string, extraHeaders?: Record<string, string>, timeoutMs?: number);
|
|
19
|
+
request(method: string, params?: unknown, opts?: {
|
|
20
|
+
notification?: boolean;
|
|
21
|
+
url?: string;
|
|
22
|
+
}): Promise<RpcReply>;
|
|
23
|
+
initialize(): Promise<RpcReply>;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function serve(): Promise<void>;
|
package/dist/packs.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Manifest, StoreId, Tool } from './types.js';
|
|
2
|
+
export interface Pack {
|
|
3
|
+
files: {
|
|
4
|
+
path: string;
|
|
5
|
+
content: string;
|
|
6
|
+
}[];
|
|
7
|
+
problems: string[];
|
|
8
|
+
todo: string[];
|
|
9
|
+
}
|
|
10
|
+
/** "Add the user's…" → "adds the user's…"; leaves non-verb openings alone. */
|
|
11
|
+
declare function thirdPerson(s: string): string | null;
|
|
12
|
+
/** One-sentence drafts from the declared hints and the tool's own description. Review them: they are a start, not an answer. */
|
|
13
|
+
declare function justify(t: Tool, product: string, own?: {
|
|
14
|
+
readOnly?: string;
|
|
15
|
+
openWorld?: string;
|
|
16
|
+
destructive?: string;
|
|
17
|
+
}): {
|
|
18
|
+
read_only_justification: string;
|
|
19
|
+
open_world_justification: string;
|
|
20
|
+
destructive_justification: string;
|
|
21
|
+
};
|
|
22
|
+
export declare function chatgptPack(m: Manifest, token?: string): Promise<Pack>;
|
|
23
|
+
export declare function claudePack(m: Manifest, token?: string): Promise<Pack>;
|
|
24
|
+
export declare function simplePack(m: Manifest, store: StoreId): Promise<Pack>;
|
|
25
|
+
/** The long tail: directories that only take a web form. One sheet with every value to paste. */
|
|
26
|
+
export declare function directoriesPack(m: Manifest): Pack;
|
|
27
|
+
export declare function writePack(pack: Pack, dir?: string): Promise<string[]>;
|
|
28
|
+
export declare const _test: {
|
|
29
|
+
thirdPerson: typeof thirdPerson;
|
|
30
|
+
justify: typeof justify;
|
|
31
|
+
};
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type PreflightResult } from './core.js';
|
|
2
|
+
import type { Manifest, StoreId } from './types.js';
|
|
3
|
+
export type { PreflightResult } from './core.js';
|
|
4
|
+
/** Preflight with everything Node can check, the TLS 1.2 handshake included. */
|
|
5
|
+
export declare function preflight(m: Manifest, opts?: {
|
|
6
|
+
stores?: StoreId[];
|
|
7
|
+
register?: boolean;
|
|
8
|
+
token?: string;
|
|
9
|
+
}): Promise<PreflightResult>;
|
package/dist/preflight.js
CHANGED
|
@@ -1,44 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/** Things no script can see, which have still cost real submissions. */
|
|
7
|
-
function reminders(m, stores) {
|
|
8
|
-
const r = [];
|
|
9
|
-
if (m.server.auth === 'oauth') {
|
|
10
|
-
r.push('Give reviewers a demo account a stranger can use: email and password (not your Google or SSO login), email already confirmed, no 2FA or SMS, seeded with data for every test case. Re-check the seed right before submitting.');
|
|
11
|
-
r.push('Sign in through your OAuth flow once as a brand-new user. A blank post-sign-in redirect stranded a ChatGPT reviewer between registration and consent.');
|
|
12
|
-
}
|
|
13
|
-
if (stores.some((s) => s === 'chatgpt' || s === 'claude-connectors')) {
|
|
14
|
-
r.push('If a tool writes somewhere public (posts, sends, publishes), give reviewers a safe target: a sandbox account or a "playground" option that validates and then discards. Reviewers test writes; they shouldn’t post to a real timeline.');
|
|
15
|
-
}
|
|
16
|
-
if (stores.includes('chatgpt')) {
|
|
17
|
-
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.');
|
|
18
|
-
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.');
|
|
19
|
-
r.push('Tool-annotation justifications are cut at 200 characters without warning.');
|
|
20
|
-
r.push('ChatGPT allows one version in review at a time. To change a submission, Cancel Review and resubmit the same draft.');
|
|
21
|
-
}
|
|
22
|
-
if (stores.includes('claude-connectors') || stores.includes('claude-plugins')) {
|
|
23
|
-
if (stores.includes('claude-connectors'))
|
|
24
|
-
r.push('Claude connectors are submitted from an organisation’s admin settings (claude.ai/directory/manage), so you need admin rights in a paid org. The tool list is synced from your server when you submit; it is not an editable field.');
|
|
25
|
-
if (stores.includes('claude-plugins'))
|
|
26
|
-
r.push('Run "claude plugin validate --strict" in the plugin repo; the portal validates every commit on the branch it tracks.');
|
|
27
|
-
r.push('Claude: "Passed review" is not the same as listed. Check claude.ai/directory for your listing after approval; with the new portal you choose when to publish.');
|
|
28
|
-
}
|
|
29
|
-
return r;
|
|
30
|
-
}
|
|
31
|
-
export async function preflight(m, opts = {}) {
|
|
32
|
-
const stores = opts.stores?.length ? opts.stores : storesOf(m);
|
|
33
|
-
const auth = m.server.auth ?? 'none';
|
|
34
|
-
const server = await serverChecks(m.server.url, auth, opts.token);
|
|
35
|
-
const [authC, listing, repo] = await Promise.all([
|
|
36
|
-
auth === 'oauth' ? authChecks(m.server.url, { register: opts.register }) : Promise.resolve([]),
|
|
37
|
-
listingChecks(m, server.tools, stores),
|
|
38
|
-
repoChecks(m, stores),
|
|
39
|
-
]);
|
|
40
|
-
const all = [...server.checks, ...authC, ...toolChecks(server.tools), ...listing, ...repo];
|
|
41
|
-
// Keep checks that apply to every store or to one we're submitting to.
|
|
42
|
-
const checks = all.filter((c) => !c.stores?.length || c.stores.some((s) => stores.includes(s)));
|
|
43
|
-
return { checks, reminders: reminders(m, stores), stores };
|
|
1
|
+
import { tls12Check } from './checks/tls.js';
|
|
2
|
+
import { preflight as core } from './core.js';
|
|
3
|
+
/** Preflight with everything Node can check, the TLS 1.2 handshake included. */
|
|
4
|
+
export function preflight(m, opts = {}) {
|
|
5
|
+
return core(m, { ...opts, extraChecks: [tls12Check] });
|
|
44
6
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Manifest, StoreId } from './types.js';
|
|
2
|
+
export interface PublishResult {
|
|
3
|
+
store: StoreId;
|
|
4
|
+
done: boolean;
|
|
5
|
+
lines: string[];
|
|
6
|
+
url?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function registryName(m: Manifest): string;
|
|
9
|
+
export declare function serverJson(m: Manifest): {
|
|
10
|
+
version: string;
|
|
11
|
+
remotes: {
|
|
12
|
+
type: string;
|
|
13
|
+
url: string;
|
|
14
|
+
}[];
|
|
15
|
+
repository?: {
|
|
16
|
+
url: string;
|
|
17
|
+
source: string;
|
|
18
|
+
} | undefined;
|
|
19
|
+
websiteUrl?: string | undefined;
|
|
20
|
+
$schema: string;
|
|
21
|
+
name: string;
|
|
22
|
+
title: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
declare function grokEntry(m: Manifest, sha: string): {
|
|
26
|
+
keywords: string[];
|
|
27
|
+
domains: string[];
|
|
28
|
+
homepage?: string | undefined;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string;
|
|
31
|
+
category: string;
|
|
32
|
+
source: {
|
|
33
|
+
source: string;
|
|
34
|
+
url: string;
|
|
35
|
+
sha: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/** Adds or re-pins our entry without reformatting the rest of the file. */
|
|
39
|
+
declare function editMarketplace(text: string, entry: ReturnType<typeof grokEntry>): {
|
|
40
|
+
text: string;
|
|
41
|
+
mode: 'add' | 'bump' | 'same';
|
|
42
|
+
};
|
|
43
|
+
export declare function publish(m: Manifest, store: StoreId, opts?: {
|
|
44
|
+
yes?: boolean;
|
|
45
|
+
dir?: string;
|
|
46
|
+
}): Promise<PublishResult>;
|
|
47
|
+
export declare const _test: {
|
|
48
|
+
editMarketplace: typeof editMarketplace;
|
|
49
|
+
grokEntry: typeof grokEntry;
|
|
50
|
+
nextPrerelease: (v: string) => string;
|
|
51
|
+
};
|
|
52
|
+
export {};
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Manifest, StoreId } from './types.js';
|
|
2
|
+
export interface Submission {
|
|
3
|
+
store: StoreId;
|
|
4
|
+
kind: 'new' | 'update' | 'resubmission';
|
|
5
|
+
version?: string;
|
|
6
|
+
submittedOn: string;
|
|
7
|
+
status: 'waiting' | 'approved' | 'rejected' | 'withdrawn';
|
|
8
|
+
decidedOn?: string;
|
|
9
|
+
reviewTimes?: {
|
|
10
|
+
token: string;
|
|
11
|
+
shareUrl: string | null;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export declare function load(dir?: string): Promise<Submission[]>;
|
|
15
|
+
export declare function recordSubmitted(m: Manifest, store: StoreId, opts: {
|
|
16
|
+
date?: string;
|
|
17
|
+
kind?: Submission['kind'];
|
|
18
|
+
version?: string;
|
|
19
|
+
appId?: string;
|
|
20
|
+
share?: boolean;
|
|
21
|
+
token?: string;
|
|
22
|
+
}, dir?: string): Promise<{
|
|
23
|
+
submission: Submission;
|
|
24
|
+
note: string;
|
|
25
|
+
}>;
|
|
26
|
+
export declare function recordDecision(store: StoreId, outcome: 'approved' | 'rejected' | 'withdrawn', date?: string, dir?: string): Promise<Submission>;
|
|
27
|
+
export interface StatusRow {
|
|
28
|
+
submission: Submission;
|
|
29
|
+
days: number;
|
|
30
|
+
typical: string | null;
|
|
31
|
+
}
|
|
32
|
+
/** Refreshes each waiting submission from Review Times (the directory watch may have closed it) and adds the store's typical wait. */
|
|
33
|
+
export declare function status(dir?: string): Promise<StatusRow[]>;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
export type Level = 'pass' | 'warn' | 'fail' | 'skip';
|
|
2
|
+
export interface Check {
|
|
3
|
+
id: string;
|
|
4
|
+
level: Level;
|
|
5
|
+
title: string;
|
|
6
|
+
/** What we found. */
|
|
7
|
+
detail?: string;
|
|
8
|
+
/** How to fix it, when it isn't a pass. */
|
|
9
|
+
fix?: string;
|
|
10
|
+
/** The stores this matters for; empty means all. */
|
|
11
|
+
stores?: StoreId[];
|
|
12
|
+
}
|
|
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 declare const STORE_NAMES: Record<StoreId, string>;
|
|
15
|
+
export interface Manifest {
|
|
16
|
+
$schema?: string;
|
|
17
|
+
name: string;
|
|
18
|
+
title: string;
|
|
19
|
+
subtitle?: string;
|
|
20
|
+
oneLiner?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
category?: string;
|
|
23
|
+
server: {
|
|
24
|
+
url: string;
|
|
25
|
+
auth?: 'none' | 'oauth';
|
|
26
|
+
};
|
|
27
|
+
repository?: string;
|
|
28
|
+
author?: {
|
|
29
|
+
name: string;
|
|
30
|
+
url?: string;
|
|
31
|
+
};
|
|
32
|
+
links?: {
|
|
33
|
+
website?: string;
|
|
34
|
+
support?: string;
|
|
35
|
+
privacy?: string;
|
|
36
|
+
terms?: string;
|
|
37
|
+
docs?: string;
|
|
38
|
+
};
|
|
39
|
+
icon?: string;
|
|
40
|
+
prompts?: string[];
|
|
41
|
+
stores?: StoreId[];
|
|
42
|
+
/** Review test cases: ChatGPT wants exactly 5 positive and 3 negative. */
|
|
43
|
+
tests?: {
|
|
44
|
+
positive?: {
|
|
45
|
+
scenario: string;
|
|
46
|
+
prompt: string;
|
|
47
|
+
tools: string[];
|
|
48
|
+
expected: string;
|
|
49
|
+
}[];
|
|
50
|
+
negative?: {
|
|
51
|
+
scenario: string;
|
|
52
|
+
prompt: string;
|
|
53
|
+
}[];
|
|
54
|
+
};
|
|
55
|
+
/** Hand-written ChatGPT hint justifications, per tool; they replace mcplane's drafts. */
|
|
56
|
+
justifications?: Record<string, {
|
|
57
|
+
readOnly?: string;
|
|
58
|
+
openWorld?: string;
|
|
59
|
+
destructive?: string;
|
|
60
|
+
}>;
|
|
61
|
+
/** Your server's version, for the MCP Registry and your own records. */
|
|
62
|
+
version?: string;
|
|
63
|
+
/** MCP Registry overrides: reverse-DNS name (io.github.<owner>/<name> by default), description (100 max), file path. */
|
|
64
|
+
registry?: {
|
|
65
|
+
name?: string;
|
|
66
|
+
description?: string;
|
|
67
|
+
file?: string;
|
|
68
|
+
};
|
|
69
|
+
/** Grok marketplace entry overrides. */
|
|
70
|
+
grok?: {
|
|
71
|
+
description?: string;
|
|
72
|
+
category?: string;
|
|
73
|
+
keywords?: string[];
|
|
74
|
+
domains?: string[];
|
|
75
|
+
};
|
|
76
|
+
/** Named workflows, run with "mcplane <lane>": each step is a command line without "mcplane". */
|
|
77
|
+
lanes?: Record<string, string[]>;
|
|
78
|
+
/** How reviewers get in, for servers behind sign-in. Never put the password here; say where it lives. */
|
|
79
|
+
reviewerAccess?: string;
|
|
80
|
+
}
|
|
81
|
+
export interface Tool {
|
|
82
|
+
name: string;
|
|
83
|
+
title?: string;
|
|
84
|
+
description?: string;
|
|
85
|
+
inputSchema?: {
|
|
86
|
+
properties?: Record<string, unknown>;
|
|
87
|
+
required?: string[];
|
|
88
|
+
};
|
|
89
|
+
outputSchema?: unknown;
|
|
90
|
+
annotations?: {
|
|
91
|
+
title?: string;
|
|
92
|
+
readOnlyHint?: boolean;
|
|
93
|
+
destructiveHint?: boolean;
|
|
94
|
+
idempotentHint?: boolean;
|
|
95
|
+
openWorldHint?: boolean;
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const VERSION = "0.3.1";
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VERSION = '0.
|
|
1
|
+
export const VERSION = '0.3.1';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcplane",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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": {
|
|
@@ -52,5 +52,13 @@
|
|
|
52
52
|
"url": "https://github.com/stevysmith/mcplane/issues"
|
|
53
53
|
},
|
|
54
54
|
"author": "Steve Smith",
|
|
55
|
-
"mcpName": "io.github.stevysmith/mcplane"
|
|
55
|
+
"mcpName": "io.github.stevysmith/mcplane",
|
|
56
|
+
"exports": {
|
|
57
|
+
"./core": {
|
|
58
|
+
"types": "./dist/core.d.ts",
|
|
59
|
+
"default": "./dist/core.js"
|
|
60
|
+
},
|
|
61
|
+
"./schema.json": "./schema.json",
|
|
62
|
+
"./package.json": "./package.json"
|
|
63
|
+
}
|
|
56
64
|
}
|