@fidacy/mcp 0.1.13 → 0.1.15
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 +6 -0
- package/dist/core.js +7 -2
- package/dist/index.js +179 -4
- package/dist/lib.js +8 -3
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -118,3 +118,9 @@ semantic versioning.
|
|
|
118
118
|
- MCP handshake now reports the real package version (was hardcoded "0.1.0"
|
|
119
119
|
through 0.1.12 — same class as the client_version telemetry bug, same fix:
|
|
120
120
|
injected from package.json at build time).
|
|
121
|
+
|
|
122
|
+
## 0.1.14 — 2026-07-03
|
|
123
|
+
|
|
124
|
+
- Granular block-type telemetry: DENY reasons now split into lookalike (BEC),
|
|
125
|
+
duplicate-invoice, payee, cap, window and invalid, so the public threat page
|
|
126
|
+
can count blocks by attack class. Still zero PII (an enum, never the rule text).
|
package/dist/core.js
CHANGED
|
@@ -423,7 +423,7 @@ function resolveMandateRules(cfg) {
|
|
|
423
423
|
}
|
|
424
424
|
|
|
425
425
|
// src/telemetry.ts
|
|
426
|
-
var CLIENT_VERSION = true ? "0.1.
|
|
426
|
+
var CLIENT_VERSION = true ? "0.1.15" : "dev";
|
|
427
427
|
var currentShell = "mcp";
|
|
428
428
|
function telemetryEnabled() {
|
|
429
429
|
const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
|
|
@@ -444,8 +444,13 @@ var flushTimer = null;
|
|
|
444
444
|
function resultOf(status, violatedRule) {
|
|
445
445
|
if (status === "ALLOW") return "allow";
|
|
446
446
|
const r = violatedRule ?? "";
|
|
447
|
-
if (r.startsWith("
|
|
447
|
+
if (r.startsWith("payee_lookalike")) return "deny_lookalike";
|
|
448
448
|
if (r.startsWith("payee_not_in_allowlist")) return "deny_payee";
|
|
449
|
+
if (r.startsWith("duplicate_invoice")) return "deny_duplicate";
|
|
450
|
+
if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
|
|
451
|
+
if (r.startsWith("mandate_revoked") || r.startsWith("before_mandate") || r.startsWith("after_mandate"))
|
|
452
|
+
return "deny_window";
|
|
453
|
+
if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
|
|
449
454
|
return "deny_scope";
|
|
450
455
|
}
|
|
451
456
|
function record(type, result) {
|
package/dist/index.js
CHANGED
|
@@ -452,7 +452,7 @@ function resolveMandateRules(cfg) {
|
|
|
452
452
|
}
|
|
453
453
|
|
|
454
454
|
// src/telemetry.ts
|
|
455
|
-
var CLIENT_VERSION = true ? "0.1.
|
|
455
|
+
var CLIENT_VERSION = true ? "0.1.15" : "dev";
|
|
456
456
|
var currentShell = "mcp";
|
|
457
457
|
function telemetryEnabled() {
|
|
458
458
|
const v = (process.env.FIDACY_DISABLE_TELEMETRY ?? "").trim().toLowerCase();
|
|
@@ -473,8 +473,13 @@ var flushTimer = null;
|
|
|
473
473
|
function resultOf(status, violatedRule) {
|
|
474
474
|
if (status === "ALLOW") return "allow";
|
|
475
475
|
const r = violatedRule ?? "";
|
|
476
|
-
if (r.startsWith("
|
|
476
|
+
if (r.startsWith("payee_lookalike")) return "deny_lookalike";
|
|
477
477
|
if (r.startsWith("payee_not_in_allowlist")) return "deny_payee";
|
|
478
|
+
if (r.startsWith("duplicate_invoice")) return "deny_duplicate";
|
|
479
|
+
if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
|
|
480
|
+
if (r.startsWith("mandate_revoked") || r.startsWith("before_mandate") || r.startsWith("after_mandate"))
|
|
481
|
+
return "deny_window";
|
|
482
|
+
if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
|
|
478
483
|
return "deny_scope";
|
|
479
484
|
}
|
|
480
485
|
function record(type, result) {
|
|
@@ -675,8 +680,72 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
|
|
|
675
680
|
return parsed;
|
|
676
681
|
}
|
|
677
682
|
|
|
683
|
+
// src/artifacts.ts
|
|
684
|
+
import { createHash } from "node:crypto";
|
|
685
|
+
import { createReadStream } from "node:fs";
|
|
686
|
+
import { pipeline } from "node:stream/promises";
|
|
687
|
+
var ARTIFACT_KINDS = [
|
|
688
|
+
"contract",
|
|
689
|
+
"invoice",
|
|
690
|
+
"prescription",
|
|
691
|
+
"claim",
|
|
692
|
+
"document",
|
|
693
|
+
"image",
|
|
694
|
+
"audio",
|
|
695
|
+
"video",
|
|
696
|
+
"custom"
|
|
697
|
+
];
|
|
698
|
+
async function hashFile(path) {
|
|
699
|
+
const hash = createHash("sha256");
|
|
700
|
+
await pipeline(createReadStream(path), async function* (source) {
|
|
701
|
+
for await (const chunk of source) {
|
|
702
|
+
hash.update(chunk);
|
|
703
|
+
yield;
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
return hash.digest("hex");
|
|
707
|
+
}
|
|
708
|
+
async function engineFetch(method, pathAndQuery, cfg, body) {
|
|
709
|
+
const base = requireSafeBaseUrl(cfg.engineUrl);
|
|
710
|
+
const controller = new AbortController();
|
|
711
|
+
const timer = setTimeout(() => controller.abort(), 1e4);
|
|
712
|
+
let res;
|
|
713
|
+
try {
|
|
714
|
+
res = await fetch(`${base}${pathAndQuery}`, {
|
|
715
|
+
method,
|
|
716
|
+
headers: {
|
|
717
|
+
authorization: `Bearer ${cfg.apiKey}`,
|
|
718
|
+
...body !== void 0 ? { "content-type": "application/json" } : {}
|
|
719
|
+
},
|
|
720
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
721
|
+
signal: controller.signal
|
|
722
|
+
});
|
|
723
|
+
} catch {
|
|
724
|
+
throw new AssessError({ type: "network_error", status: 0 });
|
|
725
|
+
} finally {
|
|
726
|
+
clearTimeout(timer);
|
|
727
|
+
}
|
|
728
|
+
let json;
|
|
729
|
+
try {
|
|
730
|
+
json = await res.json();
|
|
731
|
+
} catch {
|
|
732
|
+
throw new AssessError({ type: "bad_response", status: res.status });
|
|
733
|
+
}
|
|
734
|
+
if (!res.ok) {
|
|
735
|
+
const type = typeof json?.error === "string" ? String(json.error) : "engine_error";
|
|
736
|
+
throw new AssessError({ type, status: res.status });
|
|
737
|
+
}
|
|
738
|
+
return json;
|
|
739
|
+
}
|
|
740
|
+
async function anchorArtifact(params, cfg) {
|
|
741
|
+
return await engineFetch("POST", "/v1/artifacts", cfg, params);
|
|
742
|
+
}
|
|
743
|
+
async function findArtifacts(sha2562, cfg) {
|
|
744
|
+
return await engineFetch("GET", `/v1/artifacts?sha256=${sha2562}`, cfg);
|
|
745
|
+
}
|
|
746
|
+
|
|
678
747
|
// src/provision.ts
|
|
679
|
-
var CLIENT_VERSION2 = true ? "0.1.
|
|
748
|
+
var CLIENT_VERSION2 = true ? "0.1.15" : "dev";
|
|
680
749
|
function provisionEnabled() {
|
|
681
750
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
682
751
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -776,7 +845,7 @@ function weekOneBootNudge(upgradeToolName) {
|
|
|
776
845
|
var state = ensureState();
|
|
777
846
|
var core = makeCore();
|
|
778
847
|
var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
|
|
779
|
-
var SERVER_VERSION = true ? "0.1.
|
|
848
|
+
var SERVER_VERSION = true ? "0.1.15" : "dev";
|
|
780
849
|
var server = new McpServer({ name: "fidacy", version: SERVER_VERSION });
|
|
781
850
|
server.registerTool(
|
|
782
851
|
"request_payment",
|
|
@@ -896,6 +965,112 @@ server.registerTool(
|
|
|
896
965
|
}
|
|
897
966
|
}
|
|
898
967
|
);
|
|
968
|
+
server.registerTool(
|
|
969
|
+
"anchor_artifact",
|
|
970
|
+
{
|
|
971
|
+
title: "Anchor Artifact (Bitcoin-anchored integrity proof)",
|
|
972
|
+
description: "Prove an artifact existed exactly as-is at this moment, and make any later tampering detectable. Give a file `path` (hashed locally with SHA-256 \u2014 the file itself is NEVER uploaded) or a precomputed `sha256`. The hash is registered on Fidacy's tamper-evident audit chain, which is checkpointed to the Bitcoin blockchain, and you get a signed receipt (JWS) verifiable offline against the engine JWKS. Use it for contracts, invoices, medical prescriptions, insurance claims, images, audio, video. `kind` defaults to document; optional `label` is a short reference (an invoice number, a case id \u2014 no PII).",
|
|
973
|
+
inputSchema: {
|
|
974
|
+
path: z.string().optional(),
|
|
975
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional(),
|
|
976
|
+
kind: z.enum(ARTIFACT_KINDS).optional(),
|
|
977
|
+
label: z.string().max(120).optional(),
|
|
978
|
+
subject: z.string().max(120).optional()
|
|
979
|
+
},
|
|
980
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true }
|
|
981
|
+
},
|
|
982
|
+
async ({ path, sha256: sha2562, kind, label, subject: subj }) => {
|
|
983
|
+
const engineUrl = process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
|
|
984
|
+
const apiKey = (process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
|
|
985
|
+
if (!apiKey) {
|
|
986
|
+
return {
|
|
987
|
+
content: [{ type: "text", text: "anchor_artifact needs an engine key \u2014 free in about a minute: call the upgrade tool, or set FIDACY_ENGINE_API_KEY if you already have one." }],
|
|
988
|
+
isError: true
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
if (!path && !sha2562) {
|
|
992
|
+
return { content: [{ type: "text", text: "Give a file `path` (hashed locally) or a precomputed `sha256`." }], isError: true };
|
|
993
|
+
}
|
|
994
|
+
let hash = sha2562 ?? "";
|
|
995
|
+
if (!hash && path) {
|
|
996
|
+
try {
|
|
997
|
+
hash = await hashFile(path);
|
|
998
|
+
} catch {
|
|
999
|
+
return { content: [{ type: "text", text: "Could not read that file locally (check the path and permissions). Nothing was sent anywhere." }], isError: true };
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
try {
|
|
1003
|
+
const r = await anchorArtifact(
|
|
1004
|
+
{ sha256: hash, kind: kind ?? "document", ...label ? { label } : {}, ...subj ? { subject: subj } : {} },
|
|
1005
|
+
{ engineUrl, apiKey }
|
|
1006
|
+
);
|
|
1007
|
+
return {
|
|
1008
|
+
content: [{
|
|
1009
|
+
type: "text",
|
|
1010
|
+
text: `ANCHORED ${r.kind} \xB7 sha256 ${hash.slice(0, 16)}\u2026 \xB7 audit seq ${r.audit.seq} \xB7 Bitcoin checkpoint: ${r.anchor.status}. The file never left this machine; only its hash was registered. Keep the receipt (structured output) \u2014 it verifies offline and proves this artifact existed exactly as-is, signed, at ${r.ts}.`
|
|
1011
|
+
}],
|
|
1012
|
+
structuredContent: r
|
|
1013
|
+
};
|
|
1014
|
+
} catch (e) {
|
|
1015
|
+
if (e instanceof AssessError) {
|
|
1016
|
+
return { content: [{ type: "text", text: `ANCHOR ${e.status}: ${e.type}` }], isError: true };
|
|
1017
|
+
}
|
|
1018
|
+
return { content: [{ type: "text", text: "ANCHOR failed: unexpected error" }], isError: true };
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
);
|
|
1022
|
+
server.registerTool(
|
|
1023
|
+
"check_artifact",
|
|
1024
|
+
{
|
|
1025
|
+
title: "Check Artifact (was this hash anchored?)",
|
|
1026
|
+
description: "Check whether an artifact was anchored by this account and the state of its Bitcoin checkpoint. Give a file `path` (hashed locally, never uploaded) or a `sha256`. If the current hash of a file does NOT match any anchored record that you expected to match, the file changed since anchoring \u2014 that is exactly the tampering signal.",
|
|
1027
|
+
inputSchema: {
|
|
1028
|
+
path: z.string().optional(),
|
|
1029
|
+
sha256: z.string().regex(/^[0-9a-f]{64}$/).optional()
|
|
1030
|
+
},
|
|
1031
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
1032
|
+
},
|
|
1033
|
+
async ({ path, sha256: sha2562 }) => {
|
|
1034
|
+
const engineUrl = process.env.FIDACY_ENGINE_URL ?? "https://api.fidacy.com";
|
|
1035
|
+
const apiKey = (process.env.FIDACY_ENGINE_API_KEY ?? "").trim();
|
|
1036
|
+
if (!apiKey) {
|
|
1037
|
+
return {
|
|
1038
|
+
content: [{ type: "text", text: "check_artifact needs an engine key \u2014 free in about a minute: call the upgrade tool, or set FIDACY_ENGINE_API_KEY if you already have one." }],
|
|
1039
|
+
isError: true
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
if (!path && !sha2562) {
|
|
1043
|
+
return { content: [{ type: "text", text: "Give a file `path` (hashed locally) or a `sha256`." }], isError: true };
|
|
1044
|
+
}
|
|
1045
|
+
let hash = sha2562 ?? "";
|
|
1046
|
+
if (!hash && path) {
|
|
1047
|
+
try {
|
|
1048
|
+
hash = await hashFile(path);
|
|
1049
|
+
} catch {
|
|
1050
|
+
return { content: [{ type: "text", text: "Could not read that file locally (check the path and permissions). Nothing was sent anywhere." }], isError: true };
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
try {
|
|
1054
|
+
const r = await findArtifacts(hash, { engineUrl, apiKey });
|
|
1055
|
+
if (!r.artifacts.length) {
|
|
1056
|
+
return {
|
|
1057
|
+
content: [{ type: "text", text: `NOT FOUND \xB7 sha256 ${hash.slice(0, 16)}\u2026 has no anchored record in this account. If you expected a match, the file has changed since it was anchored.` }],
|
|
1058
|
+
structuredContent: { sha256: hash, artifacts: [] }
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
const first = r.artifacts[0];
|
|
1062
|
+
return {
|
|
1063
|
+
content: [{ type: "text", text: `FOUND ${r.artifacts.length} record(s) \xB7 newest: ${String(first.kind)} anchored ${String(first.createdAt)} (audit seq ${String(first.auditSeq)}). The current content matches the anchored hash byte for byte.` }],
|
|
1064
|
+
structuredContent: { sha256: hash, ...r }
|
|
1065
|
+
};
|
|
1066
|
+
} catch (e) {
|
|
1067
|
+
if (e instanceof AssessError) {
|
|
1068
|
+
return { content: [{ type: "text", text: `CHECK ${e.status}: ${e.type}` }], isError: true };
|
|
1069
|
+
}
|
|
1070
|
+
return { content: [{ type: "text", text: "CHECK failed: unexpected error" }], isError: true };
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
);
|
|
899
1074
|
server.registerTool(
|
|
900
1075
|
"upgrade",
|
|
901
1076
|
{
|
package/dist/lib.js
CHANGED
|
@@ -571,7 +571,7 @@ function resolveMandateRules(cfg) {
|
|
|
571
571
|
}
|
|
572
572
|
|
|
573
573
|
// src/telemetry.ts
|
|
574
|
-
var CLIENT_VERSION = true ? "0.1.
|
|
574
|
+
var CLIENT_VERSION = true ? "0.1.15" : "dev";
|
|
575
575
|
var currentShell = "mcp";
|
|
576
576
|
function setTelemetryShell(shell) {
|
|
577
577
|
currentShell = shell;
|
|
@@ -595,8 +595,13 @@ var flushTimer = null;
|
|
|
595
595
|
function resultOf(status, violatedRule) {
|
|
596
596
|
if (status === "ALLOW") return "allow";
|
|
597
597
|
const r = violatedRule ?? "";
|
|
598
|
-
if (r.startsWith("
|
|
598
|
+
if (r.startsWith("payee_lookalike")) return "deny_lookalike";
|
|
599
599
|
if (r.startsWith("payee_not_in_allowlist")) return "deny_payee";
|
|
600
|
+
if (r.startsWith("duplicate_invoice")) return "deny_duplicate";
|
|
601
|
+
if (r.startsWith("per_tx_cap") || r.startsWith("total_cap")) return "deny_cap";
|
|
602
|
+
if (r.startsWith("mandate_revoked") || r.startsWith("before_mandate") || r.startsWith("after_mandate"))
|
|
603
|
+
return "deny_window";
|
|
604
|
+
if (r.startsWith("non_positive_amount") || r.startsWith("invalid_")) return "deny_invalid";
|
|
600
605
|
return "deny_scope";
|
|
601
606
|
}
|
|
602
607
|
function record(type, result) {
|
|
@@ -694,7 +699,7 @@ function requestUpgrade() {
|
|
|
694
699
|
}
|
|
695
700
|
|
|
696
701
|
// src/provision.ts
|
|
697
|
-
var CLIENT_VERSION2 = true ? "0.1.
|
|
702
|
+
var CLIENT_VERSION2 = true ? "0.1.15" : "dev";
|
|
698
703
|
function provisionEnabled() {
|
|
699
704
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
700
705
|
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.
|
|
3
|
+
"version": "0.1.15",
|
|
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,6 +36,12 @@
|
|
|
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
|
+
},
|
|
39
45
|
"engines": {
|
|
40
46
|
"node": ">=18"
|
|
41
47
|
},
|
|
@@ -44,19 +50,14 @@
|
|
|
44
50
|
"zod": "^3.25.0"
|
|
45
51
|
},
|
|
46
52
|
"devDependencies": {
|
|
53
|
+
"@fidacy/firewall": "workspace:*",
|
|
47
54
|
"@types/node": "^22.10.0",
|
|
48
55
|
"tsx": "^4.19.2",
|
|
49
|
-
"typescript": "^5.7.2"
|
|
50
|
-
"@fidacy/firewall": "0.1.0"
|
|
56
|
+
"typescript": "^5.7.2"
|
|
51
57
|
},
|
|
52
58
|
"mcpName": "com.fidacy/mcp",
|
|
53
59
|
"repository": {
|
|
54
60
|
"type": "git",
|
|
55
61
|
"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"
|
|
61
62
|
}
|
|
62
63
|
}
|