@fidacy/mcp 0.1.17 → 0.1.19

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/README.md CHANGED
@@ -73,6 +73,9 @@ Mental model: `assess_action` -> **engine** (signed verdict);
73
73
  | `request_payment` | core | Authorize a payment action. ALLOW + grant, or DENY + rule. |
74
74
  | `verify_mandate` | core | Read the mandate envelope + Fidacy public key. |
75
75
  | `get_audit_proof` | core | Hash-chained proof for a decision id. |
76
+ | `anchor_artifact` | engine | Bitcoin-anchored integrity proof for any file (contract, invoice, prescription, claim, image, audio, video, conversation). Hashed locally; only the SHA-256 leaves. Returns a signed receipt. |
77
+ | `check_artifact` | engine | Check whether a file (or hash) was anchored, and its Bitcoin checkpoint state. A mismatch is the tampering signal. |
78
+ | `upgrade` | — | Start upgrading this local install to a real Fidacy account; preserves and migrates anonymous usage. |
76
79
 
77
80
  ### `assess_action`
78
81
 
@@ -145,6 +148,21 @@ repository stays private. Set `FIDACY_MODE=http` and implement three endpoints:
145
148
 
146
149
  No change to the MCP layer is needed.
147
150
 
151
+ ## Telemetry
152
+
153
+ The install emits **anonymous, opt-out** usage telemetry so we can measure
154
+ traction (installs, active agents, decision counts, deny-rate). It is
155
+ best-effort and never on the decision critical path, so it can never block or
156
+ slow a verdict.
157
+
158
+ - It carries an anonymous install id and counters only. It **never** includes
159
+ payment content — no payee, amount, invoice, or mandate ever leaves your
160
+ machine. Payload-carrying types are rejected by a strict schema.
161
+ - Turn it off completely with `FIDACY_DISABLE_TELEMETRY=1` (or `true`).
162
+
163
+ Consistent with the product: you don't have to trust us, you can verify. The
164
+ firewall runs fully with telemetry disabled.
165
+
148
166
  ## Dev
149
167
 
