@supernova123/defillama-mcp-server 1.0.0 → 1.0.2
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/.github/workflows/publish.yml +62 -0
- package/glama.json +6 -0
- package/package.json +15 -5
- package/dist/index.d.ts +0 -11
- package/dist/index.js +0 -413
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# GitHub Actions: Publish MCP Server to npm + Official Registry
|
|
2
|
+
#
|
|
3
|
+
# Setup per repo:
|
|
4
|
+
# 1. Add NPM_TOKEN secret to repo Settings → Secrets → Actions
|
|
5
|
+
# 2. Enable "Allow GitHub Actions to create and approve pull requests"
|
|
6
|
+
# in repo Settings → Actions → General → Workflow permissions
|
|
7
|
+
# 3. Push to main or trigger manually
|
|
8
|
+
#
|
|
9
|
+
# This workflow handles:
|
|
10
|
+
# - TypeScript build
|
|
11
|
+
# - npm publish (public)
|
|
12
|
+
# - Official MCP Registry publish (via GitHub OIDC)
|
|
13
|
+
|
|
14
|
+
name: Publish MCP Server
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
push:
|
|
18
|
+
branches: [main]
|
|
19
|
+
paths:
|
|
20
|
+
- 'src/**'
|
|
21
|
+
- 'package.json'
|
|
22
|
+
- 'tsconfig.json'
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
|
|
25
|
+
permissions:
|
|
26
|
+
contents: read
|
|
27
|
+
id-token: write # Required for OIDC auth with MCP Registry
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
publish:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- name: Checkout
|
|
34
|
+
uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- name: Setup Node.js
|
|
37
|
+
uses: actions/setup-node@v4
|
|
38
|
+
with:
|
|
39
|
+
node-version: '20'
|
|
40
|
+
registry-url: 'https://registry.npmjs.org'
|
|
41
|
+
|
|
42
|
+
- name: Install dependencies
|
|
43
|
+
run: npm ci
|
|
44
|
+
|
|
45
|
+
- name: Build
|
|
46
|
+
run: npm run build
|
|
47
|
+
|
|
48
|
+
- name: Publish to npm
|
|
49
|
+
run: npm publish --access public
|
|
50
|
+
env:
|
|
51
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
52
|
+
|
|
53
|
+
- name: Install mcp-publisher
|
|
54
|
+
run: |
|
|
55
|
+
curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64.tar.gz" | tar xz mcp-publisher
|
|
56
|
+
sudo mv mcp-publisher /usr/local/bin/
|
|
57
|
+
|
|
58
|
+
- name: Publish to Official MCP Registry
|
|
59
|
+
run: |
|
|
60
|
+
mcp-publisher login github-oidc
|
|
61
|
+
mcp-publisher init
|
|
62
|
+
mcp-publisher publish
|
package/glama.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supernova123/defillama-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "MCP server for DeFi Llama
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "MCP server for DeFi Llama — free DeFi TVL, yields, stablecoins, bridges, DEX, and fee data for AI assistants",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -33,8 +33,8 @@
|
|
|
33
33
|
"zod": "^3.23.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"
|
|
37
|
-
"
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"typescript": "^5.5.0"
|
|
38
38
|
},
|
|
39
39
|
"mcpName": "io.github.friendlygeorge/defillama-mcp-server",
|
|
40
40
|
"repository": {
|
|
@@ -47,5 +47,15 @@
|
|
|
47
47
|
},
|
|
48
48
|
"publishConfig": {
|
|
49
49
|
"access": "public"
|
|
50
|
-
}
|
|
50
|
+
},
|
|
51
|
+
"topics": [
|
|
52
|
+
"mcp",
|
|
53
|
+
"mcp-server",
|
|
54
|
+
"model-context-protocol",
|
|
55
|
+
"defi",
|
|
56
|
+
"tvl",
|
|
57
|
+
"yields",
|
|
58
|
+
"defillama",
|
|
59
|
+
"ai"
|
|
60
|
+
]
|
|
51
61
|
}
|
package/dist/index.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,413 +0,0 @@
|
|
|
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
|