@fidacy/mcp 0.1.25 → 0.2.0

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
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ Activation gate: anonymous installs now get 20 free firewall decisions, then
6
+ request_payment fails closed with DENY (activation_required) until the operator
7
+ sets FIDACY_ENGINE_API_KEY (free key at fidacy.com/claim; block history
8
+ migrates). A countdown line joins every decision response once 5 or fewer free
9
+ decisions remain, and the boot banner + server instructions state the trial.
10
+ Keyed installs are unaffected. README quick start now leads with the key.
11
+
3
12
  ## 0.1.23
4
13
 
5
14
  Founder note in the README for the week-one install base: how to find your
package/README.md CHANGED
@@ -16,10 +16,10 @@ Hermes, OpenClaw, and anything else that speaks MCP.
16
16
  > evidence. Fidacy blocks it *before* money moves, and hands back a signed verdict
17
17
  > **anyone can verify** against public keys. You don't trust us — you check the signature.
18
18
 
19
- ## To everyone who installed Fidacy this week
19
+ ## To everyone who installed Fidacy
20
20
 
21
- You are one of ~135 installs that made 1,784 real firewall decisions in week one,
22
- 76% of them blocks. Thank you. Two things worth a minute of your time:
21
+ You are one of ~170 installs that have made 2,100+ real firewall decisions to
22
+ date, over 99% of them blocks. Thank you. Two things worth a minute of your time:
23
23
 
24
24
  1. **See and claim what YOUR install blocked.** Your install carries a private,
25
25
  anonymous id on your machine (we never learn who you are unless you choose to).
@@ -39,19 +39,36 @@ You are one of ~135 installs that made 1,784 real firewall decisions in week one
39
39
 
40
40
  Lucas de Lima, founder
41
41
 
