spec_kit_mcp/mcp/
protocol.rs

1//! MCP Protocol Handler
2//!
3//! Handles JSON-RPC protocol validation and routing.
4
5use anyhow::{Context, Result};
6use serde_json::{json, Value};
7
8use super::types::*;
9
10/// Protocol handler for MCP messages
11pub struct ProtocolHandler;
12
13impl ProtocolHandler {
14    /// Create a new protocol handler
15    pub fn new() -> Self {
16        Self
17    }
18
19    /// Validate a JSON-RPC request
20    pub fn validate_request(&self, request: &JsonRpcRequest) -> Result<()> {
21        // Check JSON-RPC version
22        if request.jsonrpc != "2.0" {
23            anyhow::bail!("Invalid JSON-RPC version: {}", request.jsonrpc);
24        }
25
26        // Method must not be empty
27        if request.method.is_empty() {
28            anyhow::bail!("Method cannot be empty");
29        }
30
31        Ok(())
32    }
33
34    /// Parse tool call parameters
35    pub fn parse_tool_call(&self, params: Option<Value>) -> Result<ToolCallParams> {
36        let params = params.ok_or_else(|| anyhow::anyhow!("Missing parameters"))?;
37
38        serde_json::from_value(params).context("Failed to parse tool call parameters")
39    }
40
41    /// Create a tool list response
42    pub fn create_tool_list_response(
43        &self,
44        id: RequestId,
45        tools: Vec<ToolDefinition>,
46    ) -> JsonRpcResponse {
47        JsonRpcResponse::success(
48            id,
49            json!({
50                "tools": tools
51            }),
52        )
53    }
54
55    /// Create a tool result response
56    pub fn create_tool_result_response(
57        &self,
58        id: RequestId,
59        result: ToolResult,
60    ) -> JsonRpcResponse {
61        JsonRpcResponse::success(id, serde_json::to_value(result).unwrap())
62    }
63
64    /// Create an error response from an error
65    pub fn create_error_response(&self, id: RequestId, error: anyhow::Error) -> JsonRpcResponse {
66        let error_msg = format!("{:#}", error);
67
68        tracing::error!(error = %error_msg, "Request failed");
69
70        JsonRpcResponse::error(id, JsonRpcError::internal_error(error_msg))
71    }
72
73    /// Handle initialization request
74    pub fn handle_initialize(&self, id: RequestId) -> JsonRpcResponse {
75        tracing::info!("Handling initialize request");
76
77        JsonRpcResponse::success(
78            id,
79            json!({
80                "protocolVersion": "2024-11-05",
81                "serverInfo": {
82                    "name": "spec-kit-mcp",
83                    "version": env!("CARGO_PKG_VERSION")
84                },
85                "capabilities": {
86                    "tools": {}
87                }
88            }),
89        )
90    }
91
92    /// Handle ping request
93    pub fn handle_ping(&self, id: RequestId) -> JsonRpcResponse {
94        JsonRpcResponse::success(id, json!({}))
95    }
96}
97
98impl Default for ProtocolHandler {
99    fn default() -> Self {
100        Self::new()
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    #[test]
109    fn test_validate_request_valid() {
110        let handler = ProtocolHandler::new();
111        let request = JsonRpcRequest {
112            jsonrpc: "2.0".to_string(),
113            id: RequestId::Number(1),
114            method: "test".to_string(),
115            params: None,
116        };
117
118        assert!(handler.validate_request(&request).is_ok());
119    }
120
121    #[test]
122    fn test_validate_request_invalid_version() {
123        let handler = ProtocolHandler::new();
124        let request = JsonRpcRequest {
125            jsonrpc: "1.0".to_string(),
126            id: RequestId::Number(1),
127            method: "test".to_string(),
128            params: None,
129        };
130
131        assert!(handler.validate_request(&request).is_err());
132    }
133
134    #[test]
135    fn test_validate_request_empty_method() {
136        let handler = ProtocolHandler::new();
137        let request = JsonRpcRequest {
138            jsonrpc: "2.0".to_string(),
139            id: RequestId::Number(1),
140            method: "".to_string(),
141            params: None,
142        };
143
144        assert!(handler.validate_request(&request).is_err());
145    }
146
147    #[test]
148    fn test_initialize_response() {
149        let handler = ProtocolHandler::new();
150        let response = handler.handle_initialize(RequestId::Number(1));
151
152        assert!(response.error.is_none());
153        assert!(response.result.is_some());
154
155        let result = response.result.unwrap();
156        assert_eq!(result["serverInfo"]["name"], "spec-kit-mcp");
157    }
158
159    #[test]
160    fn test_ping_response() {
161        let handler = ProtocolHandler::new();
162        let response = handler.handle_ping(RequestId::Number(1));
163
164        assert!(response.error.is_none());
165        assert!(response.result.is_some());
166    }
167}