Skip to content

Commit c92d5ea

Browse files
Copilot0xrinegade
andcommitted
Add comprehensive e2e tests for JSON-RPC API with full MCP protocol coverage
Co-authored-by: 0xrinegade <[email protected]>
1 parent 3531ebd commit c92d5ea

File tree

3 files changed

+687
-12
lines changed

3 files changed

+687
-12
lines changed

Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,8 @@ prometheus = "0.13"
3131
axum = "0.7"
3232
tower = "0.5"
3333
clap = { version = "4.0", features = ["derive"] }
34+
35+
[dev-dependencies]
36+
tokio-test = "0.4"
37+
serde_json = "1.0"
38+
reqwest = { version = "0.11", features = ["json"] }

src/tools/mod.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2035,6 +2035,125 @@ pub async fn handle_tools_list(id: Option<Value>, _state: &ServerState) -> Resul
20352035
))
20362036
}
20372037

2038+
/// Handles the tools/call MCP method to execute a specific tool
2039+
pub async fn handle_tools_call(
2040+
params: Option<Value>,
2041+
id: Option<Value>,
2042+
state: Arc<RwLock<ServerState>>,
2043+
) -> Result<JsonRpcMessage> {
2044+
log::info!("Handling tools/call request");
2045+
2046+
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
2047+
2048+
let tool_name = params
2049+
.get("name")
2050+
.and_then(|v| v.as_str())
2051+
.ok_or_else(|| anyhow::anyhow!("Missing tool name parameter"))?;
2052+
2053+
let arguments = params.get("arguments").cloned().unwrap_or(serde_json::json!({}));
2054+
2055+
log::info!("Executing tool: {}", tool_name);
2056+
2057+
// Execute the specific tool based on the tool name
2058+
let result = match tool_name {
2059+
"getHealth" => {
2060+
let state_guard = state.read().await;
2061+
crate::rpc::system::get_health(&state_guard.rpc_client).await
2062+
.map_err(|e| anyhow::anyhow!("Health check failed: {}", e))
2063+
}
2064+
"getVersion" => {
2065+
let state_guard = state.read().await;
2066+
crate::rpc::system::get_version(&state_guard.rpc_client).await
2067+
.map_err(|e| anyhow::anyhow!("Version check failed: {}", e))
2068+
}
2069+
"getBalance" => {
2070+
let pubkey_str = arguments
2071+
.get("pubkey")
2072+
.and_then(|v| v.as_str())
2073+
.ok_or_else(|| anyhow::anyhow!("Missing pubkey parameter"))?;
2074+
let pubkey = Pubkey::try_from(pubkey_str)?;
2075+
2076+
let state_guard = state.read().await;
2077+
crate::rpc::accounts::get_balance(&state_guard.rpc_client, &pubkey).await
2078+
.map_err(|e| anyhow::anyhow!("Get balance failed: {}", e))
2079+
}
2080+
"getAccountInfo" => {
2081+
let pubkey_str = arguments
2082+
.get("pubkey")
2083+
.and_then(|v| v.as_str())
2084+
.ok_or_else(|| anyhow::anyhow!("Missing pubkey parameter"))?;
2085+
let pubkey = Pubkey::try_from(pubkey_str)?;
2086+
2087+
let state_guard = state.read().await;
2088+
crate::rpc::accounts::get_account_info(&state_guard.rpc_client, &pubkey).await
2089+
.map_err(|e| anyhow::anyhow!("Get account info failed: {}", e))
2090+
}
2091+
"getMultipleAccounts" => {
2092+
let pubkeys_array = arguments
2093+
.get("pubkeys")
2094+
.and_then(|v| v.as_array())
2095+
.ok_or_else(|| anyhow::anyhow!("Missing pubkeys parameter"))?;
2096+
2097+
let mut pubkeys = Vec::new();
2098+
for pubkey_val in pubkeys_array {
2099+
let pubkey_str = pubkey_val
2100+
.as_str()
2101+
.ok_or_else(|| anyhow::anyhow!("Invalid pubkey in array"))?;
2102+
pubkeys.push(Pubkey::try_from(pubkey_str)?);
2103+
}
2104+
2105+
let state_guard = state.read().await;
2106+
crate::rpc::accounts::get_multiple_accounts(&state_guard.rpc_client, &pubkeys).await
2107+
.map_err(|e| anyhow::anyhow!("Get multiple accounts failed: {}", e))
2108+
}
2109+
"getSlot" => {
2110+
let state_guard = state.read().await;
2111+
crate::rpc::blocks::get_slot(&state_guard.rpc_client).await
2112+
}
2113+
"getTransactionCount" => {
2114+
let state_guard = state.read().await;
2115+
crate::rpc::system::get_transaction_count(&state_guard.rpc_client).await
2116+
.map_err(|e| anyhow::anyhow!("Get transaction count failed: {}", e))
2117+
}
2118+
"getLatestBlockhash" => {
2119+
let state_guard = state.read().await;
2120+
crate::rpc::system::get_latest_blockhash(&state_guard.rpc_client).await
2121+
.map_err(|e| anyhow::anyhow!("Get latest blockhash failed: {}", e))
2122+
}
2123+
"getEpochInfo" => {
2124+
let state_guard = state.read().await;
2125+
crate::rpc::system::get_epoch_info(&state_guard.rpc_client).await
2126+
.map_err(|e| anyhow::anyhow!("Get epoch info failed: {}", e))
2127+
}
2128+
"getClusterNodes" => {
2129+
let state_guard = state.read().await;
2130+
crate::rpc::system::get_cluster_nodes(&state_guard.rpc_client).await
2131+
.map_err(|e| anyhow::anyhow!("Get cluster nodes failed: {}", e))
2132+
}
2133+
_ => {
2134+
return Ok(create_error_response(
2135+
-32601,
2136+
format!("Tool not found: {}", tool_name),
2137+
id.unwrap_or(Value::Null),
2138+
None,
2139+
));
2140+
}
2141+
};
2142+
2143+
match result {
2144+
Ok(result_value) => Ok(create_success_response(result_value, id.unwrap_or(Value::Null))),
2145+
Err(e) => {
2146+
log::error!("Tool execution failed: {}", e);
2147+
Ok(create_error_response(
2148+
-32603,
2149+
format!("Tool execution failed: {}", e),
2150+
id.unwrap_or(Value::Null),
2151+
None,
2152+
))
2153+
}
2154+
}
2155+
}
2156+
20382157
use solana_sdk::pubkey::Pubkey;
20392158

20402159
// SVM Network Management Functions
@@ -2322,6 +2441,10 @@ pub async fn handle_request(
23222441
handle_tools_list(Some(req.id.clone()), &state_guard)
23232442
.await
23242443
}
2444+
"tools/call" => {
2445+
handle_tools_call(req.params, Some(req.id.clone()), state.clone())
2446+
.await
2447+
}
23252448

23262449
// Account methods
23272450
"getAccountInfo" => {

0 commit comments

Comments
 (0)