@fidacy/mcp 0.1.8 → 0.1.10

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
@@ -74,3 +74,14 @@ semantic versioning.
74
74
  - One install delivers both the signed verdict (assess_action) and the
75
75
  non-custodial payment firewall (request_payment), with the same signed,
76
76
  publicly verifiable proof as the SDK.
77
+
78
+ ## 0.1.10 — 2026-07-02
79
+
80
+ - Telemetry: `shell` channel attribution (mcp | openclaw-plugin | crabtrap | sdk)
81
+ and `client_version` injected from package.json at build time (0.1.5 had shipped
82
+ stale through 0.1.9 in telemetry AND provision).
83
+ - DENY responses now point to the `upgrade` tool (free account, signed anchored
84
+ proof of every block).
85
+ - Onboarding: first-run `~/.fidacy/config.json` now writes the mandate template
86
+ explicitly (payees/categories/currency/caps) — adding a trusted payee is a
87
+ visible one-line edit; friendlier first-boot message (no more "dev mode" scare).
package/dist/core.js CHANGED
@@ -213,7 +213,7 @@ var DevFidacyCore = class {
213
213
  this.priv = kp.privateKey;
214
214
  this.pubPem = publicKeyPem(kp.publicKey);
215
215
  if (kp.ephemeral)
216
- console.error("[fidacy] dev mode: ephemeral signing key. Production: set FIDACY_SIGNING_KEY_B64, or FIDACY_MODE=http + FIDACY_API_URL + FIDACY_API_KEY to use the live core.");
216
+ console.error("[fidacy] local firewall active, deny-by-default. Grants are signed with a per-session key (fine for local use); set FIDACY_SIGNING_KEY_B64 for a stable key, or FIDACY_MODE=http to use the hosted core.");
217
217
  this.mandate = opts.mandate;
218
218
  this.store = new FileAuditStore(opts.auditLogPath ?? "./fidacy-audit.log");
219
219
  this.onDecision = opts.onDecision;
@@ -357,7 +357,8 @@ function resolveMandateRules(cfg) {
357
357
  }
358
358
 
359
359
  // src/telemetry.ts
360
- var CLIENT_VERSION = "0.1.5";
360
+ var CLIENT_VERSION = true ? "0.1.10" : "dev";
361
+ var currentShell = "mcp";
361
362
  function telemetryEnabled() {
362
363
  const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
363
364
  return !(v === "1" || v === "true" || v === "yes");
@@ -383,7 +384,7 @@ function resultOf(status, violatedRule) {
383
384
  }
384
385
  function record(type, result) {
385
386
  if (!telemetryEnabled()) return;
386
- buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, ...result ? { result } : {} });
387
+ buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, shell: currentShell, ...result ? { result } : {} });
387
388
  if (!flushTimer) {
388
389
  flushTimer = setTimeout(() => {
389
390
  void flush();
package/dist/index.js CHANGED
@@ -220,7 +220,7 @@ var DevFidacyCore = class {
220
220
  this.priv = kp.privateKey;
221
221
  this.pubPem = publicKeyPem(kp.publicKey);
222
222
  if (kp.ephemeral)
223
- console.error("[fidacy] dev mode: ephemeral signing key. Production: set FIDACY_SIGNING_KEY_B64, or FIDACY_MODE=http + FIDACY_API_URL + FIDACY_API_KEY to use the live core.");
223
+ console.error("[fidacy] local firewall active, deny-by-default. Grants are signed with a per-session key (fine for local use); set FIDACY_SIGNING_KEY_B64 for a stable key, or FIDACY_MODE=http to use the hosted core.");
224
224
  this.mandate = opts.mandate;
225
225
  this.store = new FileAuditStore(opts.auditLogPath ?? "./fidacy-audit.log");
226
226
  this.onDecision = opts.onDecision;
@@ -359,7 +359,12 @@ function writeConfig(cfg) {
359
359
  function ensureState() {
360
360
  const existing = readConfig();
361
361
  if (existing) return { config: existing, firstRun: false };
362
- const config = { anon_id: randomUUID2(), tier: "free", api_key: null };
362
+ const config = {
363
+ anon_id: randomUUID2(),
364
+ tier: "free",
365
+ api_key: null,
366
+ mandate: { payees: [], categories: ["*"], currency: "USD", perTxMax: 2500, maxTotal: 1e4 }
367
+ };
363
368
  try {
364
369
  writeConfig(config);
365
370
  } catch {
@@ -380,7 +385,8 @@ function resolveMandateRules(cfg) {
380
385
  }
381
386
 
382
387
  // src/telemetry.ts
383
- var CLIENT_VERSION = "0.1.5";
388
+ var CLIENT_VERSION = true ? "0.1.10" : "dev";
389
+ var currentShell = "mcp";
384
390
  function telemetryEnabled() {
385
391
  const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
386
392
  return !(v === "1" || v === "true" || v === "yes");
@@ -406,7 +412,7 @@ function resultOf(status, violatedRule) {
406
412
  }
407
413
  function record(type, result) {
408
414
  if (!telemetryEnabled()) return;
409
- buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, ...result ? { result } : {} });
415
+ buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, shell: currentShell, ...result ? { result } : {} });
410
416
  if (!flushTimer) {
411
417
  flushTimer = setTimeout(() => {
412
418
  void flush();
@@ -603,7 +609,7 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
603
609
  }
604
610
 
605
611
  // src/provision.ts
606
- var CLIENT_VERSION2 = "0.1.5";
612
+ var CLIENT_VERSION2 = true ? "0.1.10" : "dev";
607
613
  function provisionEnabled() {
608
614
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
609
615
  return !(v === "1" || v === "true" || v === "yes");
@@ -684,7 +690,7 @@ server.registerTool(
684
690
  const d = await core.decide(req, subject);
685
691
  const out = { status: d.status, decisionId: d.decisionId, grant: d.grant, violatedRule: d.violatedRule };
686
692
  const human = d.status === "ALLOW" ? `ALLOW (decision ${d.decisionId})${req.invoiceRef ? ` for invoice ${req.invoiceRef}` : ""}. To settle, call execute_payment with the SAME payee, amount, currency, and idempotencyKey, and set "grant" to EXACTLY this signed value:
687
- ${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.`;
693
+ ${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. Fidacy just blocked this for you \u2014 for server-signed, anchored proof of every block (free account, local history migrates), call the upgrade tool.`;
688
694
  return { content: [{ type: "text", text: human }], structuredContent: out };
689
695
  }
690
696
  );
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 };
@@ -308,7 +339,7 @@ var DevFidacyCore = class {
308
339
  this.priv = kp.privateKey;
309
340
  this.pubPem = publicKeyPem(kp.publicKey);
310
341
  if (kp.ephemeral)
311
- console.error("[fidacy] dev mode: ephemeral signing key. Production: set FIDACY_SIGNING_KEY_B64, or FIDACY_MODE=http + FIDACY_API_URL + FIDACY_API_KEY to use the live core.");
342
+ console.error("[fidacy] local firewall active, deny-by-default. Grants are signed with a per-session key (fine for local use); set FIDACY_SIGNING_KEY_B64 for a stable key, or FIDACY_MODE=http to use the hosted core.");
312
343
  this.mandate = opts.mandate;
313
344
  this.store = new FileAuditStore(opts.auditLogPath ?? "./fidacy-audit.log");
314
345
  this.onDecision = opts.onDecision;
@@ -399,6 +430,7 @@ var HttpFidacyCore = class {
399
430
  };
400
431
 
401
432
  // src/config.ts
433
+ import { randomUUID as randomUUID3 } from "node:crypto";
402
434
  import { homedir } from "node:os";
403
435
  import { join } from "node:path";
404
436
  import {
@@ -438,6 +470,26 @@ function readConfig() {
438
470
  return null;
439
471
  }
440
472
  }
473
+ function writeConfig(cfg) {
474
+ const dir = configDir();
475
+ mkdirSync(dir, { recursive: true, mode: 448 });
476
+ writeFileSync(configPath(), JSON.stringify(cfg, null, 2), { mode: 384 });
477
+ }
478
+ function ensureState() {
479
+ const existing = readConfig();
480
+ if (existing) return { config: existing, firstRun: false };
481
+ const config = {
482
+ anon_id: randomUUID3(),
483
+ tier: "free",
484
+ api_key: null,
485
+ mandate: { payees: [], categories: ["*"], currency: "USD", perTxMax: 2500, maxTotal: 1e4 }
486
+ };
487
+ try {
488
+ writeConfig(config);
489
+ } catch {
490
+ }
491
+ return { config, firstRun: true };
492
+ }
441
493
  function resolveMandateRules(cfg) {
442
494
  const m = cfg?.mandate ?? {};
443
495
  const envList = (v) => v === void 0 ? void 0 : v.split(",").map((s) => s.trim()).filter(Boolean);
@@ -452,7 +504,11 @@ function resolveMandateRules(cfg) {
452
504
  }
453
505
 
454
506
  // src/telemetry.ts
455
- var CLIENT_VERSION = "0.1.5";
507
+ var CLIENT_VERSION = true ? "0.1.10" : "dev";
508
+ var currentShell = "mcp";
509
+ function setTelemetryShell(shell) {
510
+ currentShell = shell;
511
+ }
456
512
  function telemetryEnabled() {
457
513
  const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
458
514
  return !(v === "1" || v === "true" || v === "yes");
@@ -478,7 +534,7 @@ function resultOf(status, violatedRule) {
478
534
  }
479
535
  function record(type, result) {
480
536
  if (!telemetryEnabled()) return;
481
- buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, ...result ? { result } : {} });
537
+ buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, shell: currentShell, ...result ? { result } : {} });
482
538
  if (!flushTimer) {
483
539
  flushTimer = setTimeout(() => {
484
540
  void flush();
@@ -486,7 +542,10 @@ function record(type, result) {
486
542
  if (typeof flushTimer.unref === "function") flushTimer.unref();
487
543
  }
488
544
  }
545
+ var recordInstall = () => record("install");
546
+ var recordAgentActive = () => record("agent_active");
489
547
  var recordDecision = (status, violatedRule) => record("decision", resultOf(status, violatedRule));
548
+ var recordUpgradeIntent = () => record("upgrade_intent");
490
549
  async function flush() {
491
550
  if (flushTimer) {
492
551
  clearTimeout(flushTimer);
@@ -554,22 +613,79 @@ function makeCore() {
554
613
  onDecision: (d) => recordDecision(d.status, d.violatedRule)
555
614
  });
556
615
  }
616
+
617
+ // src/upgrade.ts
618
+ function upgradeUrl() {
619
+ const base = (process.env.FIDACY_APP_URL ?? "https://app.fidacy.com").replace(/\/$/, "");
620
+ const anon = readConfig()?.anon_id ?? "";
621
+ return `${base}/upgrade?anon=${encodeURIComponent(anon)}`;
622
+ }
623
+ function requestUpgrade() {
624
+ recordUpgradeIntent();
625
+ const anonId2 = readConfig()?.anon_id ?? "";
626
+ return { url: upgradeUrl(), anonId: anonId2 };
627
+ }
628
+
629
+ // src/provision.ts
630
+ var CLIENT_VERSION2 = true ? "0.1.10" : "dev";
631
+ function provisionEnabled() {
632
+ const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
633
+ return !(v === "1" || v === "true" || v === "yes");
634
+ }
635
+ function endpoint2() {
636
+ const base = (process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com").replace(/\/$/, "");
637
+ return `${base}/v1/provision`;
638
+ }
639
+ async function autoProvision() {
640
+ if (!provisionEnabled()) return "skipped";
641
+ const cfg = readConfig();
642
+ if (!cfg) return "skipped";
643
+ if (cfg.api_key) return "skipped";
644
+ try {
645
+ const res = await fetch(endpoint2(), {
646
+ method: "POST",
647
+ headers: { "content-type": "application/json" },
648
+ body: JSON.stringify({ anon_id: cfg.anon_id, client_version: CLIENT_VERSION2 })
649
+ });
650
+ if (res.status === 429) return "rate_limited";
651
+ if (!res.ok) return "failed";
652
+ const data = await res.json();
653
+ if (typeof data.api_key !== "string" || !data.api_key) return "failed";
654
+ writeConfig({
655
+ ...cfg,
656
+ api_key: data.api_key,
657
+ tier: data.tier === "paid" ? "paid" : "free"
658
+ });
659
+ return "provisioned";
660
+ } catch {
661
+ return "failed";
662
+ }
663
+ }
557
664
  export {
558
665
  DevFidacyCore,
559
666
  FileAuditStore,
560
667
  GrantEnforcingExecutor,
561
668
  HttpFidacyCore,
562
669
  ReferenceRail,
670
+ autoProvision,
563
671
  canonInvoice,
672
+ ensureState,
564
673
  evaluate,
674
+ httpRedeem,
565
675
  loadOrGenerateKeyPair,
566
676
  lookalikePayee,
567
677
  makeCore,
568
678
  publicKeyPem,
679
+ readConfig,
680
+ recordAgentActive,
681
+ recordInstall,
682
+ requestUpgrade,
569
683
  requireHttpsBase,
684
+ setTelemetryShell,
570
685
  sha256,
571
686
  sign,
572
687
  stableStringify,
688
+ upgradeUrl,
573
689
  validateRequest,
574
690
  verify,
575
691
  verifyGrant
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",
@@ -36,12 +36,6 @@
36
36
  "publishConfig": {
37
37
  "access": "public"
38
38
  },
39
- "scripts": {
40
- "build": "node scripts/bundle.mjs",
41
- "dev": "tsx watch src/index.ts",
42
- "start": "node dist/index.js",
43
- "prepublishOnly": "npm run build"
44
- },
45
39
  "engines": {
46
40
  "node": ">=18"
47
41
  },
@@ -50,9 +44,19 @@
50
44
  "zod": "^3.25.0"
51
45
  },
52
46
  "devDependencies": {
53
- "@fidacy/firewall": "workspace:*",
54
47
  "@types/node": "^22.10.0",
55
48
  "tsx": "^4.19.2",
56
- "typescript": "^5.7.2"
49
+ "typescript": "^5.7.2",
50
+ "@fidacy/firewall": "0.1.0"
51
+ },
52
+ "mcpName": "com.fidacy/mcp",
53
+ "repository": {
54
+ "type": "git",
55
+ "url": "git+https://github.com/lucaslubi/fidacy-mcp.git"
56
+ },
57
+ "scripts": {
58
+ "build": "node scripts/bundle.mjs",
59
+ "dev": "tsx watch src/index.ts",
60
+ "start": "node dist/index.js"
57
61
  }
58
- }
62
+ }