/// MCP Protocol Definitions /// /// This module defines the JSON-RPC protocol contracts for the Model Context Protocol (MCP). /// It includes request/response schemas, error codes, and versioning semantics. use serde::{Deserialize, Serialize}; use serde_json::Value; /// MCP Protocol version - uses semantic versioning pub const PROTOCOL_VERSION: &str = "1.0.0"; /// JSON-RPC version constant pub const JSONRPC_VERSION: &str = "2.0"; // ============================================================================ // Error Codes and Handling // ============================================================================ /// Standard JSON-RPC error codes following the spec #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub struct ErrorCode(pub i64); impl ErrorCode { // Standard JSON-RPC 2.0 errors pub const PARSE_ERROR: Self = Self(-32700); pub const INVALID_REQUEST: Self = Self(-32600); pub const METHOD_NOT_FOUND: Self = Self(-32601); pub const INVALID_PARAMS: Self = Self(-32602); pub const INTERNAL_ERROR: Self = Self(-32603); // MCP-specific errors (range -32000 to -32099) pub const TOOL_NOT_FOUND: Self = Self(-32000); pub const TOOL_EXECUTION_FAILED: Self = Self(-32001); pub const PERMISSION_DENIED: Self = Self(-32002); pub const RESOURCE_NOT_FOUND: Self = Self(-32003); pub const TIMEOUT: Self = Self(-32004); pub const VALIDATION_ERROR: Self = Self(-32005); pub const PATH_TRAVERSAL: Self = Self(-32006); pub const RATE_LIMIT_EXCEEDED: Self = Self(-32007); } /// Structured error response #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RpcError { pub code: i64, pub message: String, #[serde(skip_serializing_if = "Option::is_none")] pub data: Option, } impl RpcError { pub fn new(code: ErrorCode, message: impl Into) -> Self { Self { code: code.0, message: message.into(), data: None, } } pub fn with_data(mut self, data: Value) -> Self { self.data = Some(data); self } pub fn parse_error(message: impl Into) -> Self { Self::new(ErrorCode::PARSE_ERROR, message) } pub fn invalid_request(message: impl Into) -> Self { Self::new(ErrorCode::INVALID_REQUEST, message) } pub fn method_not_found(method: &str) -> Self { Self::new( ErrorCode::METHOD_NOT_FOUND, format!("Method not found: {}", method), ) } pub fn invalid_params(message: impl Into) -> Self { Self::new(ErrorCode::INVALID_PARAMS, message) } pub fn internal_error(message: impl Into) -> Self { Self::new(ErrorCode::INTERNAL_ERROR, message) } pub fn tool_not_found(tool_name: &str) -> Self { Self::new( ErrorCode::TOOL_NOT_FOUND, format!("Tool not found: {}", tool_name), ) } pub fn permission_denied(message: impl Into) -> Self { Self::new(ErrorCode::PERMISSION_DENIED, message) } pub fn path_traversal() -> Self { Self::new(ErrorCode::PATH_TRAVERSAL, "Path traversal attempt detected") } } // ============================================================================ // Request/Response Structures // ============================================================================ /// JSON-RPC request structure #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RpcRequest { pub jsonrpc: String, pub id: RequestId, pub method: String, #[serde(skip_serializing_if = "Option::is_none")] pub params: Option, } impl RpcRequest { pub fn new(id: RequestId, method: impl Into, params: Option) -> Self { Self { jsonrpc: JSONRPC_VERSION.to_string(), id, method: method.into(), params, } } } /// JSON-RPC response structure (success) #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RpcResponse { pub jsonrpc: String, pub id: RequestId, pub result: Value, } impl RpcResponse { pub fn new(id: RequestId, result: Value) -> Self { Self { jsonrpc: JSONRPC_VERSION.to_string(), id, result, } } } /// JSON-RPC error response #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RpcErrorResponse { pub jsonrpc: String, pub id: RequestId, pub error: RpcError, } impl RpcErrorResponse { pub fn new(id: RequestId, error: RpcError) -> Self { Self { jsonrpc: JSONRPC_VERSION.to_string(), id, error, } } } /// JSON‑RPC notification (no id). Used for streaming partial results. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct RpcNotification { pub jsonrpc: String, pub method: String, #[serde(skip_serializing_if = "Option::is_none")] pub params: Option, } impl RpcNotification { pub fn new(method: impl Into, params: Option) -> Self { Self { jsonrpc: JSONRPC_VERSION.to_string(), method: method.into(), params, } } } /// Request ID can be string, number, or null #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] #[serde(untagged)] pub enum RequestId { Number(u64), String(String), } impl From for RequestId { fn from(n: u64) -> Self { Self::Number(n) } } impl From for RequestId { fn from(s: String) -> Self { Self::String(s) } } // ============================================================================ // MCP Method Names // ============================================================================ /// Standard MCP methods pub mod methods { pub const INITIALIZE: &str = "initialize"; pub const TOOLS_LIST: &str = "tools/list"; pub const TOOLS_CALL: &str = "tools/call"; pub const RESOURCES_LIST: &str = "resources/list"; pub const RESOURCES_GET: &str = "resources/get"; pub const RESOURCES_WRITE: &str = "resources/write"; pub const RESOURCES_DELETE: &str = "resources/delete"; pub const MODELS_LIST: &str = "models/list"; } // ============================================================================ // Initialization Protocol // ============================================================================ /// Initialize request parameters #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InitializeParams { pub protocol_version: String, pub client_info: ClientInfo, #[serde(skip_serializing_if = "Option::is_none")] pub capabilities: Option, } impl Default for InitializeParams { fn default() -> Self { Self { protocol_version: PROTOCOL_VERSION.to_string(), client_info: ClientInfo { name: "owlen".to_string(), version: env!("CARGO_PKG_VERSION").to_string(), }, capabilities: None, } } } /// Client information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientInfo { pub name: String, pub version: String, } /// Client capabilities #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ClientCapabilities { #[serde(skip_serializing_if = "Option::is_none")] pub supports_streaming: Option, #[serde(skip_serializing_if = "Option::is_none")] pub supports_cancellation: Option, } /// Initialize response #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InitializeResult { pub protocol_version: String, pub server_info: ServerInfo, pub capabilities: ServerCapabilities, } /// Server information #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerInfo { pub name: String, pub version: String, } /// Server capabilities #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ServerCapabilities { #[serde(skip_serializing_if = "Option::is_none")] pub supports_tools: Option, #[serde(skip_serializing_if = "Option::is_none")] pub supports_resources: Option, #[serde(skip_serializing_if = "Option::is_none")] pub supports_streaming: Option, } // ============================================================================ // Tool Call Protocol // ============================================================================ /// Parameters for tools/list #[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ToolsListParams { #[serde(skip_serializing_if = "Option::is_none")] pub filter: Option, } /// Parameters for tools/call #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolsCallParams { pub name: String, #[serde(skip_serializing_if = "Option::is_none")] pub arguments: Option, } /// Result of tools/call #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ToolsCallResult { pub success: bool, pub output: Value, #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, #[serde(skip_serializing_if = "Option::is_none")] pub metadata: Option, } // ============================================================================ // Resource Protocol // ============================================================================ /// Parameters for resources/list #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResourcesListParams { pub path: String, } /// Parameters for resources/get #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResourcesGetParams { pub path: String, } /// Parameters for resources/write #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResourcesWriteParams { pub path: String, pub content: String, } /// Parameters for resources/delete #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ResourcesDeleteParams { pub path: String, } // ============================================================================ // Versioning and Compatibility // ============================================================================ /// Check if a protocol version is compatible pub fn is_compatible(client_version: &str, server_version: &str) -> bool { // For now, simple exact match on major version let client_major = client_version.split('.').next().unwrap_or("0"); let server_major = server_version.split('.').next().unwrap_or("0"); client_major == server_major } #[cfg(test)] mod tests { use super::*; #[test] fn test_error_codes() { let err = RpcError::tool_not_found("test_tool"); assert_eq!(err.code, ErrorCode::TOOL_NOT_FOUND.0); assert!(err.message.contains("test_tool")); } #[test] fn test_version_compatibility() { assert!(is_compatible("1.0.0", "1.0.0")); assert!(is_compatible("1.0.0", "1.1.0")); assert!(is_compatible("1.2.5", "1.0.0")); assert!(!is_compatible("1.0.0", "2.0.0")); assert!(!is_compatible("2.0.0", "1.0.0")); } #[test] fn test_request_serialization() { let req = RpcRequest::new( RequestId::Number(1), "tools/call", Some(serde_json::json!({"name": "test"})), ); let json = serde_json::to_string(&req).unwrap(); assert!(json.contains("\"jsonrpc\":\"2.0\"")); assert!(json.contains("\"method\":\"tools/call\"")); } }