Skip to main content
Glama

Meteora DLMM MCP Server

by fciaf420
index.ts9.6 kB
// Hybrid Meteora DLMM MCP Server - API for reads, SDK for writes require('dotenv').config(); const { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js"); const { z } = require("zod"); const { DLMM } = require("@meteora-ag/dlmm"); const { Connection, PublicKey, Keypair } = require("@solana/web3.js"); const https = require('https'); // Configuration schema const configSchema = z.object({ rpcUrl: z.string() .default(process.env.RPC_URL || "https://solana-rpc.publicnode.com") .describe("Solana RPC URL (e.g., https://solana-rpc.publicnode.com)"), walletPrivateKey: z.string() .optional() .default(process.env.WALLET_PRIVATE_KEY || undefined) .describe("Base64 encoded wallet private key for transactions (KEEP SECURE!)"), debug: z.boolean() .default(process.env.DEBUG === 'true' || false) .describe("Enable debug logging"), maxRetries: z.number() .default(parseInt(process.env.MAX_RETRIES || '3')) .describe("Maximum retries for failed RPC calls"), rpcTimeout: z.number() .default(parseInt(process.env.RPC_TIMEOUT || '30000')) .describe("Timeout for RPC calls in milliseconds") }); module.exports = function ({ config }: { config: any }) { const connection = new Connection(config.rpcUrl); const apiBase = "https://dlmm-api.meteora.ag"; // Initialize wallet if private key provided let wallet: any = null; if (config.walletPrivateKey) { try { wallet = Keypair.fromSecretKey(Buffer.from(config.walletPrivateKey, 'base64')); console.log(`Wallet loaded: ${wallet.publicKey.toString()}`); } catch (error: any) { console.error("Invalid private key format. Expected base64 encoded private key."); } } // Helper function for API calls function apiCall(endpoint: string): Promise<any> { return new Promise((resolve, reject) => { const url = `${apiBase}${endpoint}`; if (config.debug) console.log(`API Call: ${url}`); https.get(url, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => { try { resolve(JSON.parse(data)); } catch (error) { reject(new Error(`Failed to parse JSON: ${error.message}`)); } }); }).on('error', reject); }); } const server = new McpServer({ name: "Meteora DLMM MCP Server (Hybrid)", version: "2.0.0", }); // Tool 1: Get Pool Information (API-based) server.tool( "get_pool_info", "Get detailed information about a Meteora DLMM pool", { poolAddress: z.string().describe("DLMM pool address"), }, async ({ poolAddress }: { poolAddress: string }) => { try { const poolData = await apiCall(`/pair/${poolAddress}`); const result = { poolAddress, name: poolData.name, tokenX: poolData.mint_x, tokenY: poolData.mint_y, activeBinId: poolData.active_bin_id, fees24h: poolData.fees_24h || 0, volume24h: poolData.volume_24h || 0, liquidity: poolData.liquidity || "0", currentPrice: poolData.current_price, binStep: poolData.bin_step }; return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error fetching pool info: ${error.message}` }] }; } } ); // Tool 2: Get User Positions (API-based) server.tool( "get_user_positions", "Get all user positions for a wallet address", { userWallet: z.string().describe("User wallet address"), }, async ({ userWallet }: { userWallet: string }) => { try { // Try API approach first try { const positions = await apiCall(`/user/${userWallet}`); if (positions && positions.length > 0) { return { content: [{ type: "text", text: JSON.stringify(positions, null, 2) }] }; } } catch (apiError) { if (config.debug) console.log('API user endpoint not available, using SDK fallback'); } // Fallback to SDK (might fail with restricted RPC) const userPositions = await DLMM.getAllLbPairPositionsByUser( connection, new PublicKey(userWallet) ); if (userPositions.length === 0) { return { content: [{ type: "text", text: "No DLMM positions found for this wallet." }] }; } const positionDetails = userPositions.map((position: any) => ({ positionAddress: position.publicKey.toString(), poolAddress: position.lbPair.toString(), })); return { content: [{ type: "text", text: JSON.stringify(positionDetails, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error fetching positions: ${error.message}` }] }; } } ); // Tool 3: Get Popular Pools (API-based) server.tool( "get_popular_pools", "Get list of popular Meteora DLMM pools", { limit: z.number().optional().default(10).describe("Number of pools to return"), }, async ({ limit }: { limit?: number }) => { try { const allPairs = await apiCall('/pair/all'); // Sort by liquidity and take top pools const sortedPairs = allPairs .filter((pair: any) => parseFloat(pair.liquidity || '0') > 0) .sort((a: any, b: any) => parseFloat(b.liquidity || '0') - parseFloat(a.liquidity || '0')) .slice(0, limit || 10); const poolInfo = sortedPairs.map((pool: any) => ({ address: pool.address, name: pool.name, tokenX: pool.mint_x, tokenY: pool.mint_y, liquidity: pool.liquidity, volume24h: pool.volume_24h || 0, fees24h: pool.fees_24h || 0 })); return { content: [{ type: "text", text: JSON.stringify(poolInfo, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error fetching pools: ${error.message}` }] }; } } ); // Tool 4: Get Claimable Fees (Hybrid: API for pool info, SDK for fee calc) server.tool( "get_claimable_fees", "Get claimable fees for a specific position", { poolAddress: z.string().describe("DLMM pool address"), positionAddress: z.string().describe("Position address"), }, async ({ poolAddress, positionAddress }: { poolAddress: string; positionAddress: string }) => { try { // Get pool info from API const poolData = await apiCall(`/pair/${poolAddress}`); // For now, return pool info - fee calculation would need SDK with unrestricted RPC const result = { positionAddress, poolAddress, poolName: poolData.name, note: "Fee calculation requires SDK with unrestricted RPC endpoint", suggestion: "Use a paid RPC provider (Helius, QuickNode) for fee calculations" }; return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error getting claimable fees: ${error.message}` }] }; } } ); // Tool 5: Claim Fees (SDK-based transaction) server.tool( "claim_fees", "Claim accumulated fees from a position (requires wallet configuration)", { poolAddress: z.string().describe("DLMM pool address"), positionAddress: z.string().describe("Position address"), }, async ({ poolAddress, positionAddress }: { poolAddress: string; positionAddress: string }) => { if (!wallet) { return { content: [{ type: "text", text: "❌ Error: Wallet not configured. Please provide 'walletPrivateKey' in the server configuration to perform transactions." }] }; } try { // This requires SDK and unrestricted RPC const dlmmPool = await DLMM.create(connection, new PublicKey(poolAddress)); const claimFeeTx = await dlmmPool.claimSwapFee({ owner: wallet.publicKey, position: new PublicKey(positionAddress), }); const signature = await connection.sendTransaction(claimFeeTx, [wallet]); await connection.confirmTransaction(signature); return { content: [{ type: "text", text: `✅ Fees claimed successfully! Transaction: ${signature} View on Solscan: https://solscan.io/tx/${signature}` }] }; } catch (error: any) { return { content: [{ type: "text", text: `❌ Error claiming fees: ${error.message} Note: This operation requires an unrestricted RPC endpoint. Consider upgrading to a paid RPC provider.` }] }; } } ); return server.server; } module.exports.configSchema = configSchema;

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/fciaf420/Meteora-DLMM-MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server