@fidacy/mcp 0.3.2 → 0.3.4

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,27 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.4
4
+
5
+ ### Patch Changes
6
+
7
+ - Declare the MCP Registry identity that matches the product name: `mcpName` is now `com.fidacy/ai-agent-firewall`. The registry validates server ownership against this field, so the listing could not carry the commercial name until the package declared it. Nothing about installing or running the server changes.
8
+
9
+ ## 0.3.3
10
+
11
+ ### Minor Changes
12
+
13
+ - The server now presents itself as **Fidacy AI Agent Firewall** (`fidacy-ai-agent-firewall`, title "Fidacy AI Agent Firewall") instead of the bare `fidacy`. The npm package is unchanged — `npx -y @fidacy/mcp` keeps working exactly as before, and no config needs editing. This is the name the product is sold under, so it is the name your client should show.
14
+
15
+ ### Patch Changes
16
+
17
+ - Fix the wall an existing customer hits. When an install that already carries an API key exhausted its monthly allowance (or reached the spending cap it set), the server answered with an invitation to create a free account — the account they already had — and threw away everything the engine said about it. It now reports what actually happened, names the pay-as-you-go price and links straight to billing, and makes explicit that the local firewall keeps deciding and blocking throughout: only server-signed verdicts and anchored proofs pause, never the protection.
18
+
19
+ ## 0.3.2
20
+
21
+ ### Patch Changes
22
+
23
+ - Security: a spending cap that failed to parse silently stopped capping. Cap values read from `FIDACY_PER_TX_MAX` / `FIDACY_MAX_TOTAL` or from `config.json` went through a bare `Number()`, so ordinary ways of writing an amount — `2,500`, `$2500`, `2500 USD` — became `NaN`. Because `amount > NaN` is false, the comparison did not raise: the cap simply stopped existing, and a firewall whose author believed it was capped at 2,500 would authorize 9,999. Two fixes: the decision engine now DENIES any mandate whose caps are not finite positive numbers (`invalid_mandate_cap`), so an unenforceable cap can never allow a payment on any path, and a cap that does not parse is now refused with an explicit message telling you what to write instead, falling back to the safe default rather than to no cap. Upgrade if you set either cap from an environment variable.
24
+
3
25
  ## 0.3.1
4
26
 
5
27
  ### Patch Changes
@@ -1,3 +1,5 @@
1
+ import { hasEngineKey } from "./config.js";
2
+ export { hasEngineKey };
1
3
  /**
2
4
  * ACTIVATION GATE — the anonymous trial that ends in a free API key.
3
5
  *
@@ -16,8 +18,6 @@
16
18
  * real client needs anyway (it powers assess_action / anchor_artifact).
17
19
  */
18
20
  export declare const FREE_DECISIONS = 20;
19
- /** True when the install carries an operator-set engine key (env or shell config). */
20
- export declare function hasEngineKey(keyOverride?: string): boolean;
21
21
  /**
22
22
  * Payment-gate decisions taken so far on this install: the on-disk counter OR
23
23
  * the in-process one, whichever is higher. The in-process floor is what keeps
package/dist/assess.d.ts CHANGED
@@ -42,16 +42,30 @@ export interface RejectionReason {
42
42
  * request body. `type` is the stable engine error code (or a client code), and
43
43
  * `status` is the HTTP status (0 for network/timeout).
44
44
  */
45
+ /**
46
+ * What a 402 actually says. The engine returns `reason` (quota grace expired vs
47
+ * spending cap reached), a human-readable `detail` and the `billing_url` that
48
+ * resolves it. All of it used to be dropped on the floor, so an operator who had
49
+ * ALREADY signed up was told to go sign up — at the one moment they were asking
50
+ * to buy more. Carry it through and say the true thing instead.
51
+ */
52
+ export interface BillingBlock {
53
+ reason?: string;
54
+ detail?: string;
55
+ billingUrl?: string;
56
+ }
45
57
  export declare class AssessError extends Error {
46
58
  readonly type: string;
47
59
  readonly status: number;
48
60
  readonly details?: unknown;
49
61
  readonly rejection_reasons?: RejectionReason[];
62
+ readonly billing?: BillingBlock;
50
63
  constructor(opts: {
51
64
  type: string;
52
65
  status: number;
53
66
  details?: unknown;
54
67
  rejection_reasons?: RejectionReason[];
68
+ billing?: BillingBlock;
55
69
  });
56
70
  }
