dexpaprika-mcp
Version:
A Model Context Protocol server for DexPaprika cryptocurrency data with network-specific pool queries
1,047 lines (1,015 loc) • 60.2 kB
JavaScript
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';
// Server version
const SERVER_VERSION = '1.3.0';
// Error code constants
const ErrorCodes = {
DP400_INVALID_NETWORK: "DP400_INVALID_NETWORK",
DP400_TOO_MANY_TOKENS: "DP400_TOO_MANY_TOKENS",
DP400_INVALID_ADDRESS: "DP400_INVALID_ADDRESS",
DP400_MISSING_REQUIRED: "DP400_MISSING_REQUIRED",
DP404_NOT_FOUND: "DP404_NOT_FOUND",
DP429_RATE_LIMIT: "DP429_RATE_LIMIT",
};
// Structured error response builder
function buildErrorResponse(code, message, retryable, suggestion, correctedExample, metadata) {
const error = {
error: {
code,
message,
retryable,
suggestion,
}
};
if (correctedExample) {
error.error.corrected_example = correctedExample;
}
if (metadata) {
error.error.metadata = metadata;
}
return error;
}
// Parse API error response and convert to structured format
function parseAPIError(status, statusText, endpoint) {
if (status === 404 && endpoint.includes('/networks/')) {
const networkMatch = endpoint.match(/\/networks\/([^\/\?]+)/);
const providedNetwork = networkMatch ? networkMatch[1] : 'unknown';
return buildErrorResponse(
ErrorCodes.DP400_INVALID_NETWORK,
`Network ID '${providedNetwork}' not recognized`,
true,
"Use normalized network ID from getNetworks. Call getCapabilities for network_synonyms.",
`getNetworkPools('ethereum', 10)`,
{
provided: providedNetwork,
suggested: "ethereum",
valid_networks: ["ethereum", "bsc", "polygon", "base", "arbitrum", "optimism", "solana", "avalanche", "fantom"]
}
);
}
if (status === 404) {
return buildErrorResponse(
ErrorCodes.DP404_NOT_FOUND,
"Resource not found",
false,
"Verify the resource exists. Use search or list endpoints to find correct identifiers.",
undefined,
{ endpoint }
);
}
if (status === 429) {
const resetTime = new Date();
resetTime.setHours(24, 0, 0, 0);
return buildErrorResponse(
ErrorCodes.DP429_RATE_LIMIT,
"Daily rate limit of 10,000 requests exceeded",
true,
"Wait until rate limit resets or use cached data",
undefined,
{
limit: 10000,
reset_at: resetTime.toISOString(),
retry_after_seconds: Math.floor((resetTime.getTime() - Date.now()) / 1000)
}
);
}
if (status === 400) {
return buildErrorResponse(
ErrorCodes.DP400_MISSING_REQUIRED,
`Bad request: ${statusText}`,
false,
"Check that all required parameters are provided with correct formats",
undefined,
{ endpoint, status }
);
}
return buildErrorResponse(
`DP${status}_ERROR`,
`API request failed: ${status} ${statusText}`,
false,
"Check API documentation or try again later",
undefined,
{ endpoint, status }
);
}
// Rate limit tracking
let requestCount = 0;
const RATE_LIMIT = 10000;
// Helper function to fetch data from DexPaprika API with structured error handling
async function fetchFromAPI(endpoint) {
const startTime = Date.now();
const response = await fetch(`${API_BASE_URL}${endpoint}`);
if (!response.ok) {
const structuredError = parseAPIError(response.status, response.statusText, endpoint);
throw structuredError;
}
const data = await response.json();
requestCount++;
const responseTime = Date.now() - startTime;
const resetTime = new Date();
resetTime.setHours(24, 0, 0, 0);
return {
data,
meta: {
rate_limit: {
limit: RATE_LIMIT,
remaining: RATE_LIMIT - requestCount,
used: requestCount,
percentage_used: (requestCount / RATE_LIMIT) * 100,
reset_at: resetTime.toISOString()
},
response_time_ms: responseTime,
cached: false,
timestamp: new Date().toISOString()
}
};
}
// Helper to format response for MCP
function formatMcpResponse(data) {
return {
content: [
{
type: "text",
text: JSON.stringify(data, null, 2)
}
]
};
}
// Helper to format MCP error response
function formatMcpError(error) {
if (error && typeof error === 'object' && 'error' in error) {
return formatMcpResponse(error);
}
return formatMcpResponse({
error: {
code: "DP500_UNEXPECTED",
message: error instanceof Error ? error.message : 'Unknown error',
retryable: false,
suggestion: "Please try again later or contact support"
}
});
}
// Build capabilities document
async function buildCapabilitiesDocument() {
let supportedNetworks = [];
try {
const res = await fetch(`${API_BASE_URL}/networks`);
if (res.ok) {
const json = await res.json();
supportedNetworks = Array.isArray(json) ? json.map(n => n.id).filter(Boolean) : [];
}
} catch { /* ignore */ }
const synonymsMaster = {
ethereum: ["eth", "ethereum", "mainnet", "erc20"],
bsc: ["binance smart chain", "bnb chain", "binance", "bnb", "bsc", "bep20"],
polygon: ["matic", "polygon", "poly"],
arbitrum: ["arb", "arbitrum", "arbitrum one"],
optimism: ["op", "optimism"],
base: ["base", "base chain"],
avalanche: ["avax", "avalanche", "c-chain"],
solana: ["sol", "solana"],
fantom: ["ftm", "fantom"],
blast: ["blast"],
zksync: ["zk", "zksync", "zksync era"],
linea: ["linea"],
scroll: ["scroll"],
mantle: ["mantle", "mnt"],
celo: ["celo"],
cronos: ["cro", "cronos"],
aptos: ["apt", "aptos"],
sui: ["sui"],
ton: ["ton", "the open network"],
tron: ["trx", "tron"],
};
const network_synonyms = {};
const keys = supportedNetworks.length > 0 ? supportedNetworks : Object.keys(synonymsMaster);
for (const k of keys) {
if (synonymsMaster[k]) network_synonyms[k] = synonymsMaster[k];
}
const now = new Date();
const lastUpdated = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
return {
service: "dexpaprika",
// Known misspellings, surfaced so MCP catalogs / tool routers / agent
// frameworks can match fuzzy references back to us (devrel#6).
aliases: ["dexpapika", "dexpaprica", "dex-paprika", "dex paprika"],
version: SERVER_VERSION,
description: "DeFi analytics across 33 blockchain networks",
server: {
name: "DexPaprika MCP Server",
version: SERVER_VERSION,
description: "DeFi data aggregation across multiple blockchain networks and DEXes",
last_updated: lastUpdated,
documentation_url: "https://docs.dexpaprika.com",
},
tools: [
{
name: "getNetworks",
description: "List all supported blockchain networks",
category: "discovery",
parameters: {},
returns: { type: "array", items: "Network" },
cost: 1
},
{
name: "getCapabilities",
description: "Return server capabilities, workflow patterns, network synonyms, common pitfalls, and best-practice sequences",
category: "discovery",
parameters: {},
returns: { type: "object" },
cost: 1
},
{
name: "getNetworkDexes",
description: "Get available DEXes on a specific network",
category: "dexes",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
page: { type: "number", required: false, default: 1, description: "Page number (1-indexed)" },
limit: { type: "number", required: false, default: 10, max: 100, description: "Items per page" },
sort: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" },
order_by: { type: "string", required: false, enum: ["pool"] }
},
returns: { type: "object", properties: ["dexes", "page_info"] },
cost: 1
},
{
name: "getNetworkPools",
description: "Get top liquidity pools on a network",
category: "pools",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
page: { type: "number", required: false, default: 1 },
limit: { type: "number", required: false, default: 10, max: 100 },
sort: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" },
order_by: { type: "string", required: false, enum: ["volume_usd", "price_usd", "transactions", "last_price_change_usd_24h", "created_at"], default: "volume_usd" }
},
returns: { type: "object", properties: ["pools", "page_info"] },
cost: 1
},
{
name: "getDexPools",
description: "Get pools from a specific DEX on a network",
category: "pools",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
dex: { type: "string", required: true, description: "DEX identifier", example: "uniswap_v3" },
page: { type: "number", required: false, default: 1 },
limit: { type: "number", required: false, default: 10, max: 100 },
sort: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" },
order_by: { type: "string", required: false, enum: ["volume_usd", "price_usd", "transactions", "last_price_change_usd_24h", "created_at"], default: "volume_usd" }
},
returns: { type: "object", properties: ["pools", "page_info"] },
cost: 1
},
{
name: "getNetworkPoolsFilter",
description: "Filter pools by volume, liquidity, transactions, and creation time",
category: "pools",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
page: { type: "number", required: false, default: 1 },
limit: { type: "number", required: false, default: 50, max: 100 },
volume_24h_min: { type: "number", required: false, description: "Minimum 24h volume USD" },
volume_24h_max: { type: "number", required: false, description: "Maximum 24h volume USD" },
volume_7d_min: { type: "number", required: false, description: "Minimum 7d volume USD" },
volume_7d_max: { type: "number", required: false, description: "Maximum 7d volume USD" },
liquidity_usd_min: { type: "number", required: false, description: "Minimum pool liquidity USD" },
liquidity_usd_max: { type: "number", required: false, description: "Maximum pool liquidity USD" },
txns_24h_min: { type: "number", required: false, description: "Minimum 24h transactions" },
created_after: { type: "number", required: false, description: "UNIX timestamp" },
created_before: { type: "number", required: false, description: "UNIX timestamp" },
sort_by: { type: "string", required: false, enum: ["volume_24h", "volume_7d", "volume_30d", "liquidity", "txns_24h", "created_at"], default: "volume_24h" },
sort_dir: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" }
},
returns: { type: "object", properties: ["results", "page_info"] },
cost: 1,
note: "Response uses 'results' key (not 'pools'). Fields: address, volume_usd_24h, volume_usd_7d, liquidity_usd, txns_24h."
},
{
name: "filterNetworkTokens",
description: "Filter tokens by volume, liquidity, FDV, transactions, and creation time",
category: "tokens",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
page: { type: "number", required: false, default: 1 },
limit: { type: "number", required: false, default: 50, max: 100 },
volume_24h_min: { type: "number", required: false, description: "Minimum 24h volume USD" },
volume_24h_max: { type: "number", required: false, description: "Maximum 24h volume USD" },
liquidity_usd_min: { type: "number", required: false, description: "Minimum token liquidity USD" },
fdv_min: { type: "number", required: false, description: "Minimum fully diluted valuation USD" },
fdv_max: { type: "number", required: false, description: "Maximum fully diluted valuation USD" },
txns_24h_min: { type: "number", required: false, description: "Minimum 24h transactions" },
created_after: { type: "number", required: false, description: "UNIX timestamp" },
created_before: { type: "number", required: false, description: "UNIX timestamp" },
sort_by: { type: "string", required: false, enum: ["volume_24h", "volume_7d", "volume_30d", "liquidity_usd", "txns_24h", "created_at", "fdv"], default: "volume_24h" },
sort_dir: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" }
},
returns: { type: "object", properties: ["results", "page_info"] },
cost: 1,
note: "Response uses 'results' key. Fields: chain, address, price_usd, volume_usd_24h, volume_usd_7d, liquidity_usd, fdv_usd, txns_24h, created_at."
},
{
name: "getTopTokens",
description: "Get top tokens on a network ranked by volume, price, liquidity, or activity",
category: "tokens",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
page: { type: "number", required: false, default: 1 },
limit: { type: "number", required: false, default: 50, max: 100 },
order_by: { type: "string", required: false, enum: ["volume_24h", "price_usd", "liquidity_usd", "txns", "price_change"], default: "volume_24h" },
sort: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" }
},
returns: { type: "object", properties: ["tokens", "page_info"] },
cost: 1,
note: "Each token includes enriched metadata + multi-timeframe metrics (24h, 1h, 5m) with volume, buys, sells, txns, price change."
},
{
name: "getPoolDetails",
description: "Get detailed info about a pool",
category: "pools",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
pool_address: { type: "string", required: true, description: "Pool address", example: "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640" },
inversed: { type: "boolean", required: false, default: false, description: "Invert price ratio" }
},
returns: { type: "object" },
cost: 1
},
{
name: "getPoolOHLCV",
description: "Get historical price data (OHLCV) for a pool",
category: "pools",
parameters: {
network: { type: "string", required: true },
pool_address: { type: "string", required: true },
start: { type: "string", required: true, description: "Start time (Unix timestamp, RFC3339, or yyyy-mm-dd)" },
end: { type: "string", required: false },
limit: { type: "number", required: false, default: 1, max: 366 },
interval: { type: "string", required: false, enum: ["1m", "5m", "10m", "15m", "30m", "1h", "6h", "12h", "24h"], default: "24h" },
inversed: { type: "boolean", required: false, default: false }
},
returns: { type: "array", items: "OHLCVRecord" },
cost: 1
},
{
name: "getPoolTransactions",
description: "Get recent transactions for a pool",
category: "pools",
parameters: {
network: { type: "string", required: true },
pool_address: { type: "string", required: true },
page: { type: "number", required: false, default: 1, max: 100 },
limit: { type: "number", required: false, default: 10, max: 100 },
cursor: { type: "string", required: false, description: "Transaction ID for cursor pagination" },
from: { type: "number", required: false, description: "Filter transactions starting from this UNIX timestamp" },
to: { type: "number", required: false, description: "Filter transactions up to this UNIX timestamp" }
},
returns: { type: "object", properties: ["transactions", "page_info"] },
cost: 1
},
{
name: "getTokenDetails",
description: "Get detailed information about a token",
category: "tokens",
parameters: {
network: { type: "string", required: true },
token_address: { type: "string", required: true, description: "Token contract address", example: "JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN" }
},
returns: { type: "object" },
cost: 1
},
{
name: "getTokenPools",
description: "Get liquidity pools containing a token",
category: "tokens",
parameters: {
network: { type: "string", required: true },
token_address: { type: "string", required: true },
page: { type: "number", required: false, default: 1 },
limit: { type: "number", required: false, default: 10, max: 100 },
sort: { type: "string", required: false, enum: ["asc", "desc"], default: "desc" },
order_by: { type: "string", required: false, enum: ["volume_usd", "price_usd", "transactions", "last_price_change_usd_24h", "created_at"], default: "volume_usd" },
reorder: { type: "boolean", required: false },
address: { type: "string", required: false, description: "Filter by additional token address" }
},
returns: { type: "object", properties: ["pools", "page_info"] },
cost: 1
},
{
name: "getTokenMultiPrices",
description: "Batch fetch prices for up to 10 tokens",
category: "tokens",
parameters: {
network: { type: "string", required: true, description: "Network ID", example: "ethereum" },
tokens: { type: "array", required: true, format: "comma-separated", max_items: 10, description: "Up to 10 token addresses", example: ["0x123...", "0x456..."] }
},
returns: { type: "array", items: "TokenPrice" },
cost: 1
},
{
name: "search",
description: "Search across ALL networks for tokens, pools, and DEXes by name, symbol, or address",
category: "search",
parameters: {
query: { type: "string", required: true, description: "Search term", example: "uniswap" }
},
returns: { type: "object", properties: ["tokens", "pools", "dexes"] },
cost: 1
},
{
name: "getStats",
description: "Get high-level statistics about the DexPaprika ecosystem",
category: "utils",
parameters: {},
returns: { type: "object", properties: ["chains", "factories", "pools", "tokens"] },
cost: 1
}
],
network_synonyms,
validation_rules: {
address_formats: {
ethereum: "^0x[a-fA-F0-9]{40}$",
bsc: "^0x[a-fA-F0-9]{40}$",
polygon: "^0x[a-fA-F0-9]{40}$",
arbitrum: "^0x[a-fA-F0-9]{40}$",
optimism: "^0x[a-fA-F0-9]{40}$",
base: "^0x[a-fA-F0-9]{40}$",
avalanche: "^0x[a-fA-F0-9]{40}$",
fantom: "^0x[a-fA-F0-9]{40}$",
blast: "^0x[a-fA-F0-9]{40}$",
zksync: "^0x[a-fA-F0-9]{40}$",
linea: "^0x[a-fA-F0-9]{40}$",
scroll: "^0x[a-fA-F0-9]{40}$",
mantle: "^0x[a-fA-F0-9]{40}$",
celo: "^0x[a-fA-F0-9]{40}$",
cronos: "^0x[a-fA-F0-9]{40}$",
solana: "^[1-9A-HJ-NP-Za-km-z]{32,44}$",
aptos: "^0x[a-fA-F0-9]{64}$",
sui: "^0x[a-fA-F0-9]{64}$",
ton: "^[A-Za-z0-9_-]{48}$",
tron: "^T[A-Za-z1-9]{33}$"
},
batch_limits: {
getTokenMultiPrices: 10
}
},
rate_limits: {
requests_per_day: 10000,
burst_limit: 100
},
error_codes: {
DP400_INVALID_NETWORK: "Network ID not recognized. Use network_synonyms to normalize input.",
DP400_TOO_MANY_TOKENS: "Exceeded batch limit. Max {limit} tokens per request.",
DP400_INVALID_ADDRESS: "Token address format invalid for this network.",
DP400_MISSING_REQUIRED: "Required parameter missing.",
DP404_NOT_FOUND: "Resource not found. May not exist or be delisted.",
DP429_RATE_LIMIT: "Daily rate limit exceeded. Resets at {reset_at}."
},
meta: {
documentation: "https://docs.dexpaprika.com",
support: "https://github.com/coinpaprika/claude-marketplace"
},
workflow_patterns: {
discovery: {
description: "Discover available networks, DEXes, and pools",
steps: [
"getNetworks - List all supported blockchain networks",
"getNetworkDexes - Find DEXes on a specific network",
"getNetworkPools - Browse top liquidity pools",
],
example_use_case: "Finding the most active trading pools on a network",
},
price_tracking: {
description: "Track token prices and historical data",
steps: [
"search - Find token by name, symbol, or address",
"getTokenDetails - Get current price and metadata",
"getTokenPools - Find all pools containing the token",
"getPoolOHLCV - Get historical price data (candlestick charts)",
],
example_use_case: "Analyzing price trends for a specific token over 7 days",
},
multi_price: {
description: "Get prices for multiple tokens at once (batch operation)",
steps: [
"getNetworks - Verify network ID",
"getTokenMultiPrices - Fetch up to 10 token prices in one call",
],
example_use_case: "Building a portfolio tracker showing multiple token prices",
limitations: "Maximum 10 tokens per request",
},
find_tokens_by_mcap: {
description: "Discover tokens within specific market cap ranges",
steps: [
"getNetworkPools - Get pools sorted by volume",
"Extract tokens[].fdv (Fully Diluted Valuation) from response",
"Filter tokens where fdv is in your target range (e.g., $10M-$100M)",
"getTokenDetails - Deep dive into promising candidates",
],
example_use_case: "Finding mid-cap tokens ($10M-$100M) with high trading volume",
note: "Currently requires client-side filtering by fdv field",
},
technical_analysis: {
description: "Perform comprehensive technical analysis on a token",
steps: [
"search - Locate token by name/symbol",
"getTokenDetails - Get current metrics (price, fdv, volume)",
"getTokenPools - Identify highest liquidity pool",
"getPoolOHLCV - Fetch price history (OHLC candlesticks)",
"getPoolTransactions - Analyze recent trading activity",
],
example_use_case: "Creating buy/sell signals based on price patterns and volume",
recommended_intervals: ["5m", "1h", "24h"],
},
whale_watching: {
description: "Monitor large transactions and whale activity",
steps: [
"getTokenPools - Find pools for target token",
"getPoolTransactions - Get recent transactions with details",
"Filter by amount_0 or amount_1 for large trades",
"Track sender/recipient addresses for patterns",
],
example_use_case: "Alert when transactions >$100K occur",
tip: "Use cursor pagination for continuous monitoring",
},
new_token_discovery: {
description: "Find newly created tokens and pools",
steps: [
"getNetworkPools - Sort by created_at descending",
"getPoolDetails - Verify liquidity and token info",
"getPoolTransactions - Check initial trading activity",
"getTokenDetails - Validate token metrics",
],
example_use_case: "Finding tokens launched in the last 24 hours",
warning: "New tokens are extremely high risk - verify contracts carefully",
},
dex_comparison: {
description: "Compare liquidity across different DEXes",
steps: [
"getNetworkDexes - List all DEXes on network",
"getDexPools - Get pools for each DEX",
"Compare volume_usd and transactions across DEXes",
],
example_use_case: "Finding best liquidity for a trading pair",
},
pool_filtering: {
description: "Find pools matching specific criteria using server-side filters",
steps: [
"getNetworkPoolsFilter - Filter by volume, transactions, or creation time",
"getPoolDetails - Deep dive into matching pools",
"getPoolOHLCV - Analyze price history of filtered pools",
],
example_use_case: "Finding high-volume pools created in the last 24 hours",
note: "More efficient than fetching all pools and filtering client-side",
},
arbitrage_opportunities: {
description: "Identify price discrepancies across pools",
steps: [
"getTokenPools - Get all pools for a token",
"Compare price_usd across different pools",
"getPoolDetails - Verify liquidity depth",
"Calculate potential arbitrage profit minus gas",
],
example_use_case: "Finding price differences between DEXes",
note: "Consider gas fees, slippage, and MEV when calculating profitability",
},
},
parameter_examples: {
getNetworkPools: {
high_volume_pools: {
description: "Top 20 pools by 24h trading volume",
params: { network: "ethereum", limit: 20, order_by: "volume_usd", sort: "desc" },
},
new_pools: {
description: "Recently created pools (last 24h)",
params: { network: "bsc", order_by: "created_at", sort: "desc", limit: 50 },
},
most_transactions: {
description: "Pools with highest transaction count",
params: { network: "base", order_by: "transactions", sort: "desc", limit: 10 },
},
price_movers: {
description: "Pools with biggest 24h price changes",
params: { network: "solana", order_by: "last_price_change_usd_24h", sort: "desc", limit: 30 },
},
},
getPoolOHLCV: {
last_24h_hourly: {
description: "Hourly candlesticks for last 24 hours",
params: { network: "ethereum", pool_address: "0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640", start: "2025-10-15", interval: "1h", limit: 24 },
},
last_week_daily: {
description: "Daily candlesticks for last 7 days",
params: { network: "bsc", pool_address: "0x...", start: "2025-10-09", interval: "24h", limit: 7 },
},
intraday_5min: {
description: "5-minute intervals for day trading",
params: { network: "base", pool_address: "0x...", start: "2025-10-16T00:00:00Z", interval: "5m", limit: 288, inversed: false },
},
monthly_overview: {
description: "30 days of daily data",
params: { network: "arbitrum", pool_address: "0x...", start: "2025-09-16", interval: "24h", limit: 30 },
},
},
getPoolTransactions: {
recent_activity: {
description: "Last 50 transactions",
params: { network: "ethereum", pool_address: "0x...", limit: 50, page: 1 },
},
whale_trades: {
description: "Large transactions (filter client-side by volume)",
params: { network: "bsc", pool_address: "0x...", limit: 100, page: 1 },
post_filter: "Filter where volume_1 > 10000 for trades >$10K",
},
cursor_pagination: {
description: "Using cursor for efficient pagination",
params: { network: "polygon", pool_address: "0x...", limit: 20, cursor: "0xabcd1234..." },
note: "Use last transaction ID as cursor for next page",
},
time_range: {
description: "Transactions within a specific time window (last 7 days max)",
params: { network: "ethereum", pool_address: "0x...", limit: 100, from: 1712700000, to: 1712800000 },
note: "from is inclusive, to is exclusive. Results always capped to last 7 days.",
},
},
getTokenMultiPrices: {
portfolio_tracking: {
description: "Get prices for multiple tokens at once",
params: {
network: "ethereum",
tokens: [
"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",
"0xdac17f958d2ee523a2206206994597c13d831ec7",
"0x6b175474e89094c44da98b954eedeac495271d0f"
]
},
note: "Maximum 10 tokens per request",
},
},
search: {
by_name: { description: "Find token by name", params: { query: "uniswap" } },
by_symbol: { description: "Find token by ticker symbol", params: { query: "USDC" } },
by_address: { description: "Find token by contract address", params: { query: "0xa0b8..." } },
},
},
important_fields: {
fdv: { name: "Fully Diluted Valuation", description: "Total supply x current price. Use this for market cap comparisons.", example: "fdv: 50000000 means $50M market cap", use_case: "Filtering tokens by market cap range" },
volume_usd: { name: "24h Trading Volume (USD)", description: "Total USD value traded in last 24 hours", use_case: "Identifying liquid vs illiquid tokens" },
last_price_change_usd_24h: { name: "24h Absolute Price Change", description: "Absolute price change in USD, NOT percentage", example: "0.5 means +$0.50", calculation: "(last_price_change_usd_24h / price_usd) x 100" },
transactions: { name: "Total Transaction Count", description: "Cumulative number of swaps since pool creation", use_case: "Gauge trading activity and pool maturity" },
inversed: { name: "Inverse Token Pair", description: "When true, flips token0/token1", example: "USDC/WETH -> WETH/USDC", use_case: "Viewing price from opposite perspective" },
price_usd: { name: "Current Price (USD)", description: "Real-time token price in USD", note: "Based on most recent pool transaction" },
created_at: { name: "Pool Creation Time", format: "ISO 8601 timestamp", example: "2025-10-16T10:00:08Z", use_case: "Finding new pools, calculating pool age" },
"amount_0 / amount_1": { name: "Transaction Token Amounts", description: "Positive = tokens added; Negative = removed", use_case: "Identifying buy vs sell transactions" },
},
common_pitfalls: {
external_apis: { issue: "Using external APIs when DexPaprika has data", solution: "Prefer built-in tools", example: "Don't fetch OHLCV elsewhere when getPoolOHLCV exists" },
batch_limits: { issue: "Exceeding 10 token limit in getTokenMultiPrices", solution: "Split requests into batches of 10", example: "For 25 tokens: 10 + 10 + 5" },
response_size: { issue: "Response too large", solution: "Reduce limit, start with 10", prevention: "Use fields and smaller limits" },
ohlcv_empty: { issue: "getPoolOHLCV returns []", cause: "Pool too new", solution: "Use getPoolTransactions for very new pools" },
invalid_network: { issue: "Network not found", solution: "Call getNetworks first", tip: "Check network_synonyms" },
pagination_confusion: { issue: "Only fetching page 0", solution: "Use cursor or increment page", note: "Page limit 100" },
price_change_misinterpretation: { issue: "Assuming 24h change is percentage", reality: "It's absolute USD", calculation: "(change/price_usd)x100" },
fdv_vs_mcap: { issue: "FDV vs circulating market cap", clarification: "FDV includes locked/unvested", note: "Circulating may be lower" },
},
best_practices: {
workflow: [
"Always call getCapabilities first",
"Call getNetworks to validate network IDs",
"Use search to find tokens by name/symbol before details",
"Start with small limit values and increase if needed",
"Cache getNetworks response",
],
performance: [
"Use getTokenMultiPrices for batch operations",
"Client-side caching for networks and DEX lists",
"Use cursor pagination for large histories",
"Request only necessary OHLCV intervals",
],
data_quality: [
"Verify pool liquidity before trading",
"Cross-reference token data across pools",
"Be cautious with pools <24h old",
"Check created_at for brand new tokens",
],
error_handling: [
"Handle empty OHLCV arrays gracefully",
"Retry with backoff for rate limits",
"Validate network IDs against getNetworks",
"Reduce limits if responses are too large",
],
},
quick_reference: {
market_cap_ranges: {
nano_cap: "< $1M (extremely high risk)",
micro_cap: "$1M - $10M (very high risk)",
small_cap: "$10M - $100M (high risk)",
mid_cap: "$100M - $1B (moderate risk)",
large_cap: "> $1B (lower risk)",
},
ohlcv_intervals: {
scalping: ["1m", "5m"],
day_trading: ["5m", "15m", "1h"],
swing_trading: ["1h", "6h", "24h"],
position_trading: ["24h"],
},
order_by_options: [
"volume_usd - 24h trading volume",
"price_usd - Current price",
"transactions - Total swap count",
"last_price_change_usd_24h - 24h price change",
"created_at - Pool creation time",
],
max_limits: {
pools_per_request: 100,
transactions_per_request: 100,
ohlcv_data_points: 366,
multi_token_prices: 10,
transaction_pages: "Unlimited (use cursor)",
},
supported_dexes: [
"Uniswap V2/V3",
"PancakeSwap V2/V3",
"SushiSwap",
"Curve",
"Balancer",
"And many more - use getNetworkDexes",
],
},
error_handling: {
empty_ohlcv_array: { error_pattern: "getPoolOHLCV returns []", cause: "Pool created too recently", solution: "Use getPoolTransactions", prevention: "Check created_at" },
response_too_large: { error_pattern: "Response exceeds size budget", cause: "Too many items", solution: "Reduce limit or paginate" },
invalid_network_id: { error_pattern: "Network not found", cause: "Incorrect network", solution: "Call getNetworks", check: "Use network_synonyms" },
rate_limit_exceeded: { error_pattern: "Too many requests", solution: "Exponential backoff", prevention: "Use batching where possible" },
invalid_time_range: { error_pattern: "Time range exceeds 1 year", cause: "start/end span too large", solution: "Split into multiple requests", note: "Limit 366 data points" },
pool_not_found: { error_pattern: "Pool address not found", cause: "Invalid or unknown pool", solution: "Find from getNetworkPools or getTokenPools", tip: "Addresses are network-specific" },
},
use_case_templates: {
find_trending_tokens: {
goal: "Find tokens with >100% gains in last 24h on BSC",
steps: [
"1. getNetworks -> confirm 'bsc' is valid",
"2. getNetworkPools(network='bsc', order_by='last_price_change_usd_24h', sort='desc', limit=50)",
"3. Filter results where (last_price_change_usd_24h / price_usd) x 100 > 100",
"4. For each promising token: getPoolDetails, getPoolOHLCV, getPoolTransactions",
"5. Analyze: volume trends, liquidity, holder activity",
],
},
build_price_alert_bot: {
goal: "Alert when ETH price crosses a threshold",
steps: [
"1. search(query='WETH') -> find WETH token address",
"2. getTokenPools(network='ethereum', token_address='0x...') -> find highest liquidity pool",
"3. Poll getPoolDetails to check price_usd",
"4. Trigger alert on threshold",
"5. Optional: getPoolTransactions to see what triggered the move",
],
},
analyze_new_token_launch: {
goal: "Evaluate a token launched recently",
steps: [
"1. getNetworkPools(order_by='created_at', sort='desc') -> find recent pools",
"2. getPoolDetails -> check liquidity depth, token info",
"3. getPoolTransactions(limit=100) -> analyze initial trades",
"4. Check: whale wallets? mostly buys or sells?",
"5. getTokenDetails -> verify fdv",
],
},
portfolio_rebalancing: {
goal: "Check prices of 15 tokens to rebalance portfolio",
steps: [
"1. Split 15 tokens into batches: [10] + [5]",
"2. getTokenMultiPrices for batch1",
"3. getTokenMultiPrices for batch2",
"4. Calculate portfolio weights",
"5. For tokens to trade: getTokenPools -> find best liquidity",
],
},
},
integration_tips: {
trading_bots: [
"Cache network IDs and DEX lists",
"Implement circuit breakers for rate limits",
"Store historical OHLCV locally for longer-term charts",
],
portfolio_trackers: [
"Use getTokenMultiPrices for efficient batch price fetching",
"Cache token metadata (symbols, names)",
"Poll price updates every 30-60s",
],
analytics_dashboards: [
"Pre-fetch and cache getNetworks and getNetworkDexes",
"Use lazy loading; avoid fetching all pools upfront",
"Use transaction data for volume charts",
],
token_screeners: [
"Fetch pools with high volume",
"Client-side filter by fdv for market cap ranges",
"Use created_at to separate new vs established tokens",
"Combine filters: volume + fdv + price_change",
],
},
version_history: {
"1.1.0": "Added capabilities endpoint and basic workflows; enhanced with detailed examples and guidance",
"1.2.0": "Added getNetworkPoolsFilter tool; fixed pagination to 1-indexed; updated stats to 33 networks, 28M+ pools, 25M+ tokens",
"1.4.0": "Added filterNetworkTokens and getTopTokens tools; expanded getNetworkPoolsFilter with volume_7d, liquidity params; enriched network/dex responses with volume, txns, pools_count",
"1.3.0": "Synced npm package with hosted MCP server; added getCapabilities, getNetworkPoolsFilter; structured error handling; snake_case parameters",
},
};
}
// MCP server instance
const server = new McpServer({
name: 'dexpaprika',
version: SERVER_VERSION,
description: 'MCP server for accessing DexPaprika API data for decentralized exchanges and tokens',
});
// getNetworks
server.tool(
'getNetworks',
'START HERE: Prefer calling getCapabilities first to see workflows and examples. Then use this to list supported networks.',
async () => {
try {
const response = await fetchFromAPI('/networks');
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getCapabilities
server.tool(
'getCapabilities',
'Return server capabilities, workflow patterns, network synonyms, common pitfalls, and best-practice sequences. Use this to onboard agents quickly.',
async () => {
try {
const doc = await buildCapabilitiesDocument();
return { content: [{ type: "text", text: JSON.stringify(doc, null, 2) }] };
} catch (error) {
return formatMcpError(error);
}
}
);
// getNetworkDexes
server.tool(
'getNetworkDexes',
'Get available DEXes on a specific network. TIP: Call getCapabilities for examples. REQUIRED: network. OPTIONAL: page, limit, sort, order_by.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
page: z.number().optional().default(1).describe("OPTIONAL: Page number for pagination (default: 1, 1-indexed)"),
limit: z.number().optional().default(10).describe("OPTIONAL: Number of items per page (default: 10, max: 100)"),
sort: z.enum(['asc', 'desc']).optional().default('desc').describe("OPTIONAL: Sort order (default: 'desc')"),
order_by: z.enum(['pool']).optional().describe("OPTIONAL: How to order the returned data")
},
async ({ network, page, limit, sort, order_by }) => {
try {
let endpoint = `/networks/${network}/dexes?page=${page}&limit=${limit}`;
if (sort) endpoint += `&sort=${sort}`;
if (order_by) endpoint += `&order_by=${order_by}`;
const response = await fetchFromAPI(endpoint);
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getNetworkPools
server.tool(
'getNetworkPools',
'PRIMARY POOL FUNCTION: Get top liquidity pools on a network. TIP: Call getCapabilities first for parameter examples. REQUIRED: network. OPTIONAL: page, limit, sort, order_by.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
page: z.number().optional().default(1).describe("OPTIONAL: Page number for pagination (default: 1, 1-indexed)"),
limit: z.number().optional().default(10).describe("OPTIONAL: Number of items per page (default: 10, max: 100)"),
sort: z.enum(['asc', 'desc']).optional().default('desc').describe("OPTIONAL: Sort order (default: 'desc')"),
order_by: z.enum(['volume_usd', 'price_usd', 'transactions', 'last_price_change_usd_24h', 'created_at']).optional().default('volume_usd').describe("OPTIONAL: Field to order by (default: 'volume_usd')")
},
async ({ network, page, limit, sort, order_by }) => {
try {
const endpoint = `/networks/${network}/pools?page=${page}&limit=${limit}&sort=${sort}&order_by=${order_by}`;
const response = await fetchFromAPI(endpoint);
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getDexPools
server.tool(
'getDexPools',
'Get pools from a specific DEX on a network. TIP: See examples in getCapabilities. REQUIRED: network, dex. OPTIONAL: page, limit, sort, order_by.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
dex: z.string().describe("REQUIRED: DEX identifier from getNetworkDexes (e.g., 'uniswap_v3')"),
page: z.number().optional().default(1).describe("OPTIONAL: Page number for pagination (default: 1, 1-indexed)"),
limit: z.number().optional().default(10).describe("OPTIONAL: Number of items per page (default: 10, max: 100)"),
sort: z.enum(['asc', 'desc']).optional().default('desc').describe("OPTIONAL: Sort order (default: 'desc')"),
order_by: z.enum(['volume_usd', 'price_usd', 'transactions', 'last_price_change_usd_24h', 'created_at']).optional().default('volume_usd').describe("OPTIONAL: Field to order by (default: 'volume_usd')")
},
async ({ network, dex, page, limit, sort, order_by }) => {
try {
const endpoint = `/networks/${network}/dexes/${dex}/pools?page=${page}&limit=${limit}&sort=${sort}&order_by=${order_by}`;
const response = await fetchFromAPI(endpoint);
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getNetworkPoolsFilter
server.tool(
'getNetworkPoolsFilter',
'Filter pools by volume, liquidity, transactions, and creation time. REQUIRED: network. OPTIONAL: page, limit, volume_24h_min/max, volume_7d_min/max, liquidity_usd_min/max, txns_24h_min, created_after, created_before, sort_by, sort_dir.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
page: z.number().optional().default(1).describe("OPTIONAL: Page number for pagination (default: 1, 1-indexed)"),
limit: z.number().optional().default(50).describe("OPTIONAL: Number of items per page (default: 50, max: 100)"),
volume_24h_min: z.number().optional().describe("OPTIONAL: Minimum 24h volume in USD"),
volume_24h_max: z.number().optional().describe("OPTIONAL: Maximum 24h volume in USD"),
volume_7d_min: z.number().optional().describe("OPTIONAL: Minimum 7d volume in USD"),
volume_7d_max: z.number().optional().describe("OPTIONAL: Maximum 7d volume in USD"),
liquidity_usd_min: z.number().optional().describe("OPTIONAL: Minimum pool liquidity in USD"),
liquidity_usd_max: z.number().optional().describe("OPTIONAL: Maximum pool liquidity in USD"),
txns_24h_min: z.number().optional().describe("OPTIONAL: Minimum number of transactions in 24h"),
created_after: z.number().optional().describe("OPTIONAL: Only pools created after this UNIX timestamp"),
created_before: z.number().optional().describe("OPTIONAL: Only pools created before this UNIX timestamp"),
sort_by: z.enum(['volume_24h', 'volume_7d', 'volume_30d', 'liquidity', 'txns_24h', 'created_at']).optional().default('volume_24h').describe("OPTIONAL: Field to sort by (default: 'volume_24h')"),
sort_dir: z.enum(['asc', 'desc']).optional().default('desc').describe("OPTIONAL: Sort direction (default: 'desc')")
},
async ({ network, page, limit, volume_24h_min, volume_24h_max, volume_7d_min, volume_7d_max, liquidity_usd_min, liquidity_usd_max, txns_24h_min, created_after, created_before, sort_by, sort_dir }) => {
try {
let endpoint = `/networks/${network}/pools/filter?page=${page}&limit=${limit}&sort_by=${sort_by}&sort_dir=${sort_dir}`;
if (volume_24h_min !== undefined) endpoint += `&volume_24h_min=${volume_24h_min}`;
if (volume_24h_max !== undefined) endpoint += `&volume_24h_max=${volume_24h_max}`;
if (volume_7d_min !== undefined) endpoint += `&volume_7d_min=${volume_7d_min}`;
if (volume_7d_max !== undefined) endpoint += `&volume_7d_max=${volume_7d_max}`;
if (liquidity_usd_min !== undefined) endpoint += `&liquidity_usd_min=${liquidity_usd_min}`;
if (liquidity_usd_max !== undefined) endpoint += `&liquidity_usd_max=${liquidity_usd_max}`;
if (txns_24h_min !== undefined) endpoint += `&txns_24h_min=${txns_24h_min}`;
if (created_after !== undefined) endpoint += `&created_after=${created_after}`;
if (created_before !== undefined) endpoint += `&created_before=${created_before}`;
const response = await fetchFromAPI(endpoint);
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getPoolDetails
server.tool(
'getPoolDetails',
'Get detailed info about a pool. TIP: Use getCapabilities for workflows. REQUIRED: network, pool_address. OPTIONAL: inversed.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
pool_address: z.string().describe("REQUIRED: Pool address or identifier (e.g., '0x88e6a0c2ddd26feeb64f039a2c41296fcb3f5640')"),
inversed: z.boolean().optional().default(false).describe("OPTIONAL: Whether to invert the price ratio (default: false)")
},
async ({ network, pool_address, inversed }) => {
try {
const endpoint = `/networks/${network}/pools/${pool_address}?inversed=${inversed}`;
const response = await fetchFromAPI(endpoint);
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getPoolOHLCV
server.tool(
'getPoolOHLCV',
'Get historical price data (OHLCV) for a pool. TIP: See intervals in getCapabilities. REQUIRED: network, pool_address, start. OPTIONAL: end, limit, interval, inversed.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
pool_address: z.string().describe("REQUIRED: Pool address or identifier"),
start: z.string().describe("REQUIRED: Start time for historical data (Unix timestamp, RFC3339 timestamp, or yyyy-mm-dd format)"),
end: z.string().optional().describe("OPTIONAL: End time for historical data (max 1 year from start)"),
limit: z.number().optional().default(1).describe("OPTIONAL: Number of data points to retrieve (default: 1, max: 366)"),
interval: z.enum(['1m', '5m', '10m', '15m', '30m', '1h', '6h', '12h', '24h']).optional().default('24h').describe("OPTIONAL: Interval granularity (default: '24h')"),
inversed: z.boolean().optional().default(false).describe("OPTIONAL: Whether to invert the price ratio for alternative pair perspective (default: false)")
},
async ({ network, pool_address, start, end, limit, interval, inversed }) => {
try {
let endpoint = `/networks/${network}/pools/${pool_address}/ohlcv?start=${encodeURIComponent(start)}&limit=${limit}&interval=${interval}&inversed=${inversed}`;
if (end) endpoint += `&end=${encodeURIComponent(end)}`;
const response = await fetchFromAPI(endpoint);
return formatMcpResponse(response);
} catch (error) {
return formatMcpError(error);
}
}
);
// getPoolTransactions
server.tool(
'getPoolTransactions',
'Get recent transactions for a pool. TIP: Use cursor for long histories (see getCapabilities). Use from/to for time-range filtering (UNIX timestamps, results capped to last 7 days). REQUIRED: network, pool_address. OPTIONAL: page, limit, cursor, from, to.',
{
network: z.string().describe("REQUIRED: Network ID from getNetworks (e.g., 'ethereum', 'solana')"),
pool_address: z.string().describe("REQUIRED: Pool address or identifier"),
page: z.number().optional().default(1).describe("OPTIONAL: Page number for pagination, up to 100 pages (default: 1, 1-indexed)"),
limit: z.number().optional().default(10).describe("OPTIONAL: Number of items per page (default: 10, max: 100)"),
cursor: z.string().optional().describe("OPTIONAL: Transaction ID used for cursor-based pagination"),
from: z.number().