/**
* @author nich
* @website x.com/nichxbt
* @github github.com/nirholas
* @license Apache-2.0
*/
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"
import { z } from "zod"
import Logger from "@/utils/logger.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) {
// ==================== SUPPORTED CHAINS ====================
server.tool(
"rubic_get_supported_chains",
"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" as const, text: textResponse }]
}
} catch (err) {
const error = err as Error
return {
content: [
{ type: "text" as const, text: `Failed to get supported blockchains: ${error.message}` }
]
}
}
}
)
// ==================== GET BEST BRIDGE QUOTE ====================
server.tool(
"rubic_get_bridge_quote",
"Get the best cross-chain bridge route for swapping tokens between different blockchains.",
{
srcTokenAddress: z
.string()
.describe(
"Source token address. Use 0x40252CFDF8B20Ed757D61ff157719F33Ec332402 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 0x40252CFDF8B20Ed757D61ff157719F33Ec332402 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`)
const requestBody = {
srcTokenBlockchain: String(srcTokenBlockchain),
srcTokenAddress: String(srcTokenAddress),
srcTokenAmount: String(srcTokenAmount),
dstTokenBlockchain: String(dstTokenBlockchain),
dstTokenAddress: String(dstTokenAddress),
referrer: "universal-crypto-mcp",
timeout: Number(timeout),
includeTestnets: Boolean(includeTestnets),
showFailedRoutes: Boolean(showFailedRoutes),
slippageTolerance: Number(slippageTolerance) / 100,
...(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" as const, text: textResponse }]
}
} catch (err) {
const error = err as Error
return {
content: [{ type: "text" as const, text: `Failed to get bridge quote: ${error.message}` }]
}
}
}
)
// ==================== GET ALL BRIDGE QUOTES ====================
server.tool(
"rubic_get_bridge_quotes",
"Get all available cross-chain bridge routes for swapping tokens between different blockchains.",
{
srcTokenAddress: z
.string()
.describe(
"Source token address. Use 0x40252CFDF8B20Ed757D61ff157719F33Ec332402 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 0x40252CFDF8B20Ed757D61ff157719F33Ec332402 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`)
const requestBody = {
srcTokenBlockchain: String(srcTokenBlockchain),
srcTokenAddress: String(srcTokenAddress),
srcTokenAmount: String(srcTokenAmount),
dstTokenBlockchain: String(dstTokenBlockchain),
dstTokenAddress: String(dstTokenAddress),
referrer: "universal-crypto-mcp",
timeout: Number(timeout),
includeTestnets: Boolean(includeTestnets),
showFailedRoutes: Boolean(showFailedRoutes),
slippageTolerance: Number(slippageTolerance) / 100,
...(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" as const, text: textResponse }]
}
} catch (err) {
const error = err as Error
return {
content: [{ type: "text" as const, text: `Failed to get bridge quotes: ${error.message}` }]
}
}
}
)
// ==================== CHECK BRIDGE STATUS ====================
server.tool(
"rubic_get_bridge_status",
"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" as const, text: textResponse }]
}
} catch (err) {
const error = err as Error
return {
content: [
{ type: "text" as const, text: `Failed to get bridge transaction status: ${error.message}` }
]
}
}
}
)
}