@mrfentmen/defillama-mcp 1.0.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/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # defillama-mcp
2
+
3
+ DefiLlama protocols, chains, and TVL (merged).
4
+
5
+ A merged MCP server that consolidates duplicate single-purpose servers in this monorepo into one focused server.
6
+
7
+ ## Tools
8
+
9
+ - `chains` — TVL by chain.
10
+ - `protocol` — TVL history for a protocol.
11
+ - `top_protocols` — Get the top DeFi protocols by TVL.
12
+ - `chain_tvl` — Get TVL for all chains.
13
+ - `protocol_info` — Get details for a specific protocol.
14
+
15
+ ## Run
16
+
17
+ ```bash
18
+ npm install
19
+ npm run build
20
+ node dist/index.js
21
+ ```
22
+
23
+ ## Source
24
+
25
+ Public free APIs only. See `src/api.ts` for exact endpoints.
package/dist/api.js ADDED
@@ -0,0 +1,110 @@
1
+ const m0 = (() => {
2
+ const BASE = 'https://api.llama.fi';
3
+ async function chains(_args) {
4
+ const res = await fetch(`${BASE}/v2/chains`, {
5
+ headers: { 'User-Agent': 'mrfentmen-defillama-mcp/1.0', Accept: 'application/json' },
6
+ signal: AbortSignal.timeout(20000),
7
+ });
8
+ if (!res.ok)
9
+ throw new Error(`DefiLlama returned ${res.status}`);
10
+ const d = (await res.json());
11
+ if (!d.length)
12
+ return 'No chain data returned.';
13
+ const fmt = (v) => {
14
+ const n = Number(v);
15
+ if (!n)
16
+ return '$0';
17
+ if (n >= 1e9)
18
+ return `$${(n / 1e9).toFixed(2)}B`;
19
+ if (n >= 1e6)
20
+ return `$${(n / 1e6).toFixed(1)}M`;
21
+ return `$${n.toFixed(0)}`;
22
+ };
23
+ return `DeFi TVL by chain (${d.length} chains):\n` +
24
+ d.slice(0, 20).map((c, i) => {
25
+ const s = (k) => (c[k] != null ? String(c[k]) : '');
26
+ return `${i + 1}. ${s('name')} | ${fmt(c.tvl)}`;
27
+ }).join('\n');
28
+ }
29
+ async function protocol(args) {
30
+ const slug = (args.slug ?? '').trim();
31
+ if (!slug)
32
+ return 'Provide a protocol slug.';
33
+ const res = await fetch(`${BASE}/protocol/${encodeURIComponent(slug)}`, {
34
+ headers: { 'User-Agent': 'mrfentmen-defillama-mcp/1.0', Accept: 'application/json' },
35
+ signal: AbortSignal.timeout(20000),
36
+ });
37
+ if (!res.ok)
38
+ throw new Error(`DefiLlama returned ${res.status}`);
39
+ const d = (await res.json());
40
+ const s = (k) => (d[k] != null ? String(d[k]) : '');
41
+ const tvl = d.tvl ?? [];
42
+ const latest = Array.isArray(tvl) && tvl.length ? tvl.at(-1) : null;
43
+ const chainTvls = (d.currentChainTvls ?? {});
44
+ return [
45
+ `Protocol: ${s('name')}`,
46
+ s('url') ? `Site: ${s('url')}` : '',
47
+ latest ? `Latest TVL: $${Number(latest.tvl ?? 0).toLocaleString()}` : '',
48
+ Object.keys(chainTvls).length ? `Chains: ${Object.entries(chainTvls).slice(0, 8).map(([k, v]) => `${k} $${Math.round(Number(v)).toLocaleString()}`).join(', ')}` : '',
49
+ ].filter(Boolean).join('\n');
50
+ }
51
+ return { chains, protocol };
52
+ })();
53
+ const m1 = (() => {
54
+ const BASE = "https://api.llama.fi";
55
+ const UA = "mrfentmen-defi-tvl-mcp/1.0 (https://github.com/mrfentmen)";
56
+ class DefiError extends Error {
57
+ }
58
+ async function get(url) {
59
+ const res = await fetch(url, { headers: { "User-Agent": UA, Accept: "application/json" }, signal: AbortSignal.timeout(25000) });
60
+ if (res.status === 429)
61
+ throw new DefiError("DefiLlama rate limit hit, wait and retry");
62
+ if (!res.ok)
63
+ throw new DefiError(`DefiLlama error ${res.status}`);
64
+ return (await res.json());
65
+ }
66
+ function fmtUsd(v) {
67
+ if (v === undefined || v === null)
68
+ return "n/a";
69
+ return "$" + v.toLocaleString(undefined, { maximumFractionDigits: 0 });
70
+ }
71
+ async function topProtocols(args) {
72
+ const limit = Math.min(args.limit ?? 10, 50);
73
+ const d = await get(`${BASE}/protocols`);
74
+ const rows = d
75
+ .filter((p) => !p?.misrepresentedTokens)
76
+ .sort((a, b) => (b?.tvl ?? 0) - (a?.tvl ?? 0))
77
+ .slice(0, limit);
78
+ if (!rows.length)
79
+ return "No protocols returned";
80
+ return rows.map((p, i) => `${i + 1}. ${p?.name ?? "n/a"} | ${fmtUsd(p?.tvl)} | chains: ${(p?.chains ?? []).slice(0, 4).join(", ") || "n/a"}`).join("\n");
81
+ }
82
+ async function chainTvl(args) {
83
+ const limit = Math.min(args.limit ?? 10, 50);
84
+ const d = await get(`${BASE}/v2/chains`);
85
+ const rows = [...d].sort((a, b) => (b?.tvl ?? 0) - (a?.tvl ?? 0)).slice(0, limit);
86
+ if (!rows.length)
87
+ return "No chains returned";
88
+ return rows.map((c, i) => `${i + 1}. ${c?.name ?? "n/a"} | ${fmtUsd(c?.tvl)} | ${c?.tokenSymbol ?? ""}`).join("\n");
89
+ }
90
+ async function protocolInfo(args) {
91
+ const slug = (args.protocol ?? "").trim().toLowerCase().replace(/ /g, "-");
92
+ if (!slug)
93
+ throw new DefiError("Provide a protocol slug");
94
+ const d = await get(`${BASE}/protocol/${encodeURIComponent(slug)}`);
95
+ return `Protocol: ${d?.name ?? slug}\nCurrent TVL: ${fmtUsd(d?.tvl)}\nChains: ${(d?.chains ?? []).join(", ") || "n/a"}\nCategory: ${d?.category ?? "n/a"}\nDescription: ${d?.description ?? "n/a"}\nWebsite: ${d?.url ?? "n/a"}`;
96
+ }
97
+ return { DefiError, chainTvl, protocolInfo, topProtocols };
98
+ })();
99
+ export const DefiError = m1.DefiError;
100
+ export const chainTvl = m1.chainTvl;
101
+ export const chains = m0.chains;
102
+ export const protocol = m0.protocol;
103
+ export const protocolInfo = m1.protocolInfo;
104
+ export const topProtocols = m1.topProtocols;
105
+ export const m0_protocol = m0.protocol;
106
+ export const m0_chains = m0.chains;
107
+ export const m1_topProtocols = m1.topProtocols;
108
+ export const m1_chainTvl = m1.chainTvl;
109
+ export const m1_protocolInfo = m1.protocolInfo;
110
+ export const m1_DefiError = m1.DefiError;
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2
+ import { createServer } from "./server.js";
3
+ const main = async () => { const server = createServer(); await server.connect(new StdioServerTransport()); };
4
+ main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });
package/dist/server.js ADDED
@@ -0,0 +1,77 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { m0_chains, m0_protocol, m1_chainTvl, m1_protocolInfo, m1_topProtocols } from './api.js';
4
+ const text = (value) => ({ content: [{ type: 'text', text: value }] });
5
+ const textError = (t) => ({ content: [{ type: "text", text: t }], isError: true });
6
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
7
+ const error = (e) => `Error: ${e instanceof Error ? e.message : String(e)}`;
8
+ const errorMessage = error;
9
+ export function createServer() {
10
+ const server = new McpServer({ name: 'defillama-mcp', version: '1.0.0' });
11
+ server.registerTool("chains", {
12
+ title: "Chains",
13
+ description: "TVL by chain.",
14
+ inputSchema: z.object({}),
15
+ annotations: READ_ONLY,
16
+ }, async (args) => {
17
+ try {
18
+ return text(await m0_chains(args));
19
+ }
20
+ catch (e) {
21
+ return textError(error(e));
22
+ }
23
+ });
24
+ server.registerTool("protocol", {
25
+ title: "Protocol",
26
+ description: "TVL history for a protocol.",
27
+ inputSchema: z.object({ slug: z.string().describe("Protocol slug.") }),
28
+ annotations: READ_ONLY,
29
+ }, async (args) => {
30
+ try {
31
+ return text(await m0_protocol(args));
32
+ }
33
+ catch (e) {
34
+ return textError(error(e));
35
+ }
36
+ });
37
+ server.registerTool("top_protocols", {
38
+ title: "Top protocols",
39
+ description: "Get the top DeFi protocols by TVL.",
40
+ inputSchema: z.object({ limit: z.number().describe("Max results.").optional() }),
41
+ annotations: READ_ONLY,
42
+ }, async (args) => {
43
+ try {
44
+ return text(await m1_topProtocols(args));
45
+ }
46
+ catch (e) {
47
+ return textError(error(e));
48
+ }
49
+ });
50
+ server.registerTool("chain_tvl", {
51
+ title: "Chain tvl",
52
+ description: "Get TVL for all chains.",
53
+ inputSchema: z.object({ limit: z.number().describe("Max results.").optional() }),
54
+ annotations: READ_ONLY,
55
+ }, async (args) => {
56
+ try {
57
+ return text(await m1_chainTvl(args));
58
+ }
59
+ catch (e) {
60
+ return textError(error(e));
61
+ }
62
+ });
63
+ server.registerTool("protocol_info", {
64
+ title: "Protocol info",
65
+ description: "Get details for a specific protocol.",
66
+ inputSchema: z.object({ protocol: z.string().describe("Protocol slug.") }),
67
+ annotations: READ_ONLY,
68
+ }, async (args) => {
69
+ try {
70
+ return text(await m1_protocolInfo(args));
71
+ }
72
+ catch (e) {
73
+ return textError(error(e));
74
+ }
75
+ });
76
+ return server;
77
+ }
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@mrfentmen/defillama-mcp",
3
+ "version": "1.0.0",
4
+ "description": "DefiLlama protocols, chains, and TVL (merged).",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build": "tsc -p tsconfig.json",
9
+ "start": "node dist/index.js"
10
+ },
11
+ "dependencies": {
12
+ "@modelcontextprotocol/sdk": "^1.0.0",
13
+ "zod": "^3.23.0"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.5.0",
17
+ "@types/node": "^20.0.0"
18
+ },
19
+ "license": "MIT",
20
+ "engines": {
21
+ "node": ">=20"
22
+ },
23
+ "mcpName": "io.github.mrfentmen/defillama-mcp"
24
+ }
package/server.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
+ "name": "io.github.mrfentmen/defillama-mcp",
4
+ "description": "DefiLlama protocols, chains, and TVL (merged).",
5
+ "repository": {
6
+ "url": "https://github.com/mrfentmen/awesome-mcps",
7
+ "source": "github"
8
+ },
9
+ "version": "1.0.0",
10
+ "packages": [
11
+ {
12
+ "registryType": "npm",
13
+ "identifier": "@mrfentmen/defillama-mcp",
14
+ "version": "1.0.0",
15
+ "transport": {
16
+ "type": "stdio"
17
+ }
18
+ }
19
+ ]
20
+ }
package/src/api.ts ADDED
@@ -0,0 +1,107 @@
1
+ const m0 = (() => {
2
+ const BASE = 'https://api.llama.fi';
3
+
4
+ async function chains(_args?: unknown): Promise<string> {
5
+ const res = await fetch(`${BASE}/v2/chains`, {
6
+ headers: { 'User-Agent': 'mrfentmen-defillama-mcp/1.0', Accept: 'application/json' },
7
+ signal: AbortSignal.timeout(20000),
8
+ });
9
+ if (!res.ok) throw new Error(`DefiLlama returned ${res.status}`);
10
+ const d = (await res.json()) as Array<Record<string, unknown>>;
11
+ if (!d.length) return 'No chain data returned.';
12
+ const fmt = (v: unknown) => {
13
+ const n = Number(v);
14
+ if (!n) return '$0';
15
+ if (n >= 1e9) return `$${(n / 1e9).toFixed(2)}B`;
16
+ if (n >= 1e6) return `$${(n / 1e6).toFixed(1)}M`;
17
+ return `$${n.toFixed(0)}`;
18
+ };
19
+ return `DeFi TVL by chain (${d.length} chains):\n` +
20
+ d.slice(0, 20).map((c, i) => {
21
+ const s = (k: string) => (c[k] != null ? String(c[k]) : '');
22
+ return `${i + 1}. ${s('name')} | ${fmt(c.tvl)}`;
23
+ }).join('\n');
24
+ }
25
+
26
+ async function protocol(args: { slug: string }): Promise<string> {
27
+ const slug = (args.slug ?? '').trim();
28
+ if (!slug) return 'Provide a protocol slug.';
29
+ const res = await fetch(`${BASE}/protocol/${encodeURIComponent(slug)}`, {
30
+ headers: { 'User-Agent': 'mrfentmen-defillama-mcp/1.0', Accept: 'application/json' },
31
+ signal: AbortSignal.timeout(20000),
32
+ });
33
+ if (!res.ok) throw new Error(`DefiLlama returned ${res.status}`);
34
+ const d = (await res.json()) as Record<string, unknown>;
35
+ const s = (k: string) => (d[k] != null ? String(d[k]) : '');
36
+ const tvl = d.tvl ?? [];
37
+ const latest = Array.isArray(tvl) && tvl.length ? (tvl as Array<Record<string, unknown>>).at(-1) : null;
38
+ const chainTvls = (d.currentChainTvls ?? {}) as Record<string, unknown>;
39
+ return [
40
+ `Protocol: ${s('name')}`,
41
+ s('url') ? `Site: ${s('url')}` : '',
42
+ latest ? `Latest TVL: $${Number(latest.tvl ?? 0).toLocaleString()}` : '',
43
+ Object.keys(chainTvls).length ? `Chains: ${Object.entries(chainTvls).slice(0, 8).map(([k, v]) => `${k} $${Math.round(Number(v)).toLocaleString()}`).join(', ')}` : '',
44
+ ].filter(Boolean).join('\n');
45
+ }
46
+
47
+ return { chains, protocol };
48
+ })();
49
+
50
+ const m1 = (() => {
51
+ const BASE = "https://api.llama.fi"
52
+ const UA = "mrfentmen-defi-tvl-mcp/1.0 (https://github.com/mrfentmen)"
53
+ class DefiError extends Error {}
54
+
55
+ async function get<T>(url: string): Promise<T> {
56
+ const res = await fetch(url, { headers: { "User-Agent": UA, Accept: "application/json" }, signal: AbortSignal.timeout(25000) })
57
+ if (res.status === 429) throw new DefiError("DefiLlama rate limit hit, wait and retry")
58
+ if (!res.ok) throw new DefiError(`DefiLlama error ${res.status}`)
59
+ return (await res.json()) as T
60
+ }
61
+
62
+ function fmtUsd(v: number | undefined): string {
63
+ if (v === undefined || v === null) return "n/a"
64
+ return "$" + v.toLocaleString(undefined, { maximumFractionDigits: 0 })
65
+ }
66
+
67
+ async function topProtocols(args: { limit?: number }): Promise<string> {
68
+ const limit = Math.min(args.limit ?? 10, 50)
69
+ const d = await get<any[]>(`${BASE}/protocols`)
70
+ const rows = d
71
+ .filter((p: any) => !p?.misrepresentedTokens)
72
+ .sort((a: any, b: any) => (b?.tvl ?? 0) - (a?.tvl ?? 0))
73
+ .slice(0, limit)
74
+ if (!rows.length) return "No protocols returned"
75
+ return rows.map((p: any, i: number) => `${i + 1}. ${p?.name ?? "n/a"} | ${fmtUsd(p?.tvl)} | chains: ${(p?.chains ?? []).slice(0, 4).join(", ") || "n/a"}`).join("\n")
76
+ }
77
+
78
+ async function chainTvl(args: { limit?: number }): Promise<string> {
79
+ const limit = Math.min(args.limit ?? 10, 50)
80
+ const d = await get<any[]>(`${BASE}/v2/chains`)
81
+ const rows = [...d].sort((a: any, b: any) => (b?.tvl ?? 0) - (a?.tvl ?? 0)).slice(0, limit)
82
+ if (!rows.length) return "No chains returned"
83
+ return rows.map((c: any, i: number) => `${i + 1}. ${c?.name ?? "n/a"} | ${fmtUsd(c?.tvl)} | ${c?.tokenSymbol ?? ""}`).join("\n")
84
+ }
85
+
86
+ async function protocolInfo(args: { protocol?: string }): Promise<string> {
87
+ const slug = (args.protocol ?? "").trim().toLowerCase().replace(/ /g, "-")
88
+ if (!slug) throw new DefiError("Provide a protocol slug")
89
+ const d = await get<any>(`${BASE}/protocol/${encodeURIComponent(slug)}`)
90
+ return `Protocol: ${d?.name ?? slug}\nCurrent TVL: ${fmtUsd(d?.tvl)}\nChains: ${(d?.chains ?? []).join(", ") || "n/a"}\nCategory: ${d?.category ?? "n/a"}\nDescription: ${d?.description ?? "n/a"}\nWebsite: ${d?.url ?? "n/a"}`
91
+ }
92
+
93
+ return { DefiError, chainTvl, protocolInfo, topProtocols };
94
+ })();
95
+
96
+ export const DefiError = m1.DefiError;
97
+ export const chainTvl = m1.chainTvl;
98
+ export const chains = m0.chains;
99
+ export const protocol = m0.protocol;
100
+ export const protocolInfo = m1.protocolInfo;
101
+ export const topProtocols = m1.topProtocols;
102
+ export const m0_protocol = m0.protocol;
103
+ export const m0_chains = m0.chains;
104
+ export const m1_topProtocols = m1.topProtocols;
105
+ export const m1_chainTvl = m1.chainTvl;
106
+ export const m1_protocolInfo = m1.protocolInfo;
107
+ export const m1_DefiError = m1.DefiError;
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
2
+ import { createServer } from "./server.js"
3
+ const main = async () => { const server = createServer(); await server.connect(new StdioServerTransport()) }
4
+ main().catch((error) => { console.error("Fatal error:", error); process.exit(1) })
package/src/server.ts ADDED
@@ -0,0 +1,74 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import { z } from 'zod'
3
+ import { m0_chains, m0_protocol, m1_chainTvl, m1_protocolInfo, m1_topProtocols } from './api.js'
4
+
5
+ const text = (value: string) => ({ content: [{ type: 'text' as const, text: value }] })
6
+ const textError = (t: string) => ({ content: [{ type: "text" as const, text: t }], isError: true as const })
7
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true } as const
8
+ const error = (e: unknown) => `Error: ${e instanceof Error ? e.message : String(e)}`
9
+ const errorMessage = error
10
+
11
+ export function createServer(): McpServer {
12
+ const server = new McpServer({ name: 'defillama-mcp', version: '1.0.0' })
13
+ server.registerTool(
14
+ "chains",
15
+ {
16
+ title: "Chains",
17
+ description: "TVL by chain.",
18
+ inputSchema: z.object( { }),
19
+ annotations: READ_ONLY,
20
+ },
21
+ async (args) => {
22
+ try { return text(await m0_chains(args)) } catch (e) { return textError(error(e)) }
23
+ }
24
+ )
25
+ server.registerTool(
26
+ "protocol",
27
+ {
28
+ title: "Protocol",
29
+ description: "TVL history for a protocol.",
30
+ inputSchema: z.object( { slug: z.string().describe("Protocol slug.") }),
31
+ annotations: READ_ONLY,
32
+ },
33
+ async (args) => {
34
+ try { return text(await m0_protocol(args)) } catch (e) { return textError(error(e)) }
35
+ }
36
+ )
37
+ server.registerTool(
38
+ "top_protocols",
39
+ {
40
+ title: "Top protocols",
41
+ description: "Get the top DeFi protocols by TVL.",
42
+ inputSchema: z.object( { limit: z.number().describe("Max results.").optional() }),
43
+ annotations: READ_ONLY,
44
+ },
45
+ async (args) => {
46
+ try { return text(await m1_topProtocols(args)) } catch (e) { return textError(error(e)) }
47
+ }
48
+ )
49
+ server.registerTool(
50
+ "chain_tvl",
51
+ {
52
+ title: "Chain tvl",
53
+ description: "Get TVL for all chains.",
54
+ inputSchema: z.object( { limit: z.number().describe("Max results.").optional() }),
55
+ annotations: READ_ONLY,
56
+ },
57
+ async (args) => {
58
+ try { return text(await m1_chainTvl(args)) } catch (e) { return textError(error(e)) }
59
+ }
60
+ )
61
+ server.registerTool(
62
+ "protocol_info",
63
+ {
64
+ title: "Protocol info",
65
+ description: "Get details for a specific protocol.",
66
+ inputSchema: z.object( { protocol: z.string().describe("Protocol slug.") }),
67
+ annotations: READ_ONLY,
68
+ },
69
+ async (args) => {
70
+ try { return text(await m1_protocolInfo(args)) } catch (e) { return textError(error(e)) }
71
+ }
72
+ )
73
+ return server
74
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true
12
+ },
13
+ "include": [
14
+ "src/**/*"
15
+ ]
16
+ }