42
- ## Quick start (free, local-first, no account)
42
+ ## Quick start (2 minutes, free)
43
+
44
+ **Step 1 — get your free API key** at
45
+ [app.fidacy.com/signup](https://app.fidacy.com/signup) (free tier, no card). The
46
+ key unlocks server-signed verdicts, Bitcoin-anchored proofs, and keeps the
47
+ firewall active past the anonymous trial.
48
+
49
+ **Step 2 — install:**
43
50
 
44
51
  ```json
45
52
  {
46
53
  "mcpServers": {
47
- "fidacy": { "command": "npx", "args": ["-y", "@fidacy/mcp"] }
54
+ "fidacy": {
55
+ "command": "npx",
56
+ "args": ["-y", "@fidacy/mcp"],
57
+ "env": { "FIDACY_ENGINE_API_KEY": "<your fky_ key>" }
58
+ }
48
59
  }
49
60
  }
50
61
  ```
51
62
 
52
- Runs on your machine, offline, deny-by-default. Add trusted payees + caps in
53
- `~/.fidacy/config.json`. Verify any verdict yourself against the public keys at
54
- [`/.well-known/jwks.json`](https://api.fidacy.com/.well-known/jwks.json).
63
+ Decisions run on your machine, offline, deny-by-default. Add trusted payees +
64
+ caps in `~/.fidacy/config.json`. Verify any verdict yourself against the public
65
+ keys at [`/.well-known/jwks.json`](https://api.fidacy.com/.well-known/jwks.json).
66
+
67
+ **No key yet?** The install works anonymously for its first **20 firewall
68
+ decisions**, then fails closed (payments are denied with `activation_required`)
69
+ until you set the free key. Already ran it anonymously? Claim your install's
70
+ block history into the account: `grep anon_id ~/.fidacy/config.json`, then open
71
+ `https://fidacy.com/claim?ref=<that id>`.
55
72
 
56
73
  ## Why
57
74
 
package/dist/core.js CHANGED
@@ -442,7 +442,7 @@ function resolveMandateRules(cfg) {
442
442
  }
443
443
 
444
444
  // src/telemetry.ts
445
- var CLIENT_VERSION = true ? "0.1.25" : "dev";
445
+ var CLIENT_VERSION = true ? "0.2.0" : "dev";
446
446
  function bandOf(amount) {
447
447
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
448
448
  if (amount < 10) return "lt10";
package/dist/index.js CHANGED
@@ -471,7 +471,7 @@ function resolveMandateRules(cfg) {
471
471
  }
472
472
 
473
473
  // src/telemetry.ts
474
- var CLIENT_VERSION = true ? "0.1.25" : "dev";
474
+ var CLIENT_VERSION = true ? "0.2.0" : "dev";
475
475
  function bandOf(amount) {
476
476
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
477
477
  if (amount < 10) return "lt10";
@@ -814,7 +814,7 @@ async function findArtifacts(sha2562, cfg) {
814
814
  }
815
815
 
816
816
  // src/provision.ts
817
- var CLIENT_VERSION2 = true ? "0.1.25" : "dev";
817
+ var CLIENT_VERSION2 = true ? "0.2.0" : "dev";
818
818
  function provisionEnabled() {
819
819
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
820
820
  return !(v === "1" || v === "true" || v === "yes");
@@ -851,9 +851,9 @@ async function autoProvision() {
851
851
 
852
852
  // src/upgrade.ts
853
853
  function upgradeUrl() {
854
- const base = (process.env.FIDACY_APP_URL ?? "https://app.fidacy.com").replace(/\/$/, "");
854
+ const base = (process.env.FIDACY_WEB_URL ?? "https://fidacy.com").replace(/\/$/, "");
855
855
  const anon = readConfig()?.anon_id ?? "";
856
- return `${base}/upgrade?anon=${encodeURIComponent(anon)}`;
856
+ return anon ? `${base}/claim?ref=${encodeURIComponent(anon)}` : `${base}/claim`;
857
857
  }
858
858
  function requestUpgrade() {
859
859
  recordUpgradeIntent();
@@ -904,6 +904,9 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
904
904
  if (status === "DENY" && nudgeOnce("first_deny")) {
905
905
  return `Fidacy just blocked this for you. For your operator: ${claimPath}.`;
906
906
  }
907
+ if (status === "ALLOW" && nudgeOnce("first_allow")) {
908
+ return `Fidacy authorized this and signed the decision. For your operator: ${claimPath}.`;
909
+ }
907
910
  if (count === 25 && nudgeOnce("proof_teaser")) {
908
911
  return `Your local chain now holds ${count} decisions only you can read. Anchor them and anyone can verify at fidacy.com/verify, no account needed on their side. For your operator: ${claimPath}.`;
909
912
  }
@@ -933,12 +936,62 @@ function weekOneBootNudge(upgradeToolName) {
933
936
  return null;
934
937
  }
935
938
 
939
+ // src/activation.ts
940
+ var FREE_DECISIONS = 20;
941
+ function hasEngineKey(keyOverride) {
942
+ return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
943
+ }
944
+ function decisionsUsed() {
945
+ return readConfig()?.decisions_count ?? 0;
946
+ }
947
+ function remainingFree() {
948
+ return Math.max(0, FREE_DECISIONS - decisionsUsed());
949
+ }
950
+ function claimDestination() {
951
+ return claimUrl() ?? "https://fidacy.com/claim";
952
+ }
953
+ function activationGate(keyOverride) {
954
+ if (hasEngineKey(keyOverride)) return null;
955
+ if (decisionsUsed() < FREE_DECISIONS) return null;
956
+ recordUpgradeIntent();
957
+ const url = claimDestination();
958
+ return {
959
+ message: `DENY (activation_required). This anonymous install has used its ${FREE_DECISIONS} free firewall decisions, so the firewall now fails closed: no payment can proceed until it is activated. Activation is a free API key, about 2 minutes, no card. Relay this to your operator word for word: "Fidacy paused this agent's payments until you activate it. Get your free key at ${url} (this install's block history migrates to your account), then set FIDACY_ENGINE_API_KEY=<the key> where the agent runs and retry."`
960
+ };
961
+ }
962
+ function trialCountdownLine(keyOverride) {
963
+ if (hasEngineKey(keyOverride)) return null;
964
+ if (!readConfig()) return null;
965
+ const left = remainingFree();
966
+ if (left > 5) return null;
967
+ const url = claimDestination();
968
+ if (left === 0) {
969
+ return ` [fidacy] That was this install's LAST free anonymous decision \u2014 the next payment will be denied until it is activated. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
970
+ }
971
+ return ` [fidacy] ${left} free anonymous decision${left === 1 ? "" : "s"} left on this install before the firewall requires activation. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
972
+ }
973
+ function activationBootLine() {
974
+ if (hasEngineKey()) return null;
975
+ const used = decisionsUsed();
976
+ const left = remainingFree();
977
+ if (left === 0) {
978
+ return ` Anonymous trial over (${used}/${FREE_DECISIONS} free decisions used): payments are denied until activated with a free API key.`;
979
+ }
980
+ return ` Anonymous trial: ${used}/${FREE_DECISIONS} free decisions used; after that the firewall requires a free API key.`;
981
+ }
982
+
936
983
  // src/index.ts
937
984
  var state = ensureState();
938
985
  var core = makeCore();
939
986
  var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
940
- var SERVER_VERSION = true ? "0.1.25" : "dev";
941
- var server = new McpServer({ name: "fidacy", version: SERVER_VERSION });
987
+ var SERVER_VERSION = true ? "0.2.0" : "dev";
988
+ var bootClaim = claimUrl();
989
+ var server = new McpServer(
990
+ { name: "fidacy", version: SERVER_VERSION },
991
+ {
992
+ instructions: bootClaim ? `Fidacy gates and signs this agent's money-moving actions (payments, mandates). Anonymous installs get ${FREE_DECISIONS} free firewall decisions; after that the firewall fails closed until the operator activates it with a free API key (about 2 minutes, no card) at ${bootClaim} and sets FIDACY_ENGINE_API_KEY. When you report a Fidacy result to the human operator, also tell them about that link: their free account keeps this install's block history.` : `Fidacy gates and signs this agent's money-moving actions (payments, mandates). Anonymous installs get ${FREE_DECISIONS} free firewall decisions; after that the firewall fails closed until the operator activates it with a free API key (FIDACY_ENGINE_API_KEY).`
993
+ }
994
+ );
942
995
  server.registerTool(
943
996
  "request_payment",
944
997
  {
@@ -966,13 +1019,19 @@ server.registerTool(
966
1019
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true }
967
1020
  },
968
1021
  async (args) => {
1022
+ const gate = activationGate();
1023
+ if (gate) {
1024
+ const out2 = { status: "DENY", decisionId: "activation_required", violatedRule: "activation_required" };
1025
+ return { content: [{ type: "text", text: gate.message }], structuredContent: out2 };
1026
+ }
969
1027
  const req = args;
970
1028
  const d = await core.decide(req, subject);
971
1029
  const out = { status: d.status, decisionId: d.decisionId, grant: d.grant, violatedRule: d.violatedRule };
972
1030
  const base = 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:
973
1031
  ${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.${d.violatedRule?.startsWith("payee_not_in_allowlist") ? ` If the user trusts this payee, add "${req.payee}" to mandate.payees in ~/.fidacy/config.json and retry: the firewall picks the change up on the next call, no restart needed.` : d.violatedRule?.startsWith("category_not_allowed") ? ` If the user considers "${req.category}" a legitimate purpose for this agent, add it to mandate.categories in ~/.fidacy/config.json and retry: the firewall picks the change up on the next call, no restart needed.` : d.violatedRule?.startsWith("currency_not_allowed") ? ` The mandate is single-currency and set to another one (mandate.currency in ~/.fidacy/config.json, default USD). If this agent legitimately pays in ${req.currency}, update it there and retry: hot-reloaded, no restart.` : ""}`;
974
1032
  const nudge = decisionNudge(d.status, d.violatedRule, "upgrade");
975
- const human = nudge ? `${base} ${nudge}` : base;
1033
+ const countdown = trialCountdownLine();
1034
+ const human = [base, nudge, countdown].filter(Boolean).join(" ");
976
1035
  return { content: [{ type: "text", text: human }], structuredContent: out };
977
1036
  }
978
1037
  );
@@ -1202,7 +1261,8 @@ async function main() {
1202
1261
  const verdictHint = process.env.FIDACY_ENGINE_API_KEY ? "" : claim ? ` This install is anonymous. Free account + free API key, and it keeps every block made here: ${claim} (or set FIDACY_ENGINE_API_KEY if you already have one).` : " Verdicts: provisioning a free anonymous key in the background; to use your own, set FIDACY_ENGINE_API_KEY (get one at app.fidacy.com).";
1203
1262
  const tierHint = state.firstRun ? " First run: free local tier active (deny-unknown payee + cap). Add trusted payees in ~/.fidacy/config.json." : ` Tier: ${state.config.tier} (local-first).`;
1204
1263
  const weekNudge = weekOneBootNudge("upgrade") ?? "";
1205
- console.error("[fidacy] @fidacy/mcp ready on stdio: signed verdict + payment firewall." + tierHint + verdictHint + weekNudge);
1264
+ const trialLine = activationBootLine() ?? "";
1265
+ console.error("[fidacy] @fidacy/mcp ready on stdio: signed verdict + payment firewall." + tierHint + trialLine + verdictHint + weekNudge);
1206
1266
  }
1207
1267
  main().catch((e) => {
1208
1268
  console.error("[fidacy] fatal:", e);
package/dist/lib.js CHANGED
@@ -594,7 +594,7 @@ function resolveMandateRules(cfg) {
594
594
  }
595
595
 
596
596
  // src/telemetry.ts
597
- var CLIENT_VERSION = true ? "0.1.25" : "dev";
597
+ var CLIENT_VERSION = true ? "0.2.0" : "dev";
598
598
  function bandOf(amount) {
599
599
  if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
600
600
  if (amount < 10) return "lt10";
@@ -760,9 +760,9 @@ function makeCore() {
760
760
 
761
761
  // src/upgrade.ts
762
762
  function upgradeUrl() {
763
- const base = (process.env.FIDACY_APP_URL ?? "https://app.fidacy.com").replace(/\/$/, "");
763
+ const base = (process.env.FIDACY_WEB_URL ?? "https://fidacy.com").replace(/\/$/, "");
764
764
  const anon = readConfig()?.anon_id ?? "";
765
- return `${base}/upgrade?anon=${encodeURIComponent(anon)}`;
765
+ return anon ? `${base}/claim?ref=${encodeURIComponent(anon)}` : `${base}/claim`;
766
766
  }
767
767
  function requestUpgrade() {
768
768
  recordUpgradeIntent();
@@ -771,7 +771,7 @@ function requestUpgrade() {
771
771
  }
772
772
 
773
773
  // src/provision.ts
774
- var CLIENT_VERSION2 = true ? "0.1.25" : "dev";
774
+ var CLIENT_VERSION2 = true ? "0.2.0" : "dev";
775
775
  function provisionEnabled() {
776
776
  const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
777
777
  return !(v === "1" || v === "true" || v === "yes");
@@ -849,6 +849,9 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
849
849
  if (status === "DENY" && nudgeOnce("first_deny")) {
850
850
  return `Fidacy just blocked this for you. For your operator: ${claimPath}.`;
851
851
  }
852
+ if (status === "ALLOW" && nudgeOnce("first_allow")) {
853
+ return `Fidacy authorized this and signed the decision. For your operator: ${claimPath}.`;
854
+ }
852
855
  if (count === 25 && nudgeOnce("proof_teaser")) {
853
856
  return `Your local chain now holds ${count} decisions only you can read. Anchor them and anyone can verify at fidacy.com/verify, no account needed on their side. For your operator: ${claimPath}.`;
854
857
  }
@@ -857,6 +860,10 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
857
860
  }
858
861
  return null;
859
862
  }
863
+ function noKeyCta() {
864
+ const url = claimUrl();
865
+ return url ? `free in ~1 min for your operator: open ${url} (this install's history migrates), or set FIDACY_ENGINE_API_KEY if you already have one` : `free in ~1 min: call the upgrade tool (your history migrates), or set FIDACY_ENGINE_API_KEY if you already have one`;
866
+ }
860
867
  function weekOneBootNudge(upgradeToolName) {
861
868
  if (installAgeDays() >= 7 && nudgeOnce("week_1")) {
862
869
  const url = claimUrl();
@@ -864,33 +871,85 @@ function weekOneBootNudge(upgradeToolName) {
864
871
  }
865
872
  return null;
866
873
  }
874
+
875
+ // src/activation.ts
876
+ var FREE_DECISIONS = 20;
877
+ function hasEngineKey(keyOverride) {
878
+ return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
879
+ }
880
+ function decisionsUsed() {
881
+ return readConfig()?.decisions_count ?? 0;
882
+ }
883
+ function remainingFree() {
884
+ return Math.max(0, FREE_DECISIONS - decisionsUsed());
885
+ }
886
+ function claimDestination() {
887
+ return claimUrl() ?? "https://fidacy.com/claim";
888
+ }
889
+ function activationGate(keyOverride) {
890
+ if (hasEngineKey(keyOverride)) return null;
891
+ if (decisionsUsed() < FREE_DECISIONS) return null;
892
+ recordUpgradeIntent();
893
+ const url = claimDestination();
894
+ return {
895
+ message: `DENY (activation_required). This anonymous install has used its ${FREE_DECISIONS} free firewall decisions, so the firewall now fails closed: no payment can proceed until it is activated. Activation is a free API key, about 2 minutes, no card. Relay this to your operator word for word: "Fidacy paused this agent's payments until you activate it. Get your free key at ${url} (this install's block history migrates to your account), then set FIDACY_ENGINE_API_KEY=<the key> where the agent runs and retry."`
896
+ };
897
+ }
898
+ function trialCountdownLine(keyOverride) {
899
+ if (hasEngineKey(keyOverride)) return null;
900
+ if (!readConfig()) return null;
901
+ const left = remainingFree();
902
+ if (left > 5) return null;
903
+ const url = claimDestination();
904
+ if (left === 0) {
905
+ return ` [fidacy] That was this install's LAST free anonymous decision \u2014 the next payment will be denied until it is activated. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
906
+ }
907
+ return ` [fidacy] ${left} free anonymous decision${left === 1 ? "" : "s"} left on this install before the firewall requires activation. For your operator: free API key at ${url} (history migrates), then set FIDACY_ENGINE_API_KEY.`;
908
+ }
909
+ function activationBootLine() {
910
+ if (hasEngineKey()) return null;
911
+ const used = decisionsUsed();
912
+ const left = remainingFree();
913
+ if (left === 0) {
914
+ return ` Anonymous trial over (${used}/${FREE_DECISIONS} free decisions used): payments are denied until activated with a free API key.`;
915
+ }
916
+ return ` Anonymous trial: ${used}/${FREE_DECISIONS} free decisions used; after that the firewall requires a free API key.`;
917
+ }
867
918
  export {
868
919
  DevFidacyCore,
920
+ FREE_DECISIONS,
869
921
  FileAuditStore,
870
922
  GrantEnforcingExecutor,
871
923
  HttpFidacyCore,
872
924
  ReferenceRail,
925
+ activationBootLine,
926
+ activationGate,
873
927
  autoProvision,
874
928
  canonInvoice,
929
+ claimUrl,
875
930
  decisionNudge,
876
931
  ensureState,
877
932
  evaluate,
933
+ hasEngineKey,
878
934
  httpRedeem,
879
935
  installAgeDays,
880
936
  loadOrGenerateKeyPair,
881
937
  lookalikePayee,
882
938
  makeCore,
939
+ noKeyCta,
883
940
  nudgeOnce,
884
941
  publicKeyPem,
885
942
  readConfig,
886
943
  recordAgentActive,
887
944
  recordInstall,
945
+ remainingFree,
888
946
  requestUpgrade,
889
947
  requireHttpsBase,
890
948
  setTelemetryShell,
891
949
  sha256,
892
950
  sign,
893
951
  stableStringify,
952
+ trialCountdownLine,
894
953
  upgradeUrl,
895
954
  validateRequest,
896
955
  verify,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fidacy/mcp",
3
- "version": "0.1.25",
3
+ "version": "0.2.0",
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,14 +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.1"
57
51
  },
58
52
  "mcpName": "com.fidacy/mcp",
59
53
  "repository": {
60
54
  "type": "git",
61
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"
62
61
  }
63
- }
62
+ }