@everfur/cli 0.2.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -50
- package/dist/{chunk-KQICUK54.js → chunk-CGWSEOW2.js} +5643 -1112
- package/dist/{chunk-XMIERMOW.js → chunk-IZGEY7H6.js} +663 -227
- package/dist/{chunk-FC23PLWG.js → chunk-KAHIDPBX.js} +1 -1
- package/dist/{chunk-4UB26D67.js → chunk-NLB5XDNO.js} +74 -30
- package/dist/{chunk-SJUEHHU6.js → chunk-TZVIWABT.js} +81 -14
- package/dist/everfur-mcp-hosted.js +4 -4
- package/dist/everfur-mcp-http.js +3 -3
- package/dist/everfur-mcp.js +2 -2
- package/dist/everfur.js +2 -2
- package/dist/index.d.ts +420 -113
- package/dist/index.js +2 -2
- package/package.json +4 -3
- package/schema/error-explanation.v1.schema.json +26 -4
- package/schema/plan.v1.schema.json +49 -4
- package/schema/verification.v1.schema.json +5 -4
- package/dist/everfur-mcp-container.d.ts +0 -2
- package/dist/everfur-mcp-container.js +0 -60
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { buildServer } from './chunk-
|
|
2
|
+
import { buildServer } from './chunk-IZGEY7H6.js';
|
|
3
3
|
import { createServer } from 'http';
|
|
4
4
|
import { timingSafeEqual } from 'crypto';
|
|
5
5
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERIFY_IDENTITY_MODES, SURFACES, ENVIRONMENTS, BASE_URL_ENV_VAR, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ALL_CAPABILITIES, expandCapabilities, errorOutcome, describeFailure, failureDetails, countsAsNotOn, diagnoseVerdict, isJsonObject, succeed, CAPABILITY_QUESTION, CAPABILITIES_TREE, WEB_EMBEDDING_QUESTION, PLATFORM_TREE, loadDecisionSpace, fetchContract, contractUnavailableOutcome, loadDecisionSpaceFromContract, parseEnvironment, detectProject, planIntegration, fail, ExitCode, DOCS_KEY, resolveContractPath, validateContractFile, buildSupportBundle, renderSupportBundle, chooseWidgetAuth, DIAGNOSTICS_PATH, diagnoseFailure, parseDiagnostics, buildDiagnosisDocument, PARTNER_KEY, SESSION_TOKEN, resolveCredential, ADMIN_TOKEN, withPrefix, parseOrigin, parseLoaderUrl, parseSurfaces, parseIdentityMode, runVerification, PARTNER_SECRET_KEY, resolveOffline,
|
|
2
|
+
import { VERIFY_IDENTITY_MODES, SURFACES, ENVIRONMENTS, BASE_URL_ENV_VAR, MIN_TIMEOUT_MS, MAX_TIMEOUT_MS, DEFAULT_TIMEOUT_MS, ALL_CAPABILITIES, expandCapabilities, errorOutcome, describeFailure, failureDetails, countsAsNotOn, diagnoseVerdict, isJsonObject, succeed, CAPABILITY_QUESTION, CAPABILITIES_TREE, WEB_EMBEDDING_QUESTION, PLATFORM_TREE, loadDecisionSpace, fetchContract, contractUnavailableOutcome, loadDecisionSpaceFromContract, parseEnvironment, checkTenantMode, detectProject, planIntegration, fail, ExitCode, DOCS_KEY, resolveContractPath, validateContractFile, buildSupportBundle, renderSupportBundle, chooseWidgetAuth, DIAGNOSTICS_PATH, diagnoseFailure, parseDiagnostics, buildDiagnosisDocument, PARTNER_KEY, SESSION_TOKEN, resolveCredential, ADMIN_TOKEN, withPrefix, parseOrigin, parseLoaderUrl, parseSurfaces, parseIdentityMode, runVerification, PARTNER_SECRET_KEY, resolveOffline, resolveWithoutRegistry, knownCodes, loadErrorRegistryFromContract, loadErrorRegistry, resolveAfterRegistryMiss, registryAlsoIn, buildExplanationDocument, renderExplanation, alsoInLines, resolveEndpoint, isCorrelatableRequestId, EMPTY_REGISTRY, createApiClient, offlineDocument, renderOffline, IDENTITY_QUESTION, IDENTITY_TREE, BACKEND_LANGUAGE_QUESTION, SESSION_MINT_TREE, CREDENTIAL_SPECS } from './chunk-CGWSEOW2.js';
|
|
3
3
|
import { isAbsolute, resolve } from 'path';
|
|
4
4
|
|
|
5
5
|
// src/cli/format.ts
|
|
@@ -186,6 +186,9 @@ function renderRefusal(document) {
|
|
|
186
186
|
lines.push(` ${fact.fact}`);
|
|
187
187
|
lines.push(` why: ${fact.reason}`);
|
|
188
188
|
lines.push(` supply: ${fact.supplyWith}`);
|
|
189
|
+
if (fact.requiredWhen !== void 0) {
|
|
190
|
+
lines.push(` only when: ${fact.requiredWhen.fact} is ${fact.requiredWhen.oneOf.join(" or ")}; answer it with that fact`);
|
|
191
|
+
}
|
|
189
192
|
}
|
|
190
193
|
lines.push("");
|
|
191
194
|
lines.push(...renderDetected(document));
|
|
@@ -523,6 +526,14 @@ var CONTRACT_FLAG = Object.freeze({
|
|
|
523
526
|
summary: "Path to a contract-public projection, used to explain any error code returned.",
|
|
524
527
|
placeholder: "file"
|
|
525
528
|
});
|
|
529
|
+
var PLAN_CONTRACT_FLAG = Object.freeze({
|
|
530
|
+
...CONTRACT_FLAG,
|
|
531
|
+
summary: "Path to a contract-public projection to plan from (its decision trees), read instead of the contract fetched with EVERFUR_DOCS_KEY."
|
|
532
|
+
});
|
|
533
|
+
var EXPLAIN_CONTRACT_FLAG = Object.freeze({
|
|
534
|
+
...CONTRACT_FLAG,
|
|
535
|
+
summary: "Path to a contract-public projection whose error registry answers the code, read instead of the contract fetched with EVERFUR_DOCS_KEY."
|
|
536
|
+
});
|
|
526
537
|
var CONTRACT_ENV_VAR = "EVERFUR_CONTRACT_PUBLIC";
|
|
527
538
|
var DEFAULT_CONTRACT_PATHS = Object.freeze([
|
|
528
539
|
"contract-public/contract-public.json",
|
|
@@ -613,6 +624,7 @@ var POLL_DELAY_MS = 500;
|
|
|
613
624
|
var MAX_ATTEMPTS = 20;
|
|
614
625
|
var VERIFICATION_SCHEMA_VERSION = 1;
|
|
615
626
|
var VERIFICATION_DOCUMENT_KIND = "everfur.verification";
|
|
627
|
+
var WEB_EMBEDDINGS = Object.freeze(["in-page", "frame"]);
|
|
616
628
|
var FLAGS = Object.freeze([
|
|
617
629
|
BASE_URL_FLAG,
|
|
618
630
|
{
|
|
@@ -630,9 +642,16 @@ var FLAGS = Object.freeze([
|
|
|
630
642
|
{
|
|
631
643
|
name: "origin",
|
|
632
644
|
kind: "string",
|
|
633
|
-
summary: "For a web
|
|
645
|
+
summary: "For a framed web page (the loader and an iframe): the page origin (https://shop.example.com) the frame renders on. Adds the CORS preflight, embed fence, CORS exposure, session policy and loader checks. An in-page integration needs no --origin.",
|
|
634
646
|
placeholder: "origin"
|
|
635
647
|
},
|
|
648
|
+
{
|
|
649
|
+
// The same flag name `plan` takes for the same fact (the argv parser knows it as a value flag).
|
|
650
|
+
name: "embedding",
|
|
651
|
+
kind: "string",
|
|
652
|
+
summary: `With --origin: how the page embeds the surface, ${WEB_EMBEDDINGS.join(" or ")}. frame (the default without --project-root) runs every check; in-page (the SDK's React components in the page) reports the embed fence and the loader N/A, since only the frame uses them. Read from --project-root when not given.`,
|
|
653
|
+
placeholder: "mode"
|
|
654
|
+
},
|
|
636
655
|
{
|
|
637
656
|
name: "loader-url",
|
|
638
657
|
kind: "string",
|
|
@@ -665,10 +684,12 @@ var FLAGS = Object.freeze([
|
|
|
665
684
|
function buildVerificationDocument(verification, baseUrl, cliVersion) {
|
|
666
685
|
const counts = { passed: 0, failed: 0, skipped: 0 };
|
|
667
686
|
let gated = 0;
|
|
687
|
+
let notApplicable = 0;
|
|
668
688
|
for (const entry of verification.steps) {
|
|
669
689
|
if (entry.result === "PASS") counts.passed += 1;
|
|
670
690
|
else if (entry.result === "FAIL") counts.failed += 1;
|
|
671
691
|
else if (entry.result === "GATED") gated += 1;
|
|
692
|
+
else if (entry.result === "N/A") notApplicable += 1;
|
|
672
693
|
else counts.skipped += 1;
|
|
673
694
|
}
|
|
674
695
|
return {
|
|
@@ -677,8 +698,9 @@ function buildVerificationDocument(verification, baseUrl, cliVersion) {
|
|
|
677
698
|
generatedBy: { name: "@everfur/cli", version: cliVersion },
|
|
678
699
|
status: verification.status,
|
|
679
700
|
baseUrl,
|
|
680
|
-
// `gated` appears only when a step was GATED, so a document from a run
|
|
681
|
-
|
|
701
|
+
// `gated` appears only when a step was GATED, and `notApplicable` only when one was N/A, so a document from a run
|
|
702
|
+
// with neither is unchanged.
|
|
703
|
+
summary: { ...counts, ...gated === 0 ? {} : { gated }, ...notApplicable === 0 ? {} : { notApplicable } },
|
|
682
704
|
steps: verification.steps.map((entry) => ({
|
|
683
705
|
id: entry.id,
|
|
684
706
|
title: entry.title,
|
|
@@ -709,6 +731,8 @@ function renderVerification(verification, baseUrl) {
|
|
|
709
731
|
lines.push("Nothing failed, but a step was skipped, so the wire was not fully exercised.");
|
|
710
732
|
} else if (verification.steps.some((entry) => entry.result === "GATED")) {
|
|
711
733
|
lines.push("Nothing failed. A GATED step is a surface Everfur has not switched on for this tenant, not a fault in the integration.");
|
|
734
|
+
} else if (verification.steps.some((entry) => entry.result === "N/A")) {
|
|
735
|
+
lines.push("Nothing failed. An N/A step is a check that does not apply to this integration, and it was not run.");
|
|
712
736
|
} else {
|
|
713
737
|
lines.push(
|
|
714
738
|
verification.steps.some((entry) => entry.id === "anonymous-identity") ? "Every step passed. The anonymous identity, the verdict read, and the verdict itself are all healthy." : "Every step passed. The session mint, the verdict read, and the verdict itself are all healthy."
|
|
@@ -716,6 +740,12 @@ function renderVerification(verification, baseUrl) {
|
|
|
716
740
|
}
|
|
717
741
|
return lines.join("\n");
|
|
718
742
|
}
|
|
743
|
+
function parseEmbedding2(raw) {
|
|
744
|
+
if (raw === null) return { value: null, errors: [] };
|
|
745
|
+
const value = raw.trim();
|
|
746
|
+
const known2 = WEB_EMBEDDINGS.find((embedding) => embedding === value);
|
|
747
|
+
return known2 === void 0 ? { value: null, errors: [`--embedding must be ${WEB_EMBEDDINGS.join(" or ")}, not "${raw}"`] } : { value: known2, errors: [] };
|
|
748
|
+
}
|
|
719
749
|
function parseAttempts(raw) {
|
|
720
750
|
if (raw === null) return { value: DEFAULT_ATTEMPTS, errors: [] };
|
|
721
751
|
const parsed = Number(raw.trim());
|
|
@@ -730,7 +760,7 @@ function parseAttempts(raw) {
|
|
|
730
760
|
var verifyCommand = {
|
|
731
761
|
path: ["verify"],
|
|
732
762
|
summary: "Exercise the wire: mint a session, read the verdict, assert it is not empty.",
|
|
733
|
-
usage: "verify [--base-url <url>] [--user-ref <ref>] [--attempts <n>] [--identity <mode>] [--origin <origin>] [--loader-url <url>] [--surfaces <list>] [--project-root <path>] [--json]",
|
|
763
|
+
usage: "verify [--base-url <url>] [--user-ref <ref>] [--attempts <n>] [--identity <mode>] [--origin <origin>] [--embedding <mode>] [--loader-url <url>] [--surfaces <list>] [--project-root <path>] [--json]",
|
|
734
764
|
flags: FLAGS,
|
|
735
765
|
/**
|
|
736
766
|
* Exactly what `mintSession` resolves: the session token first, else the key pair it mints with locally. Without
|
|
@@ -741,6 +771,7 @@ var verifyCommand = {
|
|
|
741
771
|
const attempts = parseAttempts(flags.string("attempts"));
|
|
742
772
|
const originFlag = flags.string("origin");
|
|
743
773
|
const origin = originFlag === null ? { origin: null, error: null } : parseOrigin(originFlag);
|
|
774
|
+
const embedding = parseEmbedding2(flags.string("embedding"));
|
|
744
775
|
const loaderFlag = flags.string("loader-url");
|
|
745
776
|
const loader = loaderFlag === null ? { url: null, error: null } : parseLoaderUrl(loaderFlag);
|
|
746
777
|
const surfaces = parseSurfaces(flags.string("surfaces"));
|
|
@@ -751,6 +782,7 @@ var verifyCommand = {
|
|
|
751
782
|
const flagErrors = [
|
|
752
783
|
...attempts.errors,
|
|
753
784
|
...origin.error === null ? [] : [origin.error],
|
|
785
|
+
...embedding.errors,
|
|
754
786
|
...loader.error === null ? [] : [loader.error],
|
|
755
787
|
...surfaces.errors,
|
|
756
788
|
...identityMode.errors,
|
|
@@ -771,6 +803,7 @@ var verifyCommand = {
|
|
|
771
803
|
attempts: attempts.value,
|
|
772
804
|
pollDelayMs: POLL_DELAY_MS,
|
|
773
805
|
origin: origin.origin,
|
|
806
|
+
embedding: embedding.value,
|
|
774
807
|
loaderUrl: loader.url,
|
|
775
808
|
transport: context.transport,
|
|
776
809
|
surfaces: surfaces.value ?? [],
|
|
@@ -826,7 +859,7 @@ var FLAGS2 = Object.freeze([
|
|
|
826
859
|
summary: `The Everfur deployment the generated configuration targets: ${ENVIRONMENTS.join(", ")}. Required for a plan that writes configuration; never assumed.`,
|
|
827
860
|
placeholder: "name"
|
|
828
861
|
},
|
|
829
|
-
|
|
862
|
+
PLAN_CONTRACT_FLAG
|
|
830
863
|
]);
|
|
831
864
|
function asJsonObject(document) {
|
|
832
865
|
return JSON.parse(JSON.stringify(document));
|
|
@@ -912,8 +945,10 @@ var planCommand = {
|
|
|
912
945
|
...environment.errors
|
|
913
946
|
]);
|
|
914
947
|
}
|
|
948
|
+
const mode = await checkTenantMode({ env: context.env, environment: environment.value, transport: context.transport, clock: context.clock });
|
|
949
|
+
if (mode.kind === "mismatch") return errorOutcome("usage_error", mode.message, mode.details);
|
|
915
950
|
const detection = detectProject(context.fs, root);
|
|
916
|
-
const
|
|
951
|
+
const planned = planIntegration({
|
|
917
952
|
detection,
|
|
918
953
|
capabilities: supplied.capabilities,
|
|
919
954
|
allCapabilities: supplied.allCapabilities,
|
|
@@ -924,6 +959,7 @@ var planCommand = {
|
|
|
924
959
|
space,
|
|
925
960
|
cliVersion: context.version
|
|
926
961
|
});
|
|
962
|
+
const document = mode.kind === "not_checked" && planned.status === "ready" ? Object.freeze({ ...planned, warnings: Object.freeze([...planned.warnings, mode.warning]) }) : planned;
|
|
927
963
|
const json = asJsonObject(document);
|
|
928
964
|
const text = renderPlan(document);
|
|
929
965
|
if (document.status === "underspecified") return fail(ExitCode.UNDERSPECIFIED, json, text);
|
|
@@ -1115,7 +1151,7 @@ var diagnoseCommand = {
|
|
|
1115
1151
|
};
|
|
1116
1152
|
|
|
1117
1153
|
// src/commands/explain-error/index.ts
|
|
1118
|
-
var FLAGS5 = Object.freeze([
|
|
1154
|
+
var FLAGS5 = Object.freeze([EXPLAIN_CONTRACT_FLAG]);
|
|
1119
1155
|
var POSITIONALS = Object.freeze([
|
|
1120
1156
|
{
|
|
1121
1157
|
name: "code",
|
|
@@ -1136,7 +1172,7 @@ var explainErrorCommand = {
|
|
|
1136
1172
|
return errorOutcome("usage_error", "no error code was given", ["usage: everfur explain-error <code>"]);
|
|
1137
1173
|
}
|
|
1138
1174
|
const offline = resolveOffline(code);
|
|
1139
|
-
if (offline?.kind === "sdk_member") return offlineOutcome(offline, context.version);
|
|
1175
|
+
if (offline?.kind === "sdk_member") return offlineOutcome(offline, context.version, "no_registry");
|
|
1140
1176
|
const contractFlag = flags.string("contract");
|
|
1141
1177
|
const contractPath = findContractPath(context, contractFlag);
|
|
1142
1178
|
let load;
|
|
@@ -1148,10 +1184,13 @@ var explainErrorCommand = {
|
|
|
1148
1184
|
...context.contractCache === void 0 ? {} : { cache: context.contractCache }
|
|
1149
1185
|
});
|
|
1150
1186
|
if (fetched.kind === "unavailable") {
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
return
|
|
1154
|
-
...suggestions.length > 0 ? [`did you mean: ${suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : [`no
|
|
1187
|
+
const resolved = resolveWithoutRegistry(code);
|
|
1188
|
+
if (resolved.kind !== "unknown") return offlineOutcome(resolved, context.version, "no_registry");
|
|
1189
|
+
return errorOutcome("unknown_error_code", `"${code}" is not a code this CLI knows`, [
|
|
1190
|
+
...resolved.suggestions.length > 0 ? [`did you mean: ${resolved.suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : [`no similar code exists in the ${knownCodes(null).length} codes this CLI carries`],
|
|
1191
|
+
"searched: the SDK taxonomy, the SDK contract table, the MCP tools' codes, the diagnostics reason codes, this CLI's error codes and the SDK's sub-enums",
|
|
1192
|
+
`the live error registry was not read (${fetched.message}); it can list a code these tables predate:`,
|
|
1193
|
+
...fetched.details.filter((detail) => detail.startsWith("export "))
|
|
1155
1194
|
]);
|
|
1156
1195
|
}
|
|
1157
1196
|
load = loadErrorRegistryFromContract(fetched.contract);
|
|
@@ -1168,11 +1207,11 @@ var explainErrorCommand = {
|
|
|
1168
1207
|
const entry = load.registry.lookup(code);
|
|
1169
1208
|
if (entry === null) {
|
|
1170
1209
|
const fallback = resolveAfterRegistryMiss(code, load.registry);
|
|
1171
|
-
if (fallback.kind !== "unknown") return offlineOutcome(fallback, context.version);
|
|
1210
|
+
if (fallback.kind !== "unknown") return offlineOutcome(fallback, context.version, "registry_miss");
|
|
1172
1211
|
return errorOutcome("unknown_error_code", `"${code}" is not in the error registry`, [
|
|
1173
1212
|
...fallback.suggestions.length > 0 ? [`did you mean: ${fallback.suggestions.join(", ")}`, "codes are case sensitive; a client branches on the literal"] : ["no similar code exists in the registry"],
|
|
1174
1213
|
`the registry loaded from ${load.path} lists ${load.registry.codes.length} codes`,
|
|
1175
|
-
`
|
|
1214
|
+
`no other table this CLI carries knows it either: the SDK taxonomy, the SDK contract table, the MCP tools' codes, the diagnostics reason codes, this CLI's error codes and the SDK's sub-enums (${knownCodes(load.registry).length} codes searched in all)`
|
|
1176
1215
|
]);
|
|
1177
1216
|
}
|
|
1178
1217
|
const input = {
|
|
@@ -1181,11 +1220,13 @@ var explainErrorCommand = {
|
|
|
1181
1220
|
digest: load.digest,
|
|
1182
1221
|
cliVersion: context.version
|
|
1183
1222
|
};
|
|
1184
|
-
|
|
1223
|
+
const document = { ...buildExplanationDocument(input), ...registryAlsoIn(code) };
|
|
1224
|
+
return succeed(document, [renderExplanation(input), ...alsoInLines(document)].join("\n"));
|
|
1185
1225
|
}
|
|
1186
1226
|
};
|
|
1187
|
-
function offlineOutcome(resolution, cliVersion) {
|
|
1188
|
-
|
|
1227
|
+
function offlineOutcome(resolution, cliVersion, context) {
|
|
1228
|
+
const document = offlineDocument(resolution, cliVersion, context);
|
|
1229
|
+
return succeed(document, renderOffline(resolution, document, context));
|
|
1189
1230
|
}
|
|
1190
1231
|
function contractOutcome2(outcome) {
|
|
1191
1232
|
switch (outcome.kind) {
|
|
@@ -1233,7 +1274,11 @@ var FLAGS6 = Object.freeze([
|
|
|
1233
1274
|
]);
|
|
1234
1275
|
var sandboxCreateCommand = {
|
|
1235
1276
|
path: ["sandbox", "create"],
|
|
1236
|
-
|
|
1277
|
+
/**
|
|
1278
|
+
* What the command does TODAY (ROADMAP.md), not the designed claim flow: the live route returns the secret inline and
|
|
1279
|
+
* no claim URL, so the key is revoked unread and the command exits 8 (SECRET_UNCLAIMABLE).
|
|
1280
|
+
*/
|
|
1281
|
+
summary: "Everfur-internal. Mints a partner secret key and, because no claim flow exists to hand it over safely, revokes it again and exits 8. Never prints the secret.",
|
|
1237
1282
|
usage: "sandbox create --client <client-id> [--label <text>] [--base-url <url>] [--json]",
|
|
1238
1283
|
flags: FLAGS6,
|
|
1239
1284
|
/** The one credential this command resolves. It never reads a partner key of either kind. */
|
|
@@ -1357,20 +1402,14 @@ function parseArgv(argv) {
|
|
|
1357
1402
|
const path = [];
|
|
1358
1403
|
const flags = /* @__PURE__ */ new Map();
|
|
1359
1404
|
const errors = [];
|
|
1360
|
-
let sawFlag = false;
|
|
1361
1405
|
for (let index = 0; index < argv.length; index += 1) {
|
|
1362
1406
|
const token = argv[index];
|
|
1363
1407
|
if (token === void 0) continue;
|
|
1364
1408
|
if (token === "--") break;
|
|
1365
1409
|
if (!isFlagToken(token)) {
|
|
1366
|
-
|
|
1367
|
-
errors.push(`unexpected argument "${token}" after flags began`);
|
|
1368
|
-
} else {
|
|
1369
|
-
path.push(token);
|
|
1370
|
-
}
|
|
1410
|
+
path.push(token);
|
|
1371
1411
|
continue;
|
|
1372
1412
|
}
|
|
1373
|
-
sawFlag = true;
|
|
1374
1413
|
const equalsAt = token.indexOf("=");
|
|
1375
1414
|
const rawName = equalsAt === -1 ? token : token.slice(0, equalsAt);
|
|
1376
1415
|
const inlineValue = equalsAt === -1 ? null : token.slice(equalsAt + 1);
|
|
@@ -1477,7 +1516,7 @@ function renderRootHelp(commands, version) {
|
|
|
1477
1516
|
lines.push("", "Global flags");
|
|
1478
1517
|
for (const spec of GLOBAL_FLAGS) lines.push(renderFlag(spec));
|
|
1479
1518
|
lines.push(" --version".padEnd(FLAG_COLUMN) + "Print the CLI version.");
|
|
1480
|
-
lines.push(...environmentBlock(CREDENTIAL_SPECS));
|
|
1519
|
+
lines.push(...environmentBlock(CREDENTIAL_SPECS, "all"));
|
|
1481
1520
|
lines.push(
|
|
1482
1521
|
"",
|
|
1483
1522
|
"Not in this release",
|
|
@@ -1486,9 +1525,10 @@ function renderRootHelp(commands, version) {
|
|
|
1486
1525
|
);
|
|
1487
1526
|
return lines.join("\n");
|
|
1488
1527
|
}
|
|
1489
|
-
function environmentBlock(specs) {
|
|
1528
|
+
function environmentBlock(specs, baseUrlFlag) {
|
|
1490
1529
|
const lines = ["", "Environment"];
|
|
1491
|
-
|
|
1530
|
+
const override = baseUrlFlag === "all" ? " Overridden by --base-url on the commands that take it." : baseUrlFlag ? " Overridden by --base-url." : "";
|
|
1531
|
+
lines.push(` ${BASE_URL_ENV_VAR}`.padEnd(FLAG_COLUMN) + `API base URL.${override}`);
|
|
1492
1532
|
for (const spec of specs) {
|
|
1493
1533
|
lines.push(` ${spec.envVar}`.padEnd(FLAG_COLUMN) + spec.summary);
|
|
1494
1534
|
}
|
|
@@ -1521,7 +1561,9 @@ function renderCommandHelp(command) {
|
|
|
1521
1561
|
lines.push("", "Global flags");
|
|
1522
1562
|
for (const spec of GLOBAL_FLAGS) lines.push(renderFlag(spec));
|
|
1523
1563
|
const credentials = command.credentials ?? [];
|
|
1524
|
-
if (credentials.length > 0)
|
|
1564
|
+
if (credentials.length > 0) {
|
|
1565
|
+
lines.push(...environmentBlock(credentials, command.flags.some((spec) => spec.name === "base-url")));
|
|
1566
|
+
}
|
|
1525
1567
|
return lines.join("\n");
|
|
1526
1568
|
}
|
|
1527
1569
|
|
|
@@ -1561,8 +1603,10 @@ async function runCli(options) {
|
|
|
1561
1603
|
const specs = match.command.positionals ?? [];
|
|
1562
1604
|
if (positionals.length > specs.length) {
|
|
1563
1605
|
const surplus = positionals[specs.length] ?? "";
|
|
1606
|
+
const flagErrors = bindFlags(match.command.flags, parsed);
|
|
1564
1607
|
return formatOutcome(
|
|
1565
1608
|
errorOutcome("usage_error", `unexpected argument "${surplus}" after "${commandName(match.command)}"`, [
|
|
1609
|
+
...flagErrors.ok ? [] : flagErrors.errors,
|
|
1566
1610
|
`usage: everfur ${match.command.usage}`
|
|
1567
1611
|
]),
|
|
1568
1612
|
format
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { hostnameOf, listen, close, writeJson, serveStatelessMcp } from './chunk-
|
|
3
|
-
import { createHostedTokenVerifier, tokenBucketOf, isAccessTokenShaped, buildServer, MCP_SCOPE, tenantContextOf, sendTestUserRef, hostedVerifyUserRef, REVOKED_AUTHORIZATION_REMEDY, SYNTHETIC_USER_REF_PREFIX } from './chunk-
|
|
4
|
-
import { BASE_URL_ENV_VAR, resolveEndpoint, fetchTransport, systemClock, createApiClient, withPrefix, ephemeralCredential, validateContractText, isJsonObject, describeFailure } from './chunk-
|
|
2
|
+
import { hostnameOf, listen, close, writeJson, serveStatelessMcp } from './chunk-KAHIDPBX.js';
|
|
3
|
+
import { createHostedTokenVerifier, tokenBucketOf, isAccessTokenShaped, buildServer, MCP_SCOPE, tenantContextOf, sendTestUserRef, hostedVerifyUserRef, REVOKED_AUTHORIZATION_REMEDY, SYNTHETIC_USER_REF_PREFIX } from './chunk-IZGEY7H6.js';
|
|
4
|
+
import { BASE_URL_ENV_VAR, resolveEndpoint, fetchTransport, systemClock, createApiClient, withPrefix, ephemeralCredential, validateContractText, isJsonObject, describeFailure } from './chunk-CGWSEOW2.js';
|
|
5
5
|
import { createServer } from 'http';
|
|
6
6
|
import { InvalidTokenError, TooManyRequestsError, TemporarilyUnavailableError } from '@modelcontextprotocol/sdk/server/auth/errors.js';
|
|
7
7
|
import { isIP } from 'net';
|
|
@@ -12,8 +12,9 @@ var PORT_ENV_VAR = "EVERFUR_MCP_HTTP_PORT";
|
|
|
12
12
|
var LEGACY_PORT_ENV_VAR = "MCP_PORT";
|
|
13
13
|
var ALLOWED_HOSTS_ENV_VAR = "MCP_ALLOWED_HOSTS";
|
|
14
14
|
var ALLOWED_ORIGINS_ENV_VAR = "MCP_ALLOWED_ORIGINS";
|
|
15
|
-
var DEFAULT_ALLOWED_ORIGINS = Object.freeze(["https://claude.ai", "https://claude.com"]);
|
|
15
|
+
var DEFAULT_ALLOWED_ORIGINS = Object.freeze(["https://claude.ai", "https://claude.com", "https://chatgpt.com"]);
|
|
16
16
|
var TRUSTED_PROXY_HOPS_ENV_VAR = "MCP_TRUSTED_PROXY_HOPS";
|
|
17
|
+
var OPENAI_APPS_CHALLENGE_TOKEN_ENV_VAR = "OPENAI_APPS_CHALLENGE_TOKEN";
|
|
17
18
|
var MAX_TRUSTED_PROXY_HOPS = 8;
|
|
18
19
|
var DEFAULT_HOSTED_PORT = 3100;
|
|
19
20
|
var DEFAULT_MCP_PATH = "/mcp";
|
|
@@ -106,6 +107,14 @@ function parseAllowedOrigins(raw) {
|
|
|
106
107
|
return origin;
|
|
107
108
|
});
|
|
108
109
|
}
|
|
110
|
+
function parseOpenAiAppsChallengeToken(raw) {
|
|
111
|
+
const value = (raw ?? "").trim();
|
|
112
|
+
if (value.length === 0) return null;
|
|
113
|
+
if (!/^[\x21-\x7e]+$/.test(value)) {
|
|
114
|
+
throw new Error(`${OPENAI_APPS_CHALLENGE_TOKEN_ENV_VAR} must be one token of printable ASCII, with no space or line break inside it.`);
|
|
115
|
+
}
|
|
116
|
+
return value;
|
|
117
|
+
}
|
|
109
118
|
function loadHostedMcpConfig(env) {
|
|
110
119
|
if ((env[BASE_URL_ENV_VAR] ?? "").trim().length === 0) {
|
|
111
120
|
throw new Error(`${BASE_URL_ENV_VAR} is required: the Everfur API this server verifies bearers against.`);
|
|
@@ -126,7 +135,8 @@ function loadHostedMcpConfig(env) {
|
|
|
126
135
|
port: parsePort(env),
|
|
127
136
|
allowedHosts: /* @__PURE__ */ new Set([publicHostname, ...parseAllowedHosts(env[ALLOWED_HOSTS_ENV_VAR])]),
|
|
128
137
|
trustedProxyHops: parseTrustedProxyHops(env[TRUSTED_PROXY_HOPS_ENV_VAR]),
|
|
129
|
-
allowedOrigins: /* @__PURE__ */ new Set([...DEFAULT_ALLOWED_ORIGINS, ...parseAllowedOrigins(env[ALLOWED_ORIGINS_ENV_VAR])])
|
|
138
|
+
allowedOrigins: /* @__PURE__ */ new Set([...DEFAULT_ALLOWED_ORIGINS, ...parseAllowedOrigins(env[ALLOWED_ORIGINS_ENV_VAR])]),
|
|
139
|
+
openaiAppsChallengeToken: parseOpenAiAppsChallengeToken(env[OPENAI_APPS_CHALLENGE_TOKEN_ENV_VAR])
|
|
130
140
|
});
|
|
131
141
|
}
|
|
132
142
|
function resourceMetadataUrl(config) {
|
|
@@ -265,15 +275,35 @@ The integration tools:
|
|
|
265
275
|
- everfur_plan_integration produces an integration plan, or a refusal that names each missing fact (identity mode,
|
|
266
276
|
capabilities, backend language, environment) rather than a plan built on a guess. An integration built on a guessed
|
|
267
277
|
identity mode is wrong in a way that only shows up in production. Omitted, the environment is the connected tenant's.
|
|
278
|
+
A plan comes back as a small summary with a plan_id and a number of pages: the goal, the detected facts, each step's
|
|
279
|
+
id, title, one-line intent and page, and the checklist. The steps in full, with their commands and the complete file
|
|
280
|
+
bodies, come from further everfur_plan_integration calls with the same arguments plus plan_id and page, for page 1
|
|
281
|
+
to pages; page 1 also carries the detection, the warnings and the checklist in full, and the principles. (With
|
|
282
|
+
steps, step ids, instead of page, those steps come a page at a time and remaining_steps names the rest.) Each call
|
|
283
|
+
recomputes the plan from the project_details it is given, and the plan's own commands and files change the project,
|
|
284
|
+
so every page is fetched before any command runs or any file is written. The same arguments
|
|
285
|
+
and contract always produce the same plan, byte for byte. A refusal for missing facts gives each fact the developer
|
|
286
|
+
answers a fixed question and its closed list of options, so the same refusal always asks the same thing. A ready
|
|
287
|
+
plan carries a checklist: the finished integration as a fixed list of requirements, each with the check that proves
|
|
288
|
+
it. The theme is matched to the host app's own brand colours, read from its code (a Tailwind config, the CSS that
|
|
289
|
+
declares its theme, or a React Native theme file) and passed as theme_source; when none is given, the plan's
|
|
290
|
+
match-theme step names where those colours are looked for, and no colour is chosen for the host. The tenant's console
|
|
291
|
+
branding is the other side of the theme: it fills every field the theme leaves unset and wins for a field the tenant
|
|
292
|
+
locked in the partner console.
|
|
268
293
|
- everfur_verify_integration exercises the real wire with a short-lived session the Everfur API mints for a synthetic
|
|
269
|
-
test user of a sandbox tenant (its entitlements report livemode false). On a live tenant
|
|
270
|
-
|
|
294
|
+
test user of a sandbox tenant (its entitlements report livemode false). On a live tenant with a paired sandbox the
|
|
295
|
+
session is minted on that sandbox and the call uses its publishable key; on a live tenant with no pair the signed
|
|
296
|
+
session step is reported GATED; no session can be supplied by hand. identity_mode anonymous verifies the anonymous path instead: the
|
|
271
297
|
tenant's publishable key and a device session id derived for a synthetic test user, with nothing minted. It is the
|
|
272
|
-
read-only check a live tenant takes, GET requests only.
|
|
298
|
+
read-only check a live tenant takes, GET requests only. For a web page pass origin with embedding beside it: an
|
|
299
|
+
in-page page calls the API from its own origin, so the CORS checks apply to it, and with embedding in-page the
|
|
300
|
+
frame's own checks (the embed fence and the loader) are reported N/A.
|
|
273
301
|
- everfur_diagnose separates the four byte-identical causes of an empty entitlement verdict.
|
|
274
302
|
- everfur_explain_error looks an error code up in the contract's registry, case sensitive, and refuses a code the
|
|
275
303
|
registry does not list, with near misses. Three naming families are live at once and one code is the default for
|
|
276
|
-
four different HTTP statuses, so the registry, not the shape of a code's string, says whether a retry can help.
|
|
304
|
+
four different HTTP statuses, so the registry, not the shape of a code's string, says whether a retry can help. It
|
|
305
|
+
also answers, offline, the error.code the tools on this server return (VERIFICATION_FAILED, PLAN_CHANGED and the
|
|
306
|
+
rest).
|
|
277
307
|
- everfur_send_test_message sends a real message to a sandbox tenant only, and streams the real answer.
|
|
278
308
|
|
|
279
309
|
The tenant tools read the connected tenant's own state and change nothing:
|
|
@@ -296,6 +326,13 @@ diagnose-integration lay out an order to call the tools in.`;
|
|
|
296
326
|
|
|
297
327
|
// src/mcp/hosted/tenant-env.ts
|
|
298
328
|
var VERIFY_SESSIONS_PATH = "/mcp/verify-sessions";
|
|
329
|
+
var MINTING_TOOLS = /* @__PURE__ */ new Set(["everfur_verify_integration", "everfur_send_test_message"]);
|
|
330
|
+
function hostedCallMode(context, tool, args) {
|
|
331
|
+
if (!context.tenant.livemode || context.test === null) return "connected";
|
|
332
|
+
if (!MINTING_TOOLS.has(tool)) return "connected";
|
|
333
|
+
if (tool === "everfur_verify_integration" && args["identity_mode"] === "anonymous") return "connected";
|
|
334
|
+
return "test";
|
|
335
|
+
}
|
|
299
336
|
function toolApiBaseUrl(context, fallback) {
|
|
300
337
|
const resolved = resolveEndpoint(null, { [BASE_URL_ENV_VAR]: context.apiBaseUrl });
|
|
301
338
|
return resolved.ok ? resolved.endpoint.baseUrl : fallback;
|
|
@@ -334,7 +371,9 @@ async function mintVerifySession(options) {
|
|
|
334
371
|
sessionToken: null,
|
|
335
372
|
outcome: {
|
|
336
373
|
kind: "gated",
|
|
337
|
-
|
|
374
|
+
// The live-tenant wording applies to an UNPAIRED live tenant only: a paired one is minted on its sandbox, and is
|
|
375
|
+
// refused only when that sandbox is no longer an active partner sandbox (EFBackend #1629).
|
|
376
|
+
reason: options.paired === true ? "this tenant is live and its paired sandbox is no longer an active sandbox: the Everfur API mints verify sessions on a sandbox tenant only" : "this tenant is live: the Everfur API mints verify sessions for sandbox tenants only",
|
|
338
377
|
requestId: result.requestId
|
|
339
378
|
}
|
|
340
379
|
};
|
|
@@ -391,7 +430,8 @@ async function mintVerifySession(options) {
|
|
|
391
430
|
};
|
|
392
431
|
}
|
|
393
432
|
function synthesizeTenantEnv(options) {
|
|
394
|
-
const
|
|
433
|
+
const test = options.mode === "test" ? options.context.test : null;
|
|
434
|
+
const publishableKey = test === null ? options.context.publishableKey : test.publishableKey;
|
|
395
435
|
return Object.freeze({
|
|
396
436
|
[BASE_URL_ENV_VAR]: toolApiBaseUrl(options.context, options.apiBaseUrl),
|
|
397
437
|
...publishableKey === null ? {} : { EVERFUR_PARTNER_KEY: publishableKey }
|
|
@@ -610,6 +650,7 @@ function createWellKnownMirror(options) {
|
|
|
610
650
|
|
|
611
651
|
// src/mcp/hosted/http.ts
|
|
612
652
|
var HEALTH_PATH = "/healthz";
|
|
653
|
+
var OPENAI_APPS_CHALLENGE_PATH = "/.well-known/openai-apps-challenge";
|
|
613
654
|
var MAX_BODY_BYTES = 1024 * 1024;
|
|
614
655
|
var DRAIN_CEILING_BYTES = 16 * 1024 * 1024;
|
|
615
656
|
var RETRY_AFTER_SECONDS = 5;
|
|
@@ -720,12 +761,23 @@ function createHostedMcpRequestListener(options) {
|
|
|
720
761
|
const userRef = call.tool === "everfur_send_test_message" && typeof idempotencyKey === "string" && idempotencyKey.length > 0 ? sendTestUserRef(idempotencyKey) : hostedVerifyUserRef(typeof label === "string" ? label : void 0);
|
|
721
762
|
const bearer = call.authInfo.token;
|
|
722
763
|
const onUnauthorized = () => verifier.forget(bearer);
|
|
764
|
+
const mode = hostedCallMode(tenant, call.tool, call.args);
|
|
765
|
+
const paired = tenant.tenant.livemode && tenant.test !== null;
|
|
723
766
|
return {
|
|
724
|
-
env: synthesizeTenantEnv({ context: tenant, apiBaseUrl: config.apiBaseUrl }),
|
|
767
|
+
env: synthesizeTenantEnv({ context: tenant, apiBaseUrl: config.apiBaseUrl, mode }),
|
|
725
768
|
credentialSource: "hosted",
|
|
726
|
-
hostedMint: createHostedMint({ bearer, apiBaseUrl: config.apiBaseUrl, userRef, transport, clock, onUnauthorized }),
|
|
769
|
+
hostedMint: createHostedMint({ bearer, apiBaseUrl: config.apiBaseUrl, userRef, transport, clock, onUnauthorized, paired }),
|
|
727
770
|
syntheticUserRef: userRef,
|
|
728
|
-
tenantReads: createHostedTenantReads({ bearer, apiBaseUrl: config.apiBaseUrl, transport, clock, onUnauthorized })
|
|
771
|
+
tenantReads: createHostedTenantReads({ bearer, apiBaseUrl: config.apiBaseUrl, transport, clock, onUnauthorized }),
|
|
772
|
+
tenantMode: { livemode: tenant.tenant.livemode, paired },
|
|
773
|
+
connectedTenant: {
|
|
774
|
+
slug: tenant.tenant.slug,
|
|
775
|
+
livemode: tenant.tenant.livemode,
|
|
776
|
+
pairedSandboxSlug: tenant.test === null ? null : tenant.test.slug,
|
|
777
|
+
// The prefix alone, which names the tenant mode; the key itself never leaves the context.
|
|
778
|
+
publishableKeyPrefix: ["pk_test_", "pk_live_"].find((prefix) => tenant.publishableKey?.startsWith(prefix) === true) ?? null,
|
|
779
|
+
apiBaseUrl: tenant.apiBaseUrl
|
|
780
|
+
}
|
|
729
781
|
};
|
|
730
782
|
};
|
|
731
783
|
return async (req, res) => {
|
|
@@ -738,6 +790,21 @@ function createHostedMcpRequestListener(options) {
|
|
|
738
790
|
writeJson(res, 200, { status: "ok" });
|
|
739
791
|
return;
|
|
740
792
|
}
|
|
793
|
+
const challengeToken = config.openaiAppsChallengeToken;
|
|
794
|
+
if (challengeToken !== null && url.pathname === OPENAI_APPS_CHALLENGE_PATH) {
|
|
795
|
+
const refused2 = refusalFor(req, config, ["GET", "HEAD"]);
|
|
796
|
+
if (refused2 !== null) {
|
|
797
|
+
writeJson(res, refused2.status, { error: refused2.message }, refused2.headers);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
res.writeHead(200, {
|
|
801
|
+
"content-type": "text/plain; charset=utf-8",
|
|
802
|
+
"content-length": String(Buffer.byteLength(challengeToken)),
|
|
803
|
+
"cache-control": "no-store"
|
|
804
|
+
});
|
|
805
|
+
res.end(req.method === "HEAD" ? void 0 : challengeToken);
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
741
808
|
const document = metadataPaths.get(url.pathname);
|
|
742
809
|
if (document !== void 0) {
|
|
743
810
|
if (req.method === "OPTIONS") {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { loadHostedMcpConfig, startHostedMcpServer } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import './chunk-
|
|
5
|
-
import { CLI_VERSION } from './chunk-
|
|
2
|
+
import { loadHostedMcpConfig, startHostedMcpServer } from './chunk-TZVIWABT.js';
|
|
3
|
+
import './chunk-KAHIDPBX.js';
|
|
4
|
+
import './chunk-IZGEY7H6.js';
|
|
5
|
+
import { CLI_VERSION } from './chunk-CGWSEOW2.js';
|
|
6
6
|
|
|
7
7
|
// src/bin/everfur-mcp-hosted.ts
|
|
8
8
|
async function main() {
|
package/dist/everfur-mcp-http.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { startRemoteMcpServer, loadRemoteMcpConfig } from './chunk-
|
|
3
|
-
import './chunk-
|
|
4
|
-
import { CLI_VERSION } from './chunk-
|
|
2
|
+
import { startRemoteMcpServer, loadRemoteMcpConfig } from './chunk-KAHIDPBX.js';
|
|
3
|
+
import './chunk-IZGEY7H6.js';
|
|
4
|
+
import { CLI_VERSION } from './chunk-CGWSEOW2.js';
|
|
5
5
|
|
|
6
6
|
// src/bin/everfur-mcp-http.ts
|
|
7
7
|
async function main() {
|
package/dist/everfur-mcp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runStdioServer } from './chunk-
|
|
3
|
-
import { CLI_VERSION } from './chunk-
|
|
2
|
+
import { runStdioServer } from './chunk-IZGEY7H6.js';
|
|
3
|
+
import { CLI_VERSION } from './chunk-CGWSEOW2.js';
|
|
4
4
|
|
|
5
5
|
// src/bin/everfur-mcp.ts
|
|
6
6
|
if (process.argv.slice(2).includes("--version")) {
|
package/dist/everfur.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { runCli } from './chunk-
|
|
3
|
-
import { ExitCode, systemClock, fetchTransport, CLI_VERSION, nodeFileSystem, nodeContractCache } from './chunk-
|
|
2
|
+
import { runCli } from './chunk-NLB5XDNO.js';
|
|
3
|
+
import { ExitCode, systemClock, fetchTransport, CLI_VERSION, nodeFileSystem, nodeContractCache } from './chunk-CGWSEOW2.js';
|
|
4
4
|
|
|
5
5
|
// src/bin/everfur.ts
|
|
6
6
|
async function main() {
|