@fidacy/mcp 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Critical for Claude Code users: the firewall's messages now actually reach the model. MCP hosts disagree about which half of a tool result the model sees. Some surface only the text content — which is why the signed grant has always been printed there — but Claude Code surfaces ONLY `structuredContent` when a tool declares an output schema. Verified against a live Claude Code agent: on a DENY, the model received `{"status":"DENY",...}` and none of the text — no attribution, no trial countdown, no activation wall, no protection warnings. Every human-directed message now travels in both halves (`message` in the structured output mirrors the text), so the agent can relay it regardless of which half its host shows.
8
+
3
9
  ## 0.4.1
4
10
 
5
11
  ### Patch Changes
package/dist/config.d.ts CHANGED
@@ -43,6 +43,10 @@ export interface FidacyConfig {
43
43
  * Cleared as soon as a hosted call succeeds again.
44
44
  */
45
45
  hosted_lapsed_at?: string;
46
+ /** Operator-provided email (opt-in). Consent to be contacted; never scraped. */
47
+ operator_email?: string;
48
+ /** The email last successfully registered, so we don't re-send every boot. */
49
+ registered_email?: string;
46
50
  }
47
51
  /**
48
52
  * True when the install carries an operator-set engine key (env, or the plugin's
package/dist/core.js CHANGED
@@ -455,7 +455,9 @@ function readConfig() {
455
455
  created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
456
456
  nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
457
457
  decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
458
- hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0
458
+ hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
459
+ operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
460
+ registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
459
461
  };
460
462
  } catch {
461
463
  return null;
@@ -486,7 +488,7 @@ function resolveMandateRules(cfg) {
486
488
  }
487
489
 
488
490
  // src/telemetry.ts
489
- var CLIENT_VERSION = true ? "0.4.1" : "dev";
491
+ var CLIENT_VERSION = true ? "0.5.0" : "dev";
490
492
  function bandOf(amount) {
491
493
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
492
494
  if (amount < 10) return "lt10";
package/dist/index.js CHANGED
@@ -466,7 +466,9 @@ function readConfig() {
466
466
  created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
467
467
  nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
468
468
  decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
469
- hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0
469
+ hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
470
+ operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
471
+ registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
470
472
  };
471
473
  } catch {
472
474
  return null;
@@ -518,7 +520,7 @@ function resolveMandateRules(cfg) {
518
520
  }
519
521
 
520
522
  // src/telemetry.ts
521
- var CLIENT_VERSION = true ? "0.4.1" : "dev";
523
+ var CLIENT_VERSION = true ? "0.5.0" : "dev";
522
524
  function bandOf(amount) {
523
525
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
524
526
  if (amount < 10) return "lt10";
@@ -880,7 +882,7 @@ async function findArtifacts(sha2562, cfg) {
880
882
  }
881
883
 
882
884
  // src/provision.ts
883
- var CLIENT_VERSION2 = true ? "0.4.1" : "dev";
885
+ var CLIENT_VERSION2 = true ? "0.5.0" : "dev";
884
886
  function provisionEnabled() {
885
887
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
886
888
  return !(v === "1" || v === "true" || v === "yes");
@@ -916,6 +918,46 @@ async function autoProvision() {
916
918
  }
917
919
  }
918
920
 
921
+ // src/register.ts
922
+ var CLIENT_VERSION3 = true ? "0.5.0" : "dev";
923
+ function endpoint3() {
924
+ const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
925
+ return `${base}/v1/register`;
926
+ }
927
+ function looksLikeEmail(s) {
928
+ return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
929
+ }
930
+ function operatorEmail(override) {
931
+ const raw = (override ?? process.env.FIDACY_OPERATOR_EMAIL ?? readConfig()?.operator_email ?? "").trim();
932
+ return raw && looksLikeEmail(raw) ? raw : null;
933
+ }
934
+ async function registerEmail(email, shell = "mcp") {
935
+ const cfg = readConfig();
936
+ if (!cfg) return "skipped";
937
+ const clean = email.trim();
938
+ if (!looksLikeEmail(clean)) return "skipped";
939
+ if (cfg.registered_email && cfg.registered_email.toLowerCase() === clean.toLowerCase()) return "skipped";
940
+ try {
941
+ const res = await fetch(endpoint3(), {
942
+ method: "POST",
943
+ headers: { "content-type": "application/json" },
944
+ body: JSON.stringify({ anon_id: cfg.anon_id, email: clean, client_version: CLIENT_VERSION3, shell })
945
+ });
946
+ if (res.status === 429) return "rate_limited";
947
+ if (!res.ok) return "failed";
948
+ const fresh = readConfig() ?? cfg;
949
+ writeConfig({ ...fresh, operator_email: clean, registered_email: clean });
950
+ return "registered";
951
+ } catch {
952
+ return "failed";
953
+ }
954
+ }
955
+ async function autoRegister(shell = "mcp") {
956
+ const email = operatorEmail();
957
+ if (!email) return "skipped";
958
+ return registerEmail(email, shell);
959
+ }
960
+
919
961
  // src/upgrade.ts
920
962
  function upgradeUrl() {
921
963
  const base = (process.env.FIDACY_WEB_URL ?? "https://fidacy.com").replace(/\/$/, "");
@@ -1381,7 +1423,7 @@ function renderAlertLine(alerts) {
1381
1423
  var state = ensureState();
1382
1424
  var core = makeCore();
1383
1425
  var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
1384
- var SERVER_VERSION = true ? "0.4.1" : "dev";
1426
+ var SERVER_VERSION = true ? "0.5.0" : "dev";
1385
1427
  var bootClaim = claimUrl();
1386
1428
  var server = new McpServer(
1387
1429
  // The commercial identity. `name` is the stable slug hosts key on; `title` is
@@ -1415,15 +1457,30 @@ server.registerTool(
1415
1457
  status: z.enum(["ALLOW", "DENY"]),
1416
1458
  decisionId: z.string(),
1417
1459
  grant: z.string().optional(),
1418
- violatedRule: z.string().optional()
1460
+ violatedRule: z.string().optional(),
1461
+ /**
1462
+ * The full human-directed message, duplicated from the text content ON
1463
+ * PURPOSE. Hosts disagree about which half of a tool result the model
1464
+ * sees: some surface only the text (which is why the grant is printed
1465
+ * there), and Claude Code surfaces ONLY structuredContent when an
1466
+ * outputSchema is declared — verified live 2026-07-25, where the model
1467
+ * received exactly {"status":"DENY",...} and none of the text. Anything
1468
+ * that must reach the model must therefore travel in BOTH halves.
1469
+ */
1470
+ message: z.string()
1419
1471
  },