57
71
  export interface AssessConfig {
package/dist/assess.js CHANGED
@@ -4,6 +4,7 @@ var AssessError = class extends Error {
4
4
  status;
5
5
  details;
6
6
  rejection_reasons;
7
+ billing;
7
8
  constructor(opts) {
8
9
  super(`Fidacy assess error (${opts.type}, HTTP ${opts.status})`);
9
10
  this.name = "AssessError";
@@ -11,6 +12,7 @@ var AssessError = class extends Error {
11
12
  this.status = opts.status;
12
13
  this.details = opts.details;
13
14
  this.rejection_reasons = opts.rejection_reasons;
15
+ this.billing = opts.billing;
14
16
  }
15
17
  };
16
18
  var DEFAULT_TIMEOUT = 1e4;
@@ -106,7 +108,12 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
106
108
  const type = isRecord(parsed) && typeof parsed.error === "string" && parsed.error || `http_${res.status}`;
107
109
  const details = isRecord(parsed) ? parsed.details : void 0;
108
110
  const rejection_reasons = isRecord(parsed) && Array.isArray(parsed.rejection_reasons) ? parsed.rejection_reasons : void 0;
109
- throw new AssessError({ type, status: res.status, details, rejection_reasons });
111
+ const billing = res.status === 402 && isRecord(parsed) ? {
112
+ reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
113
+ detail: typeof parsed.detail === "string" ? parsed.detail : void 0,
114
+ billingUrl: typeof parsed.billing_url === "string" ? parsed.billing_url : void 0
115
+ } : void 0;
116
+ throw new AssessError({ type, status: res.status, details, rejection_reasons, billing });
110
117
  }
111
118
  if (!isRecord(parsed)) {
112
119
  throw new AssessError({ type: "invalid_response", status: res.status });
package/dist/config.d.ts CHANGED
@@ -37,6 +37,15 @@ export interface FidacyConfig {
37
37
  /** Payment-gate decisions taken on this install (milestone nudges). */
38
38
  decisions_count?: number;
39
39
  }
40
+ /**
41
+ * True when the install carries an operator-set engine key (env, or the plugin's
42
+ * engineApiKey config passed as an override).
43
+ *
44
+ * Lives here rather than in activation.ts because nudges.ts needs it too, and
45
+ * activation.ts already imports nudges.ts — importing back would be a cycle.
46
+ * Reading the environment is this module's job anyway.
47
+ */
48
+ export declare function hasEngineKey(keyOverride?: string): boolean;
40
49
  /** Config dir: FIDACY_CONFIG_DIR override, else ~/.fidacy (XDG-friendly enough). */
41
50
  export declare function configDir(): string;
42
51
  export declare function configPath(): string;
package/dist/core.js CHANGED
@@ -485,7 +485,7 @@ function resolveMandateRules(cfg) {
485
485
  }
486
486
 
487
487
  // src/telemetry.ts
488
- var CLIENT_VERSION = true ? "0.3.2" : "dev";
488
+ var CLIENT_VERSION = true ? "0.3.4" : "dev";
489
489
  function bandOf(amount) {
490
490
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
491
491
  if (amount < 10) return "lt10";
package/dist/index.js CHANGED
@@ -432,6 +432,9 @@ import {
432
432
  readFileSync,
433
433
  writeFileSync
434
434
  } from "node:fs";
435
+ function hasEngineKey(keyOverride) {
436
+ return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
437
+ }
435
438
  function configDir() {
436
439
  return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
437
440
  }
@@ -514,7 +517,7 @@ function resolveMandateRules(cfg) {
514
517
  }
515
518
 
516
519
  // src/telemetry.ts
517
- var CLIENT_VERSION = true ? "0.3.2" : "dev";
520
+ var CLIENT_VERSION = true ? "0.3.4" : "dev";
518
521
  function bandOf(amount) {
519
522
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
520
523
  if (amount < 10) return "lt10";
@@ -693,6 +696,7 @@ var AssessError = class extends Error {
693
696
  status;
694
697
  details;
695
698
  rejection_reasons;
699
+ billing;
696
700
  constructor(opts) {
697
701
  super(`Fidacy assess error (${opts.type}, HTTP ${opts.status})`);
698
702
  this.name = "AssessError";
@@ -700,6 +704,7 @@ var AssessError = class extends Error {
700
704
  this.status = opts.status;
701
705
  this.details = opts.details;
702
706
  this.rejection_reasons = opts.rejection_reasons;
707
+ this.billing = opts.billing;
703
708
  }
704
709
  };
705
710
  var DEFAULT_TIMEOUT = 1e4;
@@ -795,7 +800,12 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
795
800
  const type = isRecord(parsed) && typeof parsed.error === "string" && parsed.error || `http_${res.status}`;
796
801
  const details = isRecord(parsed) ? parsed.details : void 0;
797
802
  const rejection_reasons = isRecord(parsed) && Array.isArray(parsed.rejection_reasons) ? parsed.rejection_reasons : void 0;
798
- throw new AssessError({ type, status: res.status, details, rejection_reasons });
803
+ const billing = res.status === 402 && isRecord(parsed) ? {
804
+ reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
805
+ detail: typeof parsed.detail === "string" ? parsed.detail : void 0,
806
+ billingUrl: typeof parsed.billing_url === "string" ? parsed.billing_url : void 0
807
+ } : void 0;
808
+ throw new AssessError({ type, status: res.status, details, rejection_reasons, billing });
799
809
  }
800
810
  if (!isRecord(parsed)) {
801
811
  throw new AssessError({ type: "invalid_response", status: res.status });
@@ -869,7 +879,7 @@ async function findArtifacts(sha2562, cfg) {
869
879
  }
870
880
 
871
881
  // src/provision.ts
872
- var CLIENT_VERSION2 = true ? "0.3.2" : "dev";
882
+ var CLIENT_VERSION2 = true ? "0.3.4" : "dev";
873
883
  function provisionEnabled() {
874
884
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
875
885
  return !(v === "1" || v === "true" || v === "yes");
@@ -976,10 +986,15 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
976
986
  }
977
987
  return null;
978
988
  }
979
- function hostedWallCta(feature) {
989
+ function hostedWallCta(feature, billing) {
990
+ const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
991
+ if (hasEngineKey()) {
992
+ const where = billing?.billingUrl ?? "https://app.fidacy.com/billing";
993
+ 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.`;
994
+ return `${what} The local firewall keeps deciding and blocking as normal \u2014 this only pauses ${noun}. For your operator: resolve it at ${where}. 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.`;
995
+ }
980
996
  const url = claimUrl();
981
997
  const cta = url ? `open ${url} \u2014 free account, this install's history migrates` : `call the upgrade tool \u2014 free account, your history migrates`;
982
- const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
983
998
  if (nudgeOnce("hosted_wall")) {
984
999
  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}.`;
985
1000
  }
@@ -999,9 +1014,6 @@ function weekOneBootNudge(upgradeToolName) {
999
1014
 
1000
1015
  // src/activation.ts
1001
1016
  var FREE_DECISIONS = 20;
1002
- function hasEngineKey(keyOverride) {
1003
- return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
1004
- }
1005
1017
  function decisionsUsed() {
1006
1018
  return Math.max(readConfig()?.decisions_count ?? 0, sessionDecisionCount());
1007
1019
  }
@@ -1174,8 +1186,8 @@ function rows(s) {
1174
1186
  return out;
1175
1187
  }
1176
1188
  function renderBanner(s) {
1177
- const title = "FIDACY \xB7 the payment firewall for AI agents";
1178
- const sub = `signs the YES and the NO \xB7 v${s.version}`;
1189
+ const title = "FIDACY AI AGENT FIREWALL";
1190
+ const sub = `blocks the payment, signs the YES and the NO \xB7 v${s.version}`;
1179
1191
  const body = rows(s);
1180
1192
  if (!fancy()) {
1181
1193
  const lines2 = [
@@ -1300,10 +1312,14 @@ function renderAlertLine(alerts) {
1300
1312
  var state = ensureState();
1301
1313
  var core = makeCore();
1302
1314
  var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
1303
- var SERVER_VERSION = true ? "0.3.2" : "dev";
1315
+ var SERVER_VERSION = true ? "0.3.4" : "dev";
1304
1316
  var bootClaim = claimUrl();
1305
1317
  var server = new McpServer(
1306
- { name: "fidacy", version: SERVER_VERSION },
1318
+ // The commercial identity. `name` is the stable slug hosts key on; `title` is
1319
+ // what a human reads in the client's server list. The npm package stays
1320
+ // @fidacy/mcp — renaming the package would split the install base and the
1321
+ // download history for nothing, since what people SEE is this.
1322
+ { name: "fidacy-ai-agent-firewall", title: "Fidacy AI Agent Firewall", version: SERVER_VERSION },
1307
1323
  {
1308
1324
  instructions: bootClaim ? `Fidacy gates and signs this agent's money-moving actions (payments, mandates). Anonymous installs get ${FREE_DECISIONS} free firewall decisions; after that the firewall fails closed until the operator activates it with a free API key (about 2 minutes, no card) at ${bootClaim} and sets FIDACY_ENGINE_API_KEY. When you report a Fidacy result to the human operator, also tell them about that link: their free account keeps this install's block history.` : `Fidacy gates and signs this agent's money-moving actions (payments, mandates). Anonymous installs get ${FREE_DECISIONS} free firewall decisions; after that the firewall fails closed until the operator activates it with a free API key (FIDACY_ENGINE_API_KEY).`
1309
1325
  }
@@ -1528,7 +1544,7 @@ server.registerTool(
1528
1544
  } catch (e) {
1529
1545
  if (e instanceof AssessError) {
1530
1546
  if (e.status === 402) {
1531
- return { content: [{ type: "text", text: hostedWallCta("verdict") }], isError: true };
1547
+ return { content: [{ type: "text", text: hostedWallCta("verdict", e.billing) }], isError: true };
1532
1548
  }
1533
1549
  const reasons = e.rejection_reasons?.length ? " (" + e.rejection_reasons.map((x) => x.key).join(",") + ")" : "";
1534
1550
  return {
@@ -1593,7 +1609,7 @@ server.registerTool(
1593
1609
  } catch (e) {
1594
1610
  if (e instanceof AssessError) {
1595
1611
  if (e.status === 402) {
1596
- return { content: [{ type: "text", text: hostedWallCta("anchor") }], isError: true };
1612
+ return { content: [{ type: "text", text: hostedWallCta("anchor", e.billing) }], isError: true };
1597
1613
  }
1598
1614
  return { content: [{ type: "text", text: `ANCHOR ${e.status}: ${e.type}` }], isError: true };
1599
1615
  }
package/dist/lib.js CHANGED
@@ -555,6 +555,9 @@ import {
555
555
  readFileSync,
556
556
  writeFileSync
557
557
  } from "node:fs";
558
+ function hasEngineKey(keyOverride) {
559
+ return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
560
+ }
558
561
  function configDir() {
559
562
  return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
560
563
  }
@@ -637,7 +640,7 @@ function resolveMandateRules(cfg) {
637
640
  }
638
641
 
639
642
  // src/telemetry.ts
640
- var CLIENT_VERSION = true ? "0.3.2" : "dev";
643
+ var CLIENT_VERSION = true ? "0.3.4" : "dev";
641
644
  function bandOf(amount) {
642
645
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
643
646
  if (amount < 10) return "lt10";
@@ -825,7 +828,7 @@ function requestUpgrade() {
825
828
  }
826
829
 
827
830
  // src/provision.ts
828
- var CLIENT_VERSION2 = true ? "0.3.2" : "dev";
831
+ var CLIENT_VERSION2 = true ? "0.3.4" : "dev";
829
832
  function provisionEnabled() {
830
833
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
831
834
  return !(v === "1" || v === "true" || v === "yes");
@@ -934,9 +937,6 @@ function weekOneBootNudge(upgradeToolName) {
934
937
 
935
938
  // src/activation.ts
936
939
  var FREE_DECISIONS = 20;
937
- function hasEngineKey(keyOverride) {
938
- return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
939
- }
940
940
  function decisionsUsed() {
941
941
  return Math.max(readConfig()?.decisions_count ?? 0, sessionDecisionCount());
942
942
  }
package/dist/nudges.d.ts CHANGED
@@ -43,7 +43,11 @@ export declare function decisionNudge(status: "ALLOW" | "DENY", violatedRule: st
43
43
  * (nudge doctrine: never nag twice), a terse line every time after — the wall is
44
44
  * a direct answer to the agent's request, so it always says SOMETHING actionable.
45
45
  */
46
- export declare function hostedWallCta(feature: "verdict" | "anchor"): string;
46
+ export declare function hostedWallCta(feature: "verdict" | "anchor", billing?: {
47
+ reason?: string;
48
+ detail?: string;
49
+ billingUrl?: string;
50
+ }): string;
47
51
  /** The no-key CTA for a hosted tool: route the human to a free account. */
48
52
  export declare function noKeyCta(): string;
49
53
  /** One-time boot line after a week of real protection; null otherwise. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
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)",
@@ -64,7 +64,7 @@
64
64
  "vitest": "^3.2.4",
65
65
  "@fidacy/firewall": "0.1.1"
66
66
  },
67
- "mcpName": "com.fidacy/mcp",
67
+ "mcpName": "com.fidacy/ai-agent-firewall",
68
68
  "repository": {
69
69
  "type": "git",
70
70
  "url": "git+https://github.com/lucaslubi/fidacy-mcp.git"