Skip to content

Commit

Permalink
fucking finally this shit works
Browse files Browse the repository at this point in the history
  • Loading branch information
0xrinegade committed Jan 23, 2025
1 parent 3e28b57 commit 11998d2
Show file tree
Hide file tree
Showing 14 changed files with 875 additions and 226 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Generated by Cargo
/target/
Cargo.lock

logs/
logs-runtime.log
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html

Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ version = "0.1.2"
edition = "2021"

[dependencies]
log = "0.4"
env_logger = "0.10"
chrono = "0.4"
url = { version = "2.4.1", features = ["serde"] }
anyhow = "1.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
7 changes: 3 additions & 4 deletions config.json
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"
}
38 changes: 38 additions & 0 deletions src/config.rs
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,
})
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
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;
18 changes: 18 additions & 0 deletions src/main.rs
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
}
222 changes: 222 additions & 0 deletions src/protocol.rs
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, "{}");
}
}
Loading

0 comments on commit 11998d2

Please sign in to comment.