1420
1472
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
1421
1473
  },
1422
1474
  async (args) => {
1423
1475
  const gate = activationGate();
1424
1476
  if (gate) {
1425
- const out2 = { status: "DENY", decisionId: "activation_required", violatedRule: "activation_required" };
1426
1477
  const halted = protectionWarning(decisionsUsed()) ?? gate.message;
1478
+ const out2 = {
1479
+ status: "DENY",
1480
+ decisionId: "activation_required",
1481
+ violatedRule: "activation_required",
1482
+ message: halted
1483
+ };
1427
1484
  return { content: [{ type: "text", text: halted }], structuredContent: out2 };
1428
1485
  }
1429
1486
  const req = args;
@@ -1434,7 +1491,6 @@ server.registerTool(
1434
1491
  } catch {
1435
1492
  }
1436
1493
  const d = await core.decide(req, subject);
1437
- const out = { status: d.status, decisionId: d.decisionId, grant: d.grant, violatedRule: d.violatedRule };
1438
1494
  const sentinelLine = d.status === "ALLOW" ? renderAlertLine(sentinelAlerts) : "";
1439
1495
  const base = d.status === "ALLOW" ? `ALLOW (decision ${d.decisionId})${req.invoiceRef ? ` for invoice ${req.invoiceRef}` : ""}.${sentinelLine} To settle, call execute_payment with the SAME payee, amount, currency, and idempotencyKey, and set "grant" to EXACTLY this signed value:
1440
1496
  ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}. No grant issued, this payment cannot proceed. The denial itself is recorded in the tamper-evident, hash-chained audit: call get_audit_proof with decisionId ${d.decisionId} for the proof of what was blocked.${d.violatedRule?.startsWith("payee_not_in_allowlist") ? ` If the user trusts this payee, add "${req.payee}" to mandate.payees in ~/.fidacy/config.json and retry: the firewall picks the change up on the next call, no restart needed.` : d.violatedRule?.startsWith("category_not_allowed") ? ` If the user considers "${req.category}" a legitimate purpose for this agent, add it to mandate.categories in ~/.fidacy/config.json and retry: the firewall picks the change up on the next call, no restart needed.` : d.violatedRule?.startsWith("currency_not_allowed") ? ` The mandate is single-currency and set to another one (mandate.currency in ~/.fidacy/config.json, default USD). If this agent legitimately pays in ${req.currency}, update it there and retry: hot-reloaded, no restart.` : ""}`;
@@ -1443,6 +1499,7 @@ ${d.grant}` : `DENY (decision ${d.decisionId}). Rule violated: ${d.violatedRule}
1443
1499
  const countdown = trialCountdownLine();
1444
1500
  const lapse = isHostedLapsed() ? protectionWarning(decisionsUsed()) : "";
1445
1501
  const human = [badge, base, nudge, countdown, lapse].filter(Boolean).join(" ");
1502
+ const out = { status: d.status, decisionId: d.decisionId, grant: d.grant, violatedRule: d.violatedRule, message: human };
1446
1503
  return { content: [{ type: "text", text: human }], structuredContent: out };
1447
1504
  }
