-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
3e28b57
commit 11998d2
Showing
14 changed files
with
875 additions
and
226 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
{ | ||
"url": "https://svmai.com", | ||
"apikey": "svmASEodmoemdwoe242424", | ||
"iamAgent": false, | ||
"tools": ["thinkchain"] | ||
"rpc_url": "https://api.mainnet-beta.solana.com", | ||
"commitment": "confirmed", | ||
"protocol_version": "2024-11-05" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
use anyhow::{Result, Context}; | ||
use serde::Deserialize; | ||
use std::{env, fs}; | ||
use crate::protocol::LATEST_PROTOCOL_VERSION; | ||
|
||
#[derive(Debug, Deserialize)] | ||
pub struct Config { | ||
pub rpc_url: String, | ||
pub commitment: String, | ||
pub protocol_version: String, | ||
} | ||
|
||
impl Config { | ||
pub fn load() -> Result<Self> { | ||
// Try to load from config file first | ||
if let Ok(content) = fs::read_to_string("config.json") { | ||
let config: Config = serde_json::from_str(&content) | ||
.context("Failed to parse config.json")?; | ||
return Ok(config); | ||
} | ||
|
||
// Fall back to environment variables | ||
let rpc_url = env::var("SOLANA_RPC_URL") | ||
.unwrap_or_else(|_| "http://api.opensvm.com".to_string()); | ||
|
||
let commitment = env::var("SOLANA_COMMITMENT") | ||
.unwrap_or_else(|_| "confirmed".to_string()); | ||
|
||
let protocol_version = env::var("SOLANA_PROTOCOL_VERSION") | ||
.unwrap_or_else(|_| LATEST_PROTOCOL_VERSION.to_string()); | ||
|
||
Ok(Config { | ||
rpc_url, | ||
commitment, | ||
protocol_version, | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,10 @@ | ||
pub mod config; | ||
pub mod protocol; | ||
pub mod rpc; | ||
pub mod server; | ||
pub mod tools; | ||
pub mod transport; | ||
|
||
pub use config::Config; | ||
pub use server::start_server; | ||
pub use transport::CustomStdioTransport; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,24 @@ | ||
use anyhow::Result; | ||
use env_logger::Builder; | ||
use log::LevelFilter; | ||
use std::io::Write; | ||
|
||
#[tokio::main] | ||
async fn main() -> Result<()> { | ||
// Set up logging to stderr | ||
Builder::new() | ||
.filter_level(LevelFilter::Error) | ||
.format(|buf, record| { | ||
writeln!( | ||
buf, | ||
"{} [{}] {}", | ||
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"), | ||
record.level(), | ||
record.args() | ||
) | ||
}) | ||
.init(); | ||
|
||
log::info!("Starting Solana MCP server..."); | ||
solana_mcp_server::server::start_server().await | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,222 @@ | ||
use std::collections::HashMap; | ||
use serde::{Deserialize, Serialize}; | ||
use url::Url; | ||
|
||
pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05"; | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct Implementation { | ||
pub name: String, | ||
pub version: String, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct InitializeRequest { | ||
pub protocol_version: String, | ||
pub capabilities: ClientCapabilities, | ||
pub client_info: Implementation, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct InitializeResponse { | ||
pub protocol_version: String, | ||
pub capabilities: ServerCapabilities, | ||
pub server_info: Implementation, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct ServerCapabilities { | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub tools: Option<HashMap<String, ToolDefinition>>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub experimental: Option<serde_json::Value>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub logging: Option<serde_json::Value>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub prompts: Option<PromptCapabilities>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub resources: Option<HashMap<String, Resource>>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct PromptCapabilities { | ||
pub list_changed: Option<bool>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct ResourceCapabilities { | ||
pub subscribe: Option<bool>, | ||
pub list_changed: Option<bool>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct ClientCapabilities { | ||
pub experimental: Option<serde_json::Value>, | ||
pub sampling: Option<serde_json::Value>, | ||
pub roots: Option<RootCapabilities>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, Default)] | ||
#[serde(rename_all = "camelCase")] | ||
#[serde(default)] | ||
pub struct RootCapabilities { | ||
pub list_changed: Option<bool>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ToolDefinition { | ||
pub name: String, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub description: Option<String>, | ||
pub input_schema: serde_json::Value, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct CallToolRequest { | ||
pub name: String, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub arguments: Option<serde_json::Value>, | ||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] | ||
pub meta: Option<serde_json::Value>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct CallToolResponse { | ||
pub content: Vec<ToolResponseContent>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub is_error: Option<bool>, | ||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] | ||
pub meta: Option<serde_json::Value>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(tag = "type")] | ||
pub enum ToolResponseContent { | ||
#[serde(rename = "text")] | ||
Text { text: String }, | ||
#[serde(rename = "image")] | ||
Image { data: String, mime_type: String }, | ||
#[serde(rename = "resource")] | ||
Resource { resource: ResourceContents }, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ResourceContents { | ||
pub uri: Url, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub mime_type: Option<String>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ListRequest { | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub cursor: Option<String>, | ||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] | ||
pub meta: Option<serde_json::Value>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ToolsListResponse { | ||
pub tools: Vec<ToolDefinition>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub next_cursor: Option<String>, | ||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] | ||
pub meta: Option<serde_json::Value>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct PromptsListResponse { | ||
pub prompts: Vec<Prompt>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub next_cursor: Option<String>, | ||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] | ||
pub meta: Option<HashMap<String, serde_json::Value>>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Prompt { | ||
pub name: String, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub description: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub arguments: Option<Vec<PromptArgument>>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct PromptArgument { | ||
pub name: String, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub description: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub required: Option<bool>, | ||
} | ||
|
||
#[derive(Debug, Deserialize, Serialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct ResourcesListResponse { | ||
pub resources: Vec<Resource>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub next_cursor: Option<String>, | ||
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")] | ||
pub meta: Option<HashMap<String, serde_json::Value>>, | ||
} | ||
|
||
#[derive(Debug, Clone, Serialize, Deserialize)] | ||
#[serde(rename_all = "camelCase")] | ||
pub struct Resource { | ||
pub uri: Url, | ||
pub name: String, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub description: Option<String>, | ||
#[serde(skip_serializing_if = "Option::is_none")] | ||
pub mime_type: Option<String>, | ||
} | ||
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
pub enum ErrorCode { | ||
// SDK error codes | ||
ConnectionClosed = -1, | ||
RequestTimeout = -2, | ||
|
||
// Standard JSON-RPC error codes | ||
ParseError = -32700, | ||
InvalidRequest = -32600, | ||
MethodNotFound = -32601, | ||
InvalidParams = -32602, | ||
InternalError = -32603, | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn test_server_capabilities() { | ||
let capabilities = ServerCapabilities::default(); | ||
let json = serde_json::to_string(&capabilities).unwrap(); | ||
assert_eq!(json, "{}"); | ||
} | ||
} |
Oops, something went wrong.