# Directory Structure ``` ├── .gitignore ├── index.js ├── LICENSE ├── package.json ├── README.md └── yarn.lock ``` # Files -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules 2 | .env 3 | docker/ ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | # Jupiter MCP Server 2 | 3 | An MCP server for executing token swaps on the Solana blockchain using Jupiter's new Ultra API. 4 | 5 |  6 |  7 |  8 | 9 | ## Features 10 | 11 | - Fetch swap orders from Jupiter's Ultra API, combining DEX routing and RFQ (Request for Quote) for optimal pricing. 12 | - Execute swaps via Jupiter's Ultra API, handling slippage, priority fees, and transaction landing. 13 | 14 | 15 | ## Prerequisites 16 | 17 | - **Node.js**: Version 18 or higher (for native `fetch` support). 18 | - **Solana Wallet**: A private key (base58-encoded) for signing transactions. 19 | - **RPC Endpoint**: Access to a Solana RPC node (e.g., `https://api.mainnet-beta.solana.com`). 20 | 21 | ## Installation 22 | 23 | 1. **Clone the Repository**: 24 | ```bash 25 | git clone https://github.com/kukapay/jupiter-mcp.git 26 | cd jupiter-mcp 27 | ``` 28 | 29 | 2. **Install Dependencies**: 30 | Ensure you have the MCP Server package installed along with other required dependencies: 31 | ```bash 32 | npm install 33 | ``` 34 | 35 | 3. **Client Configuration**: 36 | 37 | ```json 38 | { 39 | "mcpServers": { 40 | "Jupiter-MCP": { 41 | "command": "node", 42 | "args": ["path/to/jupiter-mcp/server/index.js"], 43 | "env": { 44 | "SOLANA_RPC_URL": "solana rpc url you can access", 45 | "PRIVATE_KEY": "your private key" 46 | } 47 | } 48 | } 49 | } 50 | ``` 51 | 52 | ## Tools 53 | 54 | ### Ultra API Tools 55 | - **`get-ultra-order`**: 56 | - **Description**: Fetches a swap order from Jupiter's Ultra API, leveraging both DEX routing and RFQ for optimal pricing. 57 | - **Inputs**: 58 | - `inputMint`: Input token mint address (e.g., SOL or token pubkey). 59 | - `outputMint`: Output token mint address (e.g., USDC or token pubkey). 60 | - `amount`: Input amount as a string (e.g., "1.23"). 61 | - `slippageBps`: Slippage tolerance in basis points (e.g., 50 for 0.5%). 62 | - **Output**: JSON with `requestId`, `transaction` (base64-encoded), `inputMint`, `outputMint`, `inAmount`, `outAmount`, `price`. 63 | 64 | - **`execute-ultra-order`**: 65 | - **Description**: Requests Jupiter to execute the swap transaction on behalf of the wallet owner, handling slippage, priority fees, and transaction landing. 66 | - **Inputs**: 67 | - `requestId`: Unique identifier from `get-ultra-order`. 68 | - `transaction`: Base64-encoded transaction from `get-ultra-order`. 69 | - **Output**: JSON with `status`, `transactionId`, `slot`, `inputAmountResult`, `outputAmountResult`, `swapEvents`. 70 | 71 | ## Example Interaction 72 | 73 | Below are examples of interacting with the server using natural language prompts and expected responses: 74 | 75 | ### Fetching a Swap Order 76 | - **Prompt**: "Get a swap order to trade 1.23 SOL for USDC." 77 | - **Input**: 78 | - Tool: `get-ultra-order` 79 | - Arguments: 80 | - `inputMint`: "So11111111111111111111111111111111111111112" (SOL) 81 | - `outputMint`: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" (USDC) 82 | - `amount`: "1.23" 83 | - `slippageBps`: 50 84 | - **Response**: 85 | ``` 86 | { 87 | "requestId": "a770110b-82c9-46c8-ba61-09d955b27503", 88 | "transaction": "AQAAAA...base64-encoded-transaction...==", 89 | "inputMint": "So11111111111111111111111111111111111111112", 90 | "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", 91 | "inAmount": "1230000000", 92 | "outAmount": "19950000", 93 | "price": 0.01621951219512195 94 | } 95 | ``` 96 | 97 | ### Executing a Swap 98 | - **Prompt**: "Execute the swap order with request ID 'a770110b-82c9-46c8-ba61-09d955b27503' using the transaction provided." 99 | - **Input**: 100 | - Tool: `execute-ultra-order` 101 | - Arguments: 102 | - `requestId`: "a770110b-82c9-46c8-ba61-09d955b27503" 103 | - `transaction`: "AQAAAA...base64-encoded-transaction...==" 104 | - **Response**: 105 | ``` 106 | { 107 | "status": "Success", 108 | "transactionId": "5x...solana-transaction-signature...", 109 | "slot": 299283763, 110 | "inputAmountResult": "1230000000", 111 | "outputAmountResult": "19950000", 112 | "swapEvents": [ 113 | { 114 | "type": "swap", 115 | "inputMint": "So11111111111111111111111111111111111111112", 116 | "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", 117 | "inAmount": "1230000000", 118 | "outAmount": "19950000" 119 | } 120 | ] 121 | } 122 | ``` 123 | 124 | 125 | ## License 126 | 127 | This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. 128 | 129 | 130 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "jupiter-mcp", 3 | "version": "1.0.1", 4 | "main": "index.js", 5 | "homepage": "https://github.com/kukapay/jupiter-mcp", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "description": "", 13 | "dependencies": { 14 | "@modelcontextprotocol/sdk": "^1.7.0", 15 | "@solana/spl-token": "^0.4.13", 16 | "@solana/web3.js": "^1.98.0", 17 | "bigint-buffer": "^1.1.5", 18 | "dotenv": "^16.4.7", 19 | "undici": "^7.5.0", 20 | "zod": "^3.24.2" 21 | } 22 | } 23 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- ```javascript 1 | const { McpServer } =require("@modelcontextprotocol/sdk/server/mcp.js"); 2 | const { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js"); 3 | const { z } = require("zod"); 4 | const { Connection, Keypair, VersionedTransaction, PublicKey} = require("@solana/web3.js"); 5 | const { getMint } = require("@solana/spl-token"); 6 | const bs58 = require("bs58"); 7 | 8 | const dotenv = require("dotenv") 9 | dotenv.config() 10 | 11 | // Configuration 12 | const ULTRA_API = "https://api.jup.ag/ultra/v1"; 13 | const RPC_URL = process.env.SOLANA_RPC_URL; 14 | const PRIVATE_KEY = process.env.PRIVATE_KEY; // Base58 encoded private key 15 | 16 | // Configure Solana Connection with proxy 17 | const connection = new Connection(RPC_URL, { 18 | commitment: "confirmed" 19 | }); 20 | 21 | // Load wallet from private key 22 | const walletKeypair = Keypair.fromSecretKey(bs58.decode(PRIVATE_KEY)); 23 | const walletPublicKey = walletKeypair.publicKey.toString(); 24 | 25 | // Initialize MCP server 26 | const server = new McpServer({ 27 | name: "Jupiter MCP", 28 | version: "1.0.0" 29 | }); 30 | 31 | server.tool( 32 | "get-ultra-order", 33 | "Get a swap order from both Jupiter DEX Routing Engine and Jupiter Z (RFQ).", 34 | { 35 | inputMint: z.string().describe("Input token mint address"), 36 | outputMint: z.string().describe("Output token mint address"), 37 | amount: z.string().describe("Input amount as a string (e.g., '1.23')"), 38 | slippageBps: z.number().describe("Slippage tolerance in basis points (e.g., 50 for 0.5%).") 39 | }, 40 | async ({ inputMint, outputMint, amount, slippageBps }) => { 41 | try { 42 | const effectiveInputMint = inputMint; 43 | const effectiveOutputMint = outputMint; 44 | 45 | const inputMintPublicKey = new PublicKey(effectiveInputMint); 46 | const inputMintInfo = await getMint(connection, inputMintPublicKey); 47 | const decimals = inputMintInfo.decimals; 48 | 49 | const amountFloat = parseFloat(amount); 50 | if (isNaN(amountFloat)) { 51 | throw new Error("Invalid amount format"); 52 | } 53 | const amountInt = Math.floor(amountFloat * Math.pow(10, decimals)).toString(); 54 | 55 | const params = new URLSearchParams({ 56 | inputMint: effectiveInputMint, 57 | outputMint: effectiveOutputMint, 58 | amount: amountInt, 59 | slippageBps: slippageBps.toString(), 60 | taker: walletPublicKey 61 | }); 62 | 63 | const response = await fetch(`${ULTRA_API}/order?${params}`); 64 | const order = await response.json(); 65 | if (!order.transaction) { 66 | throw new Error("No transaction field in response. Ensure taker address is valid."); 67 | } 68 | 69 | return { 70 | content: [{ 71 | type: "text", 72 | text: JSON.stringify({ 73 | requestId: order.requestId, 74 | transaction: order.transaction, 75 | inputMint: effectiveInputMint, 76 | outputMint: effectiveOutputMint, 77 | inAmount: order.inAmount, 78 | outAmount: order.outAmount, 79 | price: Number(order.outAmount) / Number(order.inAmount) 80 | }, null, 2) 81 | }] 82 | }; 83 | } catch (error) { 84 | console.log(error) 85 | return { 86 | content: [{ 87 | type: "text", 88 | text: `Error fetching order: ${error.message}` 89 | }], 90 | isError: true 91 | }; 92 | } 93 | } 94 | ); 95 | 96 | server.tool( 97 | "execute-ultra-order", 98 | "Request Jupiter to execute the swap transaction on behalf of the wallet owner. This includes handling of slippage, priority fees, transaction landing and more.", 99 | { 100 | requestId: z.string().describe("Request ID from get-swap-order"), 101 | transaction: z.string().describe("Base64 encoded transaction from get-swap-order") 102 | }, 103 | async ({ requestId, transaction, inputMint, outputMint, amount }) => { 104 | try { 105 | 106 | let tx = VersionedTransaction.deserialize(Buffer.from(transaction, "base64")); 107 | tx.sign([walletKeypair]); 108 | const signedTransaction = Buffer.from(tx.serialize()).toString("base64"); 109 | 110 | const executeResponse = await fetch(`${ULTRA_API}/execute`, { 111 | method: "POST", 112 | headers: { 113 | "Content-Type": "application/json" 114 | }, 115 | body: JSON.stringify({ 116 | signedTransaction, 117 | requestId 118 | }) 119 | }); 120 | 121 | if (!executeResponse.ok) { 122 | throw new Error(`HTTP error! status: ${executeResponse.status}`); 123 | } 124 | 125 | const result = await executeResponse.json(); 126 | 127 | return { 128 | content: [{ 129 | type: "text", 130 | text: JSON.stringify({ 131 | status: result.status, 132 | transactionId: result.signature, 133 | slot: result.slot, 134 | inputAmountResult: result.inputAmountResult, 135 | outputAmountResult: result.outputAmountResult, 136 | swapEvents: result.swapEvents 137 | }, null, 2) 138 | }] 139 | }; 140 | } catch (error) { 141 | return { 142 | content: [{ 143 | type: "text", 144 | text: `Error executing swap: ${error.message}` 145 | }], 146 | isError: true 147 | }; 148 | } 149 | } 150 | ); 151 | 152 | // Start the server 153 | async function startServer() { 154 | const transport = new StdioServerTransport(); 155 | await server.connect(transport); 156 | /* 157 | console.log(await server._registeredTools['get-swap-order'].callback({ 158 | inputMint: "So11111111111111111111111111111111111111112", 159 | outputMint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", 160 | amount: "1.23", 161 | token: walletPublicKey, 162 | slippageBps: 50 163 | })) 164 | console.log(await server._registeredTools['execute-swap-order'].callback({ 165 | requestId: "a770110b-82c9-46c8-ba61-09d955b27503", 166 | transaction: "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQAHC+oaDbp9I+uYeae3ayA/1sT4qamJ7tq3b9PZmkdhJZ1mH9RsYXp41YrTtfB/VqrENVYdGHG6rtaCOqWfAPswrbh9iVaqrUHPNEIwuvJkSS4mZY8ggefu+qFI49PsepOULZXdFYpJfuoa+lkMfRsGXmW453vsGMQqadwJft+fT84EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMlyWPTiSJ8bs9ECkUjg2DC1oTmdr/EIQEjnvY2+n4Wawfg/25zlUN6V1VjNx5VGHM9DdKxojsE6mEACIKeNoGAwZGb+UhFzL/7K26csOb57yM5bvF9xJrLEObOkAAAAC0P/on9df2SnTAmx8pWHneSwmrNt/J3VFLMhqns4zl6AR51VvyMcBu7nTFbs5oFQf9sbLeo/SOUQKxzaJWvBOPBt324ddloZPZy+FGzut5rBy0he1fWzeROoz1hX7/AKmrpZYSiXiYJPloNZKFXzIx+ssAA5/HzJnPbcFMqSRo6AcHAAUCwFwVAAcACQNpRAgAAAAAAAQCAAIMAgAAAHBtb0kAAAAACQUCAA8KBAmT8Xtk9ISudv4FBgADAA4ECgEBCRMKAAIDCQ4BCAkQAA0MCwIDChEGJOUXy5d6460qAQAAAD0AZAABgE9QSQAAAAAZSXkJAAAAAFMABQoDAgAAAQkBXebA5bRGJSJ69exFtoMFfhkdbXv3/0Pj0l8x1dXoHawDum+4BMATuXA=" 167 | })) 168 | */ 169 | } 170 | 171 | startServer().catch(console.error); 172 | 173 | ```