mcp_host/server/
handler.rs

1//! Request handling and routing
2//!
3//! Routes JSON-RPC requests to appropriate method handlers
4
5use serde_json::Value;
6use std::future::Future;
7use std::pin::Pin;
8
9use crate::protocol::errors::McpError;
10use crate::protocol::methods::McpMethod;
11use crate::protocol::types::{JsonRpcError, JsonRpcRequest, JsonRpcResponse};
12use crate::server::session::Session;
13
14/// Result type for handlers
15pub type HandlerResult = Result<Value, McpError>;
16
17/// Future type for async handlers
18pub type HandlerFuture = Pin<Box<dyn Future<Output = HandlerResult> + Send>>;
19
20/// Request context passed to handlers
21#[derive(Clone)]
22pub struct RequestContext {
23    /// The session associated with this request
24    pub session: Session,
25
26    /// The JSON-RPC request
27    pub request: JsonRpcRequest,
28}
29
30impl RequestContext {
31    /// Create new request context
32    pub fn new(session: Session, request: JsonRpcRequest) -> Self {
33        Self { session, request }
34    }
35
36    /// Get request parameters
37    pub fn params(&self) -> Option<&Value> {
38        self.request.params.as_ref()
39    }
40
41    /// Get request method
42    pub fn method(&self) -> McpMethod {
43        McpMethod::from(self.request.method.clone())
44    }
45}
46
47/// Validates that session is initialized for methods that require it
48pub fn require_initialization(ctx: &RequestContext) -> Result<(), McpError> {
49    let method = ctx.method();
50
51    // Methods that don't require initialization
52    if matches!(method, McpMethod::Initialize | McpMethod::Ping) {
53        return Ok(());
54    }
55
56    if !ctx.session.is_initialized() {
57        return Err(McpError::validation(
58            "not_initialized",
59            "Session must be initialized first",
60        ));
61    }
62
63    Ok(())
64}
65
66/// Builds a success response
67pub fn success_response(request_id: Option<Value>, result: Value) -> JsonRpcResponse {
68    JsonRpcResponse {
69        jsonrpc: "2.0".to_string(),
70        id: request_id.unwrap_or(Value::Null),
71        result: Some(result),
72        error: None,
73    }
74}
75
76/// Builds an error response
77pub fn error_response(request_id: Option<Value>, error: JsonRpcError) -> JsonRpcResponse {
78    JsonRpcResponse {
79        jsonrpc: "2.0".to_string(),
80        id: request_id.unwrap_or(Value::Null),
81        result: None,
82        error: Some(error),
83    }
84}
85
86/// Builds a method not found error response
87pub fn method_not_found(request_id: Option<Value>, method: &str) -> JsonRpcResponse {
88    error_response(
89        request_id,
90        JsonRpcError {
91            code: -32601,
92            message: format!("Method not found: {}", method),
93            data: None,
94        },
95    )
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_request_context() {
104        let session = Session::new();
105        let request = JsonRpcRequest {
106            jsonrpc: "2.0".to_string(),
107            id: Some(Value::Number(1.into())),
108            method: "tools/list".to_string(),
109            params: Some(serde_json::json!({"cursor": "abc"})),
110        };
111
112        let ctx = RequestContext::new(session.clone(), request.clone());
113
114        assert_eq!(ctx.session.id, session.id);
115        assert_eq!(ctx.request.method, "tools/list");
116        assert_eq!(ctx.method(), McpMethod::ToolsList);
117        assert!(ctx.params().is_some());
118    }
119
120    #[test]
121    fn test_require_initialization() {
122        let session = Session::new();
123
124        // Initialize method doesn't require initialization
125        let request = JsonRpcRequest {
126            jsonrpc: "2.0".to_string(),
127            id: Some(Value::Number(1.into())),
128            method: "initialize".to_string(),
129            params: None,
130        };
131        let ctx = RequestContext::new(session.clone(), request);
132        assert!(require_initialization(&ctx).is_ok());
133
134        // Ping method doesn't require initialization
135        let request = JsonRpcRequest {
136            jsonrpc: "2.0".to_string(),
137            id: Some(Value::Number(2.into())),
138            method: "ping".to_string(),
139            params: None,
140        };
141        let ctx = RequestContext::new(session.clone(), request);
142        assert!(require_initialization(&ctx).is_ok());
143
144        // Other methods require initialization
145        let request = JsonRpcRequest {
146            jsonrpc: "2.0".to_string(),
147            id: Some(Value::Number(3.into())),
148            method: "tools/list".to_string(),
149            params: None,
150        };
151        let ctx = RequestContext::new(session.clone(), request);
152        assert!(require_initialization(&ctx).is_err());
153
154        // After initialization, should succeed
155        let mut session = Session::new();
156        session.initialize(
157            crate::protocol::types::Implementation {
158                name: "test".to_string(),
159                version: "1.0".to_string(),
160            },
161            Default::default(),
162            "2025-06-18".to_string(),
163        );
164        let request = JsonRpcRequest {
165            jsonrpc: "2.0".to_string(),
166            id: Some(Value::Number(4.into())),
167            method: "tools/list".to_string(),
168            params: None,
169        };
170        let ctx = RequestContext::new(session, request);
171        assert!(require_initialization(&ctx).is_ok());
172    }
173
174    #[test]
175    fn test_success_response() {
176        let response = success_response(
177            Some(Value::Number(1.into())),
178            serde_json::json!({"status": "ok"}),
179        );
180
181        assert_eq!(response.jsonrpc, "2.0");
182        assert_eq!(response.id, Value::Number(1.into()));
183        assert!(response.result.is_some());
184        assert!(response.error.is_none());
185    }
186
187    #[test]
188    fn test_error_response() {
189        let error = JsonRpcError {
190            code: -32600,
191            message: "Invalid request".to_string(),
192            data: None,
193        };
194        let response = error_response(Some(Value::Number(1.into())), error);
195
196        assert_eq!(response.jsonrpc, "2.0");
197        assert_eq!(response.id, Value::Number(1.into()));
198        assert!(response.result.is_none());
199        assert!(response.error.is_some());
200        assert_eq!(response.error.unwrap().code, -32600);
201    }
202
203    #[test]
204    fn test_method_not_found() {
205        let response = method_not_found(Some(Value::Number(1.into())), "unknown/method");
206
207        assert!(response.error.is_some());
208        let error = response.error.unwrap();
209        assert_eq!(error.code, -32601);
210        assert!(error.message.contains("unknown/method"));
211    }
212}