apiblaze 0.21.73 → 0.21.74

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.
Files changed (2) hide show
  1. package/dist/index.js +186 -31
  2. 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.73";
37
+ version = "0.21.74";
38
38
  }
39
39
  });
40
40
 
@@ -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,13 @@ 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
- return anchor ? `${base2}${anchor.startsWith("#") ? anchor : `#${anchor}`}` : base2;
5683
+ const page = withBrandSource(base2);
5684
+ return anchor ? `${page}${anchor.startsWith("#") ? anchor : `#${anchor}`}` : page;
5675
5685
  }
5676
5686
  function stepOf(r, id) {
5677
5687
  return r?.progress?.steps.find((s) => s.id === id) ?? null;
@@ -5796,7 +5806,39 @@ function blockerLines(r, platforms) {
5796
5806
  function isApiError(e, status2) {
5797
5807
  return e instanceof ApiError && (status2 === void 0 || e.status === status2);
5798
5808
  }
5799
- var import_node_fs, import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, STEP_IDS, reportPath, DESCRIPTION_MIN;
5809
+ function claudeCodeTests(r, proj2) {
5810
+ const url = r?.report?.project?.publishedUrl;
5811
+ if (!url) return null;
5812
+ const name = ccServerName(proj2.projectId);
5813
+ const t = r?.target;
5814
+ const seen = /* @__PURE__ */ new Set();
5815
+ const cases = [];
5816
+ const add = (prompt, expect, refuse) => {
5817
+ const p = (prompt ?? "").trim();
5818
+ if (!p || seen.has(p)) return;
5819
+ seen.add(p);
5820
+ cases.push({ prompt: p, line: askLine(name, p), expect, refuse });
5821
+ };
5822
+ for (const p of t?.metadata?.examplePrompts ?? []) add(p, "it calls your tools and answers from what comes back", false);
5823
+ const tc = t?.metadata?.portalAnswers?.chatgpt_test_cases;
5824
+ for (const c of tc?.positive ?? []) add(c.prompt, c.expected_behavior || "it calls your tools and answers from what comes back", false);
5825
+ for (const c of tc?.negative ?? []) add(c.prompt, `it says it can't help${c.description ? ` (${c.description})` : ""}, and calls no tool`, true);
5826
+ const open = t?.project?.authMode === "open";
5827
+ return {
5828
+ add: `claude mcp add --transport http ${name} ${url}`,
5829
+ signIn: open ? null : `claude, then /mcp \u2192 ${name} \u2192 Authenticate, and sign in as reviewer-${proj2.projectId} (the test account); /exit after`,
5830
+ cases
5831
+ };
5832
+ }
5833
+ function claudeCodeText(cc) {
5834
+ const lines = ["Test each use case in Claude Code", ` 1. ${cc.add}`];
5835
+ if (cc.signIn) lines.push(` 2. Sign in once: ${cc.signIn}`);
5836
+ const n = cc.signIn ? 3 : 2;
5837
+ if (!cc.cases.length) lines.push(` ${n}. Add example prompts to the listing first: each becomes a line here.`);
5838
+ else lines.push(` ${n}. One line per use case:`, ...cc.cases.flatMap((c) => [` ${c.line}`, ` expect: ${c.expect}`]));
5839
+ return lines.join("\n");
5840
+ }
5841
+ var import_node_fs, import_chalk17, PLATFORMS, ATTESTATIONS, OPENAI_CATEGORIES, AUTH_MODES, DATA_HANDLING_MODES, STEP_IDS, reportPath, DESCRIPTION_MIN, ccServerName, shellQuote2, askLine;
5800
5842
  var init_directory = __esm({
5801
5843
  "src/lib/directory.ts"() {
5802
5844
  "use strict";
@@ -5806,6 +5848,7 @@ var init_directory = __esm({
5806
5848
  init_op_call();
5807
5849
  init_directory_probes();
5808
5850
  init_types();
5851
+ init_brand();
5809
5852
  PLATFORMS = ["claude", "chatgpt"];
5810
5853
  ATTESTATIONS = [
5811
5854
  // Claude's Compliance step: seven acknowledgments (submission page, 2026-10-16).
@@ -5829,6 +5872,9 @@ var init_directory = __esm({
5829
5872
  STEP_IDS = ["mode", "address", "access", "tools", "listing", "answers", "attestations", "prove", "signin", "token", "packet", "submit"];
5830
5873
  reportPath = (proj2) => `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/directory-report`;
5831
5874
  DESCRIPTION_MIN = 300;
5875
+ ccServerName = (projectId) => projectId.toLowerCase().replace(/[^a-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") || "app";
5876
+ shellQuote2 = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
5877
+ askLine = (name, prompt) => `claude -p ${shellQuote2(prompt)} --allowedTools ${shellQuote2(`mcp__${name}`)}`;
5832
5878
  }
5833
5879
  });
5834
5880
 
@@ -8248,7 +8294,7 @@ function draftUseCases(i) {
8248
8294
  }
8249
8295
  function draftReviewerInstructions(i) {
8250
8296
  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 (Settings \u2192 Connectors \u2192 Add custom connector) or ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : "Claude (Settings \u2192 Connectors \u2192 Add custom connector)";
8297
+ 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
8298
  lines.push(`Add ${i.publishedUrl} as a custom connector in ${where}.`);
8253
8299
  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
8300
  const seen = /* @__PURE__ */ new Set();
@@ -8375,7 +8421,7 @@ function detectCodex(spawn5 = import_child_process5.spawnSync) {
8375
8421
  return { installed: true, loggedIn: st.status === 0 && !/not logged in/i.test(text) };
8376
8422
  }
8377
8423
  function reportLink(projectId, apiVersion = "1.0.0") {
8378
- return `${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}`;
8424
+ return withBrandSource(`${DASHBOARD_BASE8}/directory/${encodeURIComponent(projectId)}/${encodeURIComponent(apiVersion)}`);
8379
8425
  }
8380
8426
  async function confirm(message, interactive2) {
8381
8427
  if (!interactive2) return false;
@@ -13571,6 +13617,13 @@ async function projectSpecText(caller, proj2) {
13571
13617
  }
13572
13618
  }
13573
13619
  var DOMAIN_RULE = "DOMAIN_CUSTOM_REQUIRED";
13620
+ function hostnameProblem(raw) {
13621
+ const v = (raw ?? "").trim().toLowerCase();
13622
+ if (!v) return null;
13623
+ if (/^https?:\/\/|\//.test(v)) return "Just the hostname, no https:// and no path: mcp.yourdomain.com";
13624
+ if (!v.includes(".")) return `"${v}" is only part of it. Type the whole hostname: ${v}.yourdomain.com`;
13625
+ 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";
13626
+ }
13574
13627
  async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, say) {
13575
13628
  const domainRule = r.report.results.find((x) => x.id === DOMAIN_RULE);
13576
13629
  if (!domainRule || domainRule.pass) return { active: true };
@@ -13587,14 +13640,36 @@ async function ensureMcpDomain(caller, proj2, r, opts, defaults, interactive2, s
13587
13640
  hostname2 = suggested;
13588
13641
  } else {
13589
13642
  const { default: inquirer3 } = await import("inquirer");
13590
- const { value } = await inquirer3.prompt([{ type: "input", name: "value", message: "Hostname for the MCP server (on a domain you control):", ...suggested ? { default: suggested } : {} }]);
13591
- hostname2 = value?.trim().toLowerCase();
13643
+ for (; ; ) {
13644
+ 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 }]);
13645
+ hostname2 = value?.trim().toLowerCase();
13646
+ if (!hostname2) {
13647
+ say(import_chalk32.default.dim(" Skipped: no MCP hostname yet. It stays open on the scorecard."));
13648
+ return { active: false };
13649
+ }
13650
+ const sp2 = (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
13651
+ try {
13652
+ row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
13653
+ sp2.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
13654
+ break;
13655
+ } catch (e) {
13656
+ if (!isApiError(e, 400) && !isApiError(e, 409)) {
13657
+ sp2.stop();
13658
+ throw e;
13659
+ }
13660
+ sp2.fail(`${hostname2} was refused: ${e instanceof Error ? e.message : String(e)}`);
13661
+ }
13662
+ }
13592
13663
  }
13593
13664
  }
13594
13665
  if (!hostname2) throw new Error("A custom MCP hostname is required for the directories (--domain mcp.<your domain>).");
13595
- const sp2 = opts.json ? null : (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
13596
- row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
13597
- sp2?.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
13666
+ if (!row) {
13667
+ const problem = hostnameProblem(hostname2);
13668
+ if (problem) throw new Error(`${problem} (--domain mcp.<your domain>)`);
13669
+ const sp2 = opts.json ? null : (0, import_ora12.default)(`Registering ${hostname2}\u2026`).start();
13670
+ row = await producer(caller, { method: "POST", path: base2, body: { hostname: hostname2, api_version: proj2.apiVersion, kind: "mcp" }, summary: `Add custom MCP domain ${hostname2}` });
13671
+ sp2?.succeed(`Registered ${row.hostname ?? hostname2} as the MCP server's domain.`);
13672
+ }
13598
13673
  }
13599
13674
  const hostname = row.hostname ?? opts.domain ?? "";
13600
13675
  if (row.status === "active") return { active: true, hostname, id: row.id };
@@ -13849,13 +13924,16 @@ async function runConnector(target2, opts) {
13849
13924
  const stored = r.progress?.platforms ?? null;
13850
13925
  const modeOn = r.target?.project?.directoryReady === true;
13851
13926
  if (opts.status) {
13927
+ const cc = claudeCodeTests(r, proj2);
13852
13928
  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 });
13929
+ 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
13930
  process.exit(isReady(r, platforms) ? 0 : 1);
13855
13931
  }
13856
13932
  const card2 = scorecardText(r);
13857
13933
  if (card2) console.log(card2);
13858
13934
  else printReport(r, false);
13935
+ if (cc) console.log(`
13936
+ ${claudeCodeText(cc)}`);
13859
13937
  process.exit(isReady(r, platforms) ? 0 : 1);
13860
13938
  }
13861
13939
  const dirsDiffer = explicit && !!stored && (stored.length !== platforms.length || stored.some((p) => !platforms.includes(p)));
@@ -14162,7 +14240,7 @@ async function waitForExternal(caller, proj2, r, platforms, domain, opts, intera
14162
14240
  say(" One sign-in from a real Claude client is the last proof. In another terminal:");
14163
14241
  say(` claude mcp add --transport http ${alias} ${url}`);
14164
14242
  say(` claude then /mcp \u2192 ${alias} \u2192 sign in with the reviewer account`);
14165
- say(import_chalk32.default.dim(` or in claude.ai: Settings \u2192 Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
14243
+ say(import_chalk32.default.dim(` or in claude.ai: Customize > Connectors \u2192 Add custom connector \u2192 ${url} (what a reviewer does).`));
14166
14244
  say(import_chalk32.default.dim(` Afterwards: claude mcp remove ${alias}`));
14167
14245
  const { default: inquirer3 } = await import("inquirer");
14168
14246
  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 +14661,7 @@ function printSubmitSteps(r, mdPath, platforms = PLATFORMS, watching = false) {
14583
14661
  const open = isOpenMode(r);
14584
14662
  const claude = platforms.includes("claude");
14585
14663
  const chatgpt = platforms.includes("chatgpt");
14586
- const where = [claude ? "Claude (Settings \u2192 Connectors)" : null, chatgpt ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : null].filter(Boolean).join(" and in ");
14664
+ const where = [claude ? "Claude (Customize > Connectors)" : null, chatgpt ? "ChatGPT (chatgpt.com/plugins \u2192 Add custom MCP server)" : null].filter(Boolean).join(" and in ");
14587
14665
  const steps = [
14588
14666
  `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
14667
  ...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 +14998,42 @@ function backendPort(f2) {
14920
14998
 
14921
14999
  // src/commands/skill.ts
14922
15000
  init_api();
15001
+
15002
+ // src/lib/listing-rules.ts
15003
+ var LISTING_RULES = `**Who reads what.** The listing (name, tagline, description, example prompts, categories) is read by an ordinary
15004
+ 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,
15005
+ a sandbox or hosting is. Use cases and reviewer instructions are read by the directory's review team,
15006
+ who add the App and try it.
15007
+
15008
+ **Read the input the right way.** The API's own title and description were written by developers for developers: hosting,
15009
+ sandboxes, limits, setup, git, internals. Never copy or paraphrase that; use it only to understand the domain. Learn what
15010
+ the App does from its tools: each tool is something a person can ask for.
15011
+
15012
+ **The fields.**
15013
+ - Name (\`--server-name\`): what a person would call it, often the product or brand. No "API", "MCP", "Plugin", "Server",
15014
+ version numbers or the word "App". At most 100 characters, 30 when ChatGPT is a target.
15015
+ - Tagline (\`--tagline\`): one plain sentence saying what the person gets done; starts with a verb; at most 200 characters.
15016
+ - Description (\`--description\`): 300 to 1,500 characters, two or three short paragraphs. What a person can do, in their
15017
+ terms ("find a table for four on Friday", not "createReservation endpoint"); who it is for; one sentence on whether it
15018
+ only reads or can also change things in their account. No lists of tool names, no marketing adjectives.
15019
+ - Example prompts (\`--prompt\`, three to five): what a person would really type, each answered by a tool, under 128
15020
+ characters, varied (look something up, then act on it), concrete values, no placeholders.
15021
+ - Prerequisites (\`connector answers --prerequisites\`): one sentence, what someone needs before using it.
15022
+ - Categories (\`connector answers --category\`, one to five): plain words a person would browse by.
15023
+ - Use cases (\`connector answers --use-cases\`): 200 to 1,500 characters for the review team: the real jobs, in the
15024
+ person's words, each tied to what comes back.
15025
+ - Reviewer instructions (\`connector answers --reviewer-instructions\`): 400 to 2,500 characters, numbered: where to add
15026
+ it (the MCP address), how to sign in (the test account; never write a password), then three to six prompts to try,
15027
+ each with what the reviewer should see. Build on what the last test run showed; never invent a result.
15028
+
15029
+ **Never write** claims nothing supports (secure, real-time, compliant, best, official, fastest), the names Claude,
15030
+ ChatGPT, Anthropic or OpenAI (except in reviewer instructions, to say where to add it), URLs other than the MCP address in
15031
+ reviewer instructions, prices, or anything about legal, health or finance use.
15032
+
15033
+ **Never answer for the user**: the privacy policy URL, the icon, whose API it is (data handling), health data, sponsored
15034
+ content, high-risk areas, and the attestations. Ask; these are promises only they can make.`;
15035
+
15036
+ // src/commands/skill.ts
14923
15037
  var SKILL_NAME = "ship-safely";
14924
15038
  var SKILL_RELATIVE_PATH = import_path10.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
14925
15039
  function skillNameFor(brand = BRAND) {
@@ -15184,10 +15298,10 @@ Most people new to it do not know the words tenant, OpenAPI or header, so plain
15184
15298
  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
15299
  (sign-in and per-user rules come with it; it prints the MCP address). The app runs on this machine:
15186
15300
  \`${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>\` makes it a Claude connector
15188
- (\`--compliant chatgpt\` a ChatGPT plugin, \`--compliant all\` both). It prints the scorecard first \u2014 N of 12 done
15189
- and what is left \u2014 then runs every open step in order and saves each answer as it is given; the user can stop any time
15190
- and finish on the page it links. \`${run2} connector <name> --status\` is the scorecard alone.
15301
+ 2. **"Want it listed in Claude or ChatGPT?"** \u2014 right after the MCP server exists, \`${run2} connector <name>\`
15302
+ (\`--compliant chatgpt\` for ChatGPT, \`--compliant all\` both). Its first line is the page link: give it to the user
15303
+ first, then go through the open steps with them, writing the listing yourself (\`${run2} skills connector\` has the
15304
+ steps and the writing rules). \`${run2} connector <name> --status\` says what is left, with a Claude Code test line per use case.
15191
15305
 
15192
15306
  \`${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
15307
 
@@ -15260,13 +15374,11 @@ function shipMcpMarkdown() {
15260
15374
  who can override: the dashboard, Tenant \u2192 Admins.
15261
15375
  7. **Try it from Claude Code.** \`claude mcp add --transport http <name> <the MCP address it printed>\`, then \`/mcp\` and
15262
15376
  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): the scorecard
15264
- first, then every open step in order \u2014 the MCP hostname (one CNAME the user adds at their DNS provider; relay the exact
15265
- record), the listing answers pre-filled from the spec, the reviewer account, the proof run \u2014 and the submission packet when
15266
- every check passes. It needs the hosted app, not a tunnel: have the production URL and any header the backend needs ready, and
15267
- run the proof again after switching. Stop any time; \`${run2} connector <name> --status\` says what is left and links the page where the rest
15268
- can be finished.
15269
- The user submits the packet to Anthropic or OpenAI themselves.
15377
+ 8. **List it.** \`${run2} connector <name>\` (Claude; \`--compliant chatgpt\` or \`all\` for OpenAI's directory). Give the user
15378
+ the page link it prints first, then follow \`${run2} skills connector\`: the MCP address (one CNAME; relay the exact record),
15379
+ sign-in and the test account, the listing and the review-team answers that you write by its rules, the tests (one
15380
+ \`claude -p\` per use case, from \`--status\`), and the packet. It needs the hosted app, not a tunnel. Stop any time;
15381
+ \`${run2} connector <name> --status\` says what is left. The user submits the packet to Anthropic or OpenAI themselves.
15270
15382
 
15271
15383
  Report what you did in plain words: the MCP address, who can sign in, what is locked, what is left on the scorecard.
15272
15384
 
@@ -15676,6 +15788,41 @@ Each tenant (a group of users with their own sign-in) has a developer portal whe
15676
15788
 
15677
15789
  Tenant admins (they can see and manage every record and every user): the dashboard, Tenant \u2192 Admins, by email, even
15678
15790
  before that person ever signs in. The whole APIblaze command set is here too: \`npx ${cli} help --all\`.`);
15791
+ case "connector":
15792
+ return page("Get it listed in Claude (and ChatGPT) with MCPblaze", `
15793
+ The listing is the MCP server, sent to Anthropic's directory (Claude) and, if the user wants, OpenAI's (ChatGPT).
15794
+ The user submits. Say it "meets the requirements MCPblaze can verify; listing decisions are made by Anthropic and OpenAI",
15795
+ never "certified".
15796
+
15797
+ **Start, and give the page first.** \`npx ${cli} connector <name>\` (Claude; add \`--compliant chatgpt\` or \`--compliant all\`).
15798
+ Its first line is "Finish this here or at <page link> \xB7 N left": give the user that link before anything else. The page
15799
+ has the same steps with forms, a % ready ring, and a Testing tab; the user can work there, here, or both. Every answer is
15800
+ saved as it is given; \`npx ${cli} connector <name> --status\` says what is left at any time.
15801
+
15802
+ **The steps, in the page's order.** Go through the open ones with the user, one at a time, in plain words:
15803
+ 1. **Get listed**: the command turns it on. Nothing is public until the user submits.
15804
+ 2. **Connect your API**: the MCP address on their own domain (one CNAME at their DNS provider; relay the exact record
15805
+ it prints) and the hosted app (\`--target-url\`, \`--upstream-header\`): a tunnel cannot be listed.
15806
+ 3. **How people sign in to your MCP**: sign-in by default, a test account is made for the reviewers. Public, read-only
15807
+ data can skip sign-in (\`--auth-mode open\`).
15808
+ 4. **What your App can do**: the tools (\`npx ${cli} skills mcp\` has the method).
15809
+ 5. **Your listing, and the questions for the review team**: YOU write the text, from the code and the tools, following
15810
+ the rules below. Show the user every field, then save what they approve:
15811
+ \`npx ${cli} connector <name> --step listing -y --server-name "\u2026" --tagline "\u2026" --description "\u2026" --prompt "\u2026" --prompt "\u2026" --prompt "\u2026"\`
15812
+ (plus \`--support\`, \`--vuln-contact\`; for ChatGPT \`--short-description\`, \`--developer-name\`, \`--website-url\`, \`--terms-url\`)
15813
+ and \`npx ${cli} connector answers <name> --use-cases "\u2026" --prerequisites "\u2026" --category "\u2026" --reviewer-instructions "\u2026"\`.
15814
+ 6. **Test**: \`npx ${cli} connector exercise <name>\` calls every tool as the reviewer. Then
15815
+ \`npx ${cli} connector <name> --status\` prints the Claude Code lines: one \`claude mcp add\`, one sign-in, and one
15816
+ \`claude -p '\u2026' --allowedTools 'mcp__<name>'\` per example prompt, each with what should come back. Run each, tell the user
15817
+ what came back, and fix a tool's description (\`npx ${cli} mcp ops set\`) when a prompt did not reach the right tool.
15818
+ 7. **Send**: when every check passes it writes the packet. The user submits it on claude.ai/directory/manage (and the
15819
+ OpenAI portal for ChatGPT).
15820
+
15821
+ ## Writing the listing
15822
+
15823
+ ${LISTING_RULES}
15824
+
15825
+ ${HEDGE}`);
15679
15826
  case "rules":
15680
15827
  return page("Who can change what (MCPblaze)", `
15681
15828
  - With a spec, \`dev\` and \`create\` lock every record to the person who created it: an agent acting for Alice
@@ -23513,7 +23660,8 @@ function buildProgram() {
23513
23660
  const connector = program2.command("connector").description(descriptionFor({
23514
23661
  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
23662
  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 (project, opts) => {
23663
+ })).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) => {
23664
+ const project = arg ?? opts.target;
23517
23665
  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
23666
  await runConnector(project, opts);
23519
23667
  }));
@@ -23866,14 +24014,21 @@ Error: ${err.message}`));
23866
24014
  function hintStaleCacheOnUnknownOption(cmd) {
23867
24015
  cmd.exitOverride((err) => {
23868
24016
  if (err.code === "commander.unknownOption") {
23869
- console.error(import_chalk78.default.yellow(
23870
- `
23871
- This is ${B.cli} v${version}. If that option is documented but this build
23872
- does not have it, your npx cache is serving an older copy \u2014 re-run as:
24017
+ const help = `${B.cli} ${cmd === program2 ? "" : `${cmd.name()} `}--help`;
24018
+ if (B.cli === "mcpblaze") {
24019
+ console.error(import_chalk78.default.yellow(`
24020
+ This is ${B.cli} v${version}. \`npx ${help}\` lists what this command takes.
24021
+ `));
24022
+ } else {
24023
+ console.error(import_chalk78.default.yellow(
24024
+ `
24025
+ This is ${B.cli} v${version}. \`npx ${help}\` lists what this command takes. If the option is documented
24026
+ but this build does not have it, your npx cache is serving an older copy \u2014 re-run as:
23873
24027
  `
23874
- ));
23875
- console.error(import_chalk78.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
24028
+ ));
24029
+ console.error(import_chalk78.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
23876
24030
  `));
24031
+ }
23877
24032
  }
23878
24033
  process.exit(err.exitCode);
23879
24034
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.21.73",
3
+ "version": "0.21.74",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",