Apply recent changes

This commit is contained in:
2025-10-09 11:33:27 +02:00
parent d002d35bde
commit fe414d49e6
28 changed files with 2106 additions and 634 deletions

View File

@@ -0,0 +1,369 @@
/// 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<Value>,
}
impl RpcError {
pub fn new(code: ErrorCode, message: impl Into<String>) -> 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<String>) -> Self {
Self::new(ErrorCode::PARSE_ERROR, message)
}
pub fn invalid_request(message: impl Into<String>) -> 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<String>) -> Self {
Self::new(ErrorCode::INVALID_PARAMS, message)
}
pub fn internal_error(message: impl Into<String>) -> 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<String>) -> 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<Value>,
}
impl RpcRequest {
pub fn new(id: RequestId, method: impl Into<String>, params: Option<Value>) -> 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,
}
}
}
/// 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<u64> for RequestId {
fn from(n: u64) -> Self {
Self::Number(n)
}
}
impl From<String> 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";
}
// ============================================================================
// 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<ClientCapabilities>,
}
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<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supports_cancellation: Option<bool>,
}
/// 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<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supports_resources: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub supports_streaming: Option<bool>,
}
// ============================================================================
// 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<String>,
}
/// 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<Value>,
}
/// 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<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<Value>,
}
// ============================================================================
// 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\""));
}
}