apiblaze 0.21.73 → 0.21.75
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/dist/index.js +268 -51
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
|
|
|
34
34
|
var version;
|
|
35
35
|
var init_package = __esm({
|
|
36
36
|
"package.json"() {
|
|
37
|
-
version = "0.21.
|
|
37
|
+
version = "0.21.75";
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -4243,9 +4243,9 @@ async function openServerProxy(project) {
|
|
|
4243
4243
|
}
|
|
4244
4244
|
function discoverLocalSpecs() {
|
|
4245
4245
|
const cwd = process.cwd();
|
|
4246
|
-
const
|
|
4246
|
+
const known2 = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json", "api.yaml", "api.yml", "api.json"];
|
|
4247
4247
|
const found = [];
|
|
4248
|
-
for (const n of
|
|
4248
|
+
for (const n of known2) {
|
|
4249
4249
|
try {
|
|
4250
4250
|
if (fs9.statSync(path9.join(cwd, n)).isFile()) found.push(n);
|
|
4251
4251
|
} catch {
|
|
@@ -5549,8 +5549,12 @@ __export(directory_exports, {
|
|
|
5549
5549
|
OPENAI_CATEGORIES: () => OPENAI_CATEGORIES,
|
|
5550
5550
|
PLATFORMS: () => PLATFORMS,
|
|
5551
5551
|
STEP_IDS: () => STEP_IDS,
|
|
5552
|
+
askLine: () => askLine,
|
|
5552
5553
|
attestationId: () => attestationId,
|
|
5553
5554
|
blockerLines: () => blockerLines,
|
|
5555
|
+
ccServerName: () => ccServerName,
|
|
5556
|
+
claudeCodeTests: () => claudeCodeTests,
|
|
5557
|
+
claudeCodeText: () => claudeCodeText,
|
|
5554
5558
|
compliantGiven: () => compliantGiven,
|
|
5555
5559
|
directoryBodyFromFlags: () => directoryBodyFromFlags,
|
|
5556
5560
|
doneLine: () => doneLine,
|
|
@@ -5568,10 +5572,12 @@ __export(directory_exports, {
|
|
|
5568
5572
|
printProgressTrailer: () => printProgressTrailer,
|
|
5569
5573
|
printReport: () => printReport,
|
|
5570
5574
|
scorecardText: () => scorecardText,
|
|
5575
|
+
shellQuote: () => shellQuote2,
|
|
5571
5576
|
stepOf: () => stepOf,
|
|
5572
5577
|
stepOpen: () => stepOpen,
|
|
5573
5578
|
stepRunnable: () => stepRunnable,
|
|
5574
5579
|
summaryOf: () => summaryOf,
|
|
5580
|
+
withBrandSource: () => withBrandSource,
|
|
5575
5581
|
writeJsonLine: () => writeJsonLine
|
|
5576
5582
|
});
|
|
5577
5583
|
function attestationId(flag) {
|
|
@@ -5669,9 +5675,15 @@ function directoryBodyFromFlags(f2) {
|
|
|
5669
5675
|
}
|
|
5670
5676
|
return out;
|
|
5671
5677
|
}
|
|
5678
|
+
function withBrandSource(url, cli = B.cli) {
|
|
5679
|
+
return cli === "mcpblaze" ? `${url}?source=mcpblazeCli` : url;
|
|
5680
|
+
}
|
|
5672
5681
|
function pageLinkOf(r, proj2, anchor) {
|
|
5673
5682
|
const base2 = r?.progress?.pageUrl ?? `${DASHBOARD_BASE6}/directory/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}`;
|
|
5674
|
-
|
|
5683
|
+
const team = proj2.teamId && !proj2.teamId.startsWith("team_anon_") ? proj2.teamId : null;
|
|
5684
|
+
let page = withBrandSource(base2);
|
|
5685
|
+
if (team) page += `${page.includes("?") ? "&" : "?"}team=${encodeURIComponent(team)}`;
|
|
5686
|
+
return anchor ? `${page}${anchor.startsWith("#") ? anchor : `#${anchor}`}` : page;
|
|
5675
5687
|
}
|
|
5676
5688
|
function stepOf(r, id) {
|
|
5677
5689
|
return r?.progress?.steps.find((s) => s.id === id) ?? null;
|
|
@@ -5796,7 +5808,39 @@ function blockerLines(r, platforms) {
|
|
|
5796
5808
|
function isApiError(e, status2) {
|
|
5797
5809
|
return e instanceof ApiError && (status2 === void 0 || e.status === status2);
|
|
5798
5810
|
}
|
|
5799
|
-
|
|
5811
|
+
function claudeCodeTests(r, proj2) {
|
|
5812
|
+
const url = r?.report?.project?.publishedUrl;
|
|
5813
|
+
if (!url) return null;
|
|
5814
|
+
const name = ccServerName(proj2.projectId);
|
|
5815
|
+
const t = r?.target;
|
|
5816
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5817
|
+
const cases = [];
|
|
5818
|
+
const add = (prompt, expect, refuse) => {
|
|
5819
|
+
const p = (prompt ?? "").trim();
|
|
5820
|
+
if (!p || seen.has(p)) return;
|
|
5821
|
+
seen.add(p);
|
|
5822
|
+
cases.push({ prompt: p, line: askLine(name, p), expect, refuse });
|
|
5823
|
+
};
|
|
5824
|
+
for (const p of t?.metadata?.examplePrompts ?? []) add(p, "it calls your tools and answers from what comes back", false);
|
|
5825
|
+
const tc = t?.metadata?.portalAnswers?.chatgpt_test_cases;
|
|
5826
|
+
for (const c of tc?.positive ?? []) add(c.prompt, c.expected_behavior || "it calls your tools and answers from what comes back", false);
|
|
5827
|
+
for (const c of tc?.negative ?? []) add(c.prompt, `it says it can't help${c.description ? ` (${c.description})` : ""}, and calls no tool`, true);
|
|
5828
|
+
const open = t?.project?.authMode === "open";
|
|
5829
|
+
return {
|
|
5830
|
+
add: `claude mcp add --transport http ${name} ${url}`,
|
|
5831
|
+
signIn: open ? null : `claude, then /mcp \u2192 ${name} \u2192 Authenticate, and sign in as reviewer-${proj2.projectId} (the test account); /exit after`,
|
|
5832
|
+
cases
|
|
5833
|
+
};
|
|
5834
|
+
}
|
|
5835
|
+
function claudeCodeText(cc) {
|
|
5836
|
+
const lines = ["Test each use case in Claude Code", ` 1. ${cc.add}`];
|
|
5837
|
+
if (cc.signIn) lines.push(` 2. Sign in once: ${cc.signIn}`);
|
|
5838
|
+
const n = cc.signIn ? 3 : 2;
|
|
5839
|
+
if (!cc.cases.length) lines.push(` ${n}. Add example prompts to the listing first: each becomes a line here.`);
|
|
5840
|
+
else lines.push(` ${n}. One line per use case:`, ...cc.cases.flatMap((c) => [` ${c.line}`, ` expect: ${c.expect}`]));
|
|
5841
|
+
return lines.join("\n");
|
|
5842
|
+
}
|
|
5843
|
+
var import_node_fs, import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, STEP_IDS, reportPath, DESCRIPTION_MIN, ccServerName, shellQuote2, askLine;
|
|
5800
5844
|
var init_directory = __esm({
|
|
5801
5845
|
"src/lib/directory.ts"() {
|
|
5802
5846
|
"use strict";
|
|
@@ -5806,6 +5850,7 @@ var init_directory = __esm({
|
|
|
5806
5850
|
init_op_call();
|
|
5807
5851
|
init_directory_probes();
|
|
5808
5852
|
init_types();
|
|
5853
|
+
init_brand();
|
|
5809
5854
|
PLATFORMS = ["claude", "chatgpt"];
|
|
5810
5855
|
ATTESTATIONS = [
|
|
5811
5856
|
// Claude's Compliance step: seven acknowledgments (submission page, 2026-10-16).
|
|
@@ -5829,6 +5874,9 @@ var init_directory = __esm({
|
|
|
5829
5874
|
STEP_IDS = ["mode", "address", "access", "tools", "listing", "answers", "attestations", "prove", "signin", "token", "packet", "submit"];
|
|
5830
5875
|
reportPath = (proj2) => `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/directory-report`;
|
|
5831
5876
|
DESCRIPTION_MIN = 300;
|
|
5877
|
+
ccServerName = (projectId) => projectId.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
|
|
5878
|
+
shellQuote2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
|
|
5879
|
+
askLine = (name, prompt) => `claude -p ${shellQuote2(prompt)} --allowedTools ${shellQuote2(`mcp__${name}`)}`;
|
|
5832
5880
|
}
|
|
5833
5881
|
});
|
|
5834
5882
|
|
|
@@ -6612,7 +6660,31 @@ async function printDevOutcome(o, port) {
|
|
|
6612
6660
|
}
|
|
6613
6661
|
if (o.anon) printLegalNotice(" ");
|
|
6614
6662
|
}
|
|
6663
|
+
function printListingOutcome(o) {
|
|
6664
|
+
const where = { tenant: o.tenant, anon: o.anon };
|
|
6665
|
+
const mcp = mcpUrl(o.name, o.version, o.env, where);
|
|
6666
|
+
const s = o.secure;
|
|
6667
|
+
const applied = s?.applied ?? [];
|
|
6668
|
+
console.log();
|
|
6669
|
+
console.log(` ${import_chalk20.default.green("\u2713")} ${B.thing} ${import_chalk20.default.bold(o.name)}: ${import_chalk20.default.bold(mcp)}`);
|
|
6670
|
+
if (applied.length && s?.enforced) console.log(` ${import_chalk20.default.green("\u2713")} Each person can only change the ${describeFamilies(applied)} they made ${import_chalk20.default.dim("(lists stay open: your backend filters them)")}`);
|
|
6671
|
+
else if (applied.length && s?.pending) console.log(` ${import_chalk20.default.dim("\u2013")} ${describeFamilies(applied)}: the who-can-change-what rules are being applied`);
|
|
6672
|
+
else console.log(` ${import_chalk20.default.dim("\u2013")} ${import_chalk20.default.dim(`Who can change what: not set yet. Later: npx ${B.cli} rule ${o.name}`)}`);
|
|
6673
|
+
if (o.auth.doors.includes("api_key") && o.apiKey) {
|
|
6674
|
+
const saved = process.stdout.isTTY ? null : saveEnvLocal(process.cwd(), {
|
|
6675
|
+
APIBLAZE_URL: restUrl(o.name, o.version, o.env, where),
|
|
6676
|
+
APIBLAZE_MCP_URL: mcp,
|
|
6677
|
+
APIBLAZE_SERVER_KEY: o.apiKey,
|
|
6678
|
+
...o.tenant ? { APIBLAZE_TENANT: o.tenant } : {}
|
|
6679
|
+
});
|
|
6680
|
+
if (saved?.ok) console.log(` ${import_chalk20.default.green("\u2713")} Backend key saved to .env.local as APIBLAZE_SERVER_KEY`);
|
|
6681
|
+
else console.log(` ${import_chalk20.default.green("\u2713")} Backend key ${import_chalk20.default.dim("(shown once, save it):")} ${import_chalk20.default.bold.green(o.apiKey)}`);
|
|
6682
|
+
}
|
|
6683
|
+
if (o.claim?.claimCode || o.claim?.claimUrl) console.log(import_chalk20.default.dim(` Anonymous: it expires in 30 days unless you sign in (npx ${B.cli} login claims it).`));
|
|
6684
|
+
if (o.anon) printLegalNotice(" ");
|
|
6685
|
+
}
|
|
6615
6686
|
async function printOutcome(o) {
|
|
6687
|
+
if (o.listing) return printListingOutcome(o);
|
|
6616
6688
|
const where = { tenant: o.tenant, anon: o.anon };
|
|
6617
6689
|
const mcp = mcpUrl(o.name, o.version, o.env, where);
|
|
6618
6690
|
const shown = BRAND === "mcpblaze" ? mcp : B.liveUrl(o.name, o.version, o.env, where);
|
|
@@ -6706,8 +6778,11 @@ async function runCreate(opts = {}) {
|
|
|
6706
6778
|
opts.target = void 0;
|
|
6707
6779
|
}
|
|
6708
6780
|
}
|
|
6709
|
-
const
|
|
6781
|
+
const saved = loadCredentials();
|
|
6782
|
+
const expired = !!saved && Date.now() >= saved.expiresAt && !process.env.APIBLAZE_TOKEN?.trim();
|
|
6783
|
+
const creds = expired ? null : saved;
|
|
6710
6784
|
if (!creds) {
|
|
6785
|
+
if (expired && !opts.json) console.log(import_chalk20.default.dim(`Your saved login has expired, so this is made anonymously. \`${B.cli} login\` then claims it into your team.`));
|
|
6711
6786
|
await runAnonymousCreate(opts);
|
|
6712
6787
|
return;
|
|
6713
6788
|
}
|
|
@@ -6893,6 +6968,7 @@ Create an ${B.thing}
|
|
|
6893
6968
|
return;
|
|
6894
6969
|
}
|
|
6895
6970
|
await finishSetup({
|
|
6971
|
+
listing: !!opts.directoryReady,
|
|
6896
6972
|
name,
|
|
6897
6973
|
version: version2,
|
|
6898
6974
|
env: "dev",
|
|
@@ -7128,7 +7204,8 @@ Create an ${B.thing}`));
|
|
|
7128
7204
|
});
|
|
7129
7205
|
}
|
|
7130
7206
|
if (name) await applyCreateToggles(name, opts);
|
|
7131
|
-
|
|
7207
|
+
if (anonProjectId) opts.onCreated?.({ name: anonProjectId, version: version2, tenant, teamId: result.team_id });
|
|
7208
|
+
const directory = anonProjectId && !opts.skipDirectoryReport ? await directoryAfterCreate(anonProjectId, version2, opts) : null;
|
|
7132
7209
|
if (opts.json) {
|
|
7133
7210
|
process.stdout.write(JSON.stringify({
|
|
7134
7211
|
project_id: result.project_id,
|
|
@@ -7152,6 +7229,7 @@ Create an ${B.thing}`));
|
|
|
7152
7229
|
return;
|
|
7153
7230
|
}
|
|
7154
7231
|
await finishSetup({
|
|
7232
|
+
listing: !!opts.directoryReady,
|
|
7155
7233
|
name: anonProjectId,
|
|
7156
7234
|
version: version2,
|
|
7157
7235
|
env: "prod",
|
|
@@ -7967,10 +8045,10 @@ function orderTools(tools) {
|
|
|
7967
8045
|
const rank = { read: 0, create: 1, update: 2, write: 3, delete: 4 };
|
|
7968
8046
|
return [...tools].sort((a, b) => rank[toolRole(a)] - rank[toolRole(b)] || a.name.localeCompare(b.name));
|
|
7969
8047
|
}
|
|
7970
|
-
function exampleValue(name, p,
|
|
8048
|
+
function exampleValue(name, p, known2 = {}, given = {}) {
|
|
7971
8049
|
if (given[name] !== void 0) return given[name];
|
|
7972
8050
|
if (p.example !== void 0 && p.example !== null) return p.example;
|
|
7973
|
-
if (
|
|
8051
|
+
if (known2[name] !== void 0) return known2[name];
|
|
7974
8052
|
if (Array.isArray(p.enum) && p.enum.length) return p.enum[0];
|
|
7975
8053
|
const t = (p.type ?? "string").toLowerCase();
|
|
7976
8054
|
const fmt = (p.format ?? "").toLowerCase();
|
|
@@ -7987,14 +8065,14 @@ function exampleValue(name, p, known = {}, given = {}) {
|
|
|
7987
8065
|
if (/(^|_)(q|query|search|term|text|name|title)$/i.test(name)) return "test";
|
|
7988
8066
|
return "example";
|
|
7989
8067
|
}
|
|
7990
|
-
function validArgs(t,
|
|
8068
|
+
function validArgs(t, known2 = {}, given = {}) {
|
|
7991
8069
|
const out = {};
|
|
7992
8070
|
const props = t.inputProperties ?? {};
|
|
7993
8071
|
const pathParams = new Set([...(t.path ?? "").matchAll(/\{([^}]+)\}/g)].map((m) => m[1]));
|
|
7994
8072
|
for (const [name, p] of Object.entries(props)) {
|
|
7995
|
-
const wanted = p.required || pathParams.has(name) || p.example !== void 0 && p.example !== null ||
|
|
8073
|
+
const wanted = p.required || pathParams.has(name) || p.example !== void 0 && p.example !== null || known2[name] !== void 0 || given[name] !== void 0;
|
|
7996
8074
|
if (!wanted) continue;
|
|
7997
|
-
out[name] = exampleValue(name, p,
|
|
8075
|
+
out[name] = exampleValue(name, p, known2, given);
|
|
7998
8076
|
}
|
|
7999
8077
|
return out;
|
|
8000
8078
|
}
|
|
@@ -8010,11 +8088,11 @@ function candidateFromPrompts(name, p, prompts) {
|
|
|
8010
8088
|
const quoted = text.match(/["“']([^"”']{1,80})["”']/);
|
|
8011
8089
|
return quoted ? quoted[1].trim() : null;
|
|
8012
8090
|
}
|
|
8013
|
-
function invalidArgs(t,
|
|
8091
|
+
function invalidArgs(t, known2 = {}, given = {}) {
|
|
8014
8092
|
const props = t.inputProperties ?? {};
|
|
8015
8093
|
const names = Object.keys(props);
|
|
8016
8094
|
if (!names.length) return null;
|
|
8017
|
-
const base2 = validArgs(t,
|
|
8095
|
+
const base2 = validArgs(t, known2, given);
|
|
8018
8096
|
const withEnum = names.find((n) => Array.isArray(props[n].enum) && props[n].enum.length);
|
|
8019
8097
|
if (withEnum) return { args: { ...base2, [withEnum]: "not-a-valid-choice" }, broke: `${withEnum}: out of enum` };
|
|
8020
8098
|
const typed = names.find((n) => ["integer", "number", "boolean"].includes((props[n].type ?? "").toLowerCase()));
|
|
@@ -8248,7 +8326,7 @@ function draftUseCases(i) {
|
|
|
8248
8326
|
}
|
|
8249
8327
|
function draftReviewerInstructions(i) {
|
|
8250
8328
|
const lines = [];
|
|
8251
|
-
const where = i.platforms?.length === 1 && i.platforms[0] === "chatgpt" ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : i.platforms?.includes("chatgpt") ? "Claude (
|
|
8329
|
+
const where = i.platforms?.length === 1 && i.platforms[0] === "chatgpt" ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : i.platforms?.includes("chatgpt") ? "Claude (Customize > Connectors \u2192 Add custom connector) or ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : "Claude (Customize > Connectors \u2192 Add custom connector)";
|
|
8252
8330
|
lines.push(`Add ${i.publishedUrl} as a custom connector in ${where}.`);
|
|
8253
8331
|
lines.push(i.open ? "No sign-in: the data is public and every tool is read-only. Connect and ask." : `Sign in with the username and password entered in the credential fields of this form${i.reviewerUsername ? ` (username ${i.reviewerUsername})` : ""}: the sign-in page shows a username & password form under the provider buttons; no email, SMS or MFA step.`);
|
|
8254
8332
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8375,7 +8453,7 @@ function detectCodex(spawn5 = import_child_process5.spawnSync) {
|
|
|
8375
8453
|
return { installed: true, loggedIn: st.status === 0 && !/not logged in/i.test(text) };
|
|
8376
8454
|
}
|
|
8377
8455
|
function reportLink(projectId, apiVersion = "1.0.0") {
|
|
8378
|
-
return `${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}
|
|
8456
|
+
return withBrandSource(`${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}`);
|
|
8379
8457
|
}
|
|
8380
8458
|
async function confirm(message, interactive2) {
|
|
8381
8459
|
if (!interactive2) return false;
|
|
@@ -13571,6 +13649,13 @@ async function projectSpecText(caller, proj2) {
|
|
|
13571
13649
|
}
|
|
13572
13650
|
}
|
|
13573
13651
|
var DOMAIN_RULE = "DOMAIN_CUSTOM_REQUIRED";
|
|
13652
|
+
function hostnameProblem(raw) {
|
|
13653
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
13654
|
+
if (!v) return null;
|
|
13655
|
+
if (/^https?:\/\/|\//.test(v)) return "Just the hostname, no https:// and no path: mcp.yourdomain.com";
|
|
13656
|
+
if (!v.includes(".")) return `"${v}" is only part of it. Type the whole hostname: ${v}.yourdomain.com`;
|
|
13657
|
+
return /^([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z]{2,}$/.test(v) ? null : "That is not a valid hostname: mcp.yourdomain.com";
|
|
13658
|
+
}
|
|
13574
13659
|
async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, say) {
|
|
13575
13660
|
const domainRule = r.report.results.find((x) => x.id === DOMAIN_RULE);
|
|
13576
13661
|
if (!domainRule || domainRule.pass) return { active: true };
|
|
@@ -13587,14 +13672,36 @@ async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, s
|
|
|
13587
13672
|
hostname2 = suggested;
|
|
13588
13673
|
} else {
|
|
13589
13674
|
const { default: inquirer3 } = await import("inquirer");
|
|
13590
|
-
|
|
13591
|
-
|
|
13675
|
+
for (; ; ) {
|
|
13676
|
+
const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: "Full hostname for the MCP server, on a domain you control (mcp.yourdomain.com; Enter to skip for now):", ...suggested ? { default: suggested } : {}, validate: (v) => hostnameProblem(v) ?? true }]);
|
|
13677
|
+
hostname2 = value?.trim().toLowerCase();
|
|
13678
|
+
if (!hostname2) {
|
|
13679
|
+
say(import_chalk32.default.dim(" Skipped: no MCP hostname yet. It stays open on the scorecard."));
|
|
13680
|
+
return { active: false };
|
|
13681
|
+
}
|
|
13682
|
+
const sp2 = (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
|
|
13683
|
+
try {
|
|
13684
|
+
row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
|
|
13685
|
+
sp2.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
|
|
13686
|
+
break;
|
|
13687
|
+
} catch (e) {
|
|
13688
|
+
if (!isApiError(e, 400) && !isApiError(e, 409)) {
|
|
13689
|
+
sp2.stop();
|
|
13690
|
+
throw e;
|
|
13691
|
+
}
|
|
13692
|
+
sp2.fail(`${hostname2} was refused: ${e instanceof Error ? e.message : String(e)}`);
|
|
13693
|
+
}
|
|
13694
|
+
}
|
|
13592
13695
|
}
|
|
13593
13696
|
}
|
|
13594
13697
|
if (!hostname2) throw new Error("A custom MCP hostname is required for the directories (--domain mcp.<your domain>).");
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13698
|
+
if (!row) {
|
|
13699
|
+
const problem = hostnameProblem(hostname2);
|
|
13700
|
+
if (problem) throw new Error(`${problem} (--domain mcp.<your domain>)`);
|
|
13701
|
+
const sp2 = opts.json ? null : (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
|
|
13702
|
+
row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
|
|
13703
|
+
sp2?.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
|
|
13704
|
+
}
|
|
13598
13705
|
}
|
|
13599
13706
|
const hostname = row.hostname ?? opts.domain ?? "";
|
|
13600
13707
|
if (row.status === "active") return { active: true, hostname, id: row.id };
|
|
@@ -13689,11 +13796,10 @@ async function ensureHostedUpstream(caller, teamId, proj2, r, opts, interactive2
|
|
|
13689
13796
|
let chosen = header;
|
|
13690
13797
|
let open = !!opts.upstreamOpen;
|
|
13691
13798
|
if (!chosen && !open && interactive2) {
|
|
13692
|
-
if (shape(cur).hosts?.upstreamKind === "public" && !shape(cur).hosts?.upstreamHeader) say(import_chalk32.default.dim(" An open backend can be called around sign-in and the per-user rules; a shared secret header closes that."));
|
|
13693
13799
|
const { default: inquirer3 } = await import("inquirer");
|
|
13694
|
-
const { name } = await inquirer3.prompt([{ type: "input", name: "name", message:
|
|
13800
|
+
const { name } = await inquirer3.prompt([{ type: "input", name: "name", message: "Secret header your backend checks? (e.g. X-Api-Key; Enter if none)" }]);
|
|
13695
13801
|
if (name?.trim()) {
|
|
13696
|
-
const { value } = await inquirer3.prompt([{ type: "password", name: "value", mask: "*", message: `
|
|
13802
|
+
const { value } = await inquirer3.prompt([{ type: "password", name: "value", mask: "*", message: `Its value (stored encrypted):`, validate: (v) => (v ?? "").length > 0 ? true : "A value is needed; Ctrl-C leaves." }]);
|
|
13697
13803
|
chosen = { name: name.trim(), value };
|
|
13698
13804
|
} else open = true;
|
|
13699
13805
|
}
|
|
@@ -13806,8 +13912,6 @@ function installInterrupt(ctx) {
|
|
|
13806
13912
|
return () => process.off("SIGINT", handler);
|
|
13807
13913
|
}
|
|
13808
13914
|
async function runConnector(target2, opts) {
|
|
13809
|
-
const caller = requireCaller();
|
|
13810
|
-
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
13811
13915
|
const json = !!opts.json;
|
|
13812
13916
|
const interactive2 = !!process.stdin.isTTY && !json && !opts.yes;
|
|
13813
13917
|
const say = (s) => {
|
|
@@ -13823,8 +13927,9 @@ async function runConnector(target2, opts) {
|
|
|
13823
13927
|
const created = await createFromSpec(specSource, opts, say);
|
|
13824
13928
|
projectName = created.name;
|
|
13825
13929
|
version2 = created.version;
|
|
13826
|
-
say(import_chalk32.default.green(` ${B.thing} ${import_chalk32.default.bold(created.name)} created.`));
|
|
13827
13930
|
}
|
|
13931
|
+
const caller = requireCaller();
|
|
13932
|
+
const { teamId } = await resolveActingTeam(caller, opts.team);
|
|
13828
13933
|
const proj2 = await resolveProjectVia(caller, teamId, projectName, version2);
|
|
13829
13934
|
const flagBody = directoryBodyFromFlags(opts);
|
|
13830
13935
|
const onlyAttest = !!opts.attest?.length && !flagBody.directory_metadata && !opts.full && !specSource && !only && !opts.status;
|
|
@@ -13849,13 +13954,16 @@ async function runConnector(target2, opts) {
|
|
|
13849
13954
|
const stored = r.progress?.platforms ?? null;
|
|
13850
13955
|
const modeOn = r.target?.project?.directoryReady === true;
|
|
13851
13956
|
if (opts.status) {
|
|
13957
|
+
const cc = claudeCodeTests(r, proj2);
|
|
13852
13958
|
if (json) {
|
|
13853
|
-
writeJsonLine({ ready: isReady(r, platforms), project: proj2.projectId, headline: r.rendered.headline, summary: r.progress?.summary ?? null, progress: r.progress ?? null, report: r.report });
|
|
13959
|
+
writeJsonLine({ ready: isReady(r, platforms), project: proj2.projectId, headline: r.rendered.headline, summary: r.progress?.summary ?? null, progress: r.progress ?? null, claude_code: cc, report: r.report });
|
|
13854
13960
|
process.exit(isReady(r, platforms) ? 0 : 1);
|
|
13855
13961
|
}
|
|
13856
13962
|
const card2 = scorecardText(r);
|
|
13857
13963
|
if (card2) console.log(card2);
|
|
13858
13964
|
else printReport(r, false);
|
|
13965
|
+
if (cc) console.log(`
|
|
13966
|
+
${claudeCodeText(cc)}`);
|
|
13859
13967
|
process.exit(isReady(r, platforms) ? 0 : 1);
|
|
13860
13968
|
}
|
|
13861
13969
|
const dirsDiffer = explicit && !!stored && (stored.length !== platforms.length || stored.some((p) => !platforms.includes(p)));
|
|
@@ -13869,9 +13977,23 @@ async function runConnector(target2, opts) {
|
|
|
13869
13977
|
r = await fetchDirectoryReport(caller, proj2, reportOpts());
|
|
13870
13978
|
}
|
|
13871
13979
|
const link = pageLinkOf(r, proj2);
|
|
13872
|
-
|
|
13873
|
-
|
|
13874
|
-
|
|
13980
|
+
if (r.progress) {
|
|
13981
|
+
const pct = r.progress.readiness?.percent;
|
|
13982
|
+
const where = platforms.map((p) => p === "claude" ? "Claude" : "ChatGPT").join(" and ");
|
|
13983
|
+
const rule = import_chalk32.default.dim("\u2500".repeat(64));
|
|
13984
|
+
say("");
|
|
13985
|
+
say(rule);
|
|
13986
|
+
say(` ${import_chalk32.default.bold(`Get it listed in ${where}`)}${typeof pct === "number" ? import_chalk32.default.dim(` \xB7 ${pct}% ready`) : ""}`);
|
|
13987
|
+
if (caller.mode === "anon") {
|
|
13988
|
+
say(` A listing needs an account (your own MCP address is billed to it). Sign in, then carry on:`);
|
|
13989
|
+
say(` ${import_chalk32.default.cyan(`npx ${B.cli}@latest login`)} ${import_chalk32.default.dim(`it keeps ${proj2.projectId}, moving it into your team`)}`);
|
|
13990
|
+
say(` ${import_chalk32.default.cyan(`npx ${B.cli}@latest connector ${proj2.projectId}`)} ${import_chalk32.default.dim("or finish on the page it links")}`);
|
|
13991
|
+
say(rule);
|
|
13992
|
+
return;
|
|
13993
|
+
}
|
|
13994
|
+
say(` ${import_chalk32.default.bold("Here:")} answer the questions below. Stop any time; every answer is kept.`);
|
|
13995
|
+
say(` ${import_chalk32.default.bold("Or in your browser:")} ${import_chalk32.default.cyan(link)}`);
|
|
13996
|
+
say(rule);
|
|
13875
13997
|
const done = doneLine(r);
|
|
13876
13998
|
if (done) say(import_chalk32.default.dim(` ${done}`));
|
|
13877
13999
|
} else {
|
|
@@ -14162,7 +14284,7 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
|
|
|
14162
14284
|
say(" One sign-in from a real Claude client is the last proof. In another terminal:");
|
|
14163
14285
|
say(` claude mcp add --transport http ${alias} ${url}`);
|
|
14164
14286
|
say(` claude then /mcp \u2192 ${alias} \u2192 sign in with the reviewer account`);
|
|
14165
|
-
say(import_chalk32.default.dim(` or in claude.ai:
|
|
14287
|
+
say(import_chalk32.default.dim(` or in claude.ai: Customize > Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
|
|
14166
14288
|
say(import_chalk32.default.dim(` Afterwards: claude mcp remove ${alias}`));
|
|
14167
14289
|
const { default: inquirer3 } = await import("inquirer");
|
|
14168
14290
|
const { go } = await inquirer3.prompt([{ type: "confirm", name: "go", message: "Sign in from Claude now, then press Enter to check (n skips; the step stays open):", default: true }]);
|
|
@@ -14583,7 +14705,7 @@ function printSubmitSteps(r, mdPath, platforms = PLATFORMS, watching = false) {
|
|
|
14583
14705
|
const open = isOpenMode(r);
|
|
14584
14706
|
const claude = platforms.includes("claude");
|
|
14585
14707
|
const chatgpt = platforms.includes("chatgpt");
|
|
14586
|
-
const where = [claude ? "Claude (
|
|
14708
|
+
const where = [claude ? "Claude (Customize > Connectors)" : null, chatgpt ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : null].filter(Boolean).join(" and in ");
|
|
14587
14709
|
const steps = [
|
|
14588
14710
|
`Test it yourself first: add ${import_chalk32.default.cyan(url)} as a custom connector in ${where}, ${open ? "no sign-in needed (open mode)" : "sign in with the reviewer account"}, run a tool.`,
|
|
14589
14711
|
...claude ? [`Claude: ${import_chalk32.default.underline(PORTALS.claude)} \u2192 Submit new \u2192 MCP connector. Paste the listing, ${open ? "Authentication: None with the access instructions" : "the reviewer credentials"} and the test steps from ${import_chalk32.default.cyan(import_path7.default.basename(mdPath))}.`] : [],
|
|
@@ -14920,6 +15042,42 @@ function backendPort(f2) {
|
|
|
14920
15042
|
|
|
14921
15043
|
// src/commands/skill.ts
|
|
14922
15044
|
init_api();
|
|
15045
|
+
|
|
15046
|
+
// src/lib/listing-rules.ts
|
|
15047
|
+
var LISTING_RULES = `**Who reads what.** The listing (name, tagline, description, example prompts, categories) is read by an ordinary
|
|
15048
|
+
person browsing the directory, deciding whether to add the App. They do not know or care what an API, an endpoint, a spec, a gateway,
|
|
15049
|
+
a sandbox or hosting is. Use cases and reviewer instructions are read by the directory's review team,
|
|
15050
|
+
who add the App and try it.
|
|
15051
|
+
|
|
15052
|
+
**Read the input the right way.** The API's own title and description were written by developers for developers: hosting,
|
|
15053
|
+
sandboxes, limits, setup, git, internals. Never copy or paraphrase that; use it only to understand the domain. Learn what
|
|
15054
|
+
the App does from its tools: each tool is something a person can ask for.
|
|
15055
|
+
|
|
15056
|
+
**The fields.**
|
|
15057
|
+
- Name (\`--server-name\`): what a person would call it, often the product or brand. No "API", "MCP", "Plugin", "Server",
|
|
15058
|
+
version numbers or the word "App". At most 100 characters, 30 when ChatGPT is a target.
|
|
15059
|
+
- Tagline (\`--tagline\`): one plain sentence saying what the person gets done; starts with a verb; at most 200 characters.
|
|
15060
|
+
- Description (\`--description\`): 300 to 1,500 characters, two or three short paragraphs. What a person can do, in their
|
|
15061
|
+
terms ("find a table for four on Friday", not "createReservation endpoint"); who it is for; one sentence on whether it
|
|
15062
|
+
only reads or can also change things in their account. No lists of tool names, no marketing adjectives.
|
|
15063
|
+
- Example prompts (\`--prompt\`, three to five): what a person would really type, each answered by a tool, under 128
|
|
15064
|
+
characters, varied (look something up, then act on it), concrete values, no placeholders.
|
|
15065
|
+
- Prerequisites (\`connector answers --prerequisites\`): one sentence, what someone needs before using it.
|
|
15066
|
+
- Categories (\`connector answers --category\`, one to five): plain words a person would browse by.
|
|
15067
|
+
- Use cases (\`connector answers --use-cases\`): 200 to 1,500 characters for the review team: the real jobs, in the
|
|
15068
|
+
person's words, each tied to what comes back.
|
|
15069
|
+
- Reviewer instructions (\`connector answers --reviewer-instructions\`): 400 to 2,500 characters, numbered: where to add
|
|
15070
|
+
it (the MCP address), how to sign in (the test account; never write a password), then three to six prompts to try,
|
|
15071
|
+
each with what the reviewer should see. Build on what the last test run showed; never invent a result.
|
|
15072
|
+
|
|
15073
|
+
**Never write** claims nothing supports (secure, real-time, compliant, best, official, fastest), the names Claude,
|
|
15074
|
+
ChatGPT, Anthropic or OpenAI (except in reviewer instructions, to say where to add it), URLs other than the MCP address in
|
|
15075
|
+
reviewer instructions, prices, or anything about legal, health or finance use.
|
|
15076
|
+
|
|
15077
|
+
**Never answer for the user**: the privacy policy URL, the icon, whose API it is (data handling), health data, sponsored
|
|
15078
|
+
content, high-risk areas, and the attestations. Ask; these are promises only they can make.`;
|
|
15079
|
+
|
|
15080
|
+
// src/commands/skill.ts
|
|
14923
15081
|
var SKILL_NAME = "ship-safely";
|
|
14924
15082
|
var SKILL_RELATIVE_PATH = import_path10.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
|
|
14925
15083
|
function skillNameFor(brand = BRAND) {
|
|
@@ -15184,10 +15342,10 @@ Most people new to it do not know the words tenant, OpenAPI or header, so plain
|
|
|
15184
15342
|
1. **"Want me to make an MCP server from this API?"** \u2014 the API is deployed: \`${run2} create --target <its OpenAPI spec or URL>\`
|
|
15185
15343
|
(sign-in and per-user rules come with it; it prints the MCP address). The app runs on this machine:
|
|
15186
15344
|
\`${run2} dev --port <port>\`, as its own detached process. The next step to offer for this folder: ${question}
|
|
15187
|
-
2. **"Want it listed in Claude or ChatGPT?"** \u2014 \`${run2} connector <name>\`
|
|
15188
|
-
(\`--compliant chatgpt\`
|
|
15189
|
-
|
|
15190
|
-
and
|
|
15345
|
+
2. **"Want it listed in Claude or ChatGPT?"** \u2014 right after the MCP server exists, \`${run2} connector <name>\`
|
|
15346
|
+
(\`--compliant chatgpt\` for ChatGPT, \`--compliant all\` both). Its first line is the page link: give it to the user
|
|
15347
|
+
first, then go through the open steps with them, writing the listing yourself (\`${run2} skills connector\` has the
|
|
15348
|
+
steps and the writing rules). \`${run2} connector <name> --status\` says what is left, with a Claude Code test line per use case.
|
|
15191
15349
|
|
|
15192
15350
|
\`${run2} demo\` shows a sample app with an MCP server in front of it in 30 seconds, no login (it prints the links; it opens nothing).
|
|
15193
15351
|
|
|
@@ -15260,13 +15418,11 @@ function shipMcpMarkdown() {
|
|
|
15260
15418
|
who can override: the dashboard, Tenant \u2192 Admins.
|
|
15261
15419
|
7. **Try it from Claude Code.** \`claude mcp add --transport http <name> <the MCP address it printed>\`, then \`/mcp\` and
|
|
15262
15420
|
sign in; call one tool. What a reviewer does, before a reviewer does it.
|
|
15263
|
-
8. **List it.** \`${run2} connector <name>\` (Claude; \`--compliant chatgpt\` or \`all\` for OpenAI's directory)
|
|
15264
|
-
first, then
|
|
15265
|
-
|
|
15266
|
-
|
|
15267
|
-
|
|
15268
|
-
can be finished.
|
|
15269
|
-
The user submits the packet to Anthropic or OpenAI themselves.
|
|
15421
|
+
8. **List it.** \`${run2} connector <name>\` (Claude; \`--compliant chatgpt\` or \`all\` for OpenAI's directory). Give the user
|
|
15422
|
+
the page link it prints first, then follow \`${run2} skills connector\`: the MCP address (one CNAME; relay the exact record),
|
|
15423
|
+
sign-in and the test account, the listing and the review-team answers that you write by its rules, the tests (one
|
|
15424
|
+
\`claude -p\` per use case, from \`--status\`), and the packet. It needs the hosted app, not a tunnel. Stop any time;
|
|
15425
|
+
\`${run2} connector <name> --status\` says what is left. The user submits the packet to Anthropic or OpenAI themselves.
|
|
15270
15426
|
|
|
15271
15427
|
Report what you did in plain words: the MCP address, who can sign in, what is locked, what is left on the scorecard.
|
|
15272
15428
|
|
|
@@ -15676,6 +15832,41 @@ Each tenant (a group of users with their own sign-in) has a developer portal whe
|
|
|
15676
15832
|
|
|
15677
15833
|
Tenant admins (they can see and manage every record and every user): the dashboard, Tenant \u2192 Admins, by email, even
|
|
15678
15834
|
before that person ever signs in. The whole APIblaze command set is here too: \`npx ${cli} help --all\`.`);
|
|
15835
|
+
case "connector":
|
|
15836
|
+
return page("Get it listed in Claude (and ChatGPT) with MCPblaze", `
|
|
15837
|
+
The listing is the MCP server, sent to Anthropic's directory (Claude) and, if the user wants, OpenAI's (ChatGPT).
|
|
15838
|
+
The user submits. Say it "meets the requirements MCPblaze can verify; listing decisions are made by Anthropic and OpenAI",
|
|
15839
|
+
never "certified".
|
|
15840
|
+
|
|
15841
|
+
**Start, and give the page first.** \`npx ${cli} connector <name>\` (Claude; add \`--compliant chatgpt\` or \`--compliant all\`).
|
|
15842
|
+
Its first line is "Finish this here or at <page link> \xB7 N left": give the user that link before anything else. The page
|
|
15843
|
+
has the same steps with forms, a % ready ring, and a Testing tab; the user can work there, here, or both. Every answer is
|
|
15844
|
+
saved as it is given; \`npx ${cli} connector <name> --status\` says what is left at any time.
|
|
15845
|
+
|
|
15846
|
+
**The steps, in the page's order.** Go through the open ones with the user, one at a time, in plain words:
|
|
15847
|
+
1. **Get listed**: the command turns it on. Nothing is public until the user submits.
|
|
15848
|
+
2. **Connect your API**: the MCP address on their own domain (one CNAME at their DNS provider; relay the exact record
|
|
15849
|
+
it prints) and the hosted app (\`--target-url\`, \`--upstream-header\`): a tunnel cannot be listed.
|
|
15850
|
+
3. **How people sign in to your MCP**: sign-in by default, a test account is made for the reviewers. Public, read-only
|
|
15851
|
+
data can skip sign-in (\`--auth-mode open\`).
|
|
15852
|
+
4. **What your App can do**: the tools (\`npx ${cli} skills mcp\` has the method).
|
|
15853
|
+
5. **Your listing, and the questions for the review team**: YOU write the text, from the code and the tools, following
|
|
15854
|
+
the rules below. Show the user every field, then save what they approve:
|
|
15855
|
+
\`npx ${cli} connector <name> --step listing -y --server-name "\u2026" --tagline "\u2026" --description "\u2026" --prompt "\u2026" --prompt "\u2026" --prompt "\u2026"\`
|
|
15856
|
+
(plus \`--support\`, \`--vuln-contact\`; for ChatGPT \`--short-description\`, \`--developer-name\`, \`--website-url\`, \`--terms-url\`)
|
|
15857
|
+
and \`npx ${cli} connector answers <name> --use-cases "\u2026" --prerequisites "\u2026" --category "\u2026" --reviewer-instructions "\u2026"\`.
|
|
15858
|
+
6. **Test**: \`npx ${cli} connector exercise <name>\` calls every tool as the reviewer. Then
|
|
15859
|
+
\`npx ${cli} connector <name> --status\` prints the Claude Code lines: one \`claude mcp add\`, one sign-in, and one
|
|
15860
|
+
\`claude -p '\u2026' --allowedTools 'mcp__<name>'\` per example prompt, each with what should come back. Run each, tell the user
|
|
15861
|
+
what came back, and fix a tool's description (\`npx ${cli} mcp ops set\`) when a prompt did not reach the right tool.
|
|
15862
|
+
7. **Send**: when every check passes it writes the packet. The user submits it on claude.ai/directory/manage (and the
|
|
15863
|
+
OpenAI portal for ChatGPT).
|
|
15864
|
+
|
|
15865
|
+
## Writing the listing
|
|
15866
|
+
|
|
15867
|
+
${LISTING_RULES}
|
|
15868
|
+
|
|
15869
|
+
${HEDGE}`);
|
|
15679
15870
|
case "rules":
|
|
15680
15871
|
return page("Who can change what (MCPblaze)", `
|
|
15681
15872
|
- With a spec, \`dev\` and \`create\` lock every record to the person who created it: an agent acting for Alice
|
|
@@ -23513,7 +23704,8 @@ function buildProgram() {
|
|
|
23513
23704
|
const connector = program2.command("connector").description(descriptionFor({
|
|
23514
23705
|
apiblaze: `A compliant Claude connector (--compliant chatgpt: a ChatGPT plugin), directory-ready in one command: from a project or straight from an OpenAPI URL \u2014 creates the ${B.thingShort} if needed, applies the safe fixes, sets up the custom MCP domain (one CNAME), pre-fills every listing answer from the spec, creates the reviewer account, and writes the submission packet when every check passes (you submit it to Anthropic and OpenAI yourself)`,
|
|
23515
23706
|
mcpblaze: "Make it a Claude connector (--compliant chatgpt: a ChatGPT plugin), directory-ready in one command: the scorecard first, then every open step in order \u2014 the MCP hostname (one CNAME), the listing answers pre-filled from the spec, the reviewer account, the proof run \u2014 and the submission packet when every check passes. Stop any time; `--status` shows what is left and the page to finish on. You submit. They decide."
|
|
23516
|
-
})).argument("[project-or-spec]", `The ${B.thingShort} (project name), or an OpenAPI URL/file to create a directory-ready ${B.thingShort} from first`).option("--domain <host>", "Custom hostname for the MCP server (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "How long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--target-url <url>", "Where the app runs in production (https): a listing needs the hosted app, not a tunnel; asked when missing").option("--upstream-header <header>", '"Name: value", the header the backend needs to accept these calls, stored encrypted; asked when missing').option("--upstream-header-env <name=var>", "The same with the value read from an environment variable, so it stays out of your shell history").option("--upstream-open", "The backend checks its own credentials: no header").option("--auth-mode <mode>", "How directory clients get in: signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--name <name>", `When creating from a spec: the ${B.thingShort} name (default: derived from the spec host)`).option("--privacy-url <url>", "Public https privacy policy").option("--support <email|url>", "Support contact").option("--docs-url <url>", "Public documentation URL").option("--prompt <text>", "An example prompt (repeatable; three are needed)", collect, []).option("--server-name <name>", 'Listing name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "One-line tagline").option("--description <text>", "Description").option("--icon-url <url>", "Square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--mcp-app", "Claude: the listing is an MCP App (needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT). Alone, this only records the attestation.", collect, []).option("--allowed-origin <origin>", "An extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server; served verbatim at /.well-known/openai-apps-challenge on your MCP domain").option("--data-handling <mode>", "Whose API this is (portal step 7): own \xB7 proxied (add --data-handling-statement) \xB7 third-party. Never pre-filled.").option("--data-handling-statement <text>", "For proxied: the permission you hold (who granted what, and when)").option("--health-data <yes|no>", "The tools handle health data (never pre-filled; yes adds the human-in-the-loop note to the packet)").option("--sponsored-content <yes|no>", "Tool results carry sponsored content (never pre-filled; yes is an unsupported use case)").option("--website-url <url>", "OpenAI's form: the public website (https)").option("--support-url <url>", "OpenAI's form: the support page (https; a URL, not an email)").option("--terms-url <url>", "OpenAI's form: the terms of service page (https)").option("--developer-name <name>", "OpenAI's form: the developer name (\u2264 80; the directory shows your verified identity)").option("--chatgpt-category <name>", "OpenAI's form: one of Productivity \xB7 Creativity \xB7 Developer Tools \xB7 Business & Operations \xB7 Data & Analytics \xB7 Communication \xB7 Education & Research \xB7 Security \xB7 Finance \xB7 Healthcare \xB7 Travel \xB7 Entertainment \xB7 Other").option("--demo-recording-url <url>", "OpenAI's form: the demo recording (required for MCP review)").option("--release-notes <text>", "OpenAI's form: release notes for this submission").option("--short-description <text>", "OpenAI's form: the 30-character subtitle (Claude's tagline stays \u2264 200)").option("--countries <codes>", "OpenAI's form: ISO country codes, comma-separated (default: everywhere)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--exercise", "Run every tool now even when the scorecard says step 8 is done (asked only while it is open)").option("--no-exercise", 'Skip the "Run every tool now?" step').option("--step <id>", "Run one step of the scorecard and exit: mode \xB7 address \xB7 access \xB7 tools \xB7 listing \xB7 answers \xB7 attestations \xB7 prove \xB7 signin \xB7 token \xB7 packet").option("--status", "Print the scorecard (what is done, what is left, where) and exit: 0 when ready, 1 while something is left").option("--verbose", "The full rule report at the end instead of the scorecard (also: check --directory-ready)").option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create a few records through the create tools and keep them (asked when omitted)").option("--no-seed", "Never seed the reviewer account").option("--no-email", "No email at the end of the run").option("--full", "Run the full flow even when only --attest was given").option("--compliant [directory]", "Which directory's policies this run must satisfy: bare = claude; or claude \xB7 chatgpt \xB7 all (repeat or comma-separate)", collect, []).addOption(new import_commander.Option("--policy <directory>", "Older spelling of --compliant").hideHelp()).addOption(new import_commander.Option("--platform <list>", "Older spelling of --compliant").hideHelp()).option("--out <dir>", "Where to write connector-packet-<project>.md/.json (default: current directory)").option("-y, --yes", "No questions: apply the flags, report, exit 1 on a blocker").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (
|
|
23707
|
+
})).argument("[project-or-spec]", `The ${B.thingShort} (project name), or an OpenAPI URL/file to create a directory-ready ${B.thingShort} from first`).option("--target <url|file>", "Same as giving the OpenAPI URL or file as the argument (the spelling `create` uses)").option("--domain <host>", "Custom hostname for the MCP server (mcp.<your domain>); asked for when missing, suggested from the spec").option("--wait <minutes>", "How long to wait for the custom domain to activate once its CNAME is in place (default 15; 0 = do not wait)").option("--target-url <url>", "Where the app runs in production (https): a listing needs the hosted app, not a tunnel; asked when missing").option("--upstream-header <header>", '"Name: value", the header the backend needs to accept these calls, stored encrypted; asked when missing').option("--upstream-header-env <name=var>", "The same with the value read from an environment variable, so it stays out of your shell history").option("--upstream-open", "The backend checks its own credentials: no header").option("--auth-mode <mode>", "How directory clients get in: signin (default) = OAuth sign-in on the connector, API keys alongside; open = no sign-in at all, for public read-only data (Anthropic allows it) \u2014 the API host keeps its own doors. Asked once when every tool is read-only.").option("--name <name>", `When creating from a spec: the ${B.thingShort} name (default: derived from the spec host)`).option("--privacy-url <url>", "Public https privacy policy").option("--support <email|url>", "Support contact").option("--docs-url <url>", "Public documentation URL").option("--prompt <text>", "An example prompt (repeatable; three are needed)", collect, []).option("--server-name <name>", 'Listing name (2\u201340 chars, no "MCP"/"Plugin" suffix)').option("--tagline <text>", "One-line tagline").option("--description <text>", "Description").option("--icon-url <url>", "Square icon (https)").option("--slug <slug>", "Claude: the listing slug (lowercase-with-hyphens; permanent once published)").option("--high-risk <list>", "Claude policy 1A: none, or any of legal,healthcare,insurance,finance").option("--mcp-app", "Claude: the listing is an MCP App (needs 3\u20135 --screenshot)").option("--no-mcp-app", "Claude: a plain connector (default)").option("--screenshot <url=prompt>", "Claude MCP App: a carousel screenshot and the prompt that produced it (repeatable, 3\u20135)", collect, []).option("--vuln-contact <email|url>", "Vulnerability-disclosure contact").option("--attest <id>", "Attest (repeatable): guidelines \xB7 own-backend \xB7 no-financial \xB7 no-genai-media \xB7 no-prompt-injection \xB7 no-conversation-data \xB7 public-docs (Claude's seven) \xB7 no-digital-goods \xB7 content-13-no-ads \xB7 no-restricted-data (ChatGPT). Alone, this only records the attestation.", collect, []).option("--allowed-origin <origin>", "An extra browser origin allowed on the MCP host (repeatable)", collect, []).option("--openai-challenge <token>", "ChatGPT: the domain-verification token the OpenAI portal shows under Plugins \u2192 Connect MCP server; served verbatim at /.well-known/openai-apps-challenge on your MCP domain").option("--data-handling <mode>", "Whose API this is (portal step 7): own \xB7 proxied (add --data-handling-statement) \xB7 third-party. Never pre-filled.").option("--data-handling-statement <text>", "For proxied: the permission you hold (who granted what, and when)").option("--health-data <yes|no>", "The tools handle health data (never pre-filled; yes adds the human-in-the-loop note to the packet)").option("--sponsored-content <yes|no>", "Tool results carry sponsored content (never pre-filled; yes is an unsupported use case)").option("--website-url <url>", "OpenAI's form: the public website (https)").option("--support-url <url>", "OpenAI's form: the support page (https; a URL, not an email)").option("--terms-url <url>", "OpenAI's form: the terms of service page (https)").option("--developer-name <name>", "OpenAI's form: the developer name (\u2264 80; the directory shows your verified identity)").option("--chatgpt-category <name>", "OpenAI's form: one of Productivity \xB7 Creativity \xB7 Developer Tools \xB7 Business & Operations \xB7 Data & Analytics \xB7 Communication \xB7 Education & Research \xB7 Security \xB7 Finance \xB7 Healthcare \xB7 Travel \xB7 Entertainment \xB7 Other").option("--demo-recording-url <url>", "OpenAI's form: the demo recording (required for MCP review)").option("--release-notes <text>", "OpenAI's form: release notes for this submission").option("--short-description <text>", "OpenAI's form: the 30-character subtitle (Claude's tagline stays \u2264 200)").option("--countries <codes>", "OpenAI's form: ISO country codes, comma-separated (default: everywhere)", (v) => v.split(",").map((s) => s.trim()).filter(Boolean)).option("--exercise", "Run every tool now even when the scorecard says step 8 is done (asked only while it is open)").option("--no-exercise", 'Skip the "Run every tool now?" step').option("--step <id>", "Run one step of the scorecard and exit: mode \xB7 address \xB7 access \xB7 tools \xB7 listing \xB7 answers \xB7 attestations \xB7 prove \xB7 signin \xB7 token \xB7 packet").option("--status", "Print the scorecard (what is done, what is left, where) and exit: 0 when ready, 1 while something is left").option("--verbose", "The full rule report at the end instead of the scorecard (also: check --directory-ready)").option("--seed", "Sign-in mode: when a read returns nothing for the reviewer, create a few records through the create tools and keep them (asked when omitted)").option("--no-seed", "Never seed the reviewer account").option("--no-email", "No email at the end of the run").option("--full", "Run the full flow even when only --attest was given").option("--compliant [directory]", "Which directory's policies this run must satisfy: bare = claude; or claude \xB7 chatgpt \xB7 all (repeat or comma-separate)", collect, []).addOption(new import_commander.Option("--policy <directory>", "Older spelling of --compliant").hideHelp()).addOption(new import_commander.Option("--platform <list>", "Older spelling of --compliant").hideHelp()).option("--out <dir>", "Where to write connector-packet-<project>.md/.json (default: current directory)").option("-y, --yes", "No questions: apply the flags, report, exit 1 on a blocker").option("--team <id|name>", "Team the project is in (defaults to your active team)").option("--apiversion <version>", "API version (defaults to the project's)").option("--json", "Output machine-readable JSON").action(action(async (arg, opts) => {
|
|
23708
|
+
const project = arg ?? opts.target;
|
|
23517
23709
|
if (!project) throw new Error(`Which ${B.thingShort}? ${B.cli} connector <project> \xB7 ${B.cli} connector https://\u2026/openapi.yaml \xB7 ${B.cli} connector reviewer <project> \xB7 ${B.cli} connector exercise <project> \xB7 ${B.cli} connector answers <project>`);
|
|
23518
23710
|
await runConnector(project, opts);
|
|
23519
23711
|
}));
|
|
@@ -23866,14 +24058,21 @@ Error: ${err.message}`));
|
|
|
23866
24058
|
function hintStaleCacheOnUnknownOption(cmd) {
|
|
23867
24059
|
cmd.exitOverride((err) => {
|
|
23868
24060
|
if (err.code === "commander.unknownOption") {
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
|
|
24061
|
+
const help = `${B.cli} ${cmd === program2 ? "" : `${cmd.name()} `}--help`;
|
|
24062
|
+
if (B.cli === "mcpblaze") {
|
|
24063
|
+
console.error(import_chalk78.default.yellow(`
|
|
24064
|
+
This is ${B.cli} v${version}. \`npx ${help}\` lists what this command takes.
|
|
24065
|
+
`));
|
|
24066
|
+
} else {
|
|
24067
|
+
console.error(import_chalk78.default.yellow(
|
|
24068
|
+
`
|
|
24069
|
+
This is ${B.cli} v${version}. \`npx ${help}\` lists what this command takes. If the option is documented
|
|
24070
|
+
but this build does not have it, your npx cache is serving an older copy \u2014 re-run as:
|
|
23873
24071
|
`
|
|
23874
|
-
|
|
23875
|
-
|
|
24072
|
+
));
|
|
24073
|
+
console.error(import_chalk78.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
|
|
23876
24074
|
`));
|
|
24075
|
+
}
|
|
23877
24076
|
}
|
|
23878
24077
|
process.exit(err.exitCode);
|
|
23879
24078
|
});
|
|
@@ -23883,9 +24082,27 @@ does not have it, your npx cache is serving an older copy \u2014 re-run as:
|
|
|
23883
24082
|
return program2;
|
|
23884
24083
|
}
|
|
23885
24084
|
|
|
24085
|
+
// src/lib/bare-create.ts
|
|
24086
|
+
var SPEC = /^(https?:\/\/\S+|\S+\.(ya?ml|json))$/i;
|
|
24087
|
+
function bareCreateArgv(args, commands) {
|
|
24088
|
+
if (!args.length) return args;
|
|
24089
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
24090
|
+
const a = args[i];
|
|
24091
|
+
if (a.startsWith("-")) continue;
|
|
24092
|
+
const prev = args[i - 1];
|
|
24093
|
+
const isValue = !!prev && prev.startsWith("--") && !prev.includes("=");
|
|
24094
|
+
if (!isValue && commands.has(a)) return args;
|
|
24095
|
+
}
|
|
24096
|
+
if (SPEC.test(args[0])) return ["create", "--target", ...args];
|
|
24097
|
+
if (args.some((a) => a === "--target" || a.startsWith("--target="))) return ["create", ...args];
|
|
24098
|
+
return args;
|
|
24099
|
+
}
|
|
24100
|
+
|
|
23886
24101
|
// src/index.ts
|
|
23887
24102
|
installBrandFilter();
|
|
23888
24103
|
var program = buildProgram();
|
|
24104
|
+
var known = new Set(program.commands.flatMap((c) => [c.name(), ...c.aliases()]).concat(["help"]));
|
|
24105
|
+
process.argv = [...process.argv.slice(0, 2), ...bareCreateArgv(process.argv.slice(2), known)];
|
|
23889
24106
|
process.on("exit", () => renderTrace());
|
|
23890
24107
|
installFetchGuard(version);
|
|
23891
24108
|
var firstCommand = process.argv.slice(2).find((a) => !a.startsWith("-"));
|