mcplane 0.1.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/LICENSE +21 -0
- package/README.md +201 -0
- package/dist/checks/auth.js +72 -0
- package/dist/checks/listing.js +149 -0
- package/dist/checks/server.js +148 -0
- package/dist/checks/tools.js +122 -0
- package/dist/cli.js +264 -0
- package/dist/drift.js +207 -0
- package/dist/extras.js +109 -0
- package/dist/fleet.js +112 -0
- package/dist/manifest.js +97 -0
- package/dist/mcp-client.js +70 -0
- package/dist/mcp-server.js +172 -0
- package/dist/packs.js +278 -0
- package/dist/preflight.js +39 -0
- package/dist/publish.js +271 -0
- package/dist/report.js +31 -0
- package/dist/submissions.js +126 -0
- package/dist/types.js +15 -0
- package/dist/version.js +1 -0
- package/package.json +56 -0
- package/schema.json +283 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Your submissions, kept in .mcplane/submissions.json (gitignored: it holds
|
|
3
|
+
* the private tokens that update your Review Times reports), and logged to
|
|
4
|
+
* Review Times so every wait counts toward the public numbers.
|
|
5
|
+
*/
|
|
6
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
7
|
+
import { resolve } from 'node:path';
|
|
8
|
+
import { ensureLocalDir } from './manifest.js';
|
|
9
|
+
import { markLive, takeSnapshot } from './drift.js';
|
|
10
|
+
const RT = process.env.MCPLANE_REVIEWTIMES_URL ?? 'https://reviewtimes.fyi';
|
|
11
|
+
const DIR = '.mcplane';
|
|
12
|
+
const FILE = `${DIR}/submissions.json`;
|
|
13
|
+
/** Review Times store slugs. Grok and Docker are read straight from their GitHub queues, so they aren't reported twice. */
|
|
14
|
+
const RT_SLUG = {
|
|
15
|
+
chatgpt: 'chatgpt',
|
|
16
|
+
'claude-connectors': 'claude-connectors',
|
|
17
|
+
'claude-plugins': 'claude-plugins',
|
|
18
|
+
cursor: 'cursor',
|
|
19
|
+
muse: 'muse',
|
|
20
|
+
};
|
|
21
|
+
const TRACKED_FROM_GITHUB = ['grok', 'docker'];
|
|
22
|
+
export async function load(dir = process.cwd()) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(await readFile(resolve(dir, FILE), 'utf8'));
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function save(list, dir = process.cwd()) {
|
|
31
|
+
await ensureLocalDir(dir);
|
|
32
|
+
await writeFile(resolve(dir, FILE), JSON.stringify(list, null, 2) + '\n');
|
|
33
|
+
}
|
|
34
|
+
const today = () => new Date().toISOString().slice(0, 10);
|
|
35
|
+
/** What identifies this listing to Review Times' directory watch, so the report can close itself. */
|
|
36
|
+
function listingRef(m, store, appId) {
|
|
37
|
+
if (store === 'chatgpt')
|
|
38
|
+
return appId;
|
|
39
|
+
if (store === 'claude-connectors' || store === 'muse')
|
|
40
|
+
return m.server.url;
|
|
41
|
+
if (store === 'claude-plugins' || store === 'cursor')
|
|
42
|
+
return m.repository;
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
export async function recordSubmitted(m, store, opts, dir = process.cwd()) {
|
|
46
|
+
const s = { store, kind: opts.kind ?? 'new', version: opts.version, submittedOn: opts.date ?? today(), status: 'waiting' };
|
|
47
|
+
let note;
|
|
48
|
+
const slug = RT_SLUG[store];
|
|
49
|
+
if (TRACKED_FROM_GITHUB.includes(store)) {
|
|
50
|
+
note = 'Review Times reads this store’s review queue from GitHub, so it counts your pull request already.';
|
|
51
|
+
}
|
|
52
|
+
else if (!slug) {
|
|
53
|
+
s.status = 'approved';
|
|
54
|
+
s.decidedOn = s.submittedOn;
|
|
55
|
+
note = 'This store publishes without a review, so it counts as live from today.';
|
|
56
|
+
}
|
|
57
|
+
else if (opts.share === false) {
|
|
58
|
+
note = 'Kept locally only (--private).';
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
const res = await fetch(`${RT}/api/v1/reports`, {
|
|
62
|
+
method: 'POST',
|
|
63
|
+
headers: { 'content-type': 'application/json', 'user-agent': 'mcplane' },
|
|
64
|
+
body: JSON.stringify({ store: slug, submitted_on: s.submittedOn, status: 'waiting', kind: s.kind, listing_ref: listingRef(m, store, opts.appId) }),
|
|
65
|
+
});
|
|
66
|
+
const body = (await res.json().catch(() => ({})));
|
|
67
|
+
if (!res.ok || !body.token)
|
|
68
|
+
throw new Error(`Review Times didn't take the report: ${body.error ?? `HTTP ${res.status}`}`);
|
|
69
|
+
s.reviewTimes = { token: body.token, shareUrl: body.share_url ?? null };
|
|
70
|
+
note = `Logged to Review Times (anonymous). Public page: ${body.share_url ?? `${RT}/${slug}`}`;
|
|
71
|
+
}
|
|
72
|
+
const list = await load(dir);
|
|
73
|
+
list.push(s);
|
|
74
|
+
await save(list, dir);
|
|
75
|
+
// What the store is reviewing, so "mcplane drift" can tell when the live server moves away from it.
|
|
76
|
+
await takeSnapshot(m, store, { version: opts.version, state: s.status === 'approved' ? 'live' : 'submitted', token: opts.token }, dir).catch(() => null);
|
|
77
|
+
return { submission: s, note };
|
|
78
|
+
}
|
|
79
|
+
export async function recordDecision(store, outcome, date, dir = process.cwd()) {
|
|
80
|
+
const list = await load(dir);
|
|
81
|
+
const s = [...list].reverse().find((x) => x.store === store && x.status === 'waiting');
|
|
82
|
+
if (!s)
|
|
83
|
+
throw new Error(`No waiting ${store} submission recorded. Run "mcplane submitted ${store}" first.`);
|
|
84
|
+
s.status = outcome;
|
|
85
|
+
s.decidedOn = date ?? today();
|
|
86
|
+
if (s.reviewTimes) {
|
|
87
|
+
const res = await fetch(`${RT}/api/v1/reports/${s.reviewTimes.token}`, {
|
|
88
|
+
method: 'PATCH',
|
|
89
|
+
headers: { 'content-type': 'application/json', 'user-agent': 'mcplane' },
|
|
90
|
+
body: JSON.stringify({ status: outcome, decided_on: s.decidedOn }),
|
|
91
|
+
});
|
|
92
|
+
if (!res.ok)
|
|
93
|
+
throw new Error(`Review Times didn't take the update: HTTP ${res.status}`);
|
|
94
|
+
}
|
|
95
|
+
await save(list, dir);
|
|
96
|
+
if (outcome === 'approved')
|
|
97
|
+
await markLive(store, dir);
|
|
98
|
+
return s;
|
|
99
|
+
}
|
|
100
|
+
/** Refreshes each waiting submission from Review Times (the directory watch may have closed it) and adds the store's typical wait. */
|
|
101
|
+
export async function status(dir = process.cwd()) {
|
|
102
|
+
const list = await load(dir);
|
|
103
|
+
const verdicts = new Map();
|
|
104
|
+
for (const s of list) {
|
|
105
|
+
if (s.status === 'waiting' && s.reviewTimes) {
|
|
106
|
+
const r = await fetch(`${RT}/api/v1/reports/${s.reviewTimes.token}`, { headers: { 'user-agent': 'mcplane' } }).catch(() => null);
|
|
107
|
+
const b = r?.ok ? (await r.json()) : null;
|
|
108
|
+
if (b?.status && b.status !== 'waiting') {
|
|
109
|
+
s.status = b.status;
|
|
110
|
+
s.decidedOn = b.decided_on ?? undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
const slug = RT_SLUG[s.store] ?? (s.store === 'docker' ? 'docker-mcp' : s.store === 'grok' ? 'grok' : null);
|
|
114
|
+
if (slug && !verdicts.has(slug)) {
|
|
115
|
+
const r = await fetch(`${RT}/api/v1/stores/${slug}`, { headers: { 'user-agent': 'mcplane' } }).catch(() => null);
|
|
116
|
+
verdicts.set(slug, r?.ok ? ((await r.json()).verdict ?? null) : null);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
await save(list, dir);
|
|
120
|
+
const dayMs = 86_400_000;
|
|
121
|
+
return list.map((s) => {
|
|
122
|
+
const end = s.decidedOn ? Date.parse(s.decidedOn) : Date.now();
|
|
123
|
+
const slug = RT_SLUG[s.store] ?? (s.store === 'docker' ? 'docker-mcp' : s.store === 'grok' ? 'grok' : null);
|
|
124
|
+
return { submission: s, days: Math.max(0, Math.floor((end - Date.parse(s.submittedOn)) / dayMs)), typical: slug ? (verdicts.get(slug) ?? null) : null };
|
|
125
|
+
});
|
|
126
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const STORE_NAMES = {
|
|
2
|
+
'mcp-registry': 'Official MCP Registry',
|
|
3
|
+
chatgpt: 'ChatGPT',
|
|
4
|
+
'claude-connectors': 'Claude connectors',
|
|
5
|
+
'claude-plugins': 'Claude plugins',
|
|
6
|
+
cursor: 'Cursor Marketplace',
|
|
7
|
+
grok: 'Grok plugins',
|
|
8
|
+
docker: 'Docker MCP Catalog',
|
|
9
|
+
muse: 'Muse connectors',
|
|
10
|
+
smithery: 'Smithery',
|
|
11
|
+
glama: 'Glama',
|
|
12
|
+
cline: 'Cline MCP Marketplace',
|
|
13
|
+
lobehub: 'LobeHub Market',
|
|
14
|
+
'awesome-mcp-servers': 'awesome-mcp-servers',
|
|
15
|
+
};
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const VERSION = '0.1.0';
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcplane",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "fastlane for MCP servers: preflight against every store's rejection causes, publish where you can, prepare the rest, track every review.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"mcplane": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"schema.json",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=20"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc -p .",
|
|
20
|
+
"dev": "tsx src/cli.ts",
|
|
21
|
+
"typecheck": "tsc --noEmit -p .",
|
|
22
|
+
"test": "node --import tsx --test test/*.test.ts",
|
|
23
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"mcp",
|
|
27
|
+
"model-context-protocol",
|
|
28
|
+
"chatgpt",
|
|
29
|
+
"claude",
|
|
30
|
+
"cursor",
|
|
31
|
+
"plugin",
|
|
32
|
+
"connector",
|
|
33
|
+
"submission",
|
|
34
|
+
"fastlane"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^22.20.4",
|
|
39
|
+
"tsx": "^4.23.15",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
44
|
+
"zod": "^4.6.5"
|
|
45
|
+
},
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "git+https://github.com/stevysmith/mcplane.git"
|
|
49
|
+
},
|
|
50
|
+
"homepage": "https://github.com/stevysmith/mcplane#readme",
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/stevysmith/mcplane/issues"
|
|
53
|
+
},
|
|
54
|
+
"author": "Steve Smith",
|
|
55
|
+
"mcpName": "io.github.stevysmith/mcplane"
|
|
56
|
+
}
|
package/schema.json
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://unpkg.com/mcplane/schema.json",
|
|
4
|
+
"title": "mcplane.json",
|
|
5
|
+
"description": "One description of an MCP server and its listings, used by mcplane to check, pack, publish and track store submissions.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"name",
|
|
9
|
+
"title",
|
|
10
|
+
"server"
|
|
11
|
+
],
|
|
12
|
+
"additionalProperties": false,
|
|
13
|
+
"$defs": {
|
|
14
|
+
"store": {
|
|
15
|
+
"enum": [
|
|
16
|
+
"mcp-registry",
|
|
17
|
+
"chatgpt",
|
|
18
|
+
"claude-connectors",
|
|
19
|
+
"claude-plugins",
|
|
20
|
+
"cursor",
|
|
21
|
+
"grok",
|
|
22
|
+
"docker",
|
|
23
|
+
"muse",
|
|
24
|
+
"smithery",
|
|
25
|
+
"glama",
|
|
26
|
+
"cline",
|
|
27
|
+
"lobehub",
|
|
28
|
+
"awesome-mcp-servers"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"url": {
|
|
32
|
+
"type": "string",
|
|
33
|
+
"format": "uri"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"properties": {
|
|
37
|
+
"$schema": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"name": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"pattern": "^[a-z0-9][a-z0-9-]*$",
|
|
43
|
+
"description": "Short machine name, lowercase with hyphens. Used for plugin entries and install links."
|
|
44
|
+
},
|
|
45
|
+
"title": {
|
|
46
|
+
"type": "string",
|
|
47
|
+
"description": "Display name in every store."
|
|
48
|
+
},
|
|
49
|
+
"subtitle": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"maxLength": 30,
|
|
52
|
+
"description": "ChatGPT's subtitle (30 characters)."
|
|
53
|
+
},
|
|
54
|
+
"oneLiner": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"description": "One sentence: what it does. Claude's one-liner, the registry and plugin descriptions start from it."
|
|
57
|
+
},
|
|
58
|
+
"description": {
|
|
59
|
+
"type": "string",
|
|
60
|
+
"description": "The long description."
|
|
61
|
+
},
|
|
62
|
+
"category": {
|
|
63
|
+
"type": "string",
|
|
64
|
+
"description": "ChatGPT category, e.g. DEVELOPER_TOOLS, PRODUCTIVITY, BUSINESS."
|
|
65
|
+
},
|
|
66
|
+
"server": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"required": [
|
|
69
|
+
"url"
|
|
70
|
+
],
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"properties": {
|
|
73
|
+
"url": {
|
|
74
|
+
"$ref": "#/$defs/url",
|
|
75
|
+
"description": "Your streamable HTTP endpoint, exactly as reviewers will add it."
|
|
76
|
+
},
|
|
77
|
+
"auth": {
|
|
78
|
+
"enum": [
|
|
79
|
+
"none",
|
|
80
|
+
"oauth"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"repository": {
|
|
86
|
+
"$ref": "#/$defs/url",
|
|
87
|
+
"description": "Public GitHub repository for plugin stores (Claude plugins, Cursor, Grok)."
|
|
88
|
+
},
|
|
89
|
+
"author": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"required": [
|
|
92
|
+
"name"
|
|
93
|
+
],
|
|
94
|
+
"additionalProperties": false,
|
|
95
|
+
"properties": {
|
|
96
|
+
"name": {
|
|
97
|
+
"type": "string"
|
|
98
|
+
},
|
|
99
|
+
"url": {
|
|
100
|
+
"$ref": "#/$defs/url"
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"links": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"properties": {
|
|
108
|
+
"website": {
|
|
109
|
+
"$ref": "#/$defs/url"
|
|
110
|
+
},
|
|
111
|
+
"support": {
|
|
112
|
+
"$ref": "#/$defs/url"
|
|
113
|
+
},
|
|
114
|
+
"privacy": {
|
|
115
|
+
"$ref": "#/$defs/url"
|
|
116
|
+
},
|
|
117
|
+
"terms": {
|
|
118
|
+
"$ref": "#/$defs/url"
|
|
119
|
+
},
|
|
120
|
+
"docs": {
|
|
121
|
+
"$ref": "#/$defs/url"
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"icon": {
|
|
126
|
+
"$ref": "#/$defs/url",
|
|
127
|
+
"description": "A direct link to a square PNG, 512px or larger. Not a page, not a favicon."
|
|
128
|
+
},
|
|
129
|
+
"prompts": {
|
|
130
|
+
"type": "array",
|
|
131
|
+
"items": {
|
|
132
|
+
"type": "string"
|
|
133
|
+
},
|
|
134
|
+
"description": "Example prompts shown on listings."
|
|
135
|
+
},
|
|
136
|
+
"stores": {
|
|
137
|
+
"type": "array",
|
|
138
|
+
"items": {
|
|
139
|
+
"$ref": "#/$defs/store"
|
|
140
|
+
},
|
|
141
|
+
"uniqueItems": true,
|
|
142
|
+
"description": "Where you list. Leave out to check against every store."
|
|
143
|
+
},
|
|
144
|
+
"tests": {
|
|
145
|
+
"type": "object",
|
|
146
|
+
"description": "Review test cases. ChatGPT wants exactly 5 positive and 3 negative.",
|
|
147
|
+
"additionalProperties": false,
|
|
148
|
+
"properties": {
|
|
149
|
+
"positive": {
|
|
150
|
+
"type": "array",
|
|
151
|
+
"items": {
|
|
152
|
+
"type": "object",
|
|
153
|
+
"required": [
|
|
154
|
+
"scenario",
|
|
155
|
+
"prompt",
|
|
156
|
+
"tools",
|
|
157
|
+
"expected"
|
|
158
|
+
],
|
|
159
|
+
"additionalProperties": false,
|
|
160
|
+
"properties": {
|
|
161
|
+
"scenario": {
|
|
162
|
+
"type": "string"
|
|
163
|
+
},
|
|
164
|
+
"prompt": {
|
|
165
|
+
"type": "string"
|
|
166
|
+
},
|
|
167
|
+
"tools": {
|
|
168
|
+
"type": "array",
|
|
169
|
+
"items": {
|
|
170
|
+
"type": "string"
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
"expected": {
|
|
174
|
+
"type": "string"
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
"negative": {
|
|
180
|
+
"type": "array",
|
|
181
|
+
"items": {
|
|
182
|
+
"type": "object",
|
|
183
|
+
"required": [
|
|
184
|
+
"scenario",
|
|
185
|
+
"prompt"
|
|
186
|
+
],
|
|
187
|
+
"additionalProperties": false,
|
|
188
|
+
"properties": {
|
|
189
|
+
"scenario": {
|
|
190
|
+
"type": "string"
|
|
191
|
+
},
|
|
192
|
+
"prompt": {
|
|
193
|
+
"type": "string"
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
"justifications": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"description": "Your own ChatGPT hint justifications per tool name. They replace mcplane's drafts. 200 characters each.",
|
|
203
|
+
"additionalProperties": {
|
|
204
|
+
"type": "object",
|
|
205
|
+
"additionalProperties": false,
|
|
206
|
+
"properties": {
|
|
207
|
+
"readOnly": {
|
|
208
|
+
"type": "string",
|
|
209
|
+
"maxLength": 200
|
|
210
|
+
},
|
|
211
|
+
"openWorld": {
|
|
212
|
+
"type": "string",
|
|
213
|
+
"maxLength": 200
|
|
214
|
+
},
|
|
215
|
+
"destructive": {
|
|
216
|
+
"type": "string",
|
|
217
|
+
"maxLength": 200
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
"version": {
|
|
223
|
+
"type": "string",
|
|
224
|
+
"description": "Your server's version, for the MCP Registry and your records."
|
|
225
|
+
},
|
|
226
|
+
"registry": {
|
|
227
|
+
"type": "object",
|
|
228
|
+
"additionalProperties": false,
|
|
229
|
+
"properties": {
|
|
230
|
+
"name": {
|
|
231
|
+
"type": "string",
|
|
232
|
+
"description": "Reverse-DNS name. Defaults to io.github.<owner>/<name>."
|
|
233
|
+
},
|
|
234
|
+
"description": {
|
|
235
|
+
"type": "string",
|
|
236
|
+
"maxLength": 100
|
|
237
|
+
},
|
|
238
|
+
"file": {
|
|
239
|
+
"type": "string",
|
|
240
|
+
"description": "Where to write server.json."
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
"grok": {
|
|
245
|
+
"type": "object",
|
|
246
|
+
"additionalProperties": false,
|
|
247
|
+
"properties": {
|
|
248
|
+
"description": {
|
|
249
|
+
"type": "string"
|
|
250
|
+
},
|
|
251
|
+
"category": {
|
|
252
|
+
"type": "string"
|
|
253
|
+
},
|
|
254
|
+
"keywords": {
|
|
255
|
+
"type": "array",
|
|
256
|
+
"items": {
|
|
257
|
+
"type": "string"
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
"domains": {
|
|
261
|
+
"type": "array",
|
|
262
|
+
"items": {
|
|
263
|
+
"type": "string"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
"lanes": {
|
|
269
|
+
"type": "object",
|
|
270
|
+
"description": "Named workflows run with \"mcplane <lane>\". Each step is a command without \"mcplane\"; prefix \"?\" for optional, \"handoff:\" for a step only a person can do.",
|
|
271
|
+
"additionalProperties": {
|
|
272
|
+
"type": "array",
|
|
273
|
+
"items": {
|
|
274
|
+
"type": "string"
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
"reviewerAccess": {
|
|
279
|
+
"type": "string",
|
|
280
|
+
"description": "How reviewers get in, for servers behind sign-in. Say where credentials live; never put a password here."
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|