Skip to main content
Glama

Web3 MCP Server

rubic.ts18.1 kB
import { z } from "zod"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; const RUBIC_API_BASE = 'https://api-v2.rubic.exchange/api'; // Define interfaces for type safety matching the actual Rubic API response interface TokenInfo { address: string; blockchain: string; blockchainId: number; decimals: number; name: string; symbol: string; price?: number; } interface RubicQuoteResponse { id: string; estimate: { destinationTokenAmount: string; destinationTokenMinAmount: string; destinationUsdAmount: number; destinationUsdMinAmount: number; destinationWeiAmount: string; destinationWeiMinAmount: string; durationInMinutes: number; priceImpact: number; slippage: number; }; fees: { gasTokenFees: { nativeToken: TokenInfo; protocol: { fixedAmount: string; fixedUsdAmount: number; fixedWeiAmount: string; }; provider: { fixedAmount: string; fixedUsdAmount: number; fixedWeiAmount: string; }; }; percentFees: { percent: number; token: TokenInfo | null; }; }; providerType: string; routing: Array<{ path: Array<TokenInfo & { amount: string }>; provider: string; type: string; }>; swapType: 'cross-chain' | 'on-chain'; tokens: { from: TokenInfo & { amount: string }; to: TokenInfo; }; transaction: { approvalAddress: string; }; warnings: Array<any>; } interface AvailableBlockchain { name: string; id: number; testnet: boolean; providers: { crossChain: string[]; onChain: string[]; }; proxyAvailable: boolean; type: 'EVM' | 'TRON' | 'SOLANA' | 'Other'; } interface CrossChainStatusResponse { srcTxHash: string; dstTxHash?: string; status: 'pending' | 'indexing' | 'revert' | 'failed' | 'claim' | 'success' | 'error'; message?: string; error?: string; bridgeName?: string; } export function registerRubicTools(server: McpServer) { // Get available blockchains server.tool( "getRubicSupportedChains", "Get a list of all blockchains supported by Rubic for cross-chain bridging.", { includeTestnets: z.boolean().optional().describe("Include testnet blockchains in the results."), }, async ({ includeTestnets = false }) => { try { const url = new URL(`${RUBIC_API_BASE}/info/chains`); url.searchParams.append('includeTestnets', includeTestnets.toString()); const response = await fetch(url.toString(), { method: 'GET', headers: { 'Accept': 'application/json', }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Rubic API error (${response.status}): ${errorText}`); } const data = await response.json() as AvailableBlockchain[]; // Format the response for readable output const formattedChains = data.map(chain => ({ name: chain.name, id: chain.id, testnet: chain.testnet, type: chain.type, crossChainProviders: chain.providers.crossChain, onChainProviders: chain.providers.onChain, proxyAvailable: chain.proxyAvailable })); // Create readable text response const textResponse = `Available blockchains for cross-chain bridging:\n\n${ formattedChains.map(chain => `${chain.name} (ID: ${chain.id})${chain.testnet ? ' [TESTNET]' : ''}\n` + `Type: ${chain.type}\n` + `Cross-Chain Providers: ${chain.crossChainProviders.join(', ')}\n` + `On-Chain Providers: ${chain.onChainProviders.join(', ')}\n` + `Fee Collection Available: ${chain.proxyAvailable ? 'Yes' : 'No'}\n` ).join('\n') }`; return { content: [ { type: "text", text: textResponse } ], data: formattedChains }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to get supported blockchains: ${error.message}` } ] }; } } ); // Get bridge quote server.tool( "getRubicBridgeQuote", "Get the best cross-chain bridge route for swapping tokens between different blockchains.", { srcTokenAddress: z.string().describe("Source token address. Use 0x0000000000000000000000000000000000000000 for native tokens like ETH, BNB, etc."), srcTokenBlockchain: z.string().describe("Source blockchain name (e.g., ETH, BSC, POLYGON, etc.)"), srcTokenAmount: z.string().describe("Amount of source token to bridge (as a string with decimals)"), dstTokenAddress: z.string().describe("Destination token address. Use 0x0000000000000000000000000000000000000000 for native tokens."), dstTokenBlockchain: z.string().describe("Destination blockchain name (e.g., ETH, BSC, POLYGON, etc.)"), walletAddress: z.string().optional().describe("Wallet address to send tokens to on the destination blockchain"), slippageTolerance: z.number().min(0.01).max(50).optional().describe("Slippage tolerance in percentage (min: 0.01, max: 50)"), showFailedRoutes: z.boolean().optional().describe("Show failed routes in the response"), includeTestnets: z.boolean().optional().describe("Include testnets in calculations"), timeout: z.number().min(5).max(60).optional().describe("Calculation timeout in seconds (min: 5, max: 60)"), }, async ({ srcTokenAddress, srcTokenBlockchain, srcTokenAmount, dstTokenAddress, dstTokenBlockchain, walletAddress, slippageTolerance = 1, showFailedRoutes = false, includeTestnets = false, timeout = 30 }) => { try { const url = new URL(`${RUBIC_API_BASE}/routes/quoteBest`); // Ensure all values are properly formatted as strings where needed const requestBody = { srcTokenBlockchain: String(srcTokenBlockchain), srcTokenAddress: String(srcTokenAddress), srcTokenAmount: String(srcTokenAmount), dstTokenBlockchain: String(dstTokenBlockchain), dstTokenAddress: String(dstTokenAddress), referrer: "web3-mcp", // Recommended to set your application name as referrer timeout: Number(timeout), includeTestnets: Boolean(includeTestnets), showFailedRoutes: Boolean(showFailedRoutes), slippageTolerance: Number(slippageTolerance) / 100, // Convert percentage to decimal ...(walletAddress ? { walletAddress: String(walletAddress) } : {}) }; const response = await fetch(url.toString(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Rubic API error (${response.status}): ${errorText}`); } const data = await response.json() as RubicQuoteResponse; // Format response for readable output let textResponse = `Bridge Quote Results:\n\n`; textResponse += `From: ${data.tokens.from.amount} ${data.tokens.from.symbol} (${data.tokens.from.blockchain})\n`; textResponse += `To: ${data.estimate.destinationTokenAmount} ${data.tokens.to.symbol} (${data.tokens.to.blockchain})\n\n`; if (data.tokens.from.price && data.tokens.to.price) { textResponse += `USD Value: $${(parseFloat(data.tokens.from.amount) * data.tokens.from.price).toFixed(2)} → $${data.estimate.destinationUsdAmount.toFixed(2)}\n\n`; } textResponse += `Provider: ${data.providerType.toUpperCase()}\n`; textResponse += `Type: ${data.swapType}\n`; textResponse += `Estimated Duration: ${data.estimate.durationInMinutes} minutes\n`; if (data.fees.gasTokenFees) { textResponse += `Gas Fee: ${data.fees.gasTokenFees.provider.fixedAmount} ${data.fees.gasTokenFees.nativeToken.symbol} (≈$${data.fees.gasTokenFees.provider.fixedUsdAmount.toFixed(2)})\n`; } textResponse += `\nFees:\n`; if (data.fees.percentFees) { textResponse += `Percent Fee: ${data.fees.percentFees.percent}%\n`; } if (data.estimate.priceImpact) { textResponse += `\nPrice Impact: ${(data.estimate.priceImpact * 100).toFixed(2)}%\n`; } if (data.warnings && data.warnings.length > 0) { textResponse += `\nWarnings: ${data.warnings.length}\n`; } // Add routing path details if (data.routing && data.routing.length > 0) { textResponse += `\nRouting Path:\n`; data.routing.forEach((route, i) => { textResponse += `Step ${i + 1}: ${route.provider} (${route.type})\n`; if (route.path.length > 0) { const fromToken = route.path[0]; const toToken = route.path[route.path.length - 1]; textResponse += ` ${fromToken.amount} ${fromToken.symbol} → ${toToken.amount} ${toToken.symbol}\n`; } }); } return { content: [ { type: "text", text: textResponse } ], data }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to get bridge quote: ${error.message}` } ] }; } } ); // Get multiple bridge quotes server.tool( "getRubicBridgeQuotes", "Get all available cross-chain bridge routes for swapping tokens between different blockchains.", { srcTokenAddress: z.string().describe("Source token address. Use 0x0000000000000000000000000000000000000000 for native tokens like ETH, BNB, etc."), srcTokenBlockchain: z.string().describe("Source blockchain name (e.g., ETH, BSC, POLYGON, etc.)"), srcTokenAmount: z.string().describe("Amount of source token to bridge (as a string with decimals)"), dstTokenAddress: z.string().describe("Destination token address. Use 0x0000000000000000000000000000000000000000 for native tokens."), dstTokenBlockchain: z.string().describe("Destination blockchain name (e.g., ETH, BSC, POLYGON, etc.)"), walletAddress: z.string().optional().describe("Wallet address to send tokens to on the destination blockchain"), slippageTolerance: z.number().min(0.01).max(50).optional().describe("Slippage tolerance in percentage (min: 0.01, max: 50)"), showFailedRoutes: z.boolean().optional().describe("Show failed routes in the response"), includeTestnets: z.boolean().optional().describe("Include testnets in calculations"), timeout: z.number().min(5).max(60).optional().describe("Calculation timeout in seconds (min: 5, max: 60)"), }, async ({ srcTokenAddress, srcTokenBlockchain, srcTokenAmount, dstTokenAddress, dstTokenBlockchain, walletAddress, slippageTolerance = 1, showFailedRoutes = false, includeTestnets = false, timeout = 30 }) => { try { const url = new URL(`${RUBIC_API_BASE}/routes/quoteAll`); // Ensure all values are properly formatted as strings where needed const requestBody = { srcTokenBlockchain: String(srcTokenBlockchain), srcTokenAddress: String(srcTokenAddress), srcTokenAmount: String(srcTokenAmount), dstTokenBlockchain: String(dstTokenBlockchain), dstTokenAddress: String(dstTokenAddress), referrer: "web3-mcp", // Recommended to set your application name as referrer timeout: Number(timeout), includeTestnets: Boolean(includeTestnets), showFailedRoutes: Boolean(showFailedRoutes), slippageTolerance: Number(slippageTolerance) / 100, // Convert percentage to decimal ...(walletAddress ? { walletAddress: String(walletAddress) } : {}) }; const response = await fetch(url.toString(), { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: JSON.stringify(requestBody), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Rubic API error (${response.status}): ${errorText}`); } const data = await response.json() as RubicQuoteResponse[]; // Format response for readable output let textResponse = `Available Bridge Routes:\n\n`; if (data.length === 0) { textResponse += `No available routes found.`; } else { data.forEach((route, index) => { textResponse += `Route ${index + 1}: ${route.providerType.toUpperCase()}\n`; textResponse += `From: ${route.tokens.from.amount} ${route.tokens.from.symbol} (${route.tokens.from.blockchain})\n`; textResponse += `To: ${route.estimate.destinationTokenAmount} ${route.tokens.to.symbol} (${route.tokens.to.blockchain})\n`; if (route.tokens.from.price && route.tokens.to.price) { textResponse += `USD Value: $${(parseFloat(route.tokens.from.amount) * route.tokens.from.price).toFixed(2)} → $${route.estimate.destinationUsdAmount.toFixed(2)}\n`; } textResponse += `Estimated Time: ${route.estimate.durationInMinutes} minutes\n`; if (route.fees.gasTokenFees) { textResponse += `Gas Fee: ${route.fees.gasTokenFees.provider.fixedAmount} ${route.fees.gasTokenFees.nativeToken.symbol} (≈$${route.fees.gasTokenFees.provider.fixedUsdAmount.toFixed(2)})\n`; } if (route.estimate.priceImpact) { textResponse += `Price Impact: ${(route.estimate.priceImpact * 100).toFixed(2)}%\n`; } textResponse += `\n`; }); } return { content: [ { type: "text", text: textResponse } ], data }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to get bridge quotes: ${error.message}` } ] }; } } ); // Check cross-chain transaction status server.tool( "getRubicBridgeStatus", "Check the status of a cross-chain bridge transaction.", { srcTxHash: z.string().describe("Source transaction hash to check status"), }, async ({ srcTxHash }) => { try { const url = new URL(`${RUBIC_API_BASE}/info/status`); url.searchParams.append('srcTxHash', String(srcTxHash)); const response = await fetch(url.toString(), { method: 'GET', headers: { 'Accept': 'application/json', }, }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Rubic API error (${response.status}): ${errorText}`); } const data = await response.json() as CrossChainStatusResponse; // Format response for readable output let textResponse = `Cross-Chain Transaction Status:\n\n`; textResponse += `Source Transaction: ${data.srcTxHash}\n`; if (data.dstTxHash) { textResponse += `Destination Transaction: ${data.dstTxHash}\n`; } textResponse += `Status: ${data.status.toUpperCase()}\n`; if (data.message) { textResponse += `Message: ${data.message}\n`; } if (data.error) { textResponse += `Error: ${data.error}\n`; } if (data.bridgeName) { textResponse += `Bridge Provider: ${data.bridgeName}\n`; } // Provide a human-readable explanation of the status textResponse += `\nStatus Explanation:\n`; switch (data.status) { case 'pending': textResponse += `Your transaction is still in progress. This could take a few minutes to complete.`; break; case 'indexing': textResponse += `The transaction has been detected but is still being indexed. Please check back soon.`; break; case 'revert': textResponse += `The transaction on the destination chain failed and needs to be reverted. You should collect your funds.`; break; case 'failed': textResponse += `The transaction has failed. Your funds may be reverted automatically.`; break; case 'claim': textResponse += `The transaction was successful! You can now claim your tokens on the destination chain.`; break; case 'success': textResponse += `The transaction was completed successfully! Your tokens have been sent to the destination address.`; break; case 'error': textResponse += `An error occurred during the transaction. Please check the error message for details.`; break; default: textResponse += `Unknown status. Please check the Rubic interface for more information.`; } return { content: [ { type: "text", text: textResponse } ], data }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to get bridge transaction status: ${error.message}` } ] }; } } ); }

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/strangelove-ventures/web3-mcp'

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