@fidacy/mcp 0.1.7 → 0.1.9

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
@@ -95,6 +95,50 @@ var FileAuditStore = class {
95
95
  };
96
96
 
97
97
  // ../firewall/dist/evaluate.js
98
+ function fold(s) {
99
+ return s.toLowerCase().replace(/[4@]/g, "a").replace(/[0]/g, "o").replace(/[1|!]/g, "l").replace(/[3]/g, "e").replace(/[5$]/g, "s").replace(/[7]/g, "t").replace(/[^a-z0-9]/g, "");
100
+ }
101
+ function lev(a, b, max) {
102
+ if (Math.abs(a.length - b.length) > max)
103
+ return max + 1;
104
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
105
+ for (let i = 1; i <= a.length; i++) {
106
+ const cur = [i];
107
+ let rowMin = i;
108
+ for (let j = 1; j <= b.length; j++) {
109
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
110
+ const v = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
111
+ cur[j] = v;
112
+ if (v < rowMin)
113
+ rowMin = v;
114
+ }
115
+ if (rowMin > max)
116
+ return max + 1;
117
+ prev = cur;
118
+ }
119
+ return prev[b.length];
120
+ }
121
+ function lookalikePayee(payee, allowed) {
122
+ const exact = new Set(allowed);
123
+ if (exact.has(payee) || allowed.includes("*"))
124
+ return null;
125
+ const pf = fold(payee);
126
+ if (pf.length < 4)
127
+ return null;
128
+ for (const a of allowed) {
129
+ if (a === "*")
130
+ continue;
131
+ const af = fold(a);
132
+ if (af.length < 4)
133
+ continue;
134
+ if (pf === af)
135
+ return a;
136
+ const d = lev(pf, af, 2);
137
+ if (d > 0 && d <= 2)
138
+ return a;
139
+ }
140
+ return null;
141
+ }
98
142
  function evaluate(mandate, req, spentSoFar) {
99
143
  const now = Date.now();
100
144
  if (mandate.revoked)
@@ -112,8 +156,12 @@ function evaluate(mandate, req, spentSoFar) {
112
156
  if (spentSoFar + req.amount > mandate.allow.maxTotal)
113
157
  return `total_cap_exceeded:${spentSoFar + req.amount}>${mandate.allow.maxTotal}`;
114
158
  const payeeOk = mandate.allow.payees.includes("*") || mandate.allow.payees.includes(req.payee);
115
- if (!payeeOk)
159
+ if (!payeeOk) {
160
+ const impersonated = lookalikePayee(req.payee, mandate.allow.payees);
161
+ if (impersonated)
162
+ return `payee_lookalike:${req.payee}~${impersonated}`;
116
163
  return `payee_not_in_allowlist:${req.payee}`;
164
+ }
117
165
  const catOk = mandate.allow.categories.includes("*") || mandate.allow.categories.includes(req.category);
118
166
  if (!catOk)
119
167
  return `category_not_allowed:${req.category}`;
package/dist/index.js CHANGED
@@ -102,6 +102,50 @@ var FileAuditStore = class {
102
102
  };
103
103
 
104
104
  // ../firewall/dist/evaluate.js
105
+ function fold(s) {
106
+ return s.toLowerCase().replace(/[4@]/g, "a").replace(/[0]/g, "o").replace(/[1|!]/g, "l").replace(/[3]/g, "e").replace(/[5$]/g, "s").replace(/[7]/g, "t").replace(/[^a-z0-9]/g, "");
107
+ }
108
+ function lev(a, b, max) {
109
+ if (Math.abs(a.length - b.length) > max)
110
+ return max + 1;
111
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
112
+ for (let i = 1; i <= a.length; i++) {
113
+ const cur = [i];
114
+ let rowMin = i;
115
+ for (let j = 1; j <= b.length; j++) {
116
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
117
+ const v = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
118
+ cur[j] = v;
119
+ if (v < rowMin)
120
+ rowMin = v;
121
+ }
122
+ if (rowMin > max)
123
+ return max + 1;
124
+ prev = cur;
125
+ }
126
+ return prev[b.length];
127
+ }
128
+ function lookalikePayee(payee, allowed) {
129
+ const exact = new Set(allowed);
130
+ if (exact.has(payee) || allowed.includes("*"))
131
+ return null;
132
+ const pf = fold(payee);
133
+ if (pf.length < 4)
134
+ return null;
135
+ for (const a of allowed) {
136
+ if (a === "*")
137
+ continue;
138
+ const af = fold(a);
139
+ if (af.length < 4)
140
+ continue;
141
+ if (pf === af)
142
+ return a;
143
+ const d = lev(pf, af, 2);
144
+ if (d > 0 && d <= 2)
145
+ return a;
146
+ }
147
+ return null;
148
+ }
105
149
  function evaluate(mandate, req, spentSoFar) {
106
150
  const now = Date.now();
107
151
  if (mandate.revoked)
@@ -119,8 +163,12 @@ function evaluate(mandate, req, spentSoFar) {
119
163
  if (spentSoFar + req.amount > mandate.allow.maxTotal)
120
164
  return `total_cap_exceeded:${spentSoFar + req.amount}>${mandate.allow.maxTotal}`;
121
165
  const payeeOk = mandate.allow.payees.includes("*") || mandate.allow.payees.includes(req.payee);
122
- if (!payeeOk)
166
+ if (!payeeOk) {
167
+ const impersonated = lookalikePayee(req.payee, mandate.allow.payees);
168
+ if (impersonated)
169
+ return `payee_lookalike:${req.payee}~${impersonated}`;
123
170
  return `payee_not_in_allowlist:${req.payee}`;
171
+ }
124
172
  const catOk = mandate.allow.categories.includes("*") || mandate.allow.categories.includes(req.category);
125
173
  if (!catOk)
126
174
  return `category_not_allowed:${req.category}`;
package/dist/lib.js CHANGED
@@ -146,13 +146,34 @@ var ReferenceRail = class {
146
146
  return { railRef };
147
147
  }
148
148
  };
149
+ function httpRedeem(coreUrl, apiKey) {
150
+ const base = String(coreUrl ?? "").replace(/\/+$/, "");
151
+ const u = new URL(base);
152
+ if (u.protocol !== "https:" && !(u.protocol === "http:" && (u.hostname === "localhost" || u.hostname === "127.0.0.1"))) {
153
+ throw new Error("core URL must be https:// (the API key is sent to it)");
154
+ }
155
+ return async (grant) => {
156
+ const res = await fetch(`${base}/v1/grant/redeem`, {
157
+ method: "POST",
158
+ headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
159
+ body: JSON.stringify({ grant })
160
+ });
161
+ if (res.status === 200)
162
+ return true;
163
+ if (res.status === 409)
164
+ return false;
165
+ throw new Error(`redeem -> ${res.status}`);
166
+ };
167
+ }
149
168
  var GrantEnforcingExecutor = class {
150
169
  rail;
151
170
  used = /* @__PURE__ */ new Set();
152
171
  pub;
153
- constructor(publicKeyPem2, rail) {
172
+ redeem;
173
+ constructor(publicKeyPem2, rail, opts) {
154
174
  this.rail = rail;
155
175
  this.pub = createPublicKey(publicKeyPem2);
176
+ this.redeem = opts?.redeem;
156
177
  }
157
178
  async execute(req, grant) {
158
179
  if (!grant)
@@ -183,6 +204,16 @@ var GrantEnforcingExecutor = class {
183
204
  if (req.invoiceRef != null && req.invoiceRef !== (p.invoiceRef ?? null)) {
184
205
  return { status: "REFUSED", reason: "invoice_mismatch" };
185
206
  }
207
+ if (this.redeem) {
208
+ let first;
209
+ try {
210
+ first = await this.redeem(grant);
211
+ } catch {
212
+ return { status: "REFUSED", reason: "redeem_unavailable" };
213
+ }
214
+ if (!first)
215
+ return { status: "REFUSED", reason: "grant_replayed" };
216
+ }
186
217
  this.used.add(p.decisionId);
187
218
  const { railRef } = await this.rail.execute(req);
188
219
  return { status: "EXECUTED", railRef, decisionId: p.decisionId };
@@ -190,6 +221,50 @@ var GrantEnforcingExecutor = class {
190
221
  };
191
222
 
192
223
  // ../firewall/dist/evaluate.js
224
+ function fold(s) {
225
+ return s.toLowerCase().replace(/[4@]/g, "a").replace(/[0]/g, "o").replace(/[1|!]/g, "l").replace(/[3]/g, "e").replace(/[5$]/g, "s").replace(/[7]/g, "t").replace(/[^a-z0-9]/g, "");
226
+ }
227
+ function lev(a, b, max) {
228
+ if (Math.abs(a.length - b.length) > max)
229
+ return max + 1;
230
+ let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
231
+ for (let i = 1; i <= a.length; i++) {
232
+ const cur = [i];
233
+ let rowMin = i;
234
+ for (let j = 1; j <= b.length; j++) {
235
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
236
+ const v = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
237
+ cur[j] = v;
238
+ if (v < rowMin)
239
+ rowMin = v;
240
+ }
241
+ if (rowMin > max)
242
+ return max + 1;
243
+ prev = cur;
244
+ }
245
+ return prev[b.length];
246
+ }
247
+ function lookalikePayee(payee, allowed) {
248
+ const exact = new Set(allowed);
249
+ if (exact.has(payee) || allowed.includes("*"))
250
+ return null;
251
+ const pf = fold(payee);
252
+ if (pf.length < 4)
253
+ return null;
254
+ for (const a of allowed) {
255
+ if (a === "*")
256
+ continue;
257
+ const af = fold(a);
258
+ if (af.length < 4)
259
+ continue;
260
+ if (pf === af)
261
+ return a;
262
+ const d = lev(pf, af, 2);
263
+ if (d > 0 && d <= 2)
264
+ return a;
265
+ }
266
+ return null;
267
+ }
193
268
  function evaluate(mandate, req, spentSoFar) {
194
269
  const now = Date.now();
195
270
  if (mandate.revoked)
@@ -207,8 +282,12 @@ function evaluate(mandate, req, spentSoFar) {
207
282
  if (spentSoFar + req.amount > mandate.allow.maxTotal)
208
283
  return `total_cap_exceeded:${spentSoFar + req.amount}>${mandate.allow.maxTotal}`;
209
284
  const payeeOk = mandate.allow.payees.includes("*") || mandate.allow.payees.includes(req.payee);
210
- if (!payeeOk)
285
+ if (!payeeOk) {
286
+ const impersonated = lookalikePayee(req.payee, mandate.allow.payees);
287
+ if (impersonated)
288
+ return `payee_lookalike:${req.payee}~${impersonated}`;
211
289
  return `payee_not_in_allowlist:${req.payee}`;
290
+ }
212
291
  const catOk = mandate.allow.categories.includes("*") || mandate.allow.categories.includes(req.category);
213
292
  if (!catOk)
214
293
  return `category_not_allowed:${req.category}`;
@@ -514,7 +593,9 @@ export {
514
593
  ReferenceRail,
515
594
  canonInvoice,
516
595
  evaluate,
596
+ httpRedeem,
517
597
  loadOrGenerateKeyPair,
598
+ lookalikePayee,
518
599
  makeCore,
519
600
  publicKeyPem,
520
601
  requireHttpsBase,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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
  "homepage": "https://fidacy.com",
@@ -54,5 +54,10 @@
54
54
  "@types/node": "^22.10.0",
55
55
  "tsx": "^4.19.2",
56
56
  "typescript": "^5.7.2"
57
+ },
58
+ "mcpName": "com.fidacy/mcp",
59
+ "repository": {
60
+ "type": "git",
61
+ "url": "git+https://github.com/lucaslubi/fidacy-mcp.git"
57
62
  }
58
63
  }