├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── lib.rs ├── main.rs └── tests ├── e2e.rs └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | /target/ 3 | Cargo.lock 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | 8 | # These are backup files generated by rustfmt 9 | **/*.rs.bk 10 | 11 | # IDE specific files 12 | .vscode/ 13 | .idea/ 14 | *.iml 15 | 16 | # macOS specific files 17 | .DS_Store 18 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "solana-mcp-server" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | mcp-sdk = "0.0.2" 8 | solana-client = "1.17" 9 | solana-sdk = "1.17" 10 | solana-transaction-status = "1.17" 11 | tokio = { version = "1.32", features = ["full"] } 12 | serde = { version = "1.0", features = ["derive"] } 13 | serde_json = "1.0" 14 | anyhow = "1.0" 15 | thiserror = "1.0" 16 | async-trait = "0.1" 17 | spl-token = "4.0" 18 | log = "0.4" 19 | env_logger = "0.10" 20 | url = "2.4" 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana MCP Server 2 | 3 | A Model Context Protocol (MCP) server that provides comprehensive access to Solana blockchain data through Cline. This server implements a wide range of Solana RPC methods, making it easy to query blockchain information directly through natural language conversations. 4 | 5 | solana-mcp-server MCP server 6 | 7 | ## Features 8 | 9 | The server provides 21 essential Solana RPC methods across different categories: 10 | 11 | ### Account & Balance Operations 12 | - `get_sol_balance`: Get SOL balance for an address 13 | - `get_token_balance`: Get SPL token balance 14 | - `get_account_info`: Get account information 15 | - `get_largest_accounts`: Get largest accounts on network 16 | 17 | ### Block & Transaction Information 18 | - `get_slot`: Get current slot 19 | - `get_block`: Get block information 20 | - `get_block_time`: Get block production time 21 | - `get_transaction`: Get transaction details 22 | - `get_recent_blockhash`: Get recent blockhash 23 | 24 | ### Token Operations 25 | - `get_token_accounts_by_owner`: Get token accounts by owner 26 | - `get_token_accounts_by_delegate`: Get delegated token accounts 27 | - `get_token_supply`: Get token supply information 28 | 29 | ### System Information 30 | - `get_epoch_info`: Get current epoch information 31 | - `get_version`: Get node version 32 | - `get_health`: Get node health status 33 | - `get_supply`: Get current supply 34 | - `get_inflation_rate`: Get inflation rate 35 | - `get_cluster_nodes`: Get cluster node information 36 | - `get_minimum_balance_for_rent_exemption`: Get minimum rent-exempt balance 37 | 38 | ### Staking & Governance 39 | - `get_vote_accounts`: Get vote accounts 40 | - `get_leader_schedule`: Get leader schedule 41 | 42 | ## Setup in Cline 43 | 44 | 1. Add the following configuration to your Cline MCP settings file (`~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json` on macOS): 45 | 46 | ```json 47 | { 48 | "mcpServers": { 49 | "solana": { 50 | "command": "cargo", 51 | "args": ["run"], 52 | "cwd": "/path/to/solana-mcp-server", 53 | "env": { 54 | "SOLANA_RPC_URL": "https://api.mainnet-beta.solana.com" // Or your preferred RPC endpoint 55 | } 56 | } 57 | } 58 | } 59 | ``` 60 | 61 | 2. Restart Cline to load the new MCP server. 62 | 63 | ## Usage Examples 64 | 65 | Once configured, you can interact with the Solana blockchain through natural language in Cline. Here are some example queries: 66 | 67 | - "What's the SOL balance of address Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr?" 68 | - "Show me the current slot number" 69 | - "Get information about the latest block" 70 | - "What's the current inflation rate?" 71 | - "Show me the token accounts owned by address ..." 72 | 73 | ## Environment Variables 74 | 75 | - `SOLANA_RPC_URL`: (Optional) The Solana RPC endpoint to use. Defaults to "https://api.mainnet-beta.solana.com" if not specified. 76 | 77 | ## Development 78 | 79 | ### Prerequisites 80 | - Rust and Cargo 81 | - Solana CLI tools (optional, for testing) 82 | 83 | ### Building 84 | ```bash 85 | cargo build 86 | ``` 87 | 88 | ### Running 89 | ```bash 90 | cargo run 91 | ``` 92 | 93 | ## License 94 | 95 | MIT License 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod tests; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | use url::Url; 5 | 6 | // Define a local version of ReadResourceRequest 7 | #[derive(Debug, Clone, Serialize, Deserialize)] 8 | pub struct ReadResourceRequest { 9 | pub uri: Url, 10 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use async_trait::async_trait; 3 | use log::{error, info, warn}; 4 | use mcp_sdk::{ 5 | transport::{StdioTransport, JsonRpcMessage, Transport, JsonRpcResponse, JsonRpcVersion}, 6 | types::{ 7 | CallToolRequest, CallToolResponse, ToolResponseContent, 8 | ResourceContents, 9 | }, 10 | server::{Server, ServerOptions}, 11 | protocol::ProtocolBuilder, 12 | }; 13 | use solana_mcp_server::ReadResourceRequest; 14 | // ... (rest of the imports remain the same) 15 | 16 | // ... (rest of the code remains the same until Transport impl) 17 | 18 | #[async_trait] 19 | impl Transport for SolanaMcpServer { 20 | async fn send<'life0, 'async_trait>( 21 | &'life0 self, 22 | _message: &'life0 JsonRpcMessage, 23 | ) -> Result<()> 24 | where 25 | 'life0: 'async_trait, 26 | { 27 | Ok(()) 28 | } 29 | 30 | async fn receive<'life0, 'async_trait>( 31 | &'life0 self, 32 | ) -> Result 33 | where 34 | 'life0: 'async_trait, 35 | { 36 | Ok(JsonRpcMessage::Response(JsonRpcResponse { 37 | jsonrpc: JsonRpcVersion::Version2, 38 | id: 0, 39 | result: Some(serde_json::Value::Null), 40 | error: None, 41 | })) 42 | } 43 | 44 | async fn open<'life0, 'async_trait>( 45 | &'life0 self, 46 | ) -> Result<()> 47 | where 48 | 'life0: 'async_trait, 49 | { 50 | Ok(()) 51 | } 52 | 53 | async fn close<'life0, 'async_trait>( 54 | &'life0 self, 55 | ) -> Result<()> 56 | where 57 | 'life0: 'async_trait, 58 | { 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[tokio::main] 64 | async fn main() -> Result<()> { 65 | env_logger::init(); 66 | 67 | let rpc_url = std::env::var("SOLANA_RPC_URL") 68 | .unwrap_or_else(|_| "https://api.devnet.solana.com".to_string()); 69 | 70 | let client = RpcClient::new_with_commitment( 71 | rpc_url, 72 | CommitmentConfig::confirmed(), 73 | ); 74 | 75 | let server = SolanaMcpServer::new(client); 76 | let transport = StdioTransport::default(); 77 | let protocol = ProtocolBuilder::new(server); 78 | let options = ServerOptions::default(); 79 | 80 | info!("Starting Solana MCP server..."); 81 | let server = Server::new(protocol, options); 82 | server.execute(transport).await?; 83 | 84 | Ok(()) 85 | } -------------------------------------------------------------------------------- /src/tests/e2e.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::ReadResourceRequest; 3 | use mcp_sdk::{ 4 | transport::{ 5 | StdioTransport, JsonRpcMessage, Transport, JsonRpcRequest, JsonRpcResponse, 6 | JsonRpcVersion, 7 | }, 8 | types::{ 9 | CallToolRequest, CallToolResponse, ToolResponseContent, 10 | ResourceContents, 11 | }, 12 | }; 13 | use serde_json::json; 14 | 15 | #[tokio::test] 16 | async fn test_handle_read_resource() { 17 | let client = RpcClient::new_with_commitment( 18 | "https://api.devnet.solana.com".to_string(), 19 | CommitmentConfig::confirmed(), 20 | ); 21 | let server = SolanaMcpServer::new(client); 22 | 23 | // Test supply resource 24 | let request = ReadResourceRequest { 25 | uri: "solana://supply".parse().unwrap(), 26 | }; 27 | let result = server.handle_read_resource(request).await; 28 | assert!(result.is_ok()); 29 | 30 | // Test inflation resource 31 | let request = ReadResourceRequest { 32 | uri: "solana://inflation".parse().unwrap(), 33 | }; 34 | let result = server.handle_read_resource(request).await; 35 | assert!(result.is_ok()); 36 | 37 | // Test invalid resource 38 | let request = ReadResourceRequest { 39 | uri: "solana://invalid".parse().unwrap(), 40 | }; 41 | let result = server.handle_read_resource(request).await; 42 | assert!(result.is_err()); 43 | } 44 | 45 | #[tokio::test] 46 | async fn test_handle_tool_request() { 47 | let client = RpcClient::new_with_commitment( 48 | "https://api.devnet.solana.com".to_string(), 49 | CommitmentConfig::confirmed(), 50 | ); 51 | let server = SolanaMcpServer::new(client); 52 | 53 | // Test get_slot 54 | let request = CallToolRequest { 55 | name: "get_slot".to_string(), 56 | arguments: None, 57 | }; 58 | let result = server.handle_tool_request(request).await; 59 | assert!(result.is_ok()); 60 | 61 | // Test get_health 62 | let request = CallToolRequest { 63 | name: "get_health".to_string(), 64 | arguments: None, 65 | }; 66 | let result = server.handle_tool_request(request).await; 67 | assert!(result.is_ok()); 68 | 69 | // Test get_version 70 | let request = CallToolRequest { 71 | name: "get_version".to_string(), 72 | arguments: None, 73 | }; 74 | let result = server.handle_tool_request(request).await; 75 | assert!(result.is_ok()); 76 | 77 | // Test invalid tool 78 | let request = CallToolRequest { 79 | name: "invalid_tool".to_string(), 80 | arguments: None, 81 | }; 82 | let result = server.handle_tool_request(request).await; 83 | assert!(result.is_err()); 84 | } 85 | 86 | #[tokio::test] 87 | async fn test_transport_implementation() { 88 | let client = RpcClient::new_with_commitment( 89 | "https://api.devnet.solana.com".to_string(), 90 | CommitmentConfig::confirmed(), 91 | ); 92 | let server = SolanaMcpServer::new(client); 93 | 94 | // Test send 95 | let message = JsonRpcMessage::Request(JsonRpcRequest { 96 | jsonrpc: JsonRpcVersion::Two, 97 | id: 1, 98 | method: "test".to_string(), 99 | params: Some(json!({})), 100 | }); 101 | let result = server.send(&message).await; 102 | assert!(result.is_ok()); 103 | 104 | // Test receive 105 | let result = server.receive().await; 106 | assert!(result.is_ok()); 107 | 108 | // Test open 109 | let result = server.open().await; 110 | assert!(result.is_ok()); 111 | 112 | // Test close 113 | let result = server.close().await; 114 | assert!(result.is_ok()); 115 | } -------------------------------------------------------------------------------- /src/tests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod e2e; 2 | --------------------------------------------------------------------------------