@supernova123/defillama-mcp-server 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.
@@ -0,0 +1,8 @@
1
+ # These funding sources support Nova MCP Servers
2
+
3
+ github: [nova]
4
+ # ko_fi: # Replace with a single Ko-fi username
5
+ # patreon: # Replace with a single Patreon username
6
+ # open_collective: # Replace with a single Open Collective username
7
+ # community_bridge: # Replace with a single Community Bridge project name
8
+ # custom: # Replace with up to 4 custom sponsorship URLs e.g., ["link1", "link2"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nova
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,85 @@
1
+ # Marketplace Listings — DeFi Llama MCP Server
2
+
3
+ ## Listing 1: mcp.so
4
+
5
+ **Title:** DeFi Llama MCP Server
6
+
7
+ **Category:** Finance & Crypto → DeFi
8
+
9
+ **Description:**
10
+ MCP server for DeFi Llama — the free DeFi data API powering https://defillama.com. Query protocol TVL, chain TVL, yield pools, stablecoins, bridges, DEX volumes, and protocol fees through natural language. Works with Claude Desktop, Cursor, Windsurf, and any MCP-compatible client.
11
+
12
+ Features:
13
+ - Search DeFi protocols by name with TVL, category, and chain info
14
+ - Get detailed TVL breakdown for a specific protocol (chain distribution, history, description)
15
+ - Get total TVL for any chain (Ethereum, Arbitrum, Base, Solana, etc.)
16
+ - Filter yield pools by chain, project, min TVL, and min APY
17
+ - Get stablecoin market caps and rankings
18
+ - Get cross-chain bridge TVL and volume data
19
+ - Get DEX trading volumes across chains
20
+ - Get protocol fee and revenue data
21
+ - No API key required (DeFi Llama is fully free)
22
+ - Automatic rate limiting, clean markdown output
23
+
24
+ **Tags:** defillama, defi, tvl, yields, stablecoins, bridges, dex, fees, mcp, finance, ethereum, bitcoin, lido, aave, uniswap
25
+
26
+ **Installation:**
27
+ ```bash
28
+ npx -y defillama-mcp-server
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Listing 2: Smithery (smithery.ai)
34
+
35
+ **Title:** DeFi Llama
36
+
37
+ **Description:**
38
+ Connect AI assistants to DeFi Llama's free DeFi data API. Query protocol TVL, chain TVL, yield/APY pools, stablecoin market caps, cross-chain bridges, DEX volumes, and protocol fees through the Model Context Protocol. No API key required.
39
+
40
+ **Tools:** 8 (search_protocols, get_protocol_tvl, get_tvl_by_chain, get_yields, get_stablecoins, get_bridges, get_dex_volumes, get_protocol_fees)
41
+
42
+ **Transport:** stdio
43
+
44
+ **Config:**
45
+ ```json
46
+ {
47
+ "command": "npx",
48
+ "args": ["-y", "defillama-mcp-server"]
49
+ }
50
+ ```
51
+
52
+ ---
53
+
54
+ ## Listing 3: Glama MCP
55
+
56
+ **Title:** DeFi Llama MCP Server
57
+
58
+ **Description:**
59
+ Access DeFi Llama's free DeFi data through MCP. Search protocols, get TVL breakdowns for protocols and chains, filter yield pools, view stablecoin rankings, explore cross-chain bridge TVL, compare DEX volumes, and track protocol fees. Works with Claude Desktop, Cursor, and other MCP clients. No API key needed.
60
+
61
+ **Category:** Finance
62
+
63
+ **Tools:** 8
64
+
65
+ **Tags:** defi, defillama, tvl, yields, stablecoins, bridges, dex, fees, ethereum
66
+
67
+ ---
68
+
69
+ ## Listing 4: There's An AI For That (TAAIFT)
70
+
71
+ **Title:** DeFi Llama MCP Server
72
+
73
+ **Category:** Developer Tools → AI Development → MCP Servers
74
+
75
+ **Description:**
76
+ MCP server that connects AI assistants to DeFi Llama's free DeFi data API. Ask your AI about protocol TVL, chain TVL, yield pools, stablecoins, bridges, DEX volumes, and protocol fees — no API key required. 8 tools covering the most common DeFi queries.
77
+
78
+ **Price:** Free (open source, MIT)
79
+
80
+ ---
81
+
82
+ ## Listing 5: Awesome MCP Servers (GitHub)
83
+
84
+ **PR Description:**
85
+ Add DeFi Llama MCP Server — free DeFi data for AI assistants. 8 tools: search protocols, get protocol/chain TVL, filter yield pools, stablecoin rankings, bridge TVL, DEX volumes, protocol fees. No API key required. TypeScript, MIT license, no dependencies beyond MCP SDK + zod.
package/README.md ADDED
@@ -0,0 +1,157 @@
1
+ # DeFi Llama MCP Server
2
+
3
+ > An MCP server for [DeFi Llama](https://defillama.com) — connect any MCP-compatible client to free DeFi protocol data.
4
+
5
+ [![MCP Compatible](https://img.shields.io/badge/MCP-compatible-blueviolet)](https://modelcontextprotocol.io)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.5-blue)](https://www.typescriptlang.org/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
8
+
9
+ ## What is this?
10
+
11
+ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that gives AI assistants and agents access to DeFi Llama's free DeFi data API — protocol TVL, chain TVL, yield pools, stablecoins, cross-chain bridges, DEX volumes, and protocol fees — through natural language.
12
+
13
+ Use it with **Claude Desktop**, **Cursor**, **Windsurf**, **Cline**, **Continue**, or any MCP-compatible client to ask questions about DeFi protocols, track TVL movements, compare yields, and explore the on-chain economy.
14
+
15
+ ## Why use this?
16
+
17
+ - **No API key required** — DeFi Llama is a free public API
18
+ - **8 built-in tools** — covers the most common DeFi data queries
19
+ - **Clean markdown output** — results read naturally in chat
20
+ - **Rate-limited automatically** — polite 500ms throttle across all endpoints
21
+
22
+ ## Tools
23
+
24
+ | Tool | Description |
25
+ |------|-------------|
26
+ | `search_protocols` | Search DeFi protocols by name — returns top results with TVL, chains, category |
27
+ | `get_protocol_tvl` | Get detailed TVL breakdown for a specific protocol (chain distribution, history, description) |
28
+ | `get_tvl_by_chain` | Get total TVL for a specific chain (Ethereum, Arbitrum, Base, Solana, etc.) |
29
+ | `get_yields` | Get yield/APY data for lending pools and staking, filter by chain / project / min TVL |
30
+ | `get_stablecoins` | Get stablecoin market cap data and rankings |
31
+ | `get_bridges` | Get cross-chain bridge TVL and volume data |
32
+ | `get_dex_volumes` | Get DEX trading volumes across chains |
33
+ | `get_protocol_fees` | Get protocol fee and revenue data |
34
+
35
+ ## Quick Start
36
+
37
+ ### 1. Install
38
+
39
+ ```bash
40
+ npm install -g defillama-mcp-server
41
+ ```
42
+
43
+ Or run directly with npx:
44
+
45
+ ```bash
46
+ npx -y defillama-mcp-server
47
+ ```
48
+
49
+ ### 2. Configure your MCP client
50
+
51
+ Add to your MCP client config (e.g. `claude_desktop_config.json`):
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "defillama": {
57
+ "command": "npx",
58
+ "args": ["-y", "defillama-mcp-server"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Or with global install:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "defillama": {
70
+ "command": "defillama-mcp-server"
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### 3. Use it
77
+
78
+ Ask your AI assistant things like:
79
+
80
+ - "What are the top DeFi protocols by TVL on Ethereum?"
81
+ - "Search for Aave and show me the chain breakdown"
82
+ - "What's the total TVL on Arbitrum?"
83
+ - "Show me the highest yield stablecoin pools on Base with at least $10M TVL"
84
+ - "List the top 5 stablecoins by market cap"
85
+ - "Which bridges have the most TVL?"
86
+ - "Show me DEX trading volumes for the last 24h"
87
+ - "What are the protocols with the most fees?"
88
+
89
+ ## Example Output
90
+
91
+ ### `search_protocols`
92
+
93
+ ```
94
+ Top 5 protocols matching "aave" (by TVL):
95
+
96
+ - **Aave** (AAVE) — TVL: $12.45B | Category: Lending | Chains: Ethereum, Arbitrum, Polygon, Base, Optimism, +6 | Slug: `aave`
97
+ - **Aave v2** (AAVE) — TVL: $4.20B | Category: Lending | Chains: Ethereum, Polygon, Avalanche | Slug: `aave-v2`
98
+ - **Aave v3** (AAVE) — TVL: $8.10B | Category: Lending | Chains: Ethereum, Arbitrum, Polygon, Base, Optimism | Slug: `aave-v3`
99
+ ```
100
+
101
+ ### `get_yields`
102
+
103
+ ```
104
+ Top 5 yield pools (chain: Ethereum | min TVL: $10.00M | min APY: 0%):
105
+
106
+ - **Lido** — stETH on Ethereum 🟢 | APY: 3.42% (3.42% base) | TVL: $23.45B
107
+ - **Aave v3** — USDC on Ethereum 🟢 | APY: 4.85% (1.50% base + 3.35% reward) | TVL: $1.85B
108
+ - **Compound v3** — USDC on Ethereum 🟢 | APY: 5.12% (3.20% base + 1.92% reward) | TVL: $890.45M
109
+ ```
110
+
111
+ ### `get_tvl_by_chain`
112
+
113
+ ```
114
+ Ethereum — Chain TVL
115
+
116
+ - **Total TVL:** $115.32B
117
+ - **Native Token:** ETH
118
+ - **CoinGecko ID:** ethereum
119
+
120
+ ### Top Tokens by TVL
121
+ - **ETH** (Ether): $67.23B
122
+ - **USDC** (USD Coin): $4.12B
123
+ - **USDT** (Tether): $3.45B
124
+ - **WBTC** (Wrapped BTC): $2.89B
125
+ - **WSTETH** (Wrapped stETH): $2.34B
126
+ ```
127
+
128
+ ## Requirements
129
+
130
+ - Node.js 18+
131
+ - No API key needed (DeFi Llama is a free public API)
132
+
133
+ ## Rate Limits
134
+
135
+ DeFi Llama doesn't publish hard rate limits, but the server automatically throttles requests to ~2 calls/second (500ms minimum interval) to be a polite citizen. The `/protocols` and `/pools` endpoints are large (multi-MB), so the throttle also helps avoid unnecessary load.
136
+
137
+ ## Data Sources
138
+
139
+ - **Protocols & TVL:** `https://api.llama.fi`
140
+ - **Yields:** `https://yields.llama.fi`
141
+ - **Stablecoins:** `https://stablecoins.llama.fi`
142
+
143
+ All endpoints are free and require no authentication. Full API documentation: https://defillama.com/docs/api
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ git clone https://github.com/nova/defillama-mcp-server.git
149
+ cd defillama-mcp-server
150
+ npm install
151
+ npm run build
152
+ npm start
153
+ ```
154
+
155
+ ## License
156
+
157
+ MIT
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DeFi Llama MCP Server
4
+ *
5
+ * Connect AI assistants to DeFi Llama's free DeFi data API.
6
+ * Query protocol TVL, chain TVL, yields, stablecoins, bridges,
7
+ * DEX volumes, and protocol fees through the Model Context Protocol.
8
+ *
9
+ * Works with Claude Desktop, Cursor, Windsurf, Cline, and any MCP client.
10
+ */
11
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,413 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DeFi Llama MCP Server
4
+ *
5
+ * Connect AI assistants to DeFi Llama's free DeFi data API.
6
+ * Query protocol TVL, chain TVL, yields, stablecoins, bridges,
7
+ * DEX volumes, and protocol fees through the Model Context Protocol.
8
+ *
9
+ * Works with Claude Desktop, Cursor, Windsurf, Cline, and any MCP client.
10
+ */
11
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
13
+ import { z } from "zod";
14
+ const LLAMA_BASE = "https://api.llama.fi";
15
+ const YIELDS_BASE = "https://yields.llama.fi";
16
+ const STABLECOINS_BASE = "https://stablecoins.llama.fi";
17
+ // Rate limiter: DeFi Llama doesn't publish hard limits, but be polite.
18
+ // ~500ms between calls is safe and well below any reasonable limit.
19
+ let lastCall = 0;
20
+ const MIN_INTERVAL = 500;
21
+ async function rateLimitedFetch(url) {
22
+ const now = Date.now();
23
+ const wait = MIN_INTERVAL - (now - lastCall);
24
+ if (wait > 0) {
25
+ await new Promise((r) => setTimeout(r, wait));
26
+ }
27
+ lastCall = Date.now();
28
+ const res = await fetch(url, {
29
+ headers: { Accept: "application/json" },
30
+ });
31
+ if (!res.ok) {
32
+ throw new Error(`DeFi Llama API error: ${res.status} ${res.statusText}`);
33
+ }
34
+ return res.json();
35
+ }
36
+ function formatTVL(value) {
37
+ if (value == null || isNaN(value))
38
+ return "N/A";
39
+ if (value >= 1e12)
40
+ return `$${(value / 1e12).toFixed(2)}T`;
41
+ if (value >= 1e9)
42
+ return `$${(value / 1e9).toFixed(2)}B`;
43
+ if (value >= 1e6)
44
+ return `$${(value / 1e6).toFixed(2)}M`;
45
+ if (value >= 1e3)
46
+ return `$${(value / 1e3).toFixed(2)}K`;
47
+ return `$${value.toFixed(2)}`;
48
+ }
49
+ function formatPct(value) {
50
+ if (value == null || isNaN(value))
51
+ return "N/A";
52
+ return `${value >= 0 ? "+" : ""}${value.toFixed(2)}%`;
53
+ }
54
+ // Create server
55
+ const server = new McpServer({
56
+ name: "defillama",
57
+ version: "1.0.0",
58
+ });
59
+ // ── Tool: search_protocols ──
60
+ server.tool("search_protocols", "Search DeFi protocols by name. Returns top results with TVL, chains, and category.", {
61
+ query: z.string().describe("Search term (e.g. 'aave', 'uniswap', 'lido')"),
62
+ limit: z.number().optional().default(10).describe("Max results to return (default 10)"),
63
+ }, async ({ query, limit }) => {
64
+ try {
65
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/protocols`);
66
+ if (!Array.isArray(data)) {
67
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama." }] };
68
+ }
69
+ const q = query.toLowerCase().trim();
70
+ const matches = data
71
+ .filter((p) => {
72
+ const name = (p.name || "").toLowerCase();
73
+ const symbol = (p.symbol || "").toLowerCase();
74
+ const slug = (p.slug || "").toLowerCase();
75
+ return name.includes(q) || symbol.includes(q) || slug.includes(q);
76
+ })
77
+ .sort((a, b) => (b.tvl || 0) - (a.tvl || 0))
78
+ .slice(0, limit);
79
+ if (matches.length === 0) {
80
+ return { content: [{ type: "text", text: `No protocols found for "${query}".` }] };
81
+ }
82
+ const lines = matches.map((p) => {
83
+ const chains = (p.chains || []).slice(0, 4).join(", ");
84
+ const more = (p.chains || []).length > 4 ? ` +${p.chains.length - 4}` : "";
85
+ const category = p.category || "Unknown";
86
+ return `- **${p.name}** (${(p.symbol || "").toUpperCase()}) — TVL: ${formatTVL(p.tvl)} | Category: ${category} | Chains: ${chains}${more} | Slug: \`${p.slug}\``;
87
+ });
88
+ return {
89
+ content: [{
90
+ type: "text",
91
+ text: `**Top ${matches.length} protocols matching "${query}" (by TVL):**\n\n${lines.join("\n")}`,
92
+ }],
93
+ };
94
+ }
95
+ catch (e) {
96
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
97
+ }
98
+ });
99
+ // ── Tool: get_protocol_tvl ──
100
+ server.tool("get_protocol_tvl", "Get detailed TVL breakdown for a specific DeFi protocol (chain distribution, TVL history, category, description).", {
101
+ slug: z.string().describe("Protocol slug (e.g. 'aave', 'uniswap', 'lido'). Use search_protocols to find slugs."),
102
+ }, async ({ slug }) => {
103
+ try {
104
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/protocol/${encodeURIComponent(slug)}`);
105
+ const lines = [
106
+ `**${data.name}** (${(data.symbol || "").toUpperCase()})`,
107
+ "",
108
+ `- **Category:** ${data.category || "Unknown"}`,
109
+ `- **Chain:** ${data.chain || "Multi-chain"}`,
110
+ `- **Current TVL:** ${formatTVL(data.tvl)}`,
111
+ `- **mcap / TVL:** ${data.mcap || "N/A"}`,
112
+ `- **Website:** ${data.url || "N/A"}`,
113
+ ];
114
+ // Chain breakdown
115
+ if (Array.isArray(data.currentChainTvls) && data.currentChainTvls.length > 0) {
116
+ const chainLines = data.currentChainTvls
117
+ .map((c) => ` - ${c.name}: ${formatTVL(c.tvl)}`)
118
+ .join("\n");
119
+ lines.push("", "### TVL by Chain", chainLines);
120
+ }
121
+ // Recent TVL change
122
+ if (data.change_1d != null || data.change_7d != null) {
123
+ lines.push("", "### TVL Change", `- **24h:** ${formatPct(data.change_1d)}`, `- **7d:** ${formatPct(data.change_7d)}`);
124
+ }
125
+ // Description
126
+ if (data.description) {
127
+ const desc = String(data.description).replace(/<[^>]*>/g, "").slice(0, 400);
128
+ lines.push("", "### Description", desc + (String(data.description).length > 400 ? "..." : ""));
129
+ }
130
+ // Recent TVL history (sample)
131
+ if (data.tvl && Array.isArray(data.tvl) && data.tvl.length > 0) {
132
+ const hist = data.tvl;
133
+ const step = Math.max(1, Math.floor(hist.length / 10));
134
+ const samples = hist.filter((_, i) => i % step === 0);
135
+ const histLines = samples
136
+ .map(([ts, v]) => `${new Date(ts * 1000).toISOString().split("T")[0]}: ${formatTVL(v)}`)
137
+ .join("\n");
138
+ lines.push("", "### TVL History (sampled)", histLines);
139
+ }
140
+ return { content: [{ type: "text", text: lines.join("\n") }] };
141
+ }
142
+ catch (e) {
143
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
144
+ }
145
+ });
146
+ // ── Tool: get_tvl_by_chain ──
147
+ server.tool("get_tvl_by_chain", "Get total TVL for a specific chain (Ethereum, Arbitrum, Base, Solana, etc.) plus chain-level token breakdown.", {
148
+ chain: z.string().describe("Chain name (e.g. 'Ethereum', 'Arbitrum', 'Base', 'Solana', 'BSC', 'Polygon')"),
149
+ }, async ({ chain }) => {
150
+ try {
151
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/v2/chains`);
152
+ if (!Array.isArray(data)) {
153
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama." }] };
154
+ }
155
+ const c = chain.toLowerCase().trim();
156
+ const match = data.find((x) => (x.name || "").toLowerCase() === c)
157
+ || data.find((x) => (x.name || "").toLowerCase().includes(c));
158
+ if (!match) {
159
+ return { content: [{ type: "text", text: `Chain "${chain}" not found. Try one of: ${data.slice(0, 15).map((x) => x.name).join(", ")}...` }] };
160
+ }
161
+ const lines = [
162
+ `**${match.name} — Chain TVL**`,
163
+ "",
164
+ `- **Total TVL:** ${formatTVL(match.tvl)}`,
165
+ ];
166
+ if (match.tokenSymbol)
167
+ lines.push(`- **Native Token:** ${match.tokenSymbol}`);
168
+ if (match.gecko_id)
169
+ lines.push(`- **CoinGecko ID:** ${match.gecko_id}`);
170
+ if (match.cmcdId)
171
+ lines.push(`- **CMC ID:** ${match.cmcdId}`);
172
+ // Top tokens on this chain by TVL
173
+ if (Array.isArray(match.tokens) && match.tokens.length > 0) {
174
+ const topTokens = match.tokens
175
+ .filter((t) => t.tvl != null && t.tvl > 0)
176
+ .sort((a, b) => b.tvl - a.tvl)
177
+ .slice(0, 10);
178
+ if (topTokens.length > 0) {
179
+ const tokenLines = topTokens
180
+ .map((t) => `- **${t.symbol || t.name || "?"}** (${t.name || ""}): ${formatTVL(t.tvl)}`)
181
+ .join("\n");
182
+ lines.push("", "### Top Tokens by TVL", tokenLines);
183
+ }
184
+ }
185
+ // gecko_id link hint
186
+ if (match.gecko_id) {
187
+ lines.push("", `_View chart: https://defillama.com/chain/${match.name}_${match.gecko_id}_Coingecko__${match.name}_Coingecko_?_${match.geckoId || ""}_`);
188
+ }
189
+ return { content: [{ type: "text", text: lines.join("\n") }] };
190
+ }
191
+ catch (e) {
192
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
193
+ }
194
+ });
195
+ // ── Tool: get_yields ──
196
+ server.tool("get_yields", "Get yield/APY data for lending pools and staking across DeFi protocols. Filter by chain, project, or min TVL.", {
197
+ chain: z.string().optional().describe("Filter by chain (e.g. 'Ethereum', 'Arbitrum', 'Base', 'Solana')"),
198
+ project: z.string().optional().describe("Filter by project (e.g. 'Aave', 'Lido', 'Compound')"),
199
+ min_tvl: z.number().optional().default(1_000_000).describe("Minimum TVL in USD (default $1M)"),
200
+ min_apy: z.number().optional().default(0).describe("Minimum APY % (default 0)"),
201
+ limit: z.number().optional().default(20).describe("Max results (default 20)"),
202
+ }, async ({ chain, project, min_tvl, min_apy, limit }) => {
203
+ try {
204
+ const data = await rateLimitedFetch(`${YIELDS_BASE}/pools`);
205
+ if (!data || !Array.isArray(data.data)) {
206
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama yields." }] };
207
+ }
208
+ const chainFilter = chain ? chain.toLowerCase().trim() : null;
209
+ const projectFilter = project ? project.toLowerCase().trim() : null;
210
+ const matches = data.data
211
+ .filter((p) => {
212
+ if (p.tvlUsd == null || p.tvlUsd < min_tvl)
213
+ return false;
214
+ if (p.apy == null || p.apy < min_apy)
215
+ return false;
216
+ if (p.ilRisk === "yes")
217
+ return false; // Skip impermanent-loss pools by default
218
+ if (chainFilter && !(p.chain || "").toLowerCase().includes(chainFilter))
219
+ return false;
220
+ if (projectFilter && !(p.project || "").toLowerCase().includes(projectFilter))
221
+ return false;
222
+ return true;
223
+ })
224
+ .sort((a, b) => (b.tvlUsd || 0) - (a.tvlUsd || 0))
225
+ .slice(0, limit);
226
+ if (matches.length === 0) {
227
+ return { content: [{ type: "text", text: `No yield pools matched (chain: ${chain || "any"}, project: ${project || "any"}, min TVL $${min_tvl.toLocaleString()}, min APY ${min_apy}%).` }] };
228
+ }
229
+ const lines = matches.map((p) => {
230
+ const apy = p.apy != null ? `${p.apy.toFixed(2)}%` : "N/A";
231
+ const apyBase = p.apyBase != null ? `${p.apyBase.toFixed(2)}% base` : "";
232
+ const apyReward = p.apyReward != null ? `+ ${p.apyReward.toFixed(2)}% reward` : "";
233
+ const apyDetails = [apyBase, apyReward].filter(Boolean).join(" ");
234
+ const tvl = formatTVL(p.tvlUsd);
235
+ const symbol = p.symbol || "?";
236
+ const projectName = p.project || "?";
237
+ const chainName = p.chain || "?";
238
+ const stable = p.stablecoin ? " 🟢" : "";
239
+ return `- **${projectName}** — ${symbol} on ${chainName}${stable} | APY: ${apy}${apyDetails ? ` (${apyDetails})` : ""} | TVL: ${tvl}`;
240
+ });
241
+ const headerParts = [];
242
+ if (chain)
243
+ headerParts.push(`chain: ${chain}`);
244
+ if (project)
245
+ headerParts.push(`project: ${project}`);
246
+ headerParts.push(`min TVL: ${formatTVL(min_tvl)}`);
247
+ headerParts.push(`min APY: ${min_apy}%`);
248
+ const header = headerParts.join(" | ");
249
+ return {
250
+ content: [{
251
+ type: "text",
252
+ text: `**Top ${matches.length} yield pools (${header}):**\n\n${lines.join("\n")}`,
253
+ }],
254
+ };
255
+ }
256
+ catch (e) {
257
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
258
+ }
259
+ });
260
+ // ── Tool: get_stablecoins ──
261
+ server.tool("get_stablecoins", "Get stablecoin market cap data and rankings (circulating supply, chains, prices).", {
262
+ limit: z.number().optional().default(25).describe("Max results (default 25)"),
263
+ }, async ({ limit }) => {
264
+ try {
265
+ const data = await rateLimitedFetch(`${STABLECOINS_BASE}/stablecoins`);
266
+ if (!Array.isArray(data)) {
267
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama stablecoins." }] };
268
+ }
269
+ const sorted = [...data]
270
+ .filter((s) => s.circulating != null)
271
+ .sort((a, b) => (b.circulating?.usd || 0) - (a.circulating?.usd || 0))
272
+ .slice(0, limit);
273
+ if (sorted.length === 0) {
274
+ return { content: [{ type: "text", text: "No stablecoins returned by API." }] };
275
+ }
276
+ const lines = sorted.map((s, i) => {
277
+ const mcap = formatTVL(s.circulating?.usd);
278
+ const price = s.price != null ? `$${Number(s.price).toFixed(4)}` : "N/A";
279
+ const symbol = s.symbol || s.name || "?";
280
+ const pegType = s.pegType || "USD";
281
+ const chains = Array.isArray(s.chains) ? s.chains.length : 0;
282
+ return `${i + 1}. **${symbol}** (${pegType}) — MCap: ${mcap} | Price: ${price} | Chains: ${chains}`;
283
+ });
284
+ const totalMcap = sorted.reduce((acc, s) => acc + (s.circulating?.usd || 0), 0);
285
+ return {
286
+ content: [{
287
+ type: "text",
288
+ text: `**Top ${sorted.length} Stablecoins by Market Cap (combined: ${formatTVL(totalMcap)}):**\n\n${lines.join("\n")}`,
289
+ }],
290
+ };
291
+ }
292
+ catch (e) {
293
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
294
+ }
295
+ });
296
+ // ── Tool: get_bridges ──
297
+ server.tool("get_bridges", "Get bridge TVL and volume data — cross-chain bridges ranked by total value locked.", {
298
+ limit: z.number().optional().default(20).describe("Max results (default 20)"),
299
+ }, async ({ limit }) => {
300
+ try {
301
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/v2/bridges`);
302
+ const bridges = Array.isArray(data) ? data : (data.bridges || []);
303
+ if (!Array.isArray(bridges)) {
304
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama bridges." }] };
305
+ }
306
+ const sorted = [...bridges]
307
+ .filter((b) => b.tvl != null && b.tvl > 0)
308
+ .sort((a, b) => (b.tvl || 0) - (a.tvl || 0))
309
+ .slice(0, limit);
310
+ if (sorted.length === 0) {
311
+ return { content: [{ type: "text", text: "No bridge data returned." }] };
312
+ }
313
+ const lines = sorted.map((b, i) => {
314
+ const tvl = formatTVL(b.tvl);
315
+ const dayVol = b.lastDayVolume != null ? `${formatTVL(b.lastDayVolume)} 24h` : "";
316
+ const weekVol = b.weeklyVolume != null ? `${formatTVL(b.weeklyVolume)} 7d` : "";
317
+ const volDetails = [dayVol, weekVol].filter(Boolean).join(" / ");
318
+ return `${i + 1}. **${b.name || b.displayName || "Unknown"}** — TVL: ${tvl}${volDetails ? ` | Vol: ${volDetails}` : ""}`;
319
+ });
320
+ const totalTvl = sorted.reduce((acc, b) => acc + (b.tvl || 0), 0);
321
+ return {
322
+ content: [{
323
+ type: "text",
324
+ text: `**Top ${sorted.length} Bridges by TVL (combined: ${formatTVL(totalTvl)}):**\n\n${lines.join("\n")}`,
325
+ }],
326
+ };
327
+ }
328
+ catch (e) {
329
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
330
+ }
331
+ });
332
+ // ── Tool: get_dex_volumes ──
333
+ server.tool("get_dex_volumes", "Get DEX trading volumes across chains — decentralized exchanges ranked by 24h/7d volume.", {
334
+ limit: z.number().optional().default(25).describe("Max results (default 25)"),
335
+ }, async ({ limit }) => {
336
+ try {
337
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/overview/dexs`);
338
+ const dexs = (data && (data.protocols || data)) || [];
339
+ if (!Array.isArray(dexs)) {
340
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama dexs." }] };
341
+ }
342
+ const sorted = [...dexs]
343
+ .filter((d) => d.total24h != null)
344
+ .sort((a, b) => (b.total24h || 0) - (a.total24h || 0))
345
+ .slice(0, limit);
346
+ if (sorted.length === 0) {
347
+ return { content: [{ type: "text", text: "No DEX data returned." }] };
348
+ }
349
+ const lines = sorted.map((d, i) => {
350
+ const v24 = formatTVL(d.total24h);
351
+ const v7d = d.total7d != null ? formatTVL(d.total7d) : "N/A";
352
+ const v30d = d.total30d != null ? formatTVL(d.total30d) : "N/A";
353
+ const change = d.change_1d != null ? formatPct(d.change_1d) : "N/A";
354
+ return `${i + 1}. **${d.name || d.displayName || "Unknown"}** — 24h: ${v24} | 7d: ${v7d} | 30d: ${v30d} | Δ24h: ${change}`;
355
+ });
356
+ const total24h = sorted.reduce((acc, d) => acc + (d.total24h || 0), 0);
357
+ return {
358
+ content: [{
359
+ type: "text",
360
+ text: `**Top ${sorted.length} DEXes by 24h Volume (combined: ${formatTVL(total24h)}):**\n\n${lines.join("\n")}`,
361
+ }],
362
+ };
363
+ }
364
+ catch (e) {
365
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
366
+ }
367
+ });
368
+ // ── Tool: get_protocol_fees ──
369
+ server.tool("get_protocol_fees", "Get protocol fee and revenue data — protocols ranked by 24h/7d/30d fees and revenue.", {
370
+ limit: z.number().optional().default(25).describe("Max results (default 25)"),
371
+ }, async ({ limit }) => {
372
+ try {
373
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/overview/fees`);
374
+ const protos = (data && (data.protocols || data)) || [];
375
+ if (!Array.isArray(protos)) {
376
+ return { content: [{ type: "text", text: "Unexpected API response from DeFi Llama fees." }] };
377
+ }
378
+ const sorted = [...protos]
379
+ .filter((p) => p.total24h != null)
380
+ .sort((a, b) => (b.total24h || 0) - (a.total24h || 0))
381
+ .slice(0, limit);
382
+ if (sorted.length === 0) {
383
+ return { content: [{ type: "text", text: "No fee data returned." }] };
384
+ }
385
+ const lines = sorted.map((p, i) => {
386
+ const f24 = formatTVL(p.total24h);
387
+ const f7d = p.total7d != null ? formatTVL(p.total7d) : "N/A";
388
+ const f30d = p.total30d != null ? formatTVL(p.total30d) : "N/A";
389
+ const rev = p.revenue24h != null ? `${formatTVL(p.revenue24h)} rev` : "";
390
+ return `${i + 1}. **${p.name || p.displayName || "Unknown"}** — Fees 24h: ${f24} | 7d: ${f7d} | 30d: ${f30d}${rev ? ` | ${rev}` : ""}`;
391
+ });
392
+ const total24h = sorted.reduce((acc, p) => acc + (p.total24h || 0), 0);
393
+ return {
394
+ content: [{
395
+ type: "text",
396
+ text: `**Top ${sorted.length} Protocols by 24h Fees (combined: ${formatTVL(total24h)}):**\n\n${lines.join("\n")}`,
397
+ }],
398
+ };
399
+ }
400
+ catch (e) {
401
+ return { content: [{ type: "text", text: `Error: ${e.message}` }] };
402
+ }
403
+ });
404
+ // Start server
405
+ async function main() {
406
+ const transport = new StdioServerTransport();
407
+ await server.connect(transport);
408
+ }
409
+ main().catch((err) => {
410
+ console.error("Fatal error:", err);
411
+ process.exit(1);
412
+ });
413
+ //# sourceMappingURL=index.js.map
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@supernova123/defillama-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for DeFi Llama \u2014 free DeFi TVL, yields, stablecoins, bridges, DEX, and fee data for AI assistants",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "defillama-mcp-server": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsc && node dist/index.js"
14
+ },
15
+ "keywords": [
16
+ "mcp",
17
+ "model-context-protocol",
18
+ "defillama",
19
+ "defi",
20
+ "tvl",
21
+ "yields",
22
+ "stablecoins",
23
+ "bridges",
24
+ "dex",
25
+ "ai",
26
+ "claude",
27
+ "cursor"
28
+ ],
29
+ "author": "Nova",
30
+ "license": "MIT",
31
+ "dependencies": {
32
+ "@modelcontextprotocol/sdk": "^1.0.0",
33
+ "zod": "^3.23.0"
34
+ },
35
+ "devDependencies": {
36
+ "typescript": "^5.5.0",
37
+ "@types/node": "^20.0.0"
38
+ },
39
+ "mcpName": "io.github.friendlygeorge/defillama-mcp-server",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/friendlygeorge/defillama-mcp-server.git"
43
+ },
44
+ "homepage": "https://github.com/friendlygeorge/defillama-mcp-server#readme",
45
+ "bugs": {
46
+ "url": "https://github.com/friendlygeorge/defillama-mcp-server/issues"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ }
51
+ }
package/src/index.ts ADDED
@@ -0,0 +1,484 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DeFi Llama MCP Server
4
+ *
5
+ * Connect AI assistants to DeFi Llama's free DeFi data API.
6
+ * Query protocol TVL, chain TVL, yields, stablecoins, bridges,
7
+ * DEX volumes, and protocol fees through the Model Context Protocol.
8
+ *
9
+ * Works with Claude Desktop, Cursor, Windsurf, Cline, and any MCP client.
10
+ */
11
+
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
14
+ import { z } from "zod";
15
+
16
+ const LLAMA_BASE = "https://api.llama.fi";
17
+ const YIELDS_BASE = "https://yields.llama.fi";
18
+ const STABLECOINS_BASE = "https://stablecoins.llama.fi";
19
+
20
+ // Rate limiter: DeFi Llama doesn't publish hard limits, but be polite.
21
+ // ~500ms between calls is safe and well below any reasonable limit.
22
+ let lastCall = 0;
23
+ const MIN_INTERVAL = 500;
24
+
25
+ async function rateLimitedFetch(url: string): Promise<any> {
26
+ const now = Date.now();
27
+ const wait = MIN_INTERVAL - (now - lastCall);
28
+ if (wait > 0) {
29
+ await new Promise((r) => setTimeout(r, wait));
30
+ }
31
+ lastCall = Date.now();
32
+
33
+ const res = await fetch(url, {
34
+ headers: { Accept: "application/json" },
35
+ });
36
+
37
+ if (!res.ok) {
38
+ throw new Error(`DeFi Llama API error: ${res.status} ${res.statusText}`);
39
+ }
40
+ return res.json();
41
+ }
42
+
43
+ function formatTVL(value: number | null | undefined): string {
44
+ if (value == null || isNaN(value)) return "N/A";
45
+ if (value >= 1e12) return `$${(value / 1e12).toFixed(2)}T`;
46
+ if (value >= 1e9) return `$${(value / 1e9).toFixed(2)}B`;
47
+ if (value >= 1e6) return `$${(value / 1e6).toFixed(2)}M`;
48
+ if (value >= 1e3) return `$${(value / 1e3).toFixed(2)}K`;
49
+ return `$${value.toFixed(2)}`;
50
+ }
51
+
52
+ function formatPct(value: number | null | undefined): string {
53
+ if (value == null || isNaN(value)) return "N/A";
54
+ return `${value >= 0 ? "+" : ""}${value.toFixed(2)}%`;
55
+ }
56
+
57
+ // Create server
58
+ const server = new McpServer({
59
+ name: "defillama",
60
+ version: "1.0.0",
61
+ });
62
+
63
+ // ── Tool: search_protocols ──
64
+ server.tool(
65
+ "search_protocols",
66
+ "Search DeFi protocols by name. Returns top results with TVL, chains, and category.",
67
+ {
68
+ query: z.string().describe("Search term (e.g. 'aave', 'uniswap', 'lido')"),
69
+ limit: z.number().optional().default(10).describe("Max results to return (default 10)"),
70
+ },
71
+ async ({ query, limit }) => {
72
+ try {
73
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/protocols`);
74
+ if (!Array.isArray(data)) {
75
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama." }] };
76
+ }
77
+ const q = query.toLowerCase().trim();
78
+ const matches = data
79
+ .filter((p: any) => {
80
+ const name = (p.name || "").toLowerCase();
81
+ const symbol = (p.symbol || "").toLowerCase();
82
+ const slug = (p.slug || "").toLowerCase();
83
+ return name.includes(q) || symbol.includes(q) || slug.includes(q);
84
+ })
85
+ .sort((a: any, b: any) => (b.tvl || 0) - (a.tvl || 0))
86
+ .slice(0, limit);
87
+
88
+ if (matches.length === 0) {
89
+ return { content: [{ type: "text" as const, text: `No protocols found for "${query}".` }] };
90
+ }
91
+
92
+ const lines = matches.map((p: any) => {
93
+ const chains = (p.chains || []).slice(0, 4).join(", ");
94
+ const more = (p.chains || []).length > 4 ? ` +${p.chains.length - 4}` : "";
95
+ const category = p.category || "Unknown";
96
+ return `- **${p.name}** (${(p.symbol || "").toUpperCase()}) — TVL: ${formatTVL(p.tvl)} | Category: ${category} | Chains: ${chains}${more} | Slug: \`${p.slug}\``;
97
+ });
98
+
99
+ return {
100
+ content: [{
101
+ type: "text" as const,
102
+ text: `**Top ${matches.length} protocols matching "${query}" (by TVL):**\n\n${lines.join("\n")}`,
103
+ }],
104
+ };
105
+ } catch (e: any) {
106
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
107
+ }
108
+ }
109
+ );
110
+
111
+ // ── Tool: get_protocol_tvl ──
112
+ server.tool(
113
+ "get_protocol_tvl",
114
+ "Get detailed TVL breakdown for a specific DeFi protocol (chain distribution, TVL history, category, description).",
115
+ {
116
+ slug: z.string().describe("Protocol slug (e.g. 'aave', 'uniswap', 'lido'). Use search_protocols to find slugs."),
117
+ },
118
+ async ({ slug }) => {
119
+ try {
120
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/protocol/${encodeURIComponent(slug)}`);
121
+ const lines: string[] = [
122
+ `**${data.name}** (${(data.symbol || "").toUpperCase()})`,
123
+ "",
124
+ `- **Category:** ${data.category || "Unknown"}`,
125
+ `- **Chain:** ${data.chain || "Multi-chain"}`,
126
+ `- **Current TVL:** ${formatTVL(data.tvl)}`,
127
+ `- **mcap / TVL:** ${data.mcap || "N/A"}`,
128
+ `- **Website:** ${data.url || "N/A"}`,
129
+ ];
130
+
131
+ // Chain breakdown
132
+ if (Array.isArray(data.currentChainTvls) && data.currentChainTvls.length > 0) {
133
+ const chainLines = data.currentChainTvls
134
+ .map((c: any) => ` - ${c.name}: ${formatTVL(c.tvl)}`)
135
+ .join("\n");
136
+ lines.push("", "### TVL by Chain", chainLines);
137
+ }
138
+
139
+ // Recent TVL change
140
+ if (data.change_1d != null || data.change_7d != null) {
141
+ lines.push(
142
+ "",
143
+ "### TVL Change",
144
+ `- **24h:** ${formatPct(data.change_1d)}`,
145
+ `- **7d:** ${formatPct(data.change_7d)}`,
146
+ );
147
+ }
148
+
149
+ // Description
150
+ if (data.description) {
151
+ const desc = String(data.description).replace(/<[^>]*>/g, "").slice(0, 400);
152
+ lines.push("", "### Description", desc + (String(data.description).length > 400 ? "..." : ""));
153
+ }
154
+
155
+ // Recent TVL history (sample)
156
+ if (data.tvl && Array.isArray(data.tvl) && data.tvl.length > 0) {
157
+ const hist = data.tvl;
158
+ const step = Math.max(1, Math.floor(hist.length / 10));
159
+ const samples = hist.filter((_: any, i: number) => i % step === 0);
160
+ const histLines = samples
161
+ .map(([ts, v]: [number, number]) => `${new Date(ts * 1000).toISOString().split("T")[0]}: ${formatTVL(v)}`)
162
+ .join("\n");
163
+ lines.push("", "### TVL History (sampled)", histLines);
164
+ }
165
+
166
+ return { content: [{ type: "text" as const, text: lines.join("\n") }] };
167
+ } catch (e: any) {
168
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
169
+ }
170
+ }
171
+ );
172
+
173
+ // ── Tool: get_tvl_by_chain ──
174
+ server.tool(
175
+ "get_tvl_by_chain",
176
+ "Get total TVL for a specific chain (Ethereum, Arbitrum, Base, Solana, etc.) plus chain-level token breakdown.",
177
+ {
178
+ chain: z.string().describe("Chain name (e.g. 'Ethereum', 'Arbitrum', 'Base', 'Solana', 'BSC', 'Polygon')"),
179
+ },
180
+ async ({ chain }) => {
181
+ try {
182
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/v2/chains`);
183
+ if (!Array.isArray(data)) {
184
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama." }] };
185
+ }
186
+ const c = chain.toLowerCase().trim();
187
+ const match = data.find((x: any) => (x.name || "").toLowerCase() === c)
188
+ || data.find((x: any) => (x.name || "").toLowerCase().includes(c));
189
+
190
+ if (!match) {
191
+ return { content: [{ type: "text" as const, text: `Chain "${chain}" not found. Try one of: ${data.slice(0, 15).map((x: any) => x.name).join(", ")}...` }] };
192
+ }
193
+
194
+ const lines: string[] = [
195
+ `**${match.name} — Chain TVL**`,
196
+ "",
197
+ `- **Total TVL:** ${formatTVL(match.tvl)}`,
198
+ ];
199
+
200
+ if (match.tokenSymbol) lines.push(`- **Native Token:** ${match.tokenSymbol}`);
201
+ if (match.gecko_id) lines.push(`- **CoinGecko ID:** ${match.gecko_id}`);
202
+ if (match.cmcdId) lines.push(`- **CMC ID:** ${match.cmcdId}`);
203
+
204
+ // Top tokens on this chain by TVL
205
+ if (Array.isArray(match.tokens) && match.tokens.length > 0) {
206
+ const topTokens = match.tokens
207
+ .filter((t: any) => t.tvl != null && t.tvl > 0)
208
+ .sort((a: any, b: any) => b.tvl - a.tvl)
209
+ .slice(0, 10);
210
+ if (topTokens.length > 0) {
211
+ const tokenLines = topTokens
212
+ .map((t: any) => `- **${t.symbol || t.name || "?"}** (${t.name || ""}): ${formatTVL(t.tvl)}`)
213
+ .join("\n");
214
+ lines.push("", "### Top Tokens by TVL", tokenLines);
215
+ }
216
+ }
217
+
218
+ // gecko_id link hint
219
+ if (match.gecko_id) {
220
+ lines.push("", `_View chart: https://defillama.com/chain/${match.name}_${match.gecko_id}_Coingecko__${match.name}_Coingecko_?_${match.geckoId || ""}_`);
221
+ }
222
+
223
+ return { content: [{ type: "text" as const, text: lines.join("\n") }] };
224
+ } catch (e: any) {
225
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
226
+ }
227
+ }
228
+ );
229
+
230
+ // ── Tool: get_yields ──
231
+ server.tool(
232
+ "get_yields",
233
+ "Get yield/APY data for lending pools and staking across DeFi protocols. Filter by chain, project, or min TVL.",
234
+ {
235
+ chain: z.string().optional().describe("Filter by chain (e.g. 'Ethereum', 'Arbitrum', 'Base', 'Solana')"),
236
+ project: z.string().optional().describe("Filter by project (e.g. 'Aave', 'Lido', 'Compound')"),
237
+ min_tvl: z.number().optional().default(1_000_000).describe("Minimum TVL in USD (default $1M)"),
238
+ min_apy: z.number().optional().default(0).describe("Minimum APY % (default 0)"),
239
+ limit: z.number().optional().default(20).describe("Max results (default 20)"),
240
+ },
241
+ async ({ chain, project, min_tvl, min_apy, limit }) => {
242
+ try {
243
+ const data = await rateLimitedFetch(`${YIELDS_BASE}/pools`);
244
+ if (!data || !Array.isArray(data.data)) {
245
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama yields." }] };
246
+ }
247
+
248
+ const chainFilter = chain ? chain.toLowerCase().trim() : null;
249
+ const projectFilter = project ? project.toLowerCase().trim() : null;
250
+
251
+ const matches = data.data
252
+ .filter((p: any) => {
253
+ if (p.tvlUsd == null || p.tvlUsd < min_tvl) return false;
254
+ if (p.apy == null || p.apy < min_apy) return false;
255
+ if (p.ilRisk === "yes") return false; // Skip impermanent-loss pools by default
256
+ if (chainFilter && !(p.chain || "").toLowerCase().includes(chainFilter)) return false;
257
+ if (projectFilter && !(p.project || "").toLowerCase().includes(projectFilter)) return false;
258
+ return true;
259
+ })
260
+ .sort((a: any, b: any) => (b.tvlUsd || 0) - (a.tvlUsd || 0))
261
+ .slice(0, limit);
262
+
263
+ if (matches.length === 0) {
264
+ return { content: [{ type: "text" as const, text: `No yield pools matched (chain: ${chain || "any"}, project: ${project || "any"}, min TVL $${min_tvl.toLocaleString()}, min APY ${min_apy}%).` }] };
265
+ }
266
+
267
+ const lines = matches.map((p: any) => {
268
+ const apy = p.apy != null ? `${p.apy.toFixed(2)}%` : "N/A";
269
+ const apyBase = p.apyBase != null ? `${p.apyBase.toFixed(2)}% base` : "";
270
+ const apyReward = p.apyReward != null ? `+ ${p.apyReward.toFixed(2)}% reward` : "";
271
+ const apyDetails = [apyBase, apyReward].filter(Boolean).join(" ");
272
+ const tvl = formatTVL(p.tvlUsd);
273
+ const symbol = p.symbol || "?";
274
+ const projectName = p.project || "?";
275
+ const chainName = p.chain || "?";
276
+ const stable = p.stablecoin ? " 🟢" : "";
277
+ return `- **${projectName}** — ${symbol} on ${chainName}${stable} | APY: ${apy}${apyDetails ? ` (${apyDetails})` : ""} | TVL: ${tvl}`;
278
+ });
279
+
280
+ const headerParts: string[] = [];
281
+ if (chain) headerParts.push(`chain: ${chain}`);
282
+ if (project) headerParts.push(`project: ${project}`);
283
+ headerParts.push(`min TVL: ${formatTVL(min_tvl)}`);
284
+ headerParts.push(`min APY: ${min_apy}%`);
285
+ const header = headerParts.join(" | ");
286
+
287
+ return {
288
+ content: [{
289
+ type: "text" as const,
290
+ text: `**Top ${matches.length} yield pools (${header}):**\n\n${lines.join("\n")}`,
291
+ }],
292
+ };
293
+ } catch (e: any) {
294
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
295
+ }
296
+ }
297
+ );
298
+
299
+ // ── Tool: get_stablecoins ──
300
+ server.tool(
301
+ "get_stablecoins",
302
+ "Get stablecoin market cap data and rankings (circulating supply, chains, prices).",
303
+ {
304
+ limit: z.number().optional().default(25).describe("Max results (default 25)"),
305
+ },
306
+ async ({ limit }) => {
307
+ try {
308
+ const data = await rateLimitedFetch(`${STABLECOINS_BASE}/stablecoins`);
309
+ if (!Array.isArray(data)) {
310
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama stablecoins." }] };
311
+ }
312
+ const sorted = [...data]
313
+ .filter((s: any) => s.circulating != null)
314
+ .sort((a: any, b: any) => (b.circulating?.usd || 0) - (a.circulating?.usd || 0))
315
+ .slice(0, limit);
316
+
317
+ if (sorted.length === 0) {
318
+ return { content: [{ type: "text" as const, text: "No stablecoins returned by API." }] };
319
+ }
320
+
321
+ const lines = sorted.map((s: any, i: number) => {
322
+ const mcap = formatTVL(s.circulating?.usd);
323
+ const price = s.price != null ? `$${Number(s.price).toFixed(4)}` : "N/A";
324
+ const symbol = s.symbol || s.name || "?";
325
+ const pegType = s.pegType || "USD";
326
+ const chains = Array.isArray(s.chains) ? s.chains.length : 0;
327
+ return `${i + 1}. **${symbol}** (${pegType}) — MCap: ${mcap} | Price: ${price} | Chains: ${chains}`;
328
+ });
329
+
330
+ const totalMcap = sorted.reduce((acc: number, s: any) => acc + (s.circulating?.usd || 0), 0);
331
+ return {
332
+ content: [{
333
+ type: "text" as const,
334
+ text: `**Top ${sorted.length} Stablecoins by Market Cap (combined: ${formatTVL(totalMcap)}):**\n\n${lines.join("\n")}`,
335
+ }],
336
+ };
337
+ } catch (e: any) {
338
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
339
+ }
340
+ }
341
+ );
342
+
343
+ // ── Tool: get_bridges ──
344
+ server.tool(
345
+ "get_bridges",
346
+ "Get bridge TVL and volume data — cross-chain bridges ranked by total value locked.",
347
+ {
348
+ limit: z.number().optional().default(20).describe("Max results (default 20)"),
349
+ },
350
+ async ({ limit }) => {
351
+ try {
352
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/v2/bridges`);
353
+ const bridges = Array.isArray(data) ? data : (data.bridges || []);
354
+ if (!Array.isArray(bridges)) {
355
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama bridges." }] };
356
+ }
357
+ const sorted = [...bridges]
358
+ .filter((b: any) => b.tvl != null && b.tvl > 0)
359
+ .sort((a: any, b: any) => (b.tvl || 0) - (a.tvl || 0))
360
+ .slice(0, limit);
361
+
362
+ if (sorted.length === 0) {
363
+ return { content: [{ type: "text" as const, text: "No bridge data returned." }] };
364
+ }
365
+
366
+ const lines = sorted.map((b: any, i: number) => {
367
+ const tvl = formatTVL(b.tvl);
368
+ const dayVol = b.lastDayVolume != null ? `${formatTVL(b.lastDayVolume)} 24h` : "";
369
+ const weekVol = b.weeklyVolume != null ? `${formatTVL(b.weeklyVolume)} 7d` : "";
370
+ const volDetails = [dayVol, weekVol].filter(Boolean).join(" / ");
371
+ return `${i + 1}. **${b.name || b.displayName || "Unknown"}** — TVL: ${tvl}${volDetails ? ` | Vol: ${volDetails}` : ""}`;
372
+ });
373
+
374
+ const totalTvl = sorted.reduce((acc: number, b: any) => acc + (b.tvl || 0), 0);
375
+ return {
376
+ content: [{
377
+ type: "text" as const,
378
+ text: `**Top ${sorted.length} Bridges by TVL (combined: ${formatTVL(totalTvl)}):**\n\n${lines.join("\n")}`,
379
+ }],
380
+ };
381
+ } catch (e: any) {
382
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
383
+ }
384
+ }
385
+ );
386
+
387
+ // ── Tool: get_dex_volumes ──
388
+ server.tool(
389
+ "get_dex_volumes",
390
+ "Get DEX trading volumes across chains — decentralized exchanges ranked by 24h/7d volume.",
391
+ {
392
+ limit: z.number().optional().default(25).describe("Max results (default 25)"),
393
+ },
394
+ async ({ limit }) => {
395
+ try {
396
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/overview/dexs`);
397
+ const dexs = (data && (data.protocols || data)) || [];
398
+ if (!Array.isArray(dexs)) {
399
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama dexs." }] };
400
+ }
401
+ const sorted = [...dexs]
402
+ .filter((d: any) => d.total24h != null)
403
+ .sort((a: any, b: any) => (b.total24h || 0) - (a.total24h || 0))
404
+ .slice(0, limit);
405
+
406
+ if (sorted.length === 0) {
407
+ return { content: [{ type: "text" as const, text: "No DEX data returned." }] };
408
+ }
409
+
410
+ const lines = sorted.map((d: any, i: number) => {
411
+ const v24 = formatTVL(d.total24h);
412
+ const v7d = d.total7d != null ? formatTVL(d.total7d) : "N/A";
413
+ const v30d = d.total30d != null ? formatTVL(d.total30d) : "N/A";
414
+ const change = d.change_1d != null ? formatPct(d.change_1d) : "N/A";
415
+ return `${i + 1}. **${d.name || d.displayName || "Unknown"}** — 24h: ${v24} | 7d: ${v7d} | 30d: ${v30d} | Δ24h: ${change}`;
416
+ });
417
+
418
+ const total24h = sorted.reduce((acc: number, d: any) => acc + (d.total24h || 0), 0);
419
+ return {
420
+ content: [{
421
+ type: "text" as const,
422
+ text: `**Top ${sorted.length} DEXes by 24h Volume (combined: ${formatTVL(total24h)}):**\n\n${lines.join("\n")}`,
423
+ }],
424
+ };
425
+ } catch (e: any) {
426
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
427
+ }
428
+ }
429
+ );
430
+
431
+ // ── Tool: get_protocol_fees ──
432
+ server.tool(
433
+ "get_protocol_fees",
434
+ "Get protocol fee and revenue data — protocols ranked by 24h/7d/30d fees and revenue.",
435
+ {
436
+ limit: z.number().optional().default(25).describe("Max results (default 25)"),
437
+ },
438
+ async ({ limit }) => {
439
+ try {
440
+ const data = await rateLimitedFetch(`${LLAMA_BASE}/overview/fees`);
441
+ const protos = (data && (data.protocols || data)) || [];
442
+ if (!Array.isArray(protos)) {
443
+ return { content: [{ type: "text" as const, text: "Unexpected API response from DeFi Llama fees." }] };
444
+ }
445
+ const sorted = [...protos]
446
+ .filter((p: any) => p.total24h != null)
447
+ .sort((a: any, b: any) => (b.total24h || 0) - (a.total24h || 0))
448
+ .slice(0, limit);
449
+
450
+ if (sorted.length === 0) {
451
+ return { content: [{ type: "text" as const, text: "No fee data returned." }] };
452
+ }
453
+
454
+ const lines = sorted.map((p: any, i: number) => {
455
+ const f24 = formatTVL(p.total24h);
456
+ const f7d = p.total7d != null ? formatTVL(p.total7d) : "N/A";
457
+ const f30d = p.total30d != null ? formatTVL(p.total30d) : "N/A";
458
+ const rev = p.revenue24h != null ? `${formatTVL(p.revenue24h)} rev` : "";
459
+ return `${i + 1}. **${p.name || p.displayName || "Unknown"}** — Fees 24h: ${f24} | 7d: ${f7d} | 30d: ${f30d}${rev ? ` | ${rev}` : ""}`;
460
+ });
461
+
462
+ const total24h = sorted.reduce((acc: number, p: any) => acc + (p.total24h || 0), 0);
463
+ return {
464
+ content: [{
465
+ type: "text" as const,
466
+ text: `**Top ${sorted.length} Protocols by 24h Fees (combined: ${formatTVL(total24h)}):**\n\n${lines.join("\n")}`,
467
+ }],
468
+ };
469
+ } catch (e: any) {
470
+ return { content: [{ type: "text" as const, text: `Error: ${e.message}` }] };
471
+ }
472
+ }
473
+ );
474
+
475
+ // Start server
476
+ async function main() {
477
+ const transport = new StdioServerTransport();
478
+ await server.connect(transport);
479
+ }
480
+
481
+ main().catch((err) => {
482
+ console.error("Fatal error:", err);
483
+ process.exit(1);
484
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ES2022",
5
+ "moduleResolution": "bundler",
6
+ "esModuleInterop": true,
7
+ "strict": true,
8
+ "outDir": "dist",
9
+ "rootDir": "src",
10
+ "declaration": true,
11
+ "sourceMap": true,
12
+ "skipLibCheck": true,
13
+ "forceConsistentCasingInFileNames": true
14
+ },
15
+ "include": ["src/**/*"]
16
+ }