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