1448
1505
  );
@@ -1764,6 +1821,22 @@ Your local protection keeps working meanwhile; your usage history migrates to th
1764
1821
  };
1765
1822
  }
1766
1823
  );
1824
+ server.registerTool(
1825
+ "register_email",
1826
+ {
1827
+ title: "Register operator email (opt-in)",
1828
+ description: "Register the operator's email so their agent's protection history can attach to a real Fidacy account and Fidacy can reach them. ONLY call this when the human operator has explicitly given their email and agreed to be contacted \u2014 it is consent-based. Does not create an account or move money.",
1829
+ inputSchema: { email: z.string().describe("The operator's email, exactly as they gave it.") },
1830
+ outputSchema: { registered: z.boolean(), message: z.string() },
1831
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
1832
+ },
1833
+ async ({ email }) => {
1834
+ const outcome = await registerEmail(String(email ?? ""), "mcp");
1835
+ const ok = outcome === "registered" || outcome === "skipped";
1836
+ const message = outcome === "registered" ? `Registered ${email}. This install's protection history is now tied to that email; Fidacy may contact the operator. To finish creating the free account, the operator can open fidacy.com/claim.` : outcome === "skipped" ? `Already registered, or the email was empty/malformed \u2014 nothing to do.` : outcome === "rate_limited" ? `Too many registrations from this network right now; try again shortly.` : `Could not reach Fidacy to register; the install keeps working locally. Try again later.`;
1837
+ return { content: [{ type: "text", text: message }], structuredContent: { registered: ok, message } };
1838
+ }
1839
+ );
1767
1840
  async function main() {
1768
1841
  const transport = new StdioServerTransport();
1769
1842
  await server.connect(transport);
@@ -1773,6 +1846,7 @@ async function main() {
1773
1846
  void flush();
1774
1847
  });
1775
1848
  void autoProvision();