150
168
  ```bash
package/dist/assess.js CHANGED
@@ -88,7 +88,7 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
88
88
  body: payload,
89
89
  signal: controller.signal
90
90
  });
91
- } catch (err) {
91
+ } catch {
92
92
  const aborted = controller.signal.aborted;
93
93
  throw new AssessError({ type: aborted ? "timeout" : "network_error", status: 0 });
94
94
  } finally {
package/dist/core.js CHANGED
@@ -347,6 +347,9 @@ var FileAuditStore = class {
347
347
  const digest = sha256(stableStringify({ decisionId: decision.decisionId, status: decision.status, request: decision.request, violatedRule: decision.violatedRule ?? null }));
348
348
  const hash = sha256(`${prevHash}|${digest}|${seq}|${ts}`);
349
349
  const record2 = { seq, decisionId: decision.decisionId, status: decision.status, subject: decision.subject, digest, prevHash, hash, ts };
350
+ if (typeof decision.request?.purpose === "string" && decision.request.purpose.trim()) {
351
+ record2.purpose = decision.request.purpose.slice(0, 500);
352
+ }
350
353
  if (decision.status === "ALLOW") {
351
354
  const req = decision.request;
352
355
  if (typeof req?.amount === "number" && Number.isFinite(req.amount))
@@ -439,7 +442,25 @@ function resolveMandateRules(cfg) {
439
442
  }
440
443
 
441
444
  // src/telemetry.ts
442
- var CLIENT_VERSION = true ? "0.1.17" : "dev";
445
+ var CLIENT_VERSION = true ? "0.1.19" : "dev";
446
+ function bandOf(amount) {
447
+ if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
448
+ if (amount < 10) return "lt10";
449
+ if (amount < 100) return "10_99";
450
+ if (amount < 1e3) return "100_999";
451
+ if (amount < 1e4) return "1k_10k";
452
+ if (amount < 1e5) return "10k_100k";
453
+ return "gte100k";
454
+ }
455
+ function capRatioOf(amount, perTxMax) {
456
+ if (typeof amount !== "number" || typeof perTxMax !== "number") return void 0;
457
+ if (!Number.isFinite(amount) || !Number.isFinite(perTxMax) || perTxMax <= 0 || amount <= perTxMax) return void 0;
458
+ const r = amount / perTxMax;
459
+ if (r < 2) return "1_2x";
460
+ if (r < 5) return "2_5x";
461
+ if (r < 10) return "5_10x";
462
+ return "gt10x";
463
+ }
443
464
  var currentShell = "mcp";
444
465
  function telemetryEnabled() {
445
466
  const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
@@ -469,9 +490,17 @@ function resultOf(status, violatedRule) {
469
490
  if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
470
491
  return "deny_scope";
471
492
  }
472
- function record(type, result) {
493
+ function record(type, result, band, capRatio) {
473
494
  if (!telemetryEnabled()) return;
474
- buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, shell: currentShell, ...result ? { result } : {} });
495
+ buffer.push({
496
+ type,
497
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
498
+ client_version: CLIENT_VERSION,
499
+ shell: currentShell,
500
+ ...result ? { result } : {},
501
+ ...band ? { band } : {},
502
+ ...capRatio ? { cap_ratio: capRatio } : {}
503
+ });
475
504
  if (!flushTimer) {
476
505
  flushTimer = setTimeout(() => {
477
506
  void flush();
@@ -479,7 +508,7 @@ function record(type, result) {
479
508
  if (typeof flushTimer.unref === "function") flushTimer.unref();
480
509
  }
481
510
  }
482
- var recordDecision = (status, violatedRule) => record("decision", resultOf(status, violatedRule));
511
+ var recordDecision = (status, violatedRule, amount, perTxMax) => record("decision", resultOf(status, violatedRule), bandOf(amount), capRatioOf(amount, perTxMax));
483
512
  async function flush() {
484
513
  if (flushTimer) {
485
514
  clearTimeout(flushTimer);
@@ -541,10 +570,11 @@ function makeCore() {
541
570
  if (!url || !key) throw new Error("FIDACY_MODE=http requires FIDACY_API_URL and FIDACY_API_KEY");
542
571
  return new HttpFidacyCore(url, key, process.env.FIDACY_PUBLIC_KEY_PEM ?? "");
543
572
  }
573
+ let activeMandate = buildMandate();
544
574
  const dev = new DevFidacyCore({
545
- mandate: buildMandate(),
575
+ mandate: activeMandate,
546
576
  auditLogPath: auditLogPath(),
547
- onDecision: (d) => recordDecision(d.status, d.violatedRule)
577
+ onDecision: (d) => recordDecision(d.status, d.violatedRule, d.request?.amount, activeMandate?.allow?.perTxMax)
548
578
  });
549
579
  const mtimeOf = () => statSync(configPath(), { throwIfNoEntry: false })?.mtimeMs ?? 0;
550
580
  let seenMtime = mtimeOf();
@@ -552,7 +582,8 @@ function makeCore() {
552
582
  const m = mtimeOf();
553
583
  if (m !== seenMtime) {
554
584
  seenMtime = m;
555
- dev.setMandate(buildMandate());
585
+ activeMandate = buildMandate();
586
+ dev.setMandate(activeMandate);
556
587
  }
557
588
  };
558
589
  return {
package/dist/index.js CHANGED
@@ -354,6 +354,9 @@ var FileAuditStore = class {
354
354
  const digest = sha256(stableStringify({ decisionId: decision.decisionId, status: decision.status, request: decision.request, violatedRule: decision.violatedRule ?? null }));
355
355
  const hash = sha256(`${prevHash}|${digest}|${seq}|${ts}`);
356
356
  const record2 = { seq, decisionId: decision.decisionId, status: decision.status, subject: decision.subject, digest, prevHash, hash, ts };
357
+ if (typeof decision.request?.purpose === "string" && decision.request.purpose.trim()) {
358
+ record2.purpose = decision.request.purpose.slice(0, 500);
359
+ }
357
360
  if (decision.status === "ALLOW") {
358
361
  const req = decision.request;
359
362
  if (typeof req?.amount === "number" && Number.isFinite(req.amount))
@@ -468,7 +471,25 @@ function resolveMandateRules(cfg) {
468
471
  }
469
472
 
470
473
  // src/telemetry.ts
471
- var CLIENT_VERSION = true ? "0.1.17" : "dev";
474
+ var CLIENT_VERSION = true ? "0.1.19" : "dev";
475
+ function bandOf(amount) {
476
+ if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
477
+ if (amount < 10) return "lt10";
478
+ if (amount < 100) return "10_99";
479
+ if (amount < 1e3) return "100_999";
480
+ if (amount < 1e4) return "1k_10k";
481
+ if (amount < 1e5) return "10k_100k";
482
+ return "gte100k";
483
+ }
484
+ function capRatioOf(amount, perTxMax) {
485
+ if (typeof amount !== "number" || typeof perTxMax !== "number") return void 0;
486
+ if (!Number.isFinite(amount) || !Number.isFinite(perTxMax) || perTxMax <= 0 || amount <= perTxMax) return void 0;
487
+ const r = amount / perTxMax;
488
+ if (r < 2) return "1_2x";
489
+ if (r < 5) return "2_5x";
490
+ if (r < 10) return "5_10x";
491
+ return "gt10x";
492
+ }
472
493
  var currentShell = "mcp";
473
494
  function telemetryEnabled() {
474
495
  const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
@@ -498,9 +519,17 @@ function resultOf(status, violatedRule) {
498
519
  if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
499
520
  return "deny_scope";
500
521
  }
501
- function record(type, result) {
522
+ function record(type, result, band, capRatio) {
502
523
  if (!telemetryEnabled()) return;
503
- buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, shell: currentShell, ...result ? { result } : {} });
524
+ buffer.push({
525
+ type,
526
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
527
+ client_version: CLIENT_VERSION,
528
+ shell: currentShell,
529
+ ...result ? { result } : {},
530
+ ...band ? { band } : {},
531
+ ...capRatio ? { cap_ratio: capRatio } : {}
532
+ });
504
533
  if (!flushTimer) {
505
534
  flushTimer = setTimeout(() => {
506
535
  void flush();
@@ -510,7 +539,7 @@ function record(type, result) {
510
539
  }
511
540
  var recordInstall = () => record("install");
512
541
  var recordAgentActive = () => record("agent_active");
513
- var recordDecision = (status, violatedRule) => record("decision", resultOf(status, violatedRule));
542
+ var recordDecision = (status, violatedRule, amount, perTxMax) => record("decision", resultOf(status, violatedRule), bandOf(amount), capRatioOf(amount, perTxMax));
514
543
  var recordUpgradeIntent = () => record("upgrade_intent");
515
544
  async function flush() {
516
545
  if (flushTimer) {
@@ -573,10 +602,11 @@ function makeCore() {
573
602
  if (!url || !key) throw new Error("FIDACY_MODE=http requires FIDACY_API_URL and FIDACY_API_KEY");
574
603
  return new HttpFidacyCore(url, key, process.env.FIDACY_PUBLIC_KEY_PEM ?? "");
575
604
  }
605
+ let activeMandate = buildMandate();
576
606
  const dev = new DevFidacyCore({
577
- mandate: buildMandate(),
607
+ mandate: activeMandate,
578
608
  auditLogPath: auditLogPath(),
579
- onDecision: (d) => recordDecision(d.status, d.violatedRule)
609
+ onDecision: (d) => recordDecision(d.status, d.violatedRule, d.request?.amount, activeMandate?.allow?.perTxMax)
580
610
  });
581
611
  const mtimeOf = () => statSync(configPath(), { throwIfNoEntry: false })?.mtimeMs ?? 0;
582
612
  let seenMtime = mtimeOf();
@@ -584,7 +614,8 @@ function makeCore() {
584
614
  const m = mtimeOf();
585
615
  if (m !== seenMtime) {
586
616
  seenMtime = m;
587
- dev.setMandate(buildMandate());
617
+ activeMandate = buildMandate();
618
+ dev.setMandate(activeMandate);
588
619
  }
589
620
  };
590
621
  return {
@@ -691,7 +722,7 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
691
722
  body: payload,
692
723
  signal: controller.signal
693
724
  });
694
- } catch (err) {
725
+ } catch {
695
726
  const aborted = controller.signal.aborted;
696
727
  throw new AssessError({ type: aborted ? "timeout" : "network_error", status: 0 });
697
728
  } finally {
@@ -783,7 +814,7 @@ async function findArtifacts(sha2562, cfg) {
783
814
  }
784
815
 
785
816
  // src/provision.ts
786
- var CLIENT_VERSION2 = true ? "0.1.17" : "dev";
817
+ var CLIENT_VERSION2 = true ? "0.1.19" : "dev";
787
818
  function provisionEnabled() {
788
819
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
789
820
  return !(v === "1" || v === "true" || v === "yes");
@@ -883,7 +914,7 @@ function weekOneBootNudge(upgradeToolName) {
883
914
  var state = ensureState();
884
915
  var core = makeCore();
885
916
  var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
886
- var SERVER_VERSION = true ? "0.1.17" : "dev";
917
+ var SERVER_VERSION = true ? "0.1.19" : "dev";
887
918
  var server = new McpServer({ name: "fidacy", version: SERVER_VERSION });
888
919
  server.registerTool(
889
920
  "request_payment",
@@ -894,7 +925,9 @@ server.registerTool(
894
925
  payee: z.string().describe("Payee identifier"),
895
926
  amount: z.number().positive().describe("Amount in the mandate currency"),
896
927
  currency: z.string().length(3).describe("ISO 4217 currency code"),
897
- purpose: z.string().describe("Human-readable purpose"),
928
+ purpose: z.string().describe(
929
+ "Your stated reason for this payment, in your own words. It is persisted readably on the tamper-evident audit record (ALLOW and DENY alike) and becomes part of the after-the-fact proof, so write the real reason."
930
+ ),
898
931
  category: z.string().describe("Purpose category (must be allowed by the mandate)"),
899
932
  idempotencyKey: z.string().describe("Caller-supplied idempotency key"),
900
933
  invoiceRef: z.string().describe(
package/dist/lib.js CHANGED
@@ -385,6 +385,9 @@ var FileAuditStore = class {
385
385
  const digest = sha256(stableStringify({ decisionId: decision.decisionId, status: decision.status, request: decision.request, violatedRule: decision.violatedRule ?? null }));
386
386
  const hash = sha256(`${prevHash}|${digest}|${seq}|${ts}`);
387
387
  const record2 = { seq, decisionId: decision.decisionId, status: decision.status, subject: decision.subject, digest, prevHash, hash, ts };
388
+ if (typeof decision.request?.purpose === "string" && decision.request.purpose.trim()) {
389
+ record2.purpose = decision.request.purpose.slice(0, 500);
390
+ }
388
391
  if (decision.status === "ALLOW") {
389
392
  const req = decision.request;
390
393
  if (typeof req?.amount === "number" && Number.isFinite(req.amount))
@@ -457,6 +460,10 @@ var GrantEnforcingExecutor = class {
457
460
  this.rail = rail;
458
461
  this.pub = createPublicKey(publicKeyPem2);
459
462
  this.redeem = opts?.redeem;
463
+ const isProd = typeof process !== "undefined" && process.env?.NODE_ENV === "production";
464
+ if (isProd && !this.redeem && !opts?.allowUnsafeInMemorySingleUse) {
465
+ throw new Error("GrantEnforcingExecutor in production requires a durable `redeem` (single-use across instances). Wire httpRedeem(coreUrl, apiKey), or pass allowUnsafeInMemorySingleUse:true only for a true single-process deployment.");
466
+ }
460
467
  }
461
468
  async execute(req, grant) {
462
469
  if (!grant)
@@ -587,7 +594,25 @@ function resolveMandateRules(cfg) {
587
594
  }
588
595
 
589
596
  // src/telemetry.ts
590
- var CLIENT_VERSION = true ? "0.1.17" : "dev";
597
+ var CLIENT_VERSION = true ? "0.1.19" : "dev";
598
+ function bandOf(amount) {
599
+ if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
600
+ if (amount < 10) return "lt10";
601
+ if (amount < 100) return "10_99";
602
+ if (amount < 1e3) return "100_999";
603
+ if (amount < 1e4) return "1k_10k";
604
+ if (amount < 1e5) return "10k_100k";
605
+ return "gte100k";
606
+ }
607
+ function capRatioOf(amount, perTxMax) {
608
+ if (typeof amount !== "number" || typeof perTxMax !== "number") return void 0;
609
+ if (!Number.isFinite(amount) || !Number.isFinite(perTxMax) || perTxMax <= 0 || amount <= perTxMax) return void 0;
610
+ const r = amount / perTxMax;
611
+ if (r < 2) return "1_2x";
612
+ if (r < 5) return "2_5x";
613
+ if (r < 10) return "5_10x";
614
+ return "gt10x";
615
+ }
591
616
  var currentShell = "mcp";
592
617
  function setTelemetryShell(shell) {
593
618
  currentShell = shell;
@@ -620,9 +645,17 @@ function resultOf(status, violatedRule) {
620
645
  if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
621
646
  return "deny_scope";
622
647
  }
623
- function record(type, result) {
648
+ function record(type, result, band, capRatio) {
624
649
  if (!telemetryEnabled()) return;
625
- buffer.push({ type, ts: (/* @__PURE__ */ new Date()).toISOString(), client_version: CLIENT_VERSION, shell: currentShell, ...result ? { result } : {} });
650
+ buffer.push({
651
+ type,
652
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
653
+ client_version: CLIENT_VERSION,
654
+ shell: currentShell,
655
+ ...result ? { result } : {},
656
+ ...band ? { band } : {},
657
+ ...capRatio ? { cap_ratio: capRatio } : {}
658
+ });
626
659
  if (!flushTimer) {
627
660
  flushTimer = setTimeout(() => {
628
661
  void flush();
@@ -632,7 +665,7 @@ function record(type, result) {
632
665
  }
633
666
  var recordInstall = () => record("install");
634
667
  var recordAgentActive = () => record("agent_active");
635
- var recordDecision = (status, violatedRule) => record("decision", resultOf(status, violatedRule));
668
+ var recordDecision = (status, violatedRule, amount, perTxMax) => record("decision", resultOf(status, violatedRule), bandOf(amount), capRatioOf(amount, perTxMax));
636
669
  var recordUpgradeIntent = () => record("upgrade_intent");
637
670
  async function flush() {
638
671
  if (flushTimer) {
@@ -695,10 +728,11 @@ function makeCore() {
695
728
  if (!url || !key) throw new Error("FIDACY_MODE=http requires FIDACY_API_URL and FIDACY_API_KEY");
696
729
  return new HttpFidacyCore(url, key, process.env.FIDACY_PUBLIC_KEY_PEM ?? "");
697
730
  }
731
+ let activeMandate = buildMandate();
698
732
  const dev = new DevFidacyCore({
699
- mandate: buildMandate(),
733
+ mandate: activeMandate,
700
734
  auditLogPath: auditLogPath(),
701
- onDecision: (d) => recordDecision(d.status, d.violatedRule)
735
+ onDecision: (d) => recordDecision(d.status, d.violatedRule, d.request?.amount, activeMandate?.allow?.perTxMax)
702
736
  });
703
737
  const mtimeOf = () => statSync(configPath(), { throwIfNoEntry: false })?.mtimeMs ?? 0;
704
738
  let seenMtime = mtimeOf();
@@ -706,7 +740,8 @@ function makeCore() {
706
740
  const m = mtimeOf();
707
741
  if (m !== seenMtime) {
708
742
  seenMtime = m;
709
- dev.setMandate(buildMandate());
743
+ activeMandate = buildMandate();
744
+ dev.setMandate(activeMandate);
710
745
  }
711
746
  };
712
747
  return {
@@ -736,7 +771,7 @@ function requestUpgrade() {
736
771
  }
737
772
 
738
773
  // src/provision.ts
739
- var CLIENT_VERSION2 = true ? "0.1.17" : "dev";
774
+ var CLIENT_VERSION2 = true ? "0.1.19" : "dev";
740
775
  function provisionEnabled() {
741
776
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
742
777
  return !(v === "1" || v === "true" || v === "yes");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
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",