Skip to content

Commit 11998d2

Browse files
committed
fucking finally this shit works
1 parent 3e28b57 commit 11998d2

File tree

14 files changed

+875
-226
lines changed

14 files changed

+875
-226
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
# Generated by Cargo
22
/target/
33
Cargo.lock
4-
4+
logs/
5+
logs-runtime.log
56
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
67
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
78

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ version = "0.1.2"
44
edition = "2021"
55

66
[dependencies]
7+
log = "0.4"
8+
env_logger = "0.10"
9+
chrono = "0.4"
10+
url = { version = "2.4.1", features = ["serde"] }
711
anyhow = "1.0"
812
serde = { version = "1.0", features = ["derive"] }
913
serde_json = "1.0"

config.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
2-
"url": "https://svmai.com",
3-
"apikey": "svmASEodmoemdwoe242424",
4-
"iamAgent": false,
5-
"tools": ["thinkchain"]
2+
"rpc_url": "https://api.mainnet-beta.solana.com",
3+
"commitment": "confirmed",
4+
"protocol_version": "2024-11-05"
65
}

src/config.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
use anyhow::{Result, Context};
2+
use serde::Deserialize;
3+
use std::{env, fs};
4+
use crate::protocol::LATEST_PROTOCOL_VERSION;
5+
6+
#[derive(Debug, Deserialize)]
7+
pub struct Config {
8+
pub rpc_url: String,
9+
pub commitment: String,
10+
pub protocol_version: String,
11+
}
12+
13+
impl Config {
14+
pub fn load() -> Result<Self> {
15+
// Try to load from config file first
16+
if let Ok(content) = fs::read_to_string("config.json") {
17+
let config: Config = serde_json::from_str(&content)
18+
.context("Failed to parse config.json")?;
19+
return Ok(config);
20+
}
21+
22+
// Fall back to environment variables
23+
let rpc_url = env::var("SOLANA_RPC_URL")
24+
.unwrap_or_else(|_| "http://api.opensvm.com".to_string());
25+
26+
let commitment = env::var("SOLANA_COMMITMENT")
27+
.unwrap_or_else(|_| "confirmed".to_string());
28+
29+
let protocol_version = env::var("SOLANA_PROTOCOL_VERSION")
30+
.unwrap_or_else(|_| LATEST_PROTOCOL_VERSION.to_string());
31+
32+
Ok(Config {
33+
rpc_url,
34+
commitment,
35+
protocol_version,
36+
})
37+
}
38+
}

src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
pub mod config;
2+
pub mod protocol;
13
pub mod rpc;
24
pub mod server;
35
pub mod tools;
46
pub mod transport;
57

8+
pub use config::Config;
69
pub use server::start_server;
710
pub use transport::CustomStdioTransport;

src/main.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,24 @@
11
use anyhow::Result;
2+
use env_logger::Builder;
3+
use log::LevelFilter;
4+
use std::io::Write;
25

36
#[tokio::main]
47
async fn main() -> Result<()> {
8+
// Set up logging to stderr
9+
Builder::new()
10+
.filter_level(LevelFilter::Error)
11+
.format(|buf, record| {
12+
writeln!(
13+
buf,
14+
"{} [{}] {}",
15+
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
16+
record.level(),
17+
record.args()
18+
)
19+
})
20+
.init();
21+
22+
log::info!("Starting Solana MCP server...");
523
solana_mcp_server::server::start_server().await
624
}

