Solana MCP Server

by akc2267
Verified
  • src
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { Connection, PublicKey, Keypair, Transaction, SystemProgram, sendAndConfirmTransaction, LAMPORTS_PER_SOL } from '@solana/web3.js'; import { z } from "zod"; import bs58 from 'bs58'; const SOLANA_RPC = "https://api.mainnet-beta.solana.com"; // Create server instance const server = new McpServer({ name: "solana-rpc", version: "1.0.0", }); // Initialize Solana connection const connection = new Connection(SOLANA_RPC, 'confirmed'); // Register Solana tools server.tool( "getSlot", "Get the current slot", {}, async () => { try { const slot = await connection.getSlot(); return { content: [ { type: "text", text: `Current slot: ${slot}`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to retrieve current slot: ${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 connection.getBalance(publicKey); const solBalance = balance / LAMPORTS_PER_SOL; // Using LAMPORTS_PER_SOL constant 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( "getKeypairInfo", "Get information about a keypair from its secret key", { secretKey: z.string().describe("Base58 encoded secret key or array of bytes"), }, async ({ secretKey }) => { try { // Handle both base58 encoded strings and byte arrays let keypair: Keypair; try { // First try parsing as comma-separated string const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim()))); keypair = Keypair.fromSecretKey(decoded); } catch { // If that fails, try as a byte array string keypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey))); } // Get account info and balance const publicKey = keypair.publicKey; const balance = await connection.getBalance(publicKey); const accountInfo = await connection.getAccountInfo(publicKey); return { content: [ { type: "text", text: `Keypair Information: Public Key: ${publicKey.toBase58()} Balance: ${balance / LAMPORTS_PER_SOL} SOL Account Program Owner: ${accountInfo?.owner?.toBase58() || 'N/A'} Account Size: ${accountInfo?.data.length || 0} bytes Is Executable: ${accountInfo?.executable || false} Rent Epoch: ${accountInfo?.rentEpoch || 0}`, }, ], }; } catch (err) { const error = err as Error; return { content: [ { type: "text", text: `Failed to retrieve keypair information: ${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 connection.getAccountInfo( publicKey, 'confirmed' ); if (!accountInfo) { return { content: [ { type: "text", text: `No account found for address: ${address}`, }, ], }; } // Format the data based on encoding let formattedData: string; if (encoding === 'base58') { formattedData = bs58.encode(accountInfo.data); } else if (encoding === 'base64') { formattedData = Buffer.from(accountInfo.data).toString('base64'); } else { // For jsonParsed, we'll still return base64 but note that it's not parsed 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( "transfer", "Transfer SOL from your keypair to another address", { secretKey: z.string().describe("Your keypair's secret key (as comma-separated numbers or JSON array)"), toAddress: z.string().describe("Destination wallet address"), amount: z.number().positive().describe("Amount of SOL to send"), }, async ({ secretKey, toAddress, amount }) => { try { // Parse the secret key and create keypair let fromKeypair: Keypair; try { // First try parsing as comma-separated string const decoded = Uint8Array.from(secretKey.split(',').map(num => parseInt(num.trim()))); fromKeypair = Keypair.fromSecretKey(decoded); } catch { // If that fails, try as a JSON array string fromKeypair = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(secretKey))); } // Convert SOL amount to lamports const lamports = amount * LAMPORTS_PER_SOL; // Create transfer instruction const transaction = new Transaction().add( SystemProgram.transfer({ fromPubkey: fromKeypair.publicKey, toPubkey: new PublicKey(toAddress), lamports, }) ); // Send and confirm transaction const signature = await sendAndConfirmTransaction( connection, 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}`, }, ], }; } } ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Solana MCP Server running on stdio"); } main().catch((err: unknown) => { const error = err as Error; console.error("Fatal error in main():", error.message); process.exit(1); });