use std::collections::HashMap;
use serde_json::{Value, json};
use crate::tool::{Tool, ToolInput};
#[derive(Debug)]
pub struct McpServer {
name: String,
version: String,
tools: Vec<Tool>,
tool_map: HashMap<String, usize>,
}
impl McpServer {
pub fn new(name: impl Into<String>, tools: Vec<Tool>) -> Self {
Self::with_version(name, env!("CARGO_PKG_VERSION"), tools)
}
pub fn with_version(
name: impl Into<String>,
version: impl Into<String>,
tools: Vec<Tool>,
) -> Self {
let tool_map = tools
.iter()
.enumerate()
.map(|(i, t)| (t.name().to_owned(), i))
.collect::<HashMap<_, _>>();
Self {
name: name.into(),
version: version.into(),
tools,
tool_map,
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn version(&self) -> &str {
&self.version
}
pub fn tools(&self) -> &[Tool] {
&self.tools
}
fn jsonrpc_success(id: &Value, result: Value) -> Value {
json!({
"jsonrpc": "2.0",
"id": id,
"result": result
})
}
fn jsonrpc_error(id: &Value, code: i32, message: &str) -> Value {
json!({
"jsonrpc": "2.0",
"id": id,
"error": {
"code": code,
"message": message
}
})
}
fn handle_initialize(&self, id: &Value) -> Value {
Self::jsonrpc_success(
id,
json!({
"protocolVersion": "2025-11-25",
"capabilities": { "tools": {} },
"serverInfo": {
"name": self.name,
"version": self.version
}
}),
)
}
fn handle_tools_list(&self, id: &Value) -> Value {
let tools_json = self
.tools
.iter()
.map(|tool| {
if let Some(output_schema) = tool.output_schema() {
json!({
"name": tool.name(),
"description": tool.description(),
"inputSchema": tool.input_schema(),
"outputSchema": output_schema,
})
} else {
json!({
"name": tool.name(),
"description": tool.description(),
"inputSchema": tool.input_schema(),
})
}
})
.collect::<Vec<_>>();
Self::jsonrpc_success(id, json!({ "tools": tools_json }))
}
async fn handle_tools_call(&self, id: &Value, params: &Value) -> Value {
let tool_name = match params.get("name").and_then(|v| v.as_str()) {
Some(name) => name,
None => return Self::jsonrpc_error(id, -32602, "missing 'name' parameter"),
};
let tool_idx = match self.tool_map.get(tool_name) {
Some(&idx) => idx,
None => {
return Self::jsonrpc_error(id, -32601, &format!("tool '{}' not found", tool_name));
}
};
let tool = &self.tools[tool_idx];
let arguments = params
.get("arguments")
.cloned()
.unwrap_or_else(|| json!({}));
let input = ToolInput::new(arguments);
match tool.call(input).await {
Ok(content) => Self::jsonrpc_success(
id,
if tool.output_schema().is_none() {
json!({ "content": content })
} else {
let Ok(text_content) = serde_json::to_string(&content) else {
return Self::jsonrpc_success(
id,
json!({
"content": [{"type": "text", "text": "failed to serialize tool output"}],
"isError": true
}),
);
};
json!({
"content": [{
"type": "text",
"text": text_content,
}],
"structuredContent": content,
})
},
),
Err(err) => Self::jsonrpc_success(
id,
json!({
"content": [{"type": "text", "text": err.to_string()}],
"isError": true
}),
),
}
}
pub async fn handle_json_message(&self, msg: &Value) -> Value {
let method = msg
.get("method")
.and_then(|v| v.as_str())
.unwrap_or_default();
let params = msg.get("params").cloned().unwrap_or_else(|| json!({}));
let id = msg.get("id").cloned().unwrap_or(Value::Null);
match method {
"initialize" => self.handle_initialize(&id),
"tools/list" => self.handle_tools_list(&id),
"tools/call" => self.handle_tools_call(&id, ¶ms).await,
method if method.starts_with("notifications/") => Value::Null,
_ => Self::jsonrpc_error(&id, -32601, &format!("method '{method}' not found")),
}
}
}