src/protocol.rs

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
use std::collections::HashMap;
2+
use serde::{Deserialize, Serialize};
3+
use url::Url;
4+
5+
pub const LATEST_PROTOCOL_VERSION: &str = "2024-11-05";
6+
7+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
8+
#[serde(rename_all = "camelCase")]
9+
#[serde(default)]
10+
pub struct Implementation {
11+
pub name: String,
12+
pub version: String,
13+
}
14+
15+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
16+
#[serde(rename_all = "camelCase")]
17+
#[serde(default)]
18+
pub struct InitializeRequest {
19+
pub protocol_version: String,
20+
pub capabilities: ClientCapabilities,
21+
pub client_info: Implementation,
22+
}
23+
24+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
25+
#[serde(rename_all = "camelCase")]
26+
#[serde(default)]
27+
pub struct InitializeResponse {
28+
pub protocol_version: String,
29+
pub capabilities: ServerCapabilities,
30+
pub server_info: Implementation,
31+
}
32+
33+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
34+
#[serde(rename_all = "camelCase")]
35+
#[serde(default)]
36+
pub struct ServerCapabilities {
37+
#[serde(skip_serializing_if = "Option::is_none")]
38+
pub tools: Option<HashMap<String, ToolDefinition>>,
39+
#[serde(skip_serializing_if = "Option::is_none")]
40+
pub experimental: Option<serde_json::Value>,
41+
#[serde(skip_serializing_if = "Option::is_none")]
42+
pub logging: Option<serde_json::Value>,
43+
#[serde(skip_serializing_if = "Option::is_none")]
44+
pub prompts: Option<PromptCapabilities>,
45+
#[serde(skip_serializing_if = "Option::is_none")]
46+
pub resources: Option<HashMap<String, Resource>>,
47+
}
48+
49+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
50+
#[serde(rename_all = "camelCase")]
51+
#[serde(default)]
52+
pub struct PromptCapabilities {
53+
pub list_changed: Option<bool>,
54+
}
55+
56+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
57+
#[serde(rename_all = "camelCase")]
58+
#[serde(default)]
59+
pub struct ResourceCapabilities {
60+
pub subscribe: Option<bool>,
61+
pub list_changed: Option<bool>,
62+
}
63+
64+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
65+
#[serde(rename_all = "camelCase")]
66+
#[serde(default)]
67+
pub struct ClientCapabilities {
68+
pub experimental: Option<serde_json::Value>,
69+
pub sampling: Option<serde_json::Value>,
70+
pub roots: Option<RootCapabilities>,
71+
}
72+
73+
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
74+
#[serde(rename_all = "camelCase")]
75+
#[serde(default)]
76+
pub struct RootCapabilities {
77+
pub list_changed: Option<bool>,
78+
}
79+
80+
#[derive(Debug, Clone, Serialize, Deserialize)]
81+
#[serde(rename_all = "camelCase")]
82+
pub struct ToolDefinition {
83+
pub name: String,
84+
#[serde(skip_serializing_if = "Option::is_none")]
85+
pub description: Option<String>,
86+
pub input_schema: serde_json::Value,
87+
}
88+
89+
#[derive(Debug, Clone, Serialize, Deserialize)]
90+
#[serde(rename_all = "camelCase")]
91+
pub struct CallToolRequest {
92+
pub name: String,
93+
#[serde(skip_serializing_if = "Option::is_none")]
94+
pub arguments: Option<serde_json::Value>,
95+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
96+
pub meta: Option<serde_json::Value>,
97+
}
98+
99+
#[derive(Debug, Clone, Serialize, Deserialize)]
100+
#[serde(rename_all = "camelCase")]
101+
pub struct CallToolResponse {
102+
pub content: Vec<ToolResponseContent>,
103+
#[serde(skip_serializing_if = "Option::is_none")]
104+
pub is_error: Option<bool>,
105+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
106+
pub meta: Option<serde_json::Value>,
107+
}
108+
109+
#[derive(Debug, Clone, Serialize, Deserialize)]
110+
#[serde(tag = "type")]
111+
pub enum ToolResponseContent {
112+
#[serde(rename = "text")]
113+
Text { text: String },
114+
#[serde(rename = "image")]
115+
Image { data: String, mime_type: String },
116+
#[serde(rename = "resource")]
117+
Resource { resource: ResourceContents },
118+
}
119+
120+
#[derive(Debug, Clone, Serialize, Deserialize)]
121+
#[serde(rename_all = "camelCase")]
122+
pub struct ResourceContents {
123+
pub uri: Url,
124+
#[serde(skip_serializing_if = "Option::is_none")]
125+
pub mime_type: Option<String>,
126+
}
127+
128+
#[derive(Debug, Clone, Serialize, Deserialize)]
129+
#[serde(rename_all = "camelCase")]
130+
pub struct ListRequest {
131+
#[serde(skip_serializing_if = "Option::is_none")]
132+
pub cursor: Option<String>,
133+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
134+
pub meta: Option<serde_json::Value>,
135+
}
136+
137+
#[derive(Debug, Clone, Serialize, Deserialize)]
138+
#[serde(rename_all = "camelCase")]
139+
pub struct ToolsListResponse {
140+
pub tools: Vec<ToolDefinition>,
141+
#[serde(skip_serializing_if = "Option::is_none")]
142+
pub next_cursor: Option<String>,
143+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
144+
pub meta: Option<serde_json::Value>,
145+
}
146+
147+
#[derive(Debug, Deserialize, Serialize)]
148+
#[serde(rename_all = "camelCase")]
149+
pub struct PromptsListResponse {
150+
pub prompts: Vec<Prompt>,
151+
#[serde(skip_serializing_if = "Option::is_none")]
152+
pub next_cursor: Option<String>,
153+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
154+
pub meta: Option<HashMap<String, serde_json::Value>>,
155+
}
156+
157+
#[derive(Debug, Deserialize, Serialize)]
158+
#[serde(rename_all = "camelCase")]
159+
pub struct Prompt {
160+
pub name: String,
161+
#[serde(skip_serializing_if = "Option::is_none")]
162+
pub description: Option<String>,
163+
#[serde(skip_serializing_if = "Option::is_none")]
164+
pub arguments: Option<Vec<PromptArgument>>,
165+
}
166+
167+
#[derive(Debug, Deserialize, Serialize)]
168+
#[serde(rename_all = "camelCase")]
169+
pub struct PromptArgument {
170+
pub name: String,
171+
#[serde(skip_serializing_if = "Option::is_none")]
172+
pub description: Option<String>,
173+
#[serde(skip_serializing_if = "Option::is_none")]
174+
pub required: Option<bool>,
175+
}
176+
177+
#[derive(Debug, Deserialize, Serialize)]
178+
#[serde(rename_all = "camelCase")]
179+
pub struct ResourcesListResponse {
180+
pub resources: Vec<Resource>,
181+
#[serde(skip_serializing_if = "Option::is_none")]
182+
pub next_cursor: Option<String>,
183+
#[serde(rename = "_meta", skip_serializing_if = "Option::is_none")]
184+
pub meta: Option<HashMap<String, serde_json::Value>>,
185+
}
186+
187+
#[derive(Debug, Clone, Serialize, Deserialize)]
188+
#[serde(rename_all = "camelCase")]
189+
pub struct Resource {
190+
pub uri: Url,
191+
pub name: String,
192+
#[serde(skip_serializing_if = "Option::is_none")]
193+
pub description: Option<String>,
194+
#[serde(skip_serializing_if = "Option::is_none")]
195+
pub mime_type: Option<String>,
196+
}
197+
198+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199+
pub enum ErrorCode {
200+
// SDK error codes
201+
ConnectionClosed = -1,
202+
RequestTimeout = -2,
203+
204+
// Standard JSON-RPC error codes
205+
ParseError = -32700,
206+
InvalidRequest = -32600,
207+
MethodNotFound = -32601,
208+
InvalidParams = -32602,
209+
InternalError = -32603,
210+
}
211+
212+
#[cfg(test)]
213+
mod tests {
214+
use super::*;
215+
216+
#[test]
217+
fn test_server_capabilities() {
218+
let capabilities = ServerCapabilities::default();
219+
let json = serde_json::to_string(&capabilities).unwrap();
220+
assert_eq!(json, "{}");
221+
}
222+
}

0 commit comments

Comments
 (0)