@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 +28 -4
- package/dist/firewall/evaluate.d.ts +11 -0
- package/dist/index.js +30 -6
- package/dist/lib.js +30 -5
- package/package.json +5 -2
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) =>
|
|
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.
|
|
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) =>
|
|
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.
|
|
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.
|
|
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.
|
|
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) =>
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|