gamecode_mcp2/
handlers.rs

1// Request handler - validates all LLM requests before execution.
2// No request reaches tool execution without explicit validation.
3
4use anyhow::Result;
5use serde_json::Value;
6use tracing::{debug, error, info};
7
8use crate::protocol::*;
9use crate::tools::ToolManager;
10
11pub struct RequestHandler {
12    tool_manager: ToolManager,
13}
14
15impl RequestHandler {
16    pub fn new(tool_manager: ToolManager) -> Self {
17        Self { tool_manager }
18    }
19
20    // Request dispatch - only these three methods exist, nothing else
21    pub async fn handle_request(&self, request: JsonRpcRequest) -> JsonRpcResponse {
22        debug!("Handling request: {} (id: {})", request.method, request.id);
23
24        let result = match request.method.as_str() {
25            "initialize" => self.handle_initialize(request.params).await,
26            "tools/list" => self.handle_tools_list().await,
27            "tools/call" => self.handle_tools_call(request.params).await,
28            _ => Err(JsonRpcError {
29                code: METHOD_NOT_FOUND,
30                message: format!("Method '{}' not found", request.method),
31                data: None,
32            }),
33        };
34
35        match result {
36            Ok(value) => JsonRpcResponse {
37                jsonrpc: "2.0".to_string(),
38                id: request.id,
39                result: Some(value),
40                error: None,
41            },
42            Err(error) => JsonRpcResponse {
43                jsonrpc: "2.0".to_string(),
44                id: request.id,
45                result: None,
46                error: Some(error),
47            },
48        }
49    }
50
51    pub async fn handle_notification(&self, notification: JsonRpcNotification) {
52        debug!("Handling notification: {}", notification.method);
53
54        match notification.method.as_str() {
55            "notifications/initialized" => {
56                info!("Client initialized");
57            }
58            "notifications/cancelled" => {
59                info!("Request cancelled");
60            }
61            _ => {
62                debug!("Unknown notification: {}", notification.method);
63            }
64        }
65    }
66
67    // Initialize - validate client capabilities, no negotiation
68    async fn handle_initialize(&self, params: Option<Value>) -> Result<Value, JsonRpcError> {
69        let _params: InitializeParams = if let Some(p) = params {
70            serde_json::from_value(p).map_err(|e| JsonRpcError {
71                code: INVALID_PARAMS,
72                message: format!("Invalid initialize params: {}", e),
73                data: None,
74            })?
75        } else {
76            return Err(JsonRpcError {
77                code: INVALID_PARAMS,
78                message: "Missing initialize params".to_string(),
79                data: None,
80            });
81        };
82
83        let result = InitializeResult {
84            protocol_version: "2024-11-05".to_string(),
85            capabilities: ServerCapabilities {
86                tools: ToolsCapability {},
87            },
88            server_info: ServerInfo {
89                name: "gamecode-mcp2".to_string(),
90                version: env!("CARGO_PKG_VERSION").to_string(),
91            },
92            instructions: Some(
93                "GameCode MCP Server v2 - Direct tool integration. Configure tools in tools.yaml"
94                    .to_string(),
95            ),
96        };
97
98        Ok(serde_json::to_value(result).unwrap())
99    }
100
101    // List tools - LLM sees only what we explicitly configured
102    async fn handle_tools_list(&self) -> Result<Value, JsonRpcError> {
103        let tools = self.tool_manager.get_mcp_tools();
104
105        let result = ListToolsResult { tools };
106
107        Ok(serde_json::to_value(result).unwrap())
108    }
109
110    // Tool execution - validate params, then delegate to tool manager
111    async fn handle_tools_call(&self, params: Option<Value>) -> Result<Value, JsonRpcError> {
112        let params: CallToolParams = if let Some(p) = params {
113            serde_json::from_value(p).map_err(|e| JsonRpcError {
114                code: INVALID_PARAMS,
115                message: format!("Invalid tool call params: {}", e),
116                data: None,
117            })?
118        } else {
119            return Err(JsonRpcError {
120                code: INVALID_PARAMS,
121                message: "Missing tool call params".to_string(),
122                data: None,
123            });
124        };
125
126        // Execute only configured tools with validated parameters
127        match self
128            .tool_manager
129            .execute_tool(&params.name, params.arguments)
130            .await
131        {
132            Ok(result) => {
133                let response = CallToolResult {
134                    content: vec![ContentBlock::Text {
135                        text: serde_json::to_string(&result).unwrap_or_else(|_| "null".to_string()),
136                    }],
137                    is_error: None,
138                };
139
140                Ok(serde_json::to_value(response).unwrap())
141            }
142            Err(e) => {
143                error!("Tool execution failed: {}", e);
144
145                let response = CallToolResult {
146                    content: vec![ContentBlock::Text {
147                        text: format!("Error: {}", e),
148                    }],
149                    is_error: Some(true),
150                };
151
152                Ok(serde_json::to_value(response).unwrap())
153            }
154        }
155    }
156}