Skip to main content
Glama

Web3 MCP Server

solana.ts12.6 kB
import { config } from 'dotenv'; config(); import { Connection, PublicKey, Keypair, Transaction, SystemProgram, sendAndConfirmTransaction, LAMPORTS_PER_SOL, } from '@solana/web3.js'; import { TOKEN_PROGRAM_ID, AccountLayout, } from '@solana/spl-token'; import { z } from "zod"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import bs58 from 'bs58'; import { getJupiterQuote, buildJupiterSwapTransaction, executeJupiterSwap, formatQuoteDetails } from './jupiter.js'; const SOLANA_RPC = process.env.SOLANA_RPC_URL || "https://api.mainnet-beta.solana.com"; // Initialize Solana connection const solanaConnection = new Connection(SOLANA_RPC, 'confirmed'); // Helper function to get token accounts for a wallet async function getTokenAccounts(walletAddress: string) { try { const owner = new PublicKey(walletAddress); const tokenAccounts = await solanaConnection.getParsedTokenAccountsByOwner( owner, { programId: TOKEN_PROGRAM_ID } ); return tokenAccounts.value.map(account => { const parsedAccountInfo = account.account.data.parsed.info; return { mint: new PublicKey(parsedAccountInfo.mint), amount: parsedAccountInfo.tokenAmount.uiAmount, decimals: parsedAccountInfo.tokenAmount.decimals, tokenAccount: account.pubkey.toString() }; }); } catch (error) { console.error('Error fetching token accounts:', error); return []; } } interface SwapParams { inputMint: string; outputMint: string; amount: string; slippageBps?: number; } export function registerSolanaTools(server: McpServer) { // Debug: Check environment variables console.error('Debug - ENV vars available:', Object.keys(process.env)); console.error('Debug - SOLANA_PRIVATE_KEY exists:', !!process.env.SOLANA_PRIVATE_KEY); if (!process.env.SOLANA_PRIVATE_KEY) { console.error('Warning: SOLANA_PRIVATE_KEY not found in environment'); } server.tool( "getMyAddress", "Get your Solana public address from private key in .env", {}, async () => { try { if (!process.env.SOLANA_PRIVATE_KEY) { throw new Error('SOLANA_PRIVATE_KEY not found in environment variables'); } const privateKeyBytes = bs58.decode(process.env.SOLANA_PRIVATE_KEY); const keypair = Keypair.fromSecretKey(privateKeyBytes); const publicKey = keypair.publicKey.toString(); // Get SOL balance const balance = await solanaConnection.getBalance(keypair.publicKey); const solBalance = balance / LAMPORTS_PER_SOL; // Get token balances const tokenAccounts = await getTokenAccounts(publicKey); const tokenBalances = tokenAccounts .filter(account => account.amount > 0) .map(account => `${account.amount} (Mint: ${account.mint.toString()})`) .join('\n'); return { content: [ { type: "text", text: `Your Solana Address: ${publicKey}\nSOL Balance: ${solBalance} SOL\n\nToken Balances:\n${tokenBalances}`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to get address: ${error.message}`, }, ], }; } } ); server.tool( "getBalance", "Get balance for a Solana address", { address: z.string().describe("Solana account address"), }, async ({ address }) => { try { const publicKey = new PublicKey(address); const balance = await solanaConnection.getBalance(publicKey); const solBalance = balance / LAMPORTS_PER_SOL; return { content: [ { type: "text", text: `Balance for ${address}:\n${solBalance} SOL`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to retrieve balance for address: ${error.message}`, }, ], }; } } ); server.tool( "getAccountInfo", "Get detailed account information for a Solana address", { address: z.string().describe("Solana account address"), encoding: z.enum(['base58', 'base64', 'jsonParsed']).optional().describe("Data encoding format"), }, async ({ address, encoding = 'base64' }) => { try { const publicKey = new PublicKey(address); const accountInfo = await solanaConnection.getAccountInfo( publicKey, 'confirmed' ); if (!accountInfo) { return { content: [ { type: "text", text: `No account found for address: ${address}`, }, ], }; } let formattedData: string; if (encoding === 'base58') { formattedData = bs58.encode(accountInfo.data); } else if (encoding === 'base64') { formattedData = Buffer.from(accountInfo.data).toString('base64'); } else { formattedData = Buffer.from(accountInfo.data).toString('base64'); } return { content: [ { type: "text", text: `Account Information for ${address}: Lamports: ${accountInfo.lamports} (${accountInfo.lamports / LAMPORTS_PER_SOL} SOL) Owner: ${accountInfo.owner.toBase58()} Executable: ${accountInfo.executable} Rent Epoch: ${accountInfo.rentEpoch} Data Length: ${accountInfo.data.length} bytes Data (${encoding}): ${formattedData}`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to retrieve account information: ${error.message}`, }, ], }; } } ); server.tool( "getSplTokenBalances", "Get SPL token balances for a Solana address", { address: z.string().describe("Solana account address"), }, async ({ address }) => { try { const tokenAccounts = await getTokenAccounts(address); if (tokenAccounts.length === 0) { return { content: [ { type: "text", text: `No token accounts found for address: ${address}`, }, ], }; } const balancesList = tokenAccounts .filter(account => account.amount > 0) .map(account => `Mint: ${account.mint.toString()}\nBalance: ${account.amount}\nDecimals: ${account.decimals}\nToken Account: ${account.tokenAccount}`) .join('\n\n'); return { content: [ { type: "text", text: `Token Balances for ${address}:\n\n${balancesList}`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to retrieve token balances: ${error.message}`, }, ], }; } } ); server.tool( "getSwapQuote", "Get best swap quote from Jupiter DEX aggregator", { inputMint: z.string().describe("Input token mint address"), outputMint: z.string().describe("Output token mint address"), amount: z.string().describe("Amount of input tokens (in smallest denomination)"), slippageBps: z.number().optional().describe("Slippage tolerance in basis points (optional, default 50 = 0.5%)"), }, async ({ inputMint, outputMint, amount, slippageBps }: SwapParams) => { try { // Validate input parameters inputMint = inputMint.trim(); outputMint = outputMint.trim(); amount = amount.toString().trim(); const quote = await getJupiterQuote(inputMint, outputMint, amount, slippageBps); const formattedDetails = await formatQuoteDetails(quote); return { content: [ { type: "text", text: formattedDetails, }, ], quote, // Store quote for subsequent swap }; } catch (err) { console.error('Error getting swap quote:', err); return { content: [ { type: "text", text: `Failed to get swap quote: ${err instanceof Error ? err.message : 'Unknown error'}`, }, ], }; } } ); server.tool( "executeSwap", "Execute a token swap using Jupiter DEX aggregator (using private key from .env)", { inputMint: z.string().describe("Input token mint address"), outputMint: z.string().describe("Output token mint address"), amount: z.string().describe("Amount of input tokens (in smallest denomination)"), slippageBps: z.number().optional().describe("Slippage tolerance in basis points (optional, default 50 = 0.5%)"), }, async ({ inputMint, outputMint, amount, slippageBps }: SwapParams) => { try { // Validate input parameters inputMint = inputMint.trim(); outputMint = outputMint.trim(); amount = amount.toString().trim(); // Check for private key if (!process.env.SOLANA_PRIVATE_KEY) { throw new Error('SOLANA_PRIVATE_KEY not found in environment variables'); } // Decode private key const privateKeyBytes = bs58.decode(process.env.SOLANA_PRIVATE_KEY); const keypair = Keypair.fromSecretKey(privateKeyBytes); // Get quote const quote = await getJupiterQuote(inputMint, outputMint, amount, slippageBps); const formattedQuote = await formatQuoteDetails(quote); // Build transaction const swapTransaction = await buildJupiterSwapTransaction( quote, keypair.publicKey.toString() ); // Execute swap const signature = await executeJupiterSwap( solanaConnection, swapTransaction, privateKeyBytes ); return { content: [ { type: "text", text: `${formattedQuote}\n\nSwap executed successfully!\nTransaction signature: ${signature}\nExplorer URL: https://explorer.solana.com/tx/${signature}`, }, ], }; } catch (err) { console.error('Error executing swap:', err); return { content: [ { type: "text", text: `Failed to execute swap: ${err instanceof Error ? err.message : 'Unknown error'}`, }, ], }; } } ); server.tool( "transfer", "Transfer SOL from your keypair (using private key from .env) to another address", { toAddress: z.string().describe("Destination wallet address"), amount: z.number().positive().describe("Amount of SOL to send"), }, async ({ toAddress, amount }) => { try { if (!process.env.SOLANA_PRIVATE_KEY) { throw new Error('SOLANA_PRIVATE_KEY not found in environment variables'); } const privateKeyBytes = bs58.decode(process.env.SOLANA_PRIVATE_KEY); const fromKeypair = Keypair.fromSecretKey(privateKeyBytes); const lamports = amount * LAMPORTS_PER_SOL; const transaction = new Transaction().add( SystemProgram.transfer({ fromPubkey: fromKeypair.publicKey, toPubkey: new PublicKey(toAddress), lamports, }) ); const signature = await sendAndConfirmTransaction( solanaConnection, transaction, [fromKeypair] ); return { content: [ { type: "text", text: `Transfer successful! From: ${fromKeypair.publicKey.toBase58()} To: ${toAddress} Amount: ${amount} SOL Transaction signature: ${signature} Explorer URL: https://explorer.solana.com/tx/${signature}`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to transfer SOL: ${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