#
tokens: 4305/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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 | ![License](https://img.shields.io/badge/license-MIT-blue.svg)
  6 | ![Node.js](https://img.shields.io/badge/Node.js-18.x-green.svg)
  7 | ![Status](https://img.shields.io/badge/status-active-brightgreen.svg)
  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 | 
```