DexPaprika (CoinPaprika)

by coinpaprika
Verified
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import fetch from 'node-fetch'; import { z } from 'zod'; // Base URL for DexPaprika API const API_BASE_URL = 'https://api.dexpaprika.com'; // Helper function to fetch data from DexPaprika API async function fetchFromAPI(endpoint) { try { const response = await fetch(`${API_BASE_URL}${endpoint}`); if (!response.ok) { if (response.status === 429) { throw new Error( 'Rate limit exceeded. You have reached the maximum number of requests allowed for the free tier. ' + 'To increase your rate limits and access additional features, please consider upgrading to a paid plan at https://docs.dexpaprika.com/' ); } throw new Error(`API request failed with status ${response.status}`); } return await response.json(); } catch (error) { console.error(`Error fetching from API: ${error.message}`); throw error; } } // Helper to format response for MCP function formatMcpResponse(data) { return { content: [ { type: "text", text: JSON.stringify(data) } ] }; } // MCP server instance const server = new McpServer({ name: 'dexpaprika-mcp', version: '1.0.4', description: 'MCP server for accessing DexPaprika API data for decentralized exchanges and tokens', }); // getNetworks server.tool( 'getNetworks', 'Retrieve a list of all supported blockchain networks and their metadata', {}, async () => { const data = await fetchFromAPI('/networks'); return formatMcpResponse(data); } ); // getNetworkDexes server.tool( 'getNetworkDexes', 'Get a list of available decentralized exchanges on a specific network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), page: z.number().optional().default(0).describe('Page number for pagination'), limit: z.number().optional().default(10).describe('Number of items per page') }, async ({ network, page, limit }) => { const data = await fetchFromAPI(`/networks/${network}/dexes?page=${page}&limit=${limit}`); return formatMcpResponse(data); } ); // getTopPools server.tool( 'getTopPools', 'Get a paginated list of top liquidity pools from all networks', { page: z.number().optional().default(0).describe('Page number for pagination'), limit: z.number().optional().default(10).describe('Number of items per page'), sort: z.enum(['asc', 'desc']).optional().default('desc').describe('Sort order'), orderBy: z.enum(['volume_usd', 'price_usd', 'transactions', 'last_price_change_usd_24h', 'created_at']).optional().default('volume_usd').describe('Field to order by') }, async ({ page, limit, sort, orderBy }) => { const data = await fetchFromAPI(`/pools?page=${page}&limit=${limit}&sort=${sort}&order_by=${orderBy}`); return formatMcpResponse(data); } ); // getNetworkPools server.tool( 'getNetworkPools', 'Get a list of top liquidity pools on a specific network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), page: z.number().optional().default(0).describe('Page number for pagination'), limit: z.number().optional().default(10).describe('Number of items per page'), sort: z.enum(['asc', 'desc']).optional().default('desc').describe('Sort order'), orderBy: z.enum(['volume_usd', 'price_usd', 'transactions', 'last_price_change_usd_24h', 'created_at']).optional().default('volume_usd').describe('Field to order by') }, async ({ network, page, limit, sort, orderBy }) => { const data = await fetchFromAPI(`/networks/${network}/pools?page=${page}&limit=${limit}&sort=${sort}&order_by=${orderBy}`); return formatMcpResponse(data); } ); // getDexPools server.tool( 'getDexPools', 'Get top pools on a specific DEX within a network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), dex: z.string().describe('DEX identifier'), page: z.number().optional().default(0).describe('Page number for pagination'), limit: z.number().optional().default(10).describe('Number of items per page'), sort: z.enum(['asc', 'desc']).optional().default('desc').describe('Sort order'), orderBy: z.enum(['volume_usd', 'price_usd', 'transactions', 'last_price_change_usd_24h', 'created_at']).optional().default('volume_usd').describe('Field to order by') }, async ({ network, dex, page, limit, sort, orderBy }) => { const data = await fetchFromAPI(`/networks/${network}/dexes/${dex}/pools?page=${page}&limit=${limit}&sort=${sort}&order_by=${orderBy}`); return formatMcpResponse(data); } ); // getPoolDetails server.tool( 'getPoolDetails', 'Get detailed information about a specific pool on a network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), poolAddress: z.string().describe('Pool address or identifier'), inversed: z.boolean().optional().default(false).describe('Whether to invert the price ratio') }, async ({ network, poolAddress, inversed }) => { const data = await fetchFromAPI(`/networks/${network}/pools/${poolAddress}?inversed=${inversed}`); return formatMcpResponse(data); } ); // getTokenDetails server.tool( 'getTokenDetails', 'Get detailed information about a specific token on a network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), tokenAddress: z.string().describe('Token address or identifier') }, async ({ network, tokenAddress }) => { const data = await fetchFromAPI(`/networks/${network}/tokens/${tokenAddress}`); return formatMcpResponse(data); } ); // getTokenPools server.tool( 'getTokenPools', 'Get a list of top liquidity pools for a specific token on a network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), tokenAddress: z.string().describe('Token address or identifier'), page: z.number().optional().default(0).describe('Page number for pagination'), limit: z.number().optional().default(10).describe('Number of items per page'), sort: z.enum(['asc', 'desc']).optional().default('desc').describe('Sort order'), orderBy: z.enum(['volume_usd', 'price_usd', 'transactions', 'last_price_change_usd_24h', 'created_at']).optional().default('volume_usd').describe('Field to order by'), address: z.string().optional().describe('Filter pools that contain this additional token address') }, async ({ network, tokenAddress, page, limit, sort, orderBy, address }) => { let endpoint = `/networks/${network}/tokens/${tokenAddress}/pools?page=${page}&limit=${limit}&sort=${sort}&order_by=${orderBy}`; if (address) { endpoint += `&address=${encodeURIComponent(address)}`; } const data = await fetchFromAPI(endpoint); return formatMcpResponse(data); } ); // getPoolOHLCV server.tool( 'getPoolOHLCV', 'Get OHLCV (Open-High-Low-Close-Volume) data for a specific pool', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), poolAddress: z.string().describe('Pool address or identifier'), start: z.string().describe('Start time for historical data (ISO-8601, yyyy-mm-dd, or Unix timestamp)'), end: z.string().optional().describe('End time for historical data (max 1 year from start)'), limit: z.number().optional().default(1).describe('Number of data points to retrieve (max 366)'), interval: z.string().optional().default('24h').describe('Interval granularity for OHLCV data (1m, 5m, 10m, 15m, 30m, 1h, 6h, 12h, 24h)'), inversed: z.boolean().optional().default(false).describe('Whether to invert the price ratio in OHLCV calculations') }, async ({ network, poolAddress, start, end, limit, interval, inversed }) => { let endpoint = `/networks/${network}/pools/${poolAddress}/ohlcv?start=${encodeURIComponent(start)}&interval=${interval}&limit=${limit}&inversed=${inversed}`; if (end) { endpoint += `&end=${encodeURIComponent(end)}`; } const data = await fetchFromAPI(endpoint); return formatMcpResponse(data); } ); // getPoolTransactions server.tool( 'getPoolTransactions', 'Get transactions of a pool on a network', { network: z.string().describe('Network ID (e.g., ethereum, solana)'), poolAddress: z.string().describe('Pool address or identifier'), page: z.number().optional().default(0).describe('Page number for pagination'), limit: z.number().optional().default(10).describe('Number of items per page'), cursor: z.string().optional().describe('Transaction ID used for cursor-based pagination') }, async ({ network, poolAddress, page, limit, cursor }) => { let endpoint = `/networks/${network}/pools/${poolAddress}/transactions?page=${page}&limit=${limit}`; if (cursor) { endpoint += `&cursor=${encodeURIComponent(cursor)}`; } const data = await fetchFromAPI(endpoint); return formatMcpResponse(data); } ); // search server.tool( 'search', 'Search for tokens, pools, and DEXes by name or identifier', { query: z.string().describe('Search term (e.g., "uniswap", "bitcoin", or a token address)') }, async ({ query }) => { if (!query.trim()) { throw new Error('Search query cannot be empty'); } const sanitizedQuery = encodeURIComponent(query.trim()); const data = await fetchFromAPI(`/search?query=${sanitizedQuery}`); return formatMcpResponse(data); } ); // getStats server.tool( 'getStats', 'Get high-level statistics about the DexPaprika ecosystem', {}, async () => { const data = await fetchFromAPI('/stats'); return formatMcpResponse(data); } ); // Start the server async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); console.log('DexPaprika MCP server is running...'); } catch (error) { console.error('Failed to start server:', error); process.exit(1); } } main();