use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(untagged)]
pub enum RequestId {
Uuid(Uuid),
String(String),
Number(u64),
}
impl RequestId {
pub fn new() -> Self {
Self::Uuid(Uuid::new_v4())
}
pub fn from_uuid(uuid: Uuid) -> Self {
Self::Uuid(uuid)
}
pub fn from_string(s: impl Into<String>) -> Self {
Self::String(s.into())
}
pub fn from_number(n: u64) -> Self {
Self::Number(n)
}
pub fn to_value(&self) -> Value {
match self {
RequestId::Uuid(uuid) => Value::String(uuid.to_string()),
RequestId::String(s) => Value::String(s.clone()),
RequestId::Number(n) => Value::Number(serde_json::Number::from(*n)),
}
}
pub fn from_value(value: Value) -> Option<Self> {
match value {
Value::String(s) => {
if let Ok(uuid) = Uuid::parse_str(&s) {
Some(RequestId::Uuid(uuid))
} else {
Some(RequestId::String(s))
}
}
Value::Number(n) => n.as_u64().map(RequestId::Number),
_ => None,
}
}
}
impl Default for RequestId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for RequestId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RequestId::Uuid(uuid) => write!(f, "{}", uuid),
RequestId::String(s) => write!(f, "{}", s),
RequestId::Number(n) => write!(f, "{}", n),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcRequest {
pub jsonrpc: String,
pub id: RequestId,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
pub id: RequestId,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<JsonRpcError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JsonRpcError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeParams {
#[serde(alias = "protocolVersion")]
pub protocol_version: String,
#[serde(alias = "clientInfo")]
pub client_info: ClientInfo,
pub capabilities: ClientCapabilities,
}
impl InitializeParams {
pub fn new(
protocol_version: impl Into<String>,
client_name: impl Into<String>,
client_version: impl Into<String>,
) -> Self {
Self {
protocol_version: protocol_version.into(),
client_info: ClientInfo {
name: client_name.into(),
version: client_version.into(),
},
capabilities: ClientCapabilities::default(),
}
}
pub fn with_capabilities(
protocol_version: impl Into<String>,
client_name: impl Into<String>,
client_version: impl Into<String>,
capabilities: ClientCapabilities,
) -> Self {
Self {
protocol_version: protocol_version.into(),
client_info: ClientInfo {
name: client_name.into(),
version: client_version.into(),
},
capabilities,
}
}
pub fn validate(&self) -> Result<(), JsonRpcError> {
if self.protocol_version.is_empty() {
return Err(JsonRpcError::invalid_params(
"protocol_version cannot be empty",
));
}
if self.client_info.name.is_empty() {
return Err(JsonRpcError::invalid_params(
"client_info.name cannot be empty",
));
}
if self.client_info.version.is_empty() {
return Err(JsonRpcError::invalid_params(
"client_info.version cannot be empty",
));
}
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ClientCapabilities {
#[serde(default)]
pub experimental: HashMap<String, Value>,
}
impl ClientCapabilities {
pub fn new() -> Self {
Self::default()
}
pub fn with_experimental(mut self, key: impl Into<String>, value: Value) -> Self {
self.experimental.insert(key.into(), value);
self
}
pub fn has_experimental(&self, key: &str) -> bool {
self.experimental.contains_key(key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
pub protocol_version: String,
pub server_info: ServerInfo,
pub capabilities: ServerCapabilities,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<ToolsCapability>,
#[serde(default)]
pub experimental: HashMap<String, Value>,
}
impl ServerCapabilities {
pub fn new() -> Self {
Self::default()
}
pub fn with_tools(mut self, list_changed: bool) -> Self {
self.tools = Some(ToolsCapability { list_changed });
self
}
pub fn with_experimental(mut self, key: impl Into<String>, value: Value) -> Self {
self.experimental.insert(key.into(), value);
self
}
pub fn supports_tools(&self) -> bool {
self.tools.is_some()
}
pub fn has_experimental(&self, key: &str) -> bool {
self.experimental.contains_key(key)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolsCapability {
#[serde(default)]
pub list_changed: bool,
}
impl JsonRpcRequest {
pub fn new(method: String, params: Option<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: RequestId::new(),
method,
params,
}
}
pub fn with_id(id: RequestId, method: String, params: Option<Value>) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
method,
params,
}
}
pub fn with_string_id(id: impl Into<String>, method: String, params: Option<Value>) -> Self {
Self::with_id(RequestId::from_string(id), method, params)
}
pub fn with_number_id(id: u64, method: String, params: Option<Value>) -> Self {
Self::with_id(RequestId::from_number(id), method, params)
}
pub fn validate(&self) -> Result<(), JsonRpcError> {
crate::mcp::transport::RequestValidator::validate(self)
}
pub fn create_error_response(&self, error: JsonRpcError) -> JsonRpcResponse {
JsonRpcResponse::error(self.id.clone(), error)
}
pub fn create_success_response(&self, result: serde_json::Value) -> JsonRpcResponse {
JsonRpcResponse::success(self.id.clone(), result)
}
}
impl JsonRpcResponse {
pub fn success(id: RequestId, result: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: Some(result),
error: None,
}
}
pub fn error(id: RequestId, error: JsonRpcError) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: None,
error: Some(error),
}
}
pub fn from_request_success(request: &JsonRpcRequest, result: Value) -> Self {
Self::success(request.id.clone(), result)
}
pub fn from_request_error(request: &JsonRpcRequest, error: JsonRpcError) -> Self {
Self::error(request.id.clone(), error)
}
}
impl JsonRpcError {
pub fn new(code: i32, message: String, data: Option<Value>) -> Self {
Self {
code,
message,
data,
}
}
pub fn parse_error(message: impl Into<String>) -> Self {
Self::new(-32700, message.into(), None)
}
pub fn parse_error_with_data(message: impl Into<String>, data: Value) -> Self {
Self::new(-32700, message.into(), Some(data))
}
pub fn invalid_request(message: impl Into<String>) -> Self {
Self::new(-32600, message.into(), None)
}
pub fn invalid_request_with_data(message: impl Into<String>, data: Value) -> Self {
Self::new(-32600, message.into(), Some(data))
}
pub fn method_not_found(method: impl Into<String>) -> Self {
let method = method.into();
Self::new(-32601, format!("Method not found: {}", method), None)
}
pub fn invalid_params(message: impl Into<String>) -> Self {
Self::new(-32602, message.into(), None)
}
pub fn invalid_params_with_details(message: impl Into<String>, details: Value) -> Self {
Self::new(-32602, message.into(), Some(details))
}
pub fn internal_error(message: impl Into<String>) -> Self {
Self::new(-32603, message.into(), None)
}
pub fn internal_error_with_data(message: impl Into<String>, data: Value) -> Self {
Self::new(-32603, message.into(), Some(data))
}
pub fn application_error(code: i32, message: impl Into<String>) -> Self {
assert!(
(-32099..=-32000).contains(&code),
"Application error codes must be between -32099 and -32000"
);
Self::new(code, message.into(), None)
}
pub fn application_error_with_data(code: i32, message: impl Into<String>, data: Value) -> Self {
assert!(
(-32099..=-32000).contains(&code),
"Application error codes must be between -32099 and -32000"
);
Self::new(code, message.into(), Some(data))
}
pub fn is_parse_error(&self) -> bool {
self.code == -32700
}
pub fn is_invalid_request(&self) -> bool {
self.code == -32600
}
pub fn is_method_not_found(&self) -> bool {
self.code == -32601
}
pub fn is_invalid_params(&self) -> bool {
self.code == -32602
}
pub fn is_internal_error(&self) -> bool {
self.code == -32603
}
pub fn is_application_error(&self) -> bool {
self.code >= -32099 && self.code <= -32000
}
pub fn tool_not_found(tool_name: &str) -> Self {
Self::new(
tool_errors::TOOL_NOT_FOUND,
format!("Tool not found: {}", tool_name),
None,
)
}
pub fn invalid_tool_params(message: String) -> Self {
Self::new(
tool_errors::INVALID_TOOL_PARAMS,
format!("Invalid tool parameters: {}", message),
None,
)
}
pub fn tool_execution_error(message: String) -> Self {
Self::new(
tool_errors::TOOL_EXECUTION_ERROR,
format!("Tool execution failed: {}", message),
None,
)
}
pub fn index_not_ready() -> Self {
Self::new(
tool_errors::INDEX_NOT_READY,
"Search index is not ready; wait for indexing to complete".to_string(),
None,
)
}
}
pub mod tool_errors {
pub const TOOL_NOT_FOUND: i32 = -32000;
pub const INVALID_TOOL_PARAMS: i32 = -32001;
pub const TOOL_EXECUTION_ERROR: i32 = -32002;
pub const INDEX_NOT_READY: i32 = -32003;
}
pub mod constants {
pub const PROTOCOL_VERSION: &str = "2024-11-05";
pub const SERVER_NAME: &str = "turboprop";
pub const SERVER_VERSION: &str = env!("CARGO_PKG_VERSION");
pub mod methods {
pub const INITIALIZE: &str = "initialize";
pub const TOOLS_LIST: &str = "tools/list";
pub const TOOLS_CALL: &str = "tools/call";
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_request_id_functionality() {
let uuid_id = RequestId::new();
matches!(uuid_id, RequestId::Uuid(_));
let string_id = RequestId::from_string("test-id");
matches!(string_id, RequestId::String(_));
let number_id = RequestId::from_number(42);
matches!(number_id, RequestId::Number(42));
let value = string_id.to_value();
assert_eq!(value, json!("test-id"));
let recovered = RequestId::from_value(json!("test-id")).unwrap();
matches!(recovered, RequestId::String(_));
}
#[test]
fn test_json_rpc_request_serialization() {
let request = JsonRpcRequest::with_number_id(
1,
"test_method".to_string(),
Some(json!({"param": "value"})),
);
let serialized = serde_json::to_string(&request).unwrap();
let deserialized: JsonRpcRequest = serde_json::from_str(&serialized).unwrap();
assert_eq!(request.jsonrpc, "2.0");
assert_eq!(deserialized.method, "test_method");
assert_eq!(deserialized.id, RequestId::from_number(1));
}
#[test]
fn test_json_rpc_response_success() {
let id = RequestId::from_number(1);
let response = JsonRpcResponse::success(id.clone(), json!({"success": true}));
assert_eq!(response.jsonrpc, "2.0");
assert_eq!(response.id, id);
assert!(response.result.is_some());
assert!(response.error.is_none());
}
#[test]
fn test_json_rpc_response_error() {
let id = RequestId::from_number(1);
let error = JsonRpcError::method_not_found("unknown_method".to_string());
let response = JsonRpcResponse::error(id.clone(), error);
assert_eq!(response.jsonrpc, "2.0");
assert_eq!(response.id, id);
assert!(response.result.is_none());
assert!(response.error.is_some());
assert_eq!(response.error.unwrap().code, -32601);
}
#[test]
fn test_json_rpc_response_from_request() {
let request = JsonRpcRequest::new("test_method".to_string(), None);
let response = JsonRpcResponse::from_request_success(&request, json!({"result": "ok"}));
assert_eq!(response.id, request.id);
assert!(response.result.is_some());
assert!(response.error.is_none());
}
#[test]
fn test_json_rpc_error_improvements() {
let parse_error = JsonRpcError::parse_error("Invalid JSON");
assert!(parse_error.is_parse_error());
assert_eq!(parse_error.code, -32700);
let method_error = JsonRpcError::method_not_found("unknown_method");
assert!(method_error.is_method_not_found());
assert!(method_error.message.contains("unknown_method"));
let app_error = JsonRpcError::application_error(-32001, "Custom error");
assert!(app_error.is_application_error());
assert_eq!(app_error.code, -32001);
let error_with_data = JsonRpcError::invalid_params_with_details(
"Invalid parameters",
json!({"expected": "string", "received": "number"}),
);
assert!(error_with_data.is_invalid_params());
assert!(error_with_data.data.is_some());
}
#[test]
fn test_initialize_params_helpers() {
let params = InitializeParams::new("2024-11-05", "test-client", "1.0.0");
assert_eq!(params.protocol_version, "2024-11-05");
assert_eq!(params.client_info.name, "test-client");
assert_eq!(params.client_info.version, "1.0.0");
assert!(params.validate().is_ok());
let invalid_params = InitializeParams::new("", "test-client", "1.0.0");
assert!(invalid_params.validate().is_err());
}
#[test]
fn test_capabilities_helpers() {
let client_caps = ClientCapabilities::new()
.with_experimental("feature1", json!(true))
.with_experimental("feature2", json!({"enabled": true}));
assert!(client_caps.has_experimental("feature1"));
assert!(client_caps.has_experimental("feature2"));
assert!(!client_caps.has_experimental("feature3"));
let server_caps = ServerCapabilities::new()
.with_tools(false)
.with_experimental("semantic_search", json!(true));
assert!(server_caps.supports_tools());
assert!(server_caps.has_experimental("semantic_search"));
assert!(!server_caps.has_experimental("other_feature"));
}
}