1849
+ void autoRegister("mcp");
1776
1850
  console.error(
1777
1851
  renderBanner({
1778
1852
  version: SERVER_VERSION,
package/dist/lib.d.ts CHANGED
@@ -4,8 +4,9 @@ export { ensureState, readConfig } from "./config.js";
4
4
  export { requestUpgrade, upgradeUrl } from "./upgrade.js";
5
5
  export { autoProvision } from "./provision.js";
6
6
  export { recordInstall, recordAgentActive, setTelemetryShell, flush as flushTelemetry } from "./telemetry.js";
7
- export { decisionNudge, weekOneBootNudge, nudgeOnce, installAgeDays, claimUrl, noKeyCta } from "./nudges.js";
8
- export { FREE_DECISIONS, activationGate, activationBootLine, trialCountdownLine, hasEngineKey, remainingFree } from "./activation.js";
7
+ export { decisionNudge, weekOneBootNudge, nudgeOnce, installAgeDays, claimUrl, noKeyCta, hostedWallCta } from "./nudges.js";
8
+ export { FREE_DECISIONS, activationGate, activationBootLine, trialCountdownLine, hasEngineKey, remainingFree, decisionsUsed } from "./activation.js";
9
+ export { BRAND, protectionBadge, protectionWarning, protectionBootLine, protectionState, markHostedLapsed, clearHostedLapsed, isHostedLapsed, } from "./protection.js";
9
10
  export { summarize, renderSummary, toRows, renderRows, explainRule, withinDays } from "./reporting.js";
10
11
  export type { SpendSummary, DecisionRow } from "./reporting.js";
11
12
  export { assessPattern, sweep, renderAlertLine, SENTINEL } from "./sentinel.js";
package/dist/lib.js CHANGED
@@ -589,7 +589,9 @@ function readConfig() {
589
589
  created_at: typeof raw.created_at === "string" ? raw.created_at : void 0,
590
590
  nudges: raw.nudges && typeof raw.nudges === "object" ? raw.nudges : void 0,
591
591
  decisions_count: typeof raw.decisions_count === "number" ? raw.decisions_count : void 0,
592
- hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0
592
+ hosted_lapsed_at: typeof raw.hosted_lapsed_at === "string" ? raw.hosted_lapsed_at : void 0,
593
+ operator_email: typeof raw.operator_email === "string" ? raw.operator_email : void 0,
594
+ registered_email: typeof raw.registered_email === "string" ? raw.registered_email : void 0
593
595
  };
594
596
  } catch {
595
597
  return null;
@@ -641,7 +643,7 @@ function resolveMandateRules(cfg) {
641
643
  }
642
644
 
643
645
  // src/telemetry.ts
644
- var CLIENT_VERSION = true ? "0.4.1" : "dev";
646
+ var CLIENT_VERSION = true ? "0.5.0" : "dev";
645
647
  function bandOf(amount) {
646
648
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
647
649
  if (amount < 10) return "lt10";
@@ -829,7 +831,7 @@ function requestUpgrade() {
829
831
  }
830
832
 
831
833
  // src/provision.ts
832
- var CLIENT_VERSION2 = true ? "0.4.1" : "dev";
834
+ var CLIENT_VERSION2 = true ? "0.5.0" : "dev";
833
835
  function provisionEnabled() {
834
836
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
835
837
  return !(v === "1" || v === "true" || v === "yes");
@@ -924,6 +926,20 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
924
926
  }
925
927
  return null;
926
928
  }
929
+ function hostedWallCta(feature, billing) {
930
+ const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
931
+ if (hasEngineKey()) {
932
+ const where2 = billing?.billingUrl ?? "https://app.fidacy.com/billing";
933
+ const what = billing?.reason === "spending_cap_reached" ? `This account hit the monthly spending cap you set, so ${noun} are paused.` : billing?.detail ?? `This account's included ${noun} for the month are used up.`;
934
+ return `${what} The local firewall keeps deciding and blocking as normal \u2014 this only pauses ${noun}. For your operator: resolve it at ${where2}. Pay-as-you-go needs no plan and starts at $0.04 per verdict, dropping to $0.025 past 50k and $0.015 past 250k; committed plans price below that curve.`;
935
+ }
936
+ const url = claimUrl();
937
+ const cta = url ? `open ${url} \u2014 free account, this install's history migrates` : `call the upgrade tool \u2014 free account, your history migrates`;
938
+ if (nudgeOnce("hosted_wall")) {
939
+ return `You've used this install's free allowance for ${noun} \u2014 the proofs anyone can re-verify at fidacy.com/verify, court-grade, not a local-only check. Keep them flowing: for your operator, ${cta}.`;
940
+ }
941
+ return `Free allowance for ${noun} reached. For your operator: ${cta}.`;
942
+ }
927
943
  function noKeyCta() {
928
944
  const url = claimUrl();
929
945
  return url ? `free in ~1 min for your operator: open ${url} (this install's history migrates), or set FIDACY_ENGINE_API_KEY if you already have one` : `free in ~1 min: call the upgrade tool (your history migrates), or set FIDACY_ENGINE_API_KEY if you already have one`;
@@ -977,6 +993,74 @@ function activationBootLine() {
977
993
  return ` Anonymous trial: ${used}/${FREE_DECISIONS} free decisions used; after that the firewall requires a free API key.`;
978
994
  }
979
995
 
996
+ // src/protection.ts
997
+ var BRAND = "Fidacy AI Agent Firewall";
998
+ var FREE_MONTHLY_VERDICTS = 300;
999
+ function markHostedLapsed() {
1000
+ const cfg = readConfig();
1001
+ if (!cfg || cfg.hosted_lapsed_at) return;
1002
+ cfg.hosted_lapsed_at = (/* @__PURE__ */ new Date()).toISOString();
1003
+ try {
1004
+ writeConfig(cfg);
1005
+ } catch {
1006
+ }
1007
+ }
1008
+ function clearHostedLapsed() {
1009
+ const cfg = readConfig();
1010
+ if (!cfg?.hosted_lapsed_at) return;
1011
+ delete cfg.hosted_lapsed_at;
1012
+ try {
1013
+ writeConfig(cfg);
1014
+ } catch {
1015
+ }
1016
+ }
1017
+ function isHostedLapsed() {
1018
+ return Boolean(readConfig()?.hosted_lapsed_at);
1019
+ }
1020
+ function protectionState(decisionsUsed2, keyOverride) {
1021
+ if (hasEngineKey(keyOverride)) return isHostedLapsed() ? "unproven" : "protected_keyed";
1022
+ if (decisionsUsed2 >= FREE_DECISIONS) return "halted";
1023
+ return FREE_DECISIONS - decisionsUsed2 <= 5 ? "protected_low" : "protected_trial";
1024
+ }
1025
+ function where() {
1026
+ return claimUrl() ?? "https://fidacy.com/claim";
1027
+ }
1028
+ function protectionBadge() {
1029
+ return `\u{1F6E1} ${BRAND} blocked this payment. Your agent was protected.`;
1030
+ }
1031
+ function protectionWarning(decisionsUsed2, keyOverride) {
1032
+ const state = protectionState(decisionsUsed2, keyOverride);
1033
+ const left = Math.max(0, FREE_DECISIONS - decisionsUsed2);
1034
+ switch (state) {
1035
+ case "protected_trial":
1036
+ return null;
1037
+ // working and nothing is expiring soon: silence is the respect.
1038
+ case "protected_low":
1039
+ return `\u26A0 ${BRAND}: ${left} free decision${left === 1 ? "" : "s"} left, then this agent's payments STOP DEAD. Every payment it attempts after that is refused \u2014 invoices unpaid, workflows broken, at 3am with nobody watching. And the operators who "fix" it by ripping the firewall out end up with an agent nobody is checking at all, which is how a prompt-injected agent wires money to a stranger. Keep it alive, free, in ~2 minutes: ${where()} \u2014 no card, this install's block history moves with you, plus ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
1040
+ case "halted":
1041
+ return `\u26D4 ${BRAND} IS NOT ACTIVATED \u2014 YOUR AGENT CAN NO LONGER PAY ANYTHING. Every payment it attempts is being refused right now: invoices go unpaid, subscriptions lapse, jobs fail silently. The firewall failed closed after ${FREE_DECISIONS} free decisions, which is the safe direction \u2014 but it is YOUR agent that is stopped until you act. And do not "fix" it by removing the firewall: an agent with nothing checking it is exactly how a stolen key or an injected instruction moves real money you never get back. Activate free in ~2 minutes at ${where()}: no card, your block history moves with you, ${FREE_MONTHLY_VERDICTS} verified payments every month.`;
1042
+ case "unproven":
1043
+ return `\u26D4 ${BRAND}: YOUR AGENT IS EXPOSED RIGHT NOW. The monthly allowance is spent, so the checks that ask WHO is spending are OFF: agent identity is no longer verified, a revoked or stolen agent key is no longer detected, and behavioural anomalies are no longer caught. Local rules still block the wrong payee and the over-cap amount \u2014 but a compromised or prompt-injected agent that stays inside your own allowlist and under your own cap now pays out unchallenged. That is real money, irreversible, with no signed evidence to recover it or hand an insurer. Turn the checks back on: https://app.fidacy.com/billing \u2014 pay-as-you-go, no plan, from $0.04 per payment checked. One prevented payment pays for years of it.`;
1044
+ case "protected_keyed":
1045
+ return null;
1046
+ }
1047
+ }
1048
+ function protectionBootLine(decisionsUsed2) {
1049
+ const state = protectionState(decisionsUsed2);
1050
+ switch (state) {
1051
+ case "halted":
1052
+ return ` \u26D4 NOT ACTIVATED \u2014 your agent cannot pay until you activate at ${where()} (free, no card).`;
1053
+ case "protected_low":
1054
+ return ` \u26A0 ${Math.max(0, FREE_DECISIONS - decisionsUsed2)} free decisions left, then this agent's payments halt. Free key: ${where()}`;
1055
+ case "unproven":
1056
+ return ` \u26D4 EXPOSED \u2014 identity, revocation and anomaly checks are OFF (allowance spent). A stolen or hijacked agent inside your caps now pays out unchallenged: https://app.fidacy.com/billing`;
1057
+ case "protected_trial":
1058
+ return ` ${BRAND} active \u2014 ${decisionsUsed2}/${FREE_DECISIONS} free decisions used.`;
1059
+ case "protected_keyed":
1060
+ return ` ${BRAND} active and signing \u2014 blocks are provable to third parties.`;
1061
+ }
1062
+ }
1063
+
980
1064
  // src/reporting.ts
981
1065
  function withinDays(records, days, now = Date.now()) {
982
1066
  if (!Number.isFinite(days) || days <= 0) return [...records];
@@ -1173,6 +1257,7 @@ function renderAlertLine(alerts) {
1173
1257
  return ` [${worst}: ${alerts.map((a) => a.message).join(" Also: ")}]`;
1174
1258
  }
1175
1259
  export {
1260
+ BRAND,
1176
1261
  DevFidacyCore,
1177
1262
  FREE_DECISIONS,
1178
1263
  FileAuditStore,
@@ -1186,19 +1271,28 @@ export {
1186
1271
  autoProvision,
1187
1272
  canonInvoice,
1188
1273
  claimUrl,
1274
+ clearHostedLapsed,
1189
1275
  decisionNudge,
1276
+ decisionsUsed,
1190
1277
  ensureState,
1191
1278
  evaluate,
1192
1279
  explainRule,
1193
1280
  flush as flushTelemetry,
1194
1281
  hasEngineKey,
1282
+ hostedWallCta,
1195
1283
  httpRedeem,
1196
1284
  installAgeDays,
1285
+ isHostedLapsed,
1197
1286
  loadOrGenerateKeyPair,
1198
1287
  lookalikePayee,
1199
1288
  makeCore,
1289
+ markHostedLapsed,
1200
1290
  noKeyCta,
1201
1291
  nudgeOnce,
1292
+ protectionBadge,
1293
+ protectionBootLine,
1294
+ protectionState,
1295
+ protectionWarning,
1202
1296
  publicKeyPem,
1203
1297
  readConfig,
1204
1298
  recordAgentActive,
@@ -0,0 +1,14 @@
1
+ /** The email the operator explicitly provided, if any (env wins over config). */
2
+ export declare function operatorEmail(override?: string): string | null;
3
+ /**
4
+ * Register the operator's email against this install. Explicit — call it only
5
+ * when the operator SUPPLIED the email. Persists `registered_email` on success so
6
+ * we do not re-send every boot. Returns the outcome for tests/logs.
7
+ */
8
+ export declare function registerEmail(email: string, shell?: string): Promise<"registered" | "skipped" | "rate_limited" | "failed">;
9
+ /**
10
+ * Boot-time auto-register: if the operator set FIDACY_OPERATOR_EMAIL (or the
11
+ * config field) and we have not sent it yet, register in the background. NEVER
12
+ * awaited on the startup path — fire and forget, exactly like autoProvision.
13
+ */
14
+ export declare function autoRegister(shell?: string): Promise<"registered" | "skipped" | "rate_limited" | "failed">;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "Fidacy action firewall for AI agents. Mandate-gated payment authorization as an MCP server.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Fidacy (ZeepCode Group Technology LLC) <hello@fidacy.com> (https://fidacy.com)",