# Directory Structure ``` ├── .gitignore ├── index.js ├── LICENSE ├── package.json ├── README.md └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules .env docker/ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Jupiter MCP Server An MCP server for executing token swaps on the Solana blockchain using Jupiter's new Ultra API.    ## Features - Fetch swap orders from Jupiter's Ultra API, combining DEX routing and RFQ (Request for Quote) for optimal pricing. - Execute swaps via Jupiter's Ultra API, handling slippage, priority fees, and transaction landing. ## Prerequisites - **Node.js**: Version 18 or higher (for native `fetch` support). - **Solana Wallet**: A private key (base58-encoded) for signing transactions. - **RPC Endpoint**: Access to a Solana RPC node (e.g., `https://api.mainnet-beta.solana.com`). ## Installation 1. **Clone the Repository**: ```bash git clone https://github.com/kukapay/jupiter-mcp.git cd jupiter-mcp ``` 2. **Install Dependencies**: Ensure you have the MCP Server package installed along with other required dependencies: ```bash npm install ``` 3. **Client Configuration**: ```json { "mcpServers": { "Jupiter-MCP": { "command": "node", "args": ["path/to/jupiter-mcp/server/index.js"], "env": { "SOLANA_RPC_URL": "solana rpc url you can access", "PRIVATE_KEY": "your private key" } } } } ``` ## Tools ### Ultra API Tools - **`get-ultra-order`**: - **Description**: Fetches a swap order from Jupiter's Ultra API, leveraging both DEX routing and RFQ for optimal pricing. - **Inputs**: - `inputMint`: Input token mint address (e.g., SOL or token pubkey). - `outputMint`: Output token mint address (e.g., USDC or token pubkey). - `amount`: Input amount as a string (e.g., "1.23"). - `slippageBps`: Slippage tolerance in basis points (e.g., 50 for 0.5%). - **Output**: JSON with `requestId`, `transaction` (base64-encoded), `inputMint`, `outputMint`, `inAmount`, `outAmount`, `price`. - **`execute-ultra-order`**: - **Description**: Requests Jupiter to execute the swap transaction on behalf of the wallet owner, handling slippage, priority fees, and transaction landing. - **Inputs**: - `requestId`: Unique identifier from `get-ultra-order`. - `transaction`: Base64-encoded transaction from `get-ultra-order`. - **Output**: JSON with `status`, `transactionId`, `slot`, `inputAmountResult`, `outputAmountResult`, `swapEvents`. ## Example Interaction Below are examples of interacting with the server using natural language prompts and expected responses: ### Fetching a Swap Order - **Prompt**: "Get a swap order to trade 1.23 SOL for USDC." - **Input**: - Tool: `get-ultra-order` - Arguments: - `inputMint`: "So11111111111111111111111111111111111111112" (SOL) - `outputMint`: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" (USDC) - `amount`: "1.23" - `slippageBps`: 50 - **Response**: ``` { "requestId": "a770110b-82c9-46c8-ba61-09d955b27503", "transaction": "AQAAAA...base64-encoded-transaction...==", "inputMint": "So11111111111111111111111111111111111111112", "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "inAmount": "1230000000", "outAmount": "19950000", "price": 0.01621951219512195 } ``` ### Executing a Swap - **Prompt**: "Execute the swap order with request ID 'a770110b-82c9-46c8-ba61-09d955b27503' using the transaction provided." - **Input**: - Tool: `execute-ultra-order` - Arguments: - `requestId`: "a770110b-82c9-46c8-ba61-09d955b27503" - `transaction`: "AQAAAA...base64-encoded-transaction...==" - **Response**: ``` { "status": "Success", "transactionId": "5x...solana-transaction-signature...", "slot": 299283763, "inputAmountResult": "1230000000", "outputAmountResult": "19950000", "swapEvents": [ { "type": "swap", "inputMint": "So11111111111111111111111111111111111111112", "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "inAmount": "1230000000", "outAmount": "19950000" } ] } ``` ## License This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "jupiter-mcp", "version": "1.0.1", "main": "index.js", "homepage": "https://github.com/kukapay/jupiter-mcp", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "", "license": "ISC", "description": "", "dependencies": { "@modelcontextprotocol/sdk": "^1.7.0", "@solana/spl-token": "^0.4.13", "@solana/web3.js": "^1.98.0", "bigint-buffer": "^1.1.5", "dotenv": "^16.4.7", "undici": "^7.5.0", "zod": "^3.24.2" } } ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- ```javascript const { McpServer } =require("@modelcontextprotocol/sdk/server/mcp.js"); const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); const { z } = require("zod"); const { Connection, Keypair, VersionedTransaction, PublicKey} = require("@solana/web3.js"); const { getMint } = require("@solana/spl-token"); const bs58 = require("bs58"); const dotenv = require("dotenv") dotenv.config() // Configuration const ULTRA_API = "https://api.jup.ag/ultra/v1"; const RPC_URL = process.env.SOLANA_RPC_URL; const PRIVATE_KEY = process.env.PRIVATE_KEY; // Base58 encoded private key // Configure Solana Connection with proxy const connection = new Connection(RPC_URL, { commitment: "confirmed" }); // Load wallet from private key const walletKeypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)); const walletPublicKey = walletKeypair.publicKey.toString(); // Initialize MCP server const server = new McpServer({ name: "Jupiter MCP", version: "1.0.0" }); server.tool( "get-ultra-order", "Get a swap order from both Jupiter DEX Routing Engine and Jupiter Z (RFQ).", { inputMint: z.string().describe("Input token mint address"), outputMint: z.string().describe("Output token mint address"), amount: z.string().describe("Input amount as a string (e.g., '1.23')"), slippageBps: z.number().describe("Slippage tolerance in basis points (e.g., 50 for 0.5%).") }, async ({ inputMint, outputMint, amount, slippageBps }) => { try { const effectiveInputMint = inputMint; const effectiveOutputMint = outputMint; const inputMintPublicKey = new PublicKey(effectiveInputMint); const inputMintInfo = await getMint(connection, inputMintPublicKey); const decimals = inputMintInfo.decimals; const amountFloat = parseFloat(amount); if (isNaN(amountFloat)) { throw new Error("Invalid amount format"); } const amountInt = Math.floor(amountFloat * Math.pow(10, decimals)).toString(); const params = new URLSearchParams({ inputMint: effectiveInputMint, outputMint: effectiveOutputMint, amount: amountInt, slippageBps: slippageBps.toString(), taker: walletPublicKey }); const response = await fetch(`${ULTRA_API}/order?${params}`); const order = await response.json(); if (!order.transaction) { throw new Error("No transaction field in response. Ensure taker address is valid."); } return { content: [{ type: "text", text: JSON.stringify({ requestId: order.requestId, transaction: order.transaction, inputMint: effectiveInputMint, outputMint: effectiveOutputMint, inAmount: order.inAmount, outAmount: order.outAmount, price: Number(order.outAmount) / Number(order.inAmount) }, null, 2) }] }; } catch (error) { console.log(error) return { content: [{ type: "text", text: `Error fetching order: ${error.message}` }], isError: true }; } } ); server.tool( "execute-ultra-order", "Request Jupiter to execute the swap transaction on behalf of the wallet owner. This includes handling of slippage, priority fees, transaction landing and more.", { requestId: z.string().describe("Request ID from get-swap-order"), transaction: z.string().describe("Base64 encoded transaction from get-swap-order") }, async ({ requestId, transaction, inputMint, outputMint, amount }) => { try { let tx = VersionedTransaction.deserialize(Buffer.from(transaction, "base64")); tx.sign([walletKeypair]); const signedTransaction = Buffer.from(tx.serialize()).toString("base64"); const executeResponse = await fetch(`${ULTRA_API}/execute`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ signedTransaction, requestId }) }); if (!executeResponse.ok) { throw new Error(`HTTP error! status: ${executeResponse.status}`); } const result = await executeResponse.json(); return { content: [{ type: "text", text: JSON.stringify({ status: result.status, transactionId: result.signature, slot: result.slot, inputAmountResult: result.inputAmountResult, outputAmountResult: result.outputAmountResult, swapEvents: result.swapEvents }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Error executing swap: ${error.message}` }], isError: true }; } } ); // Start the server async function startServer() { const transport = new StdioServerTransport(); await server.connect(transport); /* console.log(await server._registeredTools['get-swap-order'].callback({ inputMint: "So11111111111111111111111111111111111111112", outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", amount: "1.23", token: walletPublicKey, slippageBps: 50 })) console.log(await server._registeredTools['execute-swap-order'].callback({ requestId: "a770110b-82c9-46c8-ba61-09d955b27503", transaction: "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHC+oaDbp9I+uYeae3ayA/1sT4qamJ7tq3b9PZmkdhJZ1mH9RsYXp41YrTtfB/VqrENVYdGHG6rtaCOqWfAPswrbh9iVaqrUHPNEIwuvJkSS4mZY8ggefu+qFI49PsepOULZXdFYpJfuoa+lkMfRsGXmW453vsGMQqadwJft+fT84EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMlyWPTiSJ8bs9ECkUjg2DC1oTmdr/EIQEjnvY2+n4Wawfg/25zlUN6V1VjNx5VGHM9DdKxojsE6mEACIKeNoGAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAC0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6AR51VvyMcBu7nTFbs5oFQf9sbLeo/SOUQKxzaJWvBOPBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKmrpZYSiXiYJPloNZKFXzIx+ssAA5/HzJnPbcFMqSRo6AcHAAUCwFwVAAcACQNpRAgAAAAAAAQCAAIMAgAAAHBtb0kAAAAACQUCAA8KBAmT8Xtk9ISudv4FBgADAA4ECgEBCRMKAAIDCQ4BCAkQAA0MCwIDChEGJOUXy5d6460qAQAAAD0AZAABgE9QSQAAAAAZSXkJAAAAAFMABQoDAgAAAQkBXebA5bRGJSJ69exFtoMFfhkdbXv3/0Pj0l8x1dXoHawDum+4BMATuXA=" })) */ } startServer().catch(console.error); ```