@fidacy/mcp 0.3.2 → 0.3.4
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 +22 -0
- package/dist/activation.d.ts +2 -2
- package/dist/assess.d.ts +14 -0
- package/dist/assess.js +8 -1
- package/dist/config.d.ts +9 -0
- package/dist/core.js +1 -1
- package/dist/index.js +30 -14
- package/dist/lib.js +5 -5
- package/dist/nudges.d.ts +5 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.4
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Declare the MCP Registry identity that matches the product name: `mcpName` is now `com.fidacy/ai-agent-firewall`. The registry validates server ownership against this field, so the listing could not carry the commercial name until the package declared it. Nothing about installing or running the server changes.
|
|
8
|
+
|
|
9
|
+
## 0.3.3
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- The server now presents itself as **Fidacy AI Agent Firewall** (`fidacy-ai-agent-firewall`, title "Fidacy AI Agent Firewall") instead of the bare `fidacy`. The npm package is unchanged — `npx -y @fidacy/mcp` keeps working exactly as before, and no config needs editing. This is the name the product is sold under, so it is the name your client should show.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- Fix the wall an existing customer hits. When an install that already carries an API key exhausted its monthly allowance (or reached the spending cap it set), the server answered with an invitation to create a free account — the account they already had — and threw away everything the engine said about it. It now reports what actually happened, names the pay-as-you-go price and links straight to billing, and makes explicit that the local firewall keeps deciding and blocking throughout: only server-signed verdicts and anchored proofs pause, never the protection.
|
|
18
|
+
|
|
19
|
+
## 0.3.2
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Security: a spending cap that failed to parse silently stopped capping. Cap values read from `FIDACY_PER_TX_MAX` / `FIDACY_MAX_TOTAL` or from `config.json` went through a bare `Number()`, so ordinary ways of writing an amount — `2,500`, `$2500`, `2500 USD` — became `NaN`. Because `amount > NaN` is false, the comparison did not raise: the cap simply stopped existing, and a firewall whose author believed it was capped at 2,500 would authorize 9,999. Two fixes: the decision engine now DENIES any mandate whose caps are not finite positive numbers (`invalid_mandate_cap`), so an unenforceable cap can never allow a payment on any path, and a cap that does not parse is now refused with an explicit message telling you what to write instead, falling back to the safe default rather than to no cap. Upgrade if you set either cap from an environment variable.
|
|
24
|
+
|
|
3
25
|
## 0.3.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/activation.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { hasEngineKey } from "./config.js";
|
|
2
|
+
export { hasEngineKey };
|
|
1
3
|
/**
|
|
2
4
|
* ACTIVATION GATE — the anonymous trial that ends in a free API key.
|
|
3
5
|
*
|
|
@@ -16,8 +18,6 @@
|
|
|
16
18
|
* real client needs anyway (it powers assess_action / anchor_artifact).
|
|
17
19
|
*/
|
|
18
20
|
export declare const FREE_DECISIONS = 20;
|
|
19
|
-
/** True when the install carries an operator-set engine key (env or shell config). */
|
|
20
|
-
export declare function hasEngineKey(keyOverride?: string): boolean;
|
|
21
21
|
/**
|
|
22
22
|
* Payment-gate decisions taken so far on this install: the on-disk counter OR
|
|
23
23
|
* the in-process one, whichever is higher. The in-process floor is what keeps
|
package/dist/assess.d.ts
CHANGED
|
@@ -42,16 +42,30 @@ export interface RejectionReason {
|
|
|
42
42
|
* request body. `type` is the stable engine error code (or a client code), and
|
|
43
43
|
* `status` is the HTTP status (0 for network/timeout).
|
|
44
44
|
*/
|
|
45
|
+
/**
|
|
46
|
+
* What a 402 actually says. The engine returns `reason` (quota grace expired vs
|
|
47
|
+
* spending cap reached), a human-readable `detail` and the `billing_url` that
|
|
48
|
+
* resolves it. All of it used to be dropped on the floor, so an operator who had
|
|
49
|
+
* ALREADY signed up was told to go sign up — at the one moment they were asking
|
|
50
|
+
* to buy more. Carry it through and say the true thing instead.
|
|
51
|
+
*/
|
|
52
|
+
export interface BillingBlock {
|
|
53
|
+
reason?: string;
|
|
54
|
+
detail?: string;
|
|
55
|
+
billingUrl?: string;
|
|
56
|
+
}
|
|
45
57
|
export declare class AssessError extends Error {
|
|
46
58
|
readonly type: string;
|
|
47
59
|
readonly status: number;
|
|
48
60
|
readonly details?: unknown;
|
|
49
61
|
readonly rejection_reasons?: RejectionReason[];
|
|
62
|
+
readonly billing?: BillingBlock;
|
|
50
63
|
constructor(opts: {
|
|
51
64
|
type: string;
|
|
52
65
|
status: number;
|
|
53
66
|
details?: unknown;
|
|
54
67
|
rejection_reasons?: RejectionReason[];
|
|
68
|
+
billing?: BillingBlock;
|
|
55
69
|
});
|
|
56
70
|
}
|
|
57
71
|
export interface AssessConfig {
|
package/dist/assess.js
CHANGED
|
@@ -4,6 +4,7 @@ var AssessError = class extends Error {
|
|
|
4
4
|
status;
|
|
5
5
|
details;
|
|
6
6
|
rejection_reasons;
|
|
7
|
+
billing;
|
|
7
8
|
constructor(opts) {
|
|
8
9
|
super(`Fidacy assess error (${opts.type}, HTTP ${opts.status})`);
|
|
9
10
|
this.name = "AssessError";
|
|
@@ -11,6 +12,7 @@ var AssessError = class extends Error {
|
|
|
11
12
|
this.status = opts.status;
|
|
12
13
|
this.details = opts.details;
|
|
13
14
|
this.rejection_reasons = opts.rejection_reasons;
|
|
15
|
+
this.billing = opts.billing;
|
|
14
16
|
}
|
|
15
17
|
};
|
|
16
18
|
var DEFAULT_TIMEOUT = 1e4;
|
|
@@ -106,7 +108,12 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
|
|
|
106
108
|
const type = isRecord(parsed) && typeof parsed.error === "string" && parsed.error || `http_${res.status}`;
|
|
107
109
|
const details = isRecord(parsed) ? parsed.details : void 0;
|
|
108
110
|
const rejection_reasons = isRecord(parsed) && Array.isArray(parsed.rejection_reasons) ? parsed.rejection_reasons : void 0;
|
|
109
|
-
|
|
111
|
+
const billing = res.status === 402 && isRecord(parsed) ? {
|
|
112
|
+
reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
|
|
113
|
+
detail: typeof parsed.detail === "string" ? parsed.detail : void 0,
|
|
114
|
+
billingUrl: typeof parsed.billing_url === "string" ? parsed.billing_url : void 0
|
|
115
|
+
} : void 0;
|
|
116
|
+
throw new AssessError({ type, status: res.status, details, rejection_reasons, billing });
|
|
110
117
|
}
|
|
111
118
|
if (!isRecord(parsed)) {
|
|
112
119
|
throw new AssessError({ type: "invalid_response", status: res.status });
|
package/dist/config.d.ts
CHANGED
|
@@ -37,6 +37,15 @@ export interface FidacyConfig {
|
|
|
37
37
|
/** Payment-gate decisions taken on this install (milestone nudges). */
|
|
38
38
|
decisions_count?: number;
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* True when the install carries an operator-set engine key (env, or the plugin's
|
|
42
|
+
* engineApiKey config passed as an override).
|
|
43
|
+
*
|
|
44
|
+
* Lives here rather than in activation.ts because nudges.ts needs it too, and
|
|
45
|
+
* activation.ts already imports nudges.ts — importing back would be a cycle.
|
|
46
|
+
* Reading the environment is this module's job anyway.
|
|
47
|
+
*/
|
|
48
|
+
export declare function hasEngineKey(keyOverride?: string): boolean;
|
|
40
49
|
/** Config dir: FIDACY_CONFIG_DIR override, else ~/.fidacy (XDG-friendly enough). */
|
|
41
50
|
export declare function configDir(): string;
|
|
42
51
|
export declare function configPath(): string;
|
package/dist/core.js
CHANGED
|
@@ -485,7 +485,7 @@ function resolveMandateRules(cfg) {
|
|
|
485
485
|
}
|
|
486
486
|
|
|
487
487
|
// src/telemetry.ts
|
|
488
|
-
var CLIENT_VERSION = true ? "0.3.
|
|
488
|
+
var CLIENT_VERSION = true ? "0.3.4" : "dev";
|
|
489
489
|
function bandOf(amount) {
|
|
490
490
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
491
491
|
if (amount < 10) return "lt10";
|
package/dist/index.js
CHANGED
|
@@ -432,6 +432,9 @@ import {
|
|
|
432
432
|
readFileSync,
|
|
433
433
|
writeFileSync
|
|
434
434
|
} from "node:fs";
|
|
435
|
+
function hasEngineKey(keyOverride) {
|
|
436
|
+
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
437
|
+
}
|
|
435
438
|
function configDir() {
|
|
436
439
|
return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
|
|
437
440
|
}
|
|
@@ -514,7 +517,7 @@ function resolveMandateRules(cfg) {
|
|
|
514
517
|
}
|
|
515
518
|
|
|
516
519
|
// src/telemetry.ts
|
|
517
|
-
var CLIENT_VERSION = true ? "0.3.
|
|
520
|
+
var CLIENT_VERSION = true ? "0.3.4" : "dev";
|
|
518
521
|
function bandOf(amount) {
|
|
519
522
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
520
523
|
if (amount < 10) return "lt10";
|
|
@@ -693,6 +696,7 @@ var AssessError = class extends Error {
|
|
|
693
696
|
status;
|
|
694
697
|
details;
|
|
695
698
|
rejection_reasons;
|
|
699
|
+
billing;
|
|
696
700
|
constructor(opts) {
|
|
697
701
|
super(`Fidacy assess error (${opts.type}, HTTP ${opts.status})`);
|
|
698
702
|
this.name = "AssessError";
|
|
@@ -700,6 +704,7 @@ var AssessError = class extends Error {
|
|
|
700
704
|
this.status = opts.status;
|
|
701
705
|
this.details = opts.details;
|
|
702
706
|
this.rejection_reasons = opts.rejection_reasons;
|
|
707
|
+
this.billing = opts.billing;
|
|
703
708
|
}
|
|
704
709
|
};
|
|
705
710
|
var DEFAULT_TIMEOUT = 1e4;
|
|
@@ -795,7 +800,12 @@ async function postOnce(fetchImpl, url, headers, payload, timeoutMs) {
|
|
|
795
800
|
const type = isRecord(parsed) && typeof parsed.error === "string" && parsed.error || `http_${res.status}`;
|
|
796
801
|
const details = isRecord(parsed) ? parsed.details : void 0;
|
|
797
802
|
const rejection_reasons = isRecord(parsed) && Array.isArray(parsed.rejection_reasons) ? parsed.rejection_reasons : void 0;
|
|
798
|
-
|
|
803
|
+
const billing = res.status === 402 && isRecord(parsed) ? {
|
|
804
|
+
reason: typeof parsed.reason === "string" ? parsed.reason : void 0,
|
|
805
|
+
detail: typeof parsed.detail === "string" ? parsed.detail : void 0,
|
|
806
|
+
billingUrl: typeof parsed.billing_url === "string" ? parsed.billing_url : void 0
|
|
807
|
+
} : void 0;
|
|
808
|
+
throw new AssessError({ type, status: res.status, details, rejection_reasons, billing });
|
|
799
809
|
}
|
|
800
810
|
if (!isRecord(parsed)) {
|
|
801
811
|
throw new AssessError({ type: "invalid_response", status: res.status });
|
|
@@ -869,7 +879,7 @@ async function findArtifacts(sha2562, cfg) {
|
|
|
869
879
|
}
|
|
870
880
|
|
|
871
881
|
// src/provision.ts
|
|
872
|
-
var CLIENT_VERSION2 = true ? "0.3.
|
|
882
|
+
var CLIENT_VERSION2 = true ? "0.3.4" : "dev";
|
|
873
883
|
function provisionEnabled() {
|
|
874
884
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
875
885
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -976,10 +986,15 @@ function decisionNudge(status, violatedRule, upgradeToolName) {
|
|
|
976
986
|
}
|
|
977
987
|
return null;
|
|
978
988
|
}
|
|
979
|
-
function hostedWallCta(feature) {
|
|
989
|
+
function hostedWallCta(feature, billing) {
|
|
990
|
+
const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
|
|
991
|
+
if (hasEngineKey()) {
|
|
992
|
+
const where = billing?.billingUrl ?? "https://app.fidacy.com/billing";
|
|
993
|
+
const what = billing?.reason === "spending_cap_reached" ? `This account hit the monthly spending cap you set, so ${noun} are paused.` : billing?.detail ?? `This account's included ${noun} for the month are used up.`;
|
|
994
|
+
return `${what} The local firewall keeps deciding and blocking as normal \u2014 this only pauses ${noun}. For your operator: resolve it at ${where}. Pay-as-you-go needs no plan and starts at $0.04 per verdict, dropping to $0.025 past 50k and $0.015 past 250k; committed plans price below that curve.`;
|
|
995
|
+
}
|
|
980
996
|
const url = claimUrl();
|
|
981
997
|
const cta = url ? `open ${url} \u2014 free account, this install's history migrates` : `call the upgrade tool \u2014 free account, your history migrates`;
|
|
982
|
-
const noun = feature === "anchor" ? "Bitcoin-anchored proofs" : "server-signed verdicts";
|
|
983
998
|
if (nudgeOnce("hosted_wall")) {
|
|
984
999
|
return `You've used this install's free allowance for ${noun} \u2014 the proofs anyone can re-verify at fidacy.com/verify, court-grade, not a local-only check. Keep them flowing: for your operator, ${cta}.`;
|
|
985
1000
|
}
|
|
@@ -999,9 +1014,6 @@ function weekOneBootNudge(upgradeToolName) {
|
|
|
999
1014
|
|
|
1000
1015
|
// src/activation.ts
|
|
1001
1016
|
var FREE_DECISIONS = 20;
|
|
1002
|
-
function hasEngineKey(keyOverride) {
|
|
1003
|
-
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
1004
|
-
}
|
|
1005
1017
|
function decisionsUsed() {
|
|
1006
1018
|
return Math.max(readConfig()?.decisions_count ?? 0, sessionDecisionCount());
|
|
1007
1019
|
}
|
|
@@ -1174,8 +1186,8 @@ function rows(s) {
|
|
|
1174
1186
|
return out;
|
|
1175
1187
|
}
|
|
1176
1188
|
function renderBanner(s) {
|
|
1177
|
-
const title = "FIDACY
|
|
1178
|
-
const sub = `signs the YES and the NO \xB7 v${s.version}`;
|
|
1189
|
+
const title = "FIDACY AI AGENT FIREWALL";
|
|
1190
|
+
const sub = `blocks the payment, signs the YES and the NO \xB7 v${s.version}`;
|
|
1179
1191
|
const body = rows(s);
|
|
1180
1192
|
if (!fancy()) {
|
|
1181
1193
|
const lines2 = [
|
|
@@ -1300,10 +1312,14 @@ function renderAlertLine(alerts) {
|
|
|
1300
1312
|
var state = ensureState();
|
|
1301
1313
|
var core = makeCore();
|
|
1302
1314
|
var subject = process.env.FIDACY_SUBJECT ?? "agent:demo";
|
|
1303
|
-
var SERVER_VERSION = true ? "0.3.
|
|
1315
|
+
var SERVER_VERSION = true ? "0.3.4" : "dev";
|
|
1304
1316
|
var bootClaim = claimUrl();
|
|
1305
1317
|
var server = new McpServer(
|
|
1306
|
-
|
|
1318
|
+
// The commercial identity. `name` is the stable slug hosts key on; `title` is
|
|
1319
|
+
// what a human reads in the client's server list. The npm package stays
|
|
1320
|
+
// @fidacy/mcp — renaming the package would split the install base and the
|
|
1321
|
+
// download history for nothing, since what people SEE is this.
|
|
1322
|
+
{ name: "fidacy-ai-agent-firewall", title: "Fidacy AI Agent Firewall", version: SERVER_VERSION },
|
|
1307
1323
|
{
|
|
1308
1324
|
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).`
|
|
1309
1325
|
}
|
|
@@ -1528,7 +1544,7 @@ server.registerTool(
|
|
|
1528
1544
|
} catch (e) {
|
|
1529
1545
|
if (e instanceof AssessError) {
|
|
1530
1546
|
if (e.status === 402) {
|
|
1531
|
-
return { content: [{ type: "text", text: hostedWallCta("verdict") }], isError: true };
|
|
1547
|
+
return { content: [{ type: "text", text: hostedWallCta("verdict", e.billing) }], isError: true };
|
|
1532
1548
|
}
|
|
1533
1549
|
const reasons = e.rejection_reasons?.length ? " (" + e.rejection_reasons.map((x) => x.key).join(",") + ")" : "";
|
|
1534
1550
|
return {
|
|
@@ -1593,7 +1609,7 @@ server.registerTool(
|
|
|
1593
1609
|
} catch (e) {
|
|
1594
1610
|
if (e instanceof AssessError) {
|
|
1595
1611
|
if (e.status === 402) {
|
|
1596
|
-
return { content: [{ type: "text", text: hostedWallCta("anchor") }], isError: true };
|
|
1612
|
+
return { content: [{ type: "text", text: hostedWallCta("anchor", e.billing) }], isError: true };
|
|
1597
1613
|
}
|
|
1598
1614
|
return { content: [{ type: "text", text: `ANCHOR ${e.status}: ${e.type}` }], isError: true };
|
|
1599
1615
|
}
|
package/dist/lib.js
CHANGED
|
@@ -555,6 +555,9 @@ import {
|
|
|
555
555
|
readFileSync,
|
|
556
556
|
writeFileSync
|
|
557
557
|
} from "node:fs";
|
|
558
|
+
function hasEngineKey(keyOverride) {
|
|
559
|
+
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
560
|
+
}
|
|
558
561
|
function configDir() {
|
|
559
562
|
return process.env.FIDACY_CONFIG_DIR ?? join(homedir(), ".fidacy");
|
|
560
563
|
}
|
|
@@ -637,7 +640,7 @@ function resolveMandateRules(cfg) {
|
|
|
637
640
|
}
|
|
638
641
|
|
|
639
642
|
// src/telemetry.ts
|
|
640
|
-
var CLIENT_VERSION = true ? "0.3.
|
|
643
|
+
var CLIENT_VERSION = true ? "0.3.4" : "dev";
|
|
641
644
|
function bandOf(amount) {
|
|
642
645
|
if (typeof amount !== "number" || !Number.isFinite(amount) || amount <= 0) return void 0;
|
|
643
646
|
if (amount < 10) return "lt10";
|
|
@@ -825,7 +828,7 @@ function requestUpgrade() {
|
|
|
825
828
|
}
|
|
826
829
|
|
|
827
830
|
// src/provision.ts
|
|
828
|
-
var CLIENT_VERSION2 = true ? "0.3.
|
|
831
|
+
var CLIENT_VERSION2 = true ? "0.3.4" : "dev";
|
|
829
832
|
function provisionEnabled() {
|
|
830
833
|
const v = (process.env.FIDACY_DISABLE_PROVISION ?? "").trim().toLowerCase();
|
|
831
834
|
return !(v === "1" || v === "true" || v === "yes");
|
|
@@ -934,9 +937,6 @@ function weekOneBootNudge(upgradeToolName) {
|
|
|
934
937
|
|
|
935
938
|
// src/activation.ts
|
|
936
939
|
var FREE_DECISIONS = 20;
|
|
937
|
-
function hasEngineKey(keyOverride) {
|
|
938
|
-
return Boolean((keyOverride ?? process.env.FIDACY_ENGINE_API_KEY ?? "").trim());
|
|
939
|
-
}
|
|
940
940
|
function decisionsUsed() {
|
|
941
941
|
return Math.max(readConfig()?.decisions_count ?? 0, sessionDecisionCount());
|
|
942
942
|
}
|
package/dist/nudges.d.ts
CHANGED
|
@@ -43,7 +43,11 @@ export declare function decisionNudge(status: "ALLOW" | "DENY", violatedRule: st
|
|
|
43
43
|
* (nudge doctrine: never nag twice), a terse line every time after — the wall is
|
|
44
44
|
* a direct answer to the agent's request, so it always says SOMETHING actionable.
|
|
45
45
|
*/
|
|
46
|
-
export declare function hostedWallCta(feature: "verdict" | "anchor"
|
|
46
|
+
export declare function hostedWallCta(feature: "verdict" | "anchor", billing?: {
|
|
47
|
+
reason?: string;
|
|
48
|
+
detail?: string;
|
|
49
|
+
billingUrl?: string;
|
|
50
|
+
}): string;
|
|
47
51
|
/** The no-key CTA for a hosted tool: route the human to a free account. */
|
|
48
52
|
export declare function noKeyCta(): string;
|
|
49
53
|
/** One-time boot line after a week of real protection; null otherwise. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fidacy/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
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
|
"author": "Fidacy (ZeepCode Group Technology LLC) <hello@fidacy.com> (https://fidacy.com)",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"vitest": "^3.2.4",
|
|
65
65
|
"@fidacy/firewall": "0.1.1"
|
|
66
66
|
},
|
|
67
|
-
"mcpName": "com.fidacy/
|
|
67
|
+
"mcpName": "com.fidacy/ai-agent-firewall",
|
|
68
68
|
"repository": {
|
|
69
69
|
"type": "git",
|
|
70
70
|
"url": "git+https://github.com/lucaslubi/fidacy-mcp.git"
|