use crate::jsonrpc::{ErrorObject, Outgoing};
use crate::protocol::{self, CallToolResult, ServerInfo, Tool, ToolAnnotations, ToolsListResult};
use crate::zod::{Issues, Schema};
use serde::de::DeserializeOwned;
use serde_json::{Value, json};
use std::collections::BTreeMap;
use std::sync::Arc;
use std::time::Duration;
pub type ToolResult = Result<Value, String>;
type ToolFn = dyn Fn(&Value) -> Result<Value, ToolFailure> + Send + Sync;
#[derive(Debug)]
enum ToolFailure {
Tool(String),
Protocol(i64, String),
}
#[derive(Clone)]
struct ToolDef {
description: Option<String>,
schema: Option<Arc<dyn Schema>>,
annotations: Option<ToolAnnotations>,
f: Arc<ToolFn>,
}
#[derive(Clone)]
pub struct ServerCore {
name: String,
version: String,
tools: Arc<BTreeMap<String, ToolDef>>,
}
impl ServerCore {
pub fn dispatch(&self, body: &[u8]) -> Option<Vec<u8>> {
let incoming = match crate::jsonrpc::parse_incoming(body) {
Ok(incoming) => incoming,
Err(crate::jsonrpc::ParseFail::Syntax) => {
return Some(Outgoing::parse_error().to_vec());
}
Err(crate::jsonrpc::ParseFail::Invalid(message)) => {
return Some(
Outgoing::invalid_request(None, format!("requête invalide : {message}"))
.to_vec(),
);
}
};
if incoming.is_notification() {
return None;
}
let id = incoming
.id
.as_ref()
.map(|id| serde_json::to_value(id).unwrap_or(Value::Null))
.unwrap_or(Value::Null);
let outgoing = match incoming.method.as_str() {
protocol::method::PING => Outgoing::success(id, json!({})),
protocol::method::INITIALIZE => Outgoing::success(
id,
json!({
"protocolVersion": protocol::PROTOCOL_VERSION,
"capabilities": { "tools": {} },
"serverInfo": ServerInfo {
name: self.name.clone(),
version: self.version.clone(),
},
}),
),
protocol::method::TOOLS_LIST => Outgoing::success(
id,
serde_json::to_value(self.list_tools()).unwrap_or_else(|_| json!({ "tools": [] })),
),
protocol::method::TOOLS_CALL => self.call_tool(&id, incoming.params),
other => Outgoing::failure(
id,
ErrorObject::new(
ErrorObject::METHOD_NOT_FOUND,
format!("Méthode inconnue : {other}"),
),
),
};
Some(outgoing.to_vec())
}
fn list_tools(&self) -> ToolsListResult {
let tools = self
.tools
.iter()
.map(|(name, def)| Tool {
name: name.clone(),
description: def.description.clone(),
annotations: def.annotations.clone(),
input_schema: Some(
def.schema
.as_ref()
.map(|schema| schema.json())
.unwrap_or_else(|| json!({ "type": "object" })),
),
})
.collect();
ToolsListResult { tools }
}
fn call_tool(&self, id: &Value, params: Option<Value>) -> Outgoing {
let params = params.unwrap_or_else(|| json!({}));
let Some(name) = params.get("name").and_then(Value::as_str) else {
return Outgoing::failure(
id.clone(),
ErrorObject::new(ErrorObject::INVALID_PARAMS, "params.name manquant"),
);
};
let Some(tool) = self.tools.get(name) else {
return Outgoing::failure(
id.clone(),
ErrorObject::new(
ErrorObject::INVALID_PARAMS,
format!("Outil inconnu : {name}"),
),
);
};
let arguments = params
.get("arguments")
.cloned()
.unwrap_or_else(|| json!({}));
if let Some(schema) = &tool.schema {
let mut issues = Issues::new();
schema.validate("arguments", &arguments, &mut issues);
if !issues.is_empty() {
let rendered = issues.render();
return Outgoing::failure(
id.clone(),
ErrorObject::with_data(
ErrorObject::INVALID_PARAMS,
format!("Arguments invalides : {}", rendered.join(" ; ")),
json!({ "issues": rendered }),
),
);
}
}
let result = match (tool.f)(&arguments) {
Ok(value) => CallToolResult::from(Ok::<Value, String>(value)),
Err(ToolFailure::Tool(message)) => CallToolResult::from(Err::<Value, String>(message)),
Err(ToolFailure::Protocol(code, message)) => {
return Outgoing::failure(id.clone(), ErrorObject::new(code, message));
}
};
Outgoing::success(
id.clone(),
serde_json::to_value(result).unwrap_or(Value::Null),
)
}
}
#[derive(Clone)]
pub struct ServerBuilder {
name: String,
version: String,
route: String,
allow_origins: Vec<String>,
read_timeout: Duration,
max_connections: usize,
tools: BTreeMap<String, ToolDef>,
}
impl ServerBuilder {
#[must_use]
pub fn new(name: &str, version: &str) -> Self {
Self {
name: name.to_string(),
version: version.to_string(),
route: "/mcp".to_string(),
allow_origins: Vec::new(),
read_timeout: crate::http::READ_TIMEOUT,
max_connections: crate::http::MAX_CONNECTIONS,
tools: BTreeMap::new(),
}
}
#[must_use]
pub fn read_timeout(mut self, timeout: Duration) -> Self {
self.read_timeout = timeout;
self
}
#[must_use]
pub fn max_connections(mut self, max: usize) -> Self {
self.max_connections = max;
self
}
#[must_use]
pub fn route(mut self, route: &str) -> Self {
self.route = route.to_string();
self
}
#[must_use]
pub fn allow_origins(mut self, origins: &[&str]) -> Self {
self.allow_origins = origins.iter().map(|o| (*o).to_string()).collect();
self
}
pub fn tool<F>(mut self, name: &str, description: &str, f: F) -> Self
where
F: Fn(&Value) -> ToolResult + Send + Sync + 'static,
{
let wrapped = move |args: &Value| f(args).map_err(ToolFailure::Tool);
self.tools.insert(
name.to_string(),
ToolDef {
description: Some(description.to_string()),
schema: None,
annotations: None,
f: Arc::new(wrapped),
},
);
self
}
pub fn tool_typed<S, F>(mut self, name: &str, description: &str, schema: S, f: F) -> Self
where
S: Schema + 'static,
F: Fn(&Value) -> ToolResult + Send + Sync + 'static,
{
let wrapped = move |args: &Value| f(args).map_err(ToolFailure::Tool);
self.tools.insert(
name.to_string(),
ToolDef {
description: Some(description.to_string()),
schema: Some(Arc::new(schema)),
annotations: None,
f: Arc::new(wrapped),
},
);
self
}
pub fn tool_parse<S, T, F>(mut self, name: &str, description: &str, schema: S, f: F) -> Self
where
S: Schema + 'static,
T: DeserializeOwned,
F: Fn(T) -> ToolResult + Send + Sync + 'static,
{
let handler =
Arc::new(
move |value: &Value| match serde_json::from_value::<T>(value.clone()) {
Ok(parsed) => f(parsed).map_err(ToolFailure::Tool),
Err(error) => Err(ToolFailure::Protocol(
ErrorObject::INVALID_PARAMS,
format!("arguments invalides pour le type attendu : {error}"),
)),
},
);
self.tools.insert(
name.to_string(),
ToolDef {
description: Some(description.to_string()),
schema: Some(Arc::new(schema)),
annotations: None,
f: handler,
},
);
self
}
#[must_use]
pub fn annotate(mut self, tool: &str, annotations: ToolAnnotations) -> Self {
if let Some(def) = self.tools.get_mut(tool) {
def.annotations = Some(annotations);
}
self
}
#[must_use]
pub fn core(&self) -> ServerCore {
ServerCore {
name: self.name.clone(),
version: self.version.clone(),
tools: Arc::new(self.tools.clone()),
}
}
pub fn bind(self, addr: &str) -> std::io::Result<crate::http::BoundServer> {
let listener = std::net::TcpListener::bind(addr)?;
let core = Arc::new(ServerCore {
name: self.name,
version: self.version,
tools: Arc::new(self.tools),
});
Ok(crate::http::BoundServer {
listener,
core,
route: self.route,
allow_origins: self.allow_origins,
read_timeout: self.read_timeout,
max_connections: self.max_connections,
live_connections: Arc::new(std::sync::atomic::AtomicUsize::new(0)),
})
}
}