@fidacy/mcp 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -85,10 +85,23 @@ function lookalikePayee(payee, allowed) {
85
85
  }
86
86
  return null;
87
87
  }
88
+ function validateMandateCaps(mandate) {
89
+ const bad = (v) => typeof v !== "number" || !Number.isFinite(v) || v <= 0;
90
+ if (!mandate.allow || typeof mandate.allow !== "object")
91
+ return "invalid_mandate:missing_allow";
92
+ if (bad(mandate.allow.perTxMax))
93
+ return `invalid_mandate_cap:perTxMax=${String(mandate.allow.perTxMax)}`;
94
+ if (bad(mandate.allow.maxTotal))
95
+ return `invalid_mandate_cap:maxTotal=${String(mandate.allow.maxTotal)}`;
96
+ return null;
97
+ }
88
98
  function evaluate(mandate, req, spentSoFar) {
89
99
  const now = Date.now();
90
100
  if (mandate.revoked)
91
101
  return "mandate_revoked";
102
+ const badCap = validateMandateCaps(mandate);
103
+ if (badCap)
104
+ return badCap;
92
105
  if (now < Date.parse(mandate.window.notBefore))
93
106
  return "before_mandate_window";
94
107
  if (now > Date.parse(mandate.window.notAfter))
@@ -450,18 +463,29 @@ function readConfig() {
450
463
  function resolveMandateRules(cfg) {
451
464
  const m = cfg?.mandate ?? {};
452
465
  const envList = (v) => v === void 0 ? void 0 : v.split(",").map((s) => s.trim()).filter(Boolean);
453
- const envNum = (v) => v === void 0 || v === "" ? void 0 : Number(v);
466
+ const envNum = (name, v) => {
467
+ if (v === void 0 || v.trim() === "") return void 0;
468
+ const n = Number(v);
469
+ if (!Number.isFinite(n) || n <= 0) {
470
+ console.error(
471
+ `[fidacy] ${name}="${v}" is not a positive number, so it cannot be enforced as a cap. Ignoring it and using the safe default. Write digits only, with no thousands separator, currency symbol or unit (e.g. ${name}=2500).`
472
+ );
473
+ return void 0;
474
+ }
475
+ return n;
476
+ };
477
+ const fileNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
454
478
  return {
455
479
  payees: envList(process.env.FIDACY_ALLOW_PAYEES) ?? m.payees ?? [],
456
480
  categories: envList(process.env.FIDACY_ALLOW_CATEGORIES) ?? m.categories ?? ["*"],
457
481
  currency: process.env.FIDACY_CURRENCY ?? m.currency ?? "USD",
458
- perTxMax: envNum(process.env.FIDACY_PER_TX_MAX) ?? m.perTxMax ?? 2500,
459
- maxTotal: envNum(process.env.FIDACY_MAX_TOTAL) ?? m.maxTotal ?? 1e4
482
+ perTxMax: envNum("FIDACY_PER_TX_MAX", process.env.FIDACY_PER_TX_MAX) ?? fileNum(m.perTxMax) ?? 2500,
483
+ maxTotal: envNum("FIDACY_MAX_TOTAL", process.env.FIDACY_MAX_TOTAL) ?? fileNum(m.maxTotal) ?? 1e4
460
484
  };
461
485
  }
462
486
 
463
487
  // src/telemetry.ts
464
- var CLIENT_VERSION = true ? "0.3.1" : "dev";
488
+ var CLIENT_VERSION = true ? "0.3.2" : "dev";
465
489
  function bandOf(amount) {
466
490
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
467
491
  if (amount < 10) return "lt10";
@@ -7,4 +7,15 @@ import { Mandate, PaymentRequest } from "./types.js";
7
7
  * min length so short, genuinely-distinct payees don't false-positive.
8
8
  */
9
9
  export declare function lookalikePayee(payee: string, allowed: string[]): string | null;
10
+ /**
11
+ * The MANDATE's own caps must be finite positive numbers, for exactly the reason
12
+ * validateRequest() checks the request's amount: `amount > NaN` is false, so a
13
+ * non-numeric cap does not raise an error — it SILENTLY DISABLES the cap and the
14
+ * firewall fails OPEN. The caps arrive from human-authored surfaces (env vars,
15
+ * config.json, FIDACY_MANDATE_JSON), where `2,500`, `$2500` and `2500 USD` are
16
+ * all normal things to type and all coerce to NaN. Verified 2026-07-24: each of
17
+ * those let a 9,999 payment through a mandate whose author believed it was
18
+ * capped at 2,500. A cap we cannot enforce must deny, never allow.
19
+ */
20
+ export declare function validateMandateCaps(mandate: Mandate): string | null;
10
21
  export declare function evaluate(mandate: Mandate, req: PaymentRequest, spentSoFar: number): string | null;
package/dist/index.js CHANGED
@@ -92,10 +92,23 @@ function lookalikePayee(payee, allowed) {
92
92
  }
93
93
  return null;
94
94
  }
95
+ function validateMandateCaps(mandate) {
96
+ const bad = (v) => typeof v !== "number" || !Number.isFinite(v) || v <= 0;
97
+ if (!mandate.allow || typeof mandate.allow !== "object")
98
+ return "invalid_mandate:missing_allow";
99
+ if (bad(mandate.allow.perTxMax))
100
+ return `invalid_mandate_cap:perTxMax=${String(mandate.allow.perTxMax)}`;
101
+ if (bad(mandate.allow.maxTotal))
102
+ return `invalid_mandate_cap:maxTotal=${String(mandate.allow.maxTotal)}`;
103
+ return null;
104
+ }
95
105
  function evaluate(mandate, req, spentSoFar) {
96
106
  const now = Date.now();
97
107
  if (mandate.revoked)
98
108
  return "mandate_revoked";
109
+ const badCap = validateMandateCaps(mandate);
110
+ if (badCap)
111
+ return badCap;
99
112
  if (now < Date.parse(mandate.window.notBefore))
100
113
  return "before_mandate_window";
101
114
  if (now > Date.parse(mandate.window.notAfter))
@@ -479,18 +492,29 @@ function ensureState() {
479
492
  function resolveMandateRules(cfg) {
480
493
  const m = cfg?.mandate ?? {};
481
494
  const envList = (v) => v === void 0 ? void 0 : v.split(",").map((s) => s.trim()).filter(Boolean);
482
- const envNum = (v) => v === void 0 || v === "" ? void 0 : Number(v);
495
+ const envNum = (name, v) => {
496
+ if (v === void 0 || v.trim() === "") return void 0;
497
+ const n = Number(v);
498
+ if (!Number.isFinite(n) || n <= 0) {
499
+ console.error(
500
+ `[fidacy] ${name}="${v}" is not a positive number, so it cannot be enforced as a cap. Ignoring it and using the safe default. Write digits only, with no thousands separator, currency symbol or unit (e.g. ${name}=2500).`
501
+ );
502
+ return void 0;
503
+ }
504
+ return n;
505
+ };
506
+ const fileNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
483
507
  return {
484
508
  payees: envList(process.env.FIDACY_ALLOW_PAYEES) ?? m.payees ?? [],
485
509
  categories: envList(process.env.FIDACY_ALLOW_CATEGORIES) ?? m.categories ?? ["*"],
486
510
  currency: process.env.FIDACY_CURRENCY ?? m.currency ?? "USD",
487
- perTxMax: envNum(process.env.FIDACY_PER_TX_MAX) ?? m.perTxMax ?? 2500,
488
- maxTotal: envNum(process.env.FIDACY_MAX_TOTAL) ?? m.maxTotal ?? 1e4
511
+ perTxMax: envNum("FIDACY_PER_TX_MAX", process.env.FIDACY_PER_TX_MAX) ?? fileNum(m.perTxMax) ?? 2500,
512
+ maxTotal: envNum("FIDACY_MAX_TOTAL", process.env.FIDACY_MAX_TOTAL) ?? fileNum(m.maxTotal) ?? 1e4
489
513
  };
490
514
  }
491
515
 
492
516
  // src/telemetry.ts
493
- var CLIENT_VERSION = true ? "0.3.1" : "dev";
517
+ var CLIENT_VERSION = true ? "0.3.2" : "dev";
494
518
  function bandOf(amount) {
495
519
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
496
520
  if (amount < 10) return "lt10";
@@ -845,7 +869,7 @@ async function findArtifacts(sha2562, cfg) {
845
869
  }
846
870
 
847
871
  // src/provision.ts
848
- var CLIENT_VERSION2 = true ? "0.3.1" : "dev";
872
+ var CLIENT_VERSION2 = true ? "0.3.2" : "dev";
849
873
  function provisionEnabled() {
850
874
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
851
875
  return !(v === "1" || v === "true" || v === "yes");
@@ -1276,7 +1300,7 @@ function renderAlertLine(alerts) {
1276
1300
  var state = ensureState();
1277
1301
  var core = makeCore();
1278
1302
  var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
1279
- var SERVER_VERSION = true ? "0.3.1" : "dev";
1303
+ var SERVER_VERSION = true ? "0.3.2" : "dev";
1280
1304
  var bootClaim = claimUrl();
1281
1305
  var server = new McpServer(
1282
1306
  { name: "fidacy", version: SERVER_VERSION },
package/dist/lib.js CHANGED
@@ -123,10 +123,23 @@ function lookalikePayee(payee, allowed) {
123
123
  }
124
124
  return null;
125
125
  }
126
+ function validateMandateCaps(mandate) {
127
+ const bad = (v) => typeof v !== "number" || !Number.isFinite(v) || v <= 0;
128
+ if (!mandate.allow || typeof mandate.allow !== "object")
129
+ return "invalid_mandate:missing_allow";
130
+ if (bad(mandate.allow.perTxMax))
131
+ return `invalid_mandate_cap:perTxMax=${String(mandate.allow.perTxMax)}`;
132
+ if (bad(mandate.allow.maxTotal))
133
+ return `invalid_mandate_cap:maxTotal=${String(mandate.allow.maxTotal)}`;
134
+ return null;
135
+ }
126
136
  function evaluate(mandate, req, spentSoFar) {
127
137
  const now = Date.now();
128
138
  if (mandate.revoked)
129
139
  return "mandate_revoked";
140
+ const badCap = validateMandateCaps(mandate);
141
+ if (badCap)
142
+ return badCap;
130
143
  if (now < Date.parse(mandate.window.notBefore))
131
144
  return "before_mandate_window";
132
145
  if (now > Date.parse(mandate.window.notAfter))
@@ -602,18 +615,29 @@ function ensureState() {
602
615
  function resolveMandateRules(cfg) {
603
616
  const m = cfg?.mandate ?? {};
604
617
  const envList = (v) => v === void 0 ? void 0 : v.split(",").map((s) => s.trim()).filter(Boolean);
605
- const envNum = (v) => v === void 0 || v === "" ? void 0 : Number(v);
618
+ const envNum = (name, v) => {
619
+ if (v === void 0 || v.trim() === "") return void 0;
620
+ const n = Number(v);
621
+ if (!Number.isFinite(n) || n <= 0) {
622
+ console.error(
623
+ `[fidacy] ${name}="${v}" is not a positive number, so it cannot be enforced as a cap. Ignoring it and using the safe default. Write digits only, with no thousands separator, currency symbol or unit (e.g. ${name}=2500).`
624
+ );
625
+ return void 0;
626
+ }
627
+ return n;
628
+ };
629
+ const fileNum = (v) => typeof v === "number" && Number.isFinite(v) && v > 0 ? v : void 0;
606
630
  return {
607
631
  payees: envList(process.env.FIDACY_ALLOW_PAYEES) ?? m.payees ?? [],
608
632
  categories: envList(process.env.FIDACY_ALLOW_CATEGORIES) ?? m.categories ?? ["*"],
609
633
  currency: process.env.FIDACY_CURRENCY ?? m.currency ?? "USD",
610
- perTxMax: envNum(process.env.FIDACY_PER_TX_MAX) ?? m.perTxMax ?? 2500,
611
- maxTotal: envNum(process.env.FIDACY_MAX_TOTAL) ?? m.maxTotal ?? 1e4
634
+ perTxMax: envNum("FIDACY_PER_TX_MAX", process.env.FIDACY_PER_TX_MAX) ?? fileNum(m.perTxMax) ?? 2500,
635
+ maxTotal: envNum("FIDACY_MAX_TOTAL", process.env.FIDACY_MAX_TOTAL) ?? fileNum(m.maxTotal) ?? 1e4
612
636
  };
613
637
  }
614
638
 
615
639
  // src/telemetry.ts
616
- var CLIENT_VERSION = true ? "0.3.1" : "dev";
640
+ var CLIENT_VERSION = true ? "0.3.2" : "dev";
617
641
  function bandOf(amount) {
618
642
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
619
643
  if (amount < 10) return "lt10";
@@ -801,7 +825,7 @@ function requestUpgrade() {
801
825
  }
802
826
 
803
827
  // src/provision.ts
804
- var CLIENT_VERSION2 = true ? "0.3.1" : "dev";
828
+ var CLIENT_VERSION2 = true ? "0.3.2" : "dev";
805
829
  function provisionEnabled() {
806
830
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
807
831
  return !(v === "1" || v === "true" || v === "yes");
@@ -1193,6 +1217,7 @@ export {
1193
1217
  toRows,
1194
1218
  trialCountdownLine,
1195
1219
  upgradeUrl,
1220
+ validateMandateCaps,
1196
1221
  validateRequest,
1197
1222
  verify,
1198
1223
  verifyGrant,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
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)",
@@ -61,6 +61,7 @@
61
61
  "@types/node": "^22.10.0",
62
62
  "tsx": "^4.19.2",
63
63
  "typescript": "^5.7.2",
64
+ "vitest": "^3.2.4",
64
65
  "@fidacy/firewall": "0.1.1"
65
66
  },
66
67
  "mcpName": "com.fidacy/mcp",
@@ -71,6 +72,8 @@
71
72
  "scripts": {
72
73
  "build": "node scripts/bundle.mjs",
73
74
  "dev": "tsx watch src/index.ts",
74
- "start": "node dist/index.js"
75
+ "start": "node dist/index.js",
76
+ "typecheck": "tsc --noEmit",
77
+ "test": "vitest run"
75
78
  }
76
79
  }