use crate::{
traits::{AsToolsList, Dispatch},
types::{
CacheScope, CallToolResult, DiscoverResult, Implementation, InitializeResult, JsonRpcError,
JsonRpcRequest, JsonRpcResponse, LATEST_HANDSHAKE_PROTOCOL_VERSION, ListToolsResult,
RequestContext, ResultType, SUPPORTED_PROTOCOL_VERSIONS, ServerCapabilities, meta_keys,
},
};
use serde_json::{Map, Value, json};
use std::fmt::Debug;
pub const DEFAULT_TOOLS_TTL_MS: u64 = 60 * 60 * 1000;
#[derive(Debug, Clone)]
pub struct ServerConfig {
server_info: Implementation,
instructions: Option<&'static str>,
tools_ttl_ms: u64,
tools_cache_scope: CacheScope,
}
impl ServerConfig {
pub fn new(server_info: Implementation) -> Self {
Self {
server_info,
instructions: None,
tools_ttl_ms: DEFAULT_TOOLS_TTL_MS,
tools_cache_scope: CacheScope::Private,
}
}
pub fn with_instructions(mut self, instructions: &'static str) -> Self {
self.instructions = Some(instructions);
self
}
pub fn with_tools_ttl_ms(mut self, tools_ttl_ms: u64) -> Self {
self.tools_ttl_ms = tools_ttl_ms;
self
}
pub fn with_tools_cache_scope(mut self, tools_cache_scope: CacheScope) -> Self {
self.tools_cache_scope = tools_cache_scope;
self
}
fn result_meta(&self) -> Option<Map<String, Value>> {
Some(
json!({ meta_keys::SERVER_INFO: self.server_info })
.as_object()
.cloned()
.unwrap_or_default(),
)
}
}
pub fn handle_request<Tools: Debug + AsToolsList + Dispatch<State>, State>(
request: JsonRpcRequest,
state: &mut State,
config: &ServerConfig,
) -> JsonRpcResponse {
let JsonRpcRequest {
id, method, params, ..
} = request;
let instructions = config.instructions;
let server_info = &config.server_info;
let context = RequestContext::from_params(params.as_ref());
match method.as_str() {
"initialize" => {
let requested = params
.as_ref()
.and_then(|params| params.get("protocolVersion"))
.and_then(Value::as_str);
let protocol_version = match requested {
Some(v) if SUPPORTED_PROTOCOL_VERSIONS.contains(&v) => v.to_string(),
_ => LATEST_HANDSHAKE_PROTOCOL_VERSION.to_string(),
};
JsonRpcResponse::success(
id,
InitializeResult {
protocol_version,
capabilities: ServerCapabilities::tools_only(),
server_info: server_info.clone(),
instructions: instructions.map(String::from),
meta: config.result_meta(),
},
)
}
"server/discover" => JsonRpcResponse::success(
id,
DiscoverResult {
supported_versions: SUPPORTED_PROTOCOL_VERSIONS
.iter()
.map(|v| v.to_string())
.collect(),
capabilities: ServerCapabilities::tools_only(),
instructions: instructions.map(String::from),
ttl_ms: Some(config.tools_ttl_ms),
cache_scope: Some(config.tools_cache_scope),
result_type: Some(ResultType::Complete),
meta: config.result_meta(),
},
),
"ping" => JsonRpcResponse::success(id, json!({})),
"tools/list" => JsonRpcResponse::success(
id,
ListToolsResult {
tools: Tools::tools_list(),
ttl_ms: Some(config.tools_ttl_ms),
cache_scope: Some(config.tools_cache_scope),
result_type: Some(ResultType::Complete),
meta: config.result_meta(),
..ListToolsResult::default()
},
),
"tools/call" => {
match serde_json::from_value::<Tools>(params.unwrap_or(serde_json::Value::Null)) {
Ok(tool) => {
log::info!("{tool:?}");
match tool.call(state, &context) {
Ok(result) => {
log::debug!("{result:?}");
JsonRpcResponse::success(
id,
CallToolResult {
meta: config.result_meta(),
..result
},
)
}
Err(e) => {
log::error!("{e}");
JsonRpcResponse::success(
id,
CallToolResult {
meta: config.result_meta(),
..CallToolResult::error(e.to_string())
},
)
}
}
}
Err(e) => {
log::error!("{e}");
JsonRpcResponse::error(id, JsonRpcError::invalid_params(e.to_string()))
}
}
}
_ => JsonRpcResponse::error(id, JsonRpcError::method_not_found(&method)),
}
}