Skip to main content

codetether_agent/mcp/
server.rs

1//! MCP Server - Exposes CodeTether tools to MCP clients
2//!
3//! Runs as a stdio-based MCP server that can be connected to by:
4//! - Claude Desktop
5//! - Other MCP clients
6//!
7//! Exposed tools include:
8//! - run_command: Execute shell commands
9//! - read_file: Read file contents
10//! - write_file: Write file contents
11//! - search_files: Search for files
12//! - swarm: Execute tasks with parallel sub-agents
13//! - rlm: Analyze large content
14//! - ralph: Autonomous PRD-driven execution
15
16use super::transport::{McpMessage, StdioTransport, Transport};
17use super::types::*;
18use anyhow::Result;
19use serde_json::{json, Value};
20use std::collections::HashMap;
21use std::sync::Arc;
22use tokio::sync::RwLock;
23use tracing::{debug, info, warn};
24
25/// MCP Server implementation
26pub struct McpServer {
27    transport: Arc<dyn Transport>,
28    tools: RwLock<HashMap<String, McpToolHandler>>,
29    resources: RwLock<HashMap<String, McpResourceHandler>>,
30    /// Prompt handlers for MCP prompts (reserved for future use)
31    #[allow(dead_code)]
32    prompts: RwLock<HashMap<String, McpPromptHandler>>,
33    initialized: RwLock<bool>,
34    server_info: ServerInfo,
35    /// Tool metadata storage for querying tool information
36    metadata: RwLock<HashMap<String, ToolMetadata>>,
37    /// Resource metadata storage for querying resource information
38    resource_metadata: RwLock<HashMap<String, ResourceMetadata>>,
39}
40
41type McpToolHandler = Arc<dyn Fn(Value) -> Result<CallToolResult> + Send + Sync>;
42type McpResourceHandler = Arc<dyn Fn(String) -> Result<ReadResourceResult> + Send + Sync>;
43type McpPromptHandler = Arc<dyn Fn(Value) -> Result<GetPromptResult> + Send + Sync>;
44
45impl McpServer {
46    /// Create a new MCP server over stdio
47    pub fn new_stdio() -> Self {
48        let transport = Arc::new(StdioTransport::new());
49        Self::new(transport)
50    }
51    
52    /// Create a new MCP server with custom transport
53    pub fn new(transport: Arc<dyn Transport>) -> Self {
54        let mut server = Self {
55            transport,
56            tools: RwLock::new(HashMap::new()),
57            resources: RwLock::new(HashMap::new()),
58            prompts: RwLock::new(HashMap::new()),
59            initialized: RwLock::new(false),
60            server_info: ServerInfo {
61                name: "codetether".to_string(),
62                version: env!("CARGO_PKG_VERSION").to_string(),
63            },
64            metadata: RwLock::new(HashMap::new()),
65            resource_metadata: RwLock::new(HashMap::new()),
66        };
67        
68        // Register default tools
69        server.register_default_tools();
70        
71        server
72    }
73    
74    /// Register default CodeTether tools
75    fn register_default_tools(&mut self) {
76        // These will be registered synchronously in the constructor
77        // The actual tool handlers will be added in run()
78    }
79    
80    /// Register a tool
81    pub async fn register_tool(&self, name: &str, description: &str, input_schema: Value, handler: McpToolHandler) {
82        // Store tool metadata
83        let metadata = ToolMetadata::new(
84            name.to_string(),
85            Some(description.to_string()),
86            input_schema.clone(),
87        );
88        
89        let mut metadata_map = self.metadata.write().await;
90        metadata_map.insert(name.to_string(), metadata);
91        drop(metadata_map);
92        
93        let mut tools = self.tools.write().await;
94        tools.insert(name.to_string(), handler);
95        
96        debug!("Registered MCP tool: {}", name);
97    }
98    
99    /// Register a resource
100    pub async fn register_resource(&self, uri: &str, name: &str, description: &str, mime_type: Option<&str>, handler: McpResourceHandler) {
101        // Store resource metadata
102        let metadata = ResourceMetadata::new(
103            uri.to_string(),
104            name.to_string(),
105            Some(description.to_string()),
106            mime_type.map(|s| s.to_string()),
107        );
108        
109        let mut metadata_map = self.resource_metadata.write().await;
110        metadata_map.insert(uri.to_string(), metadata);
111        drop(metadata_map);
112        
113        let mut resources = self.resources.write().await;
114        resources.insert(uri.to_string(), handler);
115        
116        debug!("Registered MCP resource: {}", uri);
117    }
118    
119    /// Get tool metadata by name
120    pub async fn get_tool_metadata(&self, name: &str) -> Option<ToolMetadata> {
121        let metadata = self.metadata.read().await;
122        metadata.get(name).cloned()
123    }
124    
125    /// Get all tool metadata
126    pub async fn get_all_tool_metadata(&self) -> Vec<ToolMetadata> {
127        let metadata = self.metadata.read().await;
128        metadata.values().cloned().collect()
129    }
130    
131    /// Get resource metadata by URI
132    pub async fn get_resource_metadata(&self, uri: &str) -> Option<ResourceMetadata> {
133        let metadata = self.resource_metadata.read().await;
134        metadata.get(uri).cloned()
135    }
136    
137    /// Get all resource metadata
138    pub async fn get_all_resource_metadata(&self) -> Vec<ResourceMetadata> {
139        let metadata = self.resource_metadata.read().await;
140        metadata.values().cloned().collect()
141    }
142    
143    /// Register a prompt handler
144    pub async fn register_prompt(&self, name: &str, handler: McpPromptHandler) {
145        let mut prompts = self.prompts.write().await;
146        prompts.insert(name.to_string(), handler);
147        debug!("Registered MCP prompt: {}", name);
148    }
149    
150    /// Get a prompt handler by name
151    pub async fn get_prompt_handler(&self, name: &str) -> Option<McpPromptHandler> {
152        let prompts = self.prompts.read().await;
153        prompts.get(name).cloned()
154    }
155    
156    /// List all registered prompt names
157    pub async fn list_prompts(&self) -> Vec<String> {
158        let prompts = self.prompts.read().await;
159        prompts.keys().cloned().collect()
160    }
161    
162    /// Run the MCP server (main loop)
163    pub async fn run(&self) -> Result<()> {
164        info!("Starting MCP server...");
165        
166        // Register tools before starting
167        self.setup_tools().await;
168        
169        loop {
170            match self.transport.receive().await? {
171                Some(McpMessage::Request(request)) => {
172                    let response = self.handle_request(request).await;
173                    self.transport.send_response(response).await?;
174                }
175                Some(McpMessage::Notification(notification)) => {
176                    self.handle_notification(notification).await;
177                }
178                Some(McpMessage::Response(response)) => {
179                    // We received a response (shouldn't happen in server mode)
180                    warn!("Unexpected response received: {:?}", response.id);
181                }
182                None => {
183                    info!("Transport closed, shutting down MCP server");
184                    break;
185                }
186            }
187        }
188        
189        Ok(())
190    }
191    
192    /// Setup default tools
193    async fn setup_tools(&self) {
194        // run_command tool
195        self.register_tool(
196            "run_command",
197            "Execute a shell command and return the output",
198            json!({
199                "type": "object",
200                "properties": {
201                    "command": {
202                        "type": "string",
203                        "description": "The command to execute"
204                    },
205                    "cwd": {
206                        "type": "string",
207                        "description": "Working directory (optional)"
208                    },
209                    "timeout_ms": {
210                        "type": "integer",
211                        "description": "Timeout in milliseconds (default: 30000)"
212                    }
213                },
214                "required": ["command"]
215            }),
216            Arc::new(|args| {
217                let command = args.get("command")
218                    .and_then(|v| v.as_str())
219                    .ok_or_else(|| anyhow::anyhow!("Missing command"))?;
220                
221                let cwd = args.get("cwd").and_then(|v| v.as_str());
222                
223                let mut cmd = std::process::Command::new("/bin/sh");
224                cmd.arg("-c").arg(command);
225                
226                if let Some(dir) = cwd {
227                    cmd.current_dir(dir);
228                }
229                
230                let output = cmd.output()?;
231                let stdout = String::from_utf8_lossy(&output.stdout);
232                let stderr = String::from_utf8_lossy(&output.stderr);
233                
234                let result = if output.status.success() {
235                    format!("{}{}", stdout, stderr)
236                } else {
237                    format!("Exit code: {}\n{}{}", output.status.code().unwrap_or(-1), stdout, stderr)
238                };
239                
240                Ok(CallToolResult {
241                    content: vec![ToolContent::Text { text: result }],
242                    is_error: !output.status.success(),
243                })
244            }),
245        ).await;
246        
247        // read_file tool
248        self.register_tool(
249            "read_file",
250            "Read the contents of a file",
251            json!({
252                "type": "object",
253                "properties": {
254                    "path": {
255                        "type": "string",
256                        "description": "Path to the file to read"
257                    },
258                    "offset": {
259                        "type": "integer",
260                        "description": "Line offset to start reading from (1-indexed)"
261                    },
262                    "limit": {
263                        "type": "integer",
264                        "description": "Maximum number of lines to read"
265                    }
266                },
267                "required": ["path"]
268            }),
269            Arc::new(|args| {
270                let path = args.get("path")
271                    .and_then(|v| v.as_str())
272                    .ok_or_else(|| anyhow::anyhow!("Missing path"))?;
273                
274                let content = std::fs::read_to_string(path)?;
275                
276                let offset = args.get("offset").and_then(|v| v.as_u64()).unwrap_or(1) as usize;
277                let limit = args.get("limit").and_then(|v| v.as_u64());
278                
279                let lines: Vec<&str> = content.lines().collect();
280                let start = (offset.saturating_sub(1)).min(lines.len());
281                let end = if let Some(l) = limit {
282                    (start + l as usize).min(lines.len())
283                } else {
284                    lines.len()
285                };
286                
287                let result = lines[start..end].join("\n");
288                
289                Ok(CallToolResult {
290                    content: vec![ToolContent::Text { text: result }],
291                    is_error: false,
292                })
293            }),
294        ).await;
295        
296        // write_file tool
297        self.register_tool(
298            "write_file",
299            "Write content to a file",
300            json!({
301                "type": "object",
302                "properties": {
303                    "path": {
304                        "type": "string",
305                        "description": "Path to the file to write"
306                    },
307                    "content": {
308                        "type": "string",
309                        "description": "Content to write"
310                    },
311                    "create_dirs": {
312                        "type": "boolean",
313                        "description": "Create parent directories if they don't exist"
314                    }
315                },
316                "required": ["path", "content"]
317            }),
318            Arc::new(|args| {
319                let path = args.get("path")
320                    .and_then(|v| v.as_str())
321                    .ok_or_else(|| anyhow::anyhow!("Missing path"))?;
322                
323                let content = args.get("content")
324                    .and_then(|v| v.as_str())
325                    .ok_or_else(|| anyhow::anyhow!("Missing content"))?;
326                
327                let create_dirs = args.get("create_dirs")
328                    .and_then(|v| v.as_bool())
329                    .unwrap_or(false);
330                
331                if create_dirs {
332                    if let Some(parent) = std::path::Path::new(path).parent() {
333                        std::fs::create_dir_all(parent)?;
334                    }
335                }
336                
337                std::fs::write(path, content)?;
338                
339                Ok(CallToolResult {
340                    content: vec![ToolContent::Text { text: format!("Wrote {} bytes to {}", content.len(), path) }],
341                    is_error: false,
342                })
343            }),
344        ).await;
345        
346        // list_directory tool
347        self.register_tool(
348            "list_directory",
349            "List contents of a directory",
350            json!({
351                "type": "object",
352                "properties": {
353                    "path": {
354                        "type": "string",
355                        "description": "Path to the directory"
356                    },
357                    "recursive": {
358                        "type": "boolean",
359                        "description": "List recursively"
360                    },
361                    "max_depth": {
362                        "type": "integer",
363                        "description": "Maximum depth for recursive listing"
364                    }
365                },
366                "required": ["path"]
367            }),
368            Arc::new(|args| {
369                let path = args.get("path")
370                    .and_then(|v| v.as_str())
371                    .ok_or_else(|| anyhow::anyhow!("Missing path"))?;
372                
373                let mut entries = Vec::new();
374                for entry in std::fs::read_dir(path)? {
375                    let entry = entry?;
376                    let file_type = entry.file_type()?;
377                    let name = entry.file_name().to_string_lossy().to_string();
378                    let suffix = if file_type.is_dir() { "/" } else { "" };
379                    entries.push(format!("{}{}", name, suffix));
380                }
381                
382                entries.sort();
383                
384                Ok(CallToolResult {
385                    content: vec![ToolContent::Text { text: entries.join("\n") }],
386                    is_error: false,
387                })
388            }),
389        ).await;
390        
391        // search_files tool
392        self.register_tool(
393            "search_files",
394            "Search for files matching a pattern",
395            json!({
396                "type": "object",
397                "properties": {
398                    "pattern": {
399                        "type": "string",
400                        "description": "Search pattern (glob or regex)"
401                    },
402                    "path": {
403                        "type": "string",
404                        "description": "Directory to search in"
405                    },
406                    "content_pattern": {
407                        "type": "string",
408                        "description": "Pattern to search in file contents"
409                    }
410                },
411                "required": ["pattern"]
412            }),
413            Arc::new(|args| {
414                let pattern = args.get("pattern")
415                    .and_then(|v| v.as_str())
416                    .ok_or_else(|| anyhow::anyhow!("Missing pattern"))?;
417                
418                let path = args.get("path")
419                    .and_then(|v| v.as_str())
420                    .unwrap_or(".");
421                
422                // Simple glob using find command
423                let output = std::process::Command::new("find")
424                    .args([path, "-name", pattern, "-type", "f"])
425                    .output()?;
426                
427                let result = String::from_utf8_lossy(&output.stdout);
428                
429                Ok(CallToolResult {
430                    content: vec![ToolContent::Text { text: result.to_string() }],
431                    is_error: !output.status.success(),
432                })
433            }),
434        ).await;
435        
436        // grep_search tool
437        self.register_tool(
438            "grep_search",
439            "Search file contents using grep",
440            json!({
441                "type": "object",
442                "properties": {
443                    "query": {
444                        "type": "string",
445                        "description": "Search pattern"
446                    },
447                    "path": { 
448                        "type": "string",
449                        "description": "Directory or file to search"
450                    },
451                    "is_regex": {
452                        "type": "boolean",
453                        "description": "Treat pattern as regex"
454                    },
455                    "case_sensitive": {
456                        "type": "boolean",
457                        "description": "Case-sensitive search"
458                    }
459                },
460                "required": ["query"]
461            }),
462            Arc::new(|args| {
463                let query = args.get("query")
464                    .and_then(|v| v.as_str())
465                    .ok_or_else(|| anyhow::anyhow!("Missing query"))?;
466                
467                let path = args.get("path")
468                    .and_then(|v| v.as_str())
469                    .unwrap_or(".");
470                
471                let is_regex = args.get("is_regex")
472                    .and_then(|v| v.as_bool())
473                    .unwrap_or(false);
474                
475                let case_sensitive = args.get("case_sensitive")
476                    .and_then(|v| v.as_bool())
477                    .unwrap_or(false);
478                
479                let mut cmd = std::process::Command::new("grep");
480                cmd.arg("-r").arg("-n");
481                
482                if !case_sensitive {
483                    cmd.arg("-i");
484                }
485                
486                if is_regex {
487                    cmd.arg("-E");
488                } else {
489                    cmd.arg("-F");
490                }
491                
492                cmd.arg(query).arg(path);
493                
494                let output = cmd.output()?;
495                let result = String::from_utf8_lossy(&output.stdout);
496                
497                Ok(CallToolResult {
498                    content: vec![ToolContent::Text { text: result.to_string() }],
499                    is_error: false,
500                })
501            }),
502        ).await;
503        
504        info!("Registered {} MCP tools", self.tools.read().await.len());
505    }
506    
507    /// Handle a JSON-RPC request
508    async fn handle_request(&self, request: JsonRpcRequest) -> JsonRpcResponse {
509        debug!("Handling request: {} (id: {:?})", request.method, request.id);
510        
511        let result = match request.method.as_str() {
512            "initialize" => self.handle_initialize(request.params).await,
513            "initialized" => Ok(json!({})),
514            "ping" => Ok(json!({})),
515            "tools/list" => self.handle_list_tools(request.params).await,
516            "tools/call" => self.handle_call_tool(request.params).await,
517            "resources/list" => self.handle_list_resources(request.params).await,
518            "resources/read" => self.handle_read_resource(request.params).await,
519            "prompts/list" => self.handle_list_prompts(request.params).await,
520            "prompts/get" => self.handle_get_prompt(request.params).await,
521            _ => Err(JsonRpcError::method_not_found(&request.method)),
522        };
523        
524        match result {
525            Ok(value) => JsonRpcResponse::success(request.id, value),
526            Err(error) => JsonRpcResponse::error(request.id, error),
527        }
528    }
529    
530    /// Handle a notification
531    async fn handle_notification(&self, notification: JsonRpcNotification) {
532        debug!("Handling notification: {}", notification.method);
533        
534        match notification.method.as_str() {
535            "notifications/initialized" => {
536                *self.initialized.write().await = true;
537                info!("MCP client initialized");
538            }
539            "notifications/cancelled" => {
540                // Handle cancellation
541            }
542            _ => {
543                debug!("Unknown notification: {}", notification.method);
544            }
545        }
546    }
547    
548    /// Handle initialize request
549    async fn handle_initialize(&self, params: Option<Value>) -> Result<Value, JsonRpcError> {
550        let _params: InitializeParams = if let Some(p) = params {
551            serde_json::from_value(p).map_err(|e| JsonRpcError::invalid_params(e.to_string()))?
552        } else {
553            return Err(JsonRpcError::invalid_params("Missing params"));
554        };
555        
556        let result = InitializeResult {
557            protocol_version: PROTOCOL_VERSION.to_string(),
558            capabilities: ServerCapabilities {
559                tools: Some(ToolsCapability { list_changed: true }),
560                resources: Some(ResourcesCapability { subscribe: false, list_changed: true }),
561                prompts: Some(PromptsCapability { list_changed: true }),
562                logging: Some(LoggingCapability {}),
563                experimental: None,
564            },
565            server_info: self.server_info.clone(),
566            instructions: Some(
567                "CodeTether is an AI coding agent with tools for file operations, \
568                 command execution, code search, and autonomous task execution. \
569                 Use the swarm tool for complex tasks requiring parallel execution, \
570                 and ralph for PRD-driven development.".to_string()
571            ),
572        };
573        
574        serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
575    }
576    
577    /// Handle list tools request
578    async fn handle_list_tools(&self, _params: Option<Value>) -> Result<Value, JsonRpcError> {
579        let _tools = self.tools.read().await;
580        
581        let tool_list: Vec<McpTool> = vec![
582            McpTool {
583                name: "run_command".to_string(),
584                description: Some("Execute a shell command".to_string()),
585                input_schema: json!({
586                    "type": "object",
587                    "properties": {
588                        "command": { "type": "string" },
589                        "cwd": { "type": "string" }
590                    },
591                    "required": ["command"]
592                }),
593            },
594            McpTool {
595                name: "read_file".to_string(),
596                description: Some("Read file contents".to_string()),
597                input_schema: json!({
598                    "type": "object",
599                    "properties": {
600                        "path": { "type": "string" },
601                        "offset": { "type": "integer" },
602                        "limit": { "type": "integer" }
603                    },
604                    "required": ["path"]
605                }),
606            },
607            McpTool {
608                name: "write_file".to_string(),
609                description: Some("Write content to a file".to_string()),
610                input_schema: json!({
611                    "type": "object",
612                    "properties": {
613                        "path": { "type": "string" },
614                        "content": { "type": "string" }
615                    },
616                    "required": ["path", "content"]
617                }),
618            },
619            McpTool {
620                name: "list_directory".to_string(),
621                description: Some("List directory contents".to_string()),
622                input_schema: json!({
623                    "type": "object",
624                    "properties": {
625                        "path": { "type": "string" }
626                    },
627                    "required": ["path"]
628                }),
629            },
630            McpTool {
631                name: "search_files".to_string(),
632                description: Some("Search for files by name pattern".to_string()),
633                input_schema: json!({
634                    "type": "object",
635                    "properties": {
636                        "pattern": { "type": "string" },
637                        "path": { "type": "string" }
638                    },
639                    "required": ["pattern"]
640                }),
641            },
642            McpTool {
643                name: "grep_search".to_string(),
644                description: Some("Search file contents".to_string()),
645                input_schema: json!({
646                    "type": "object",
647                    "properties": {
648                        "query": { "type": "string" },
649                        "path": { "type": "string" },
650                        "is_regex": { "type": "boolean" }
651                    },
652                    "required": ["query"]
653                }),
654            },
655        ];
656        
657        let result = ListToolsResult {
658            tools: tool_list,
659            next_cursor: None,
660        };
661        
662        serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
663    }
664    
665    /// Handle call tool request
666    async fn handle_call_tool(&self, params: Option<Value>) -> Result<Value, JsonRpcError> {
667        let params: CallToolParams = if let Some(p) = params {
668            serde_json::from_value(p).map_err(|e| JsonRpcError::invalid_params(e.to_string()))?
669        } else {
670            return Err(JsonRpcError::invalid_params("Missing params"));
671        };
672        
673        let tools = self.tools.read().await;
674        let handler = tools.get(&params.name)
675            .ok_or_else(|| JsonRpcError::method_not_found(&params.name))?;
676        
677        match handler(params.arguments) {
678            Ok(result) => serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string())),
679            Err(e) => {
680                let result = CallToolResult {
681                    content: vec![ToolContent::Text { text: e.to_string() }],
682                    is_error: true,
683                };
684                serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
685            }
686        }
687    }
688    
689    /// Handle list resources request
690    async fn handle_list_resources(&self, _params: Option<Value>) -> Result<Value, JsonRpcError> {
691        let result = ListResourcesResult {
692            resources: vec![],
693            next_cursor: None,
694        };
695        
696        serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
697    }
698    
699    /// Handle read resource request
700    async fn handle_read_resource(&self, params: Option<Value>) -> Result<Value, JsonRpcError> {
701        let params: ReadResourceParams = if let Some(p) = params {
702            serde_json::from_value(p).map_err(|e| JsonRpcError::invalid_params(e.to_string()))?
703        } else {
704            return Err(JsonRpcError::invalid_params("Missing params"));
705        };
706        
707        let resources = self.resources.read().await;
708        let handler = resources.get(&params.uri)
709            .ok_or_else(|| JsonRpcError::method_not_found(&params.uri))?;
710        
711        match handler(params.uri.clone()) {
712            Ok(result) => serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string())),
713            Err(e) => Err(JsonRpcError::internal_error(e.to_string())),
714        }
715    }
716    
717    /// Handle list prompts request
718    async fn handle_list_prompts(&self, _params: Option<Value>) -> Result<Value, JsonRpcError> {
719        let result = ListPromptsResult {
720            prompts: vec![
721                McpPrompt {
722                    name: "code_review".to_string(),
723                    description: Some("Review code for issues and improvements".to_string()),
724                    arguments: vec![
725                        PromptArgument {
726                            name: "file".to_string(),
727                            description: Some("File to review".to_string()),
728                            required: true,
729                        },
730                    ],
731                },
732                McpPrompt {
733                    name: "explain_code".to_string(),
734                    description: Some("Explain what code does".to_string()),
735                    arguments: vec![
736                        PromptArgument {
737                            name: "file".to_string(),
738                            description: Some("File to explain".to_string()),
739                            required: true,
740                        },
741                    ],
742                },
743            ],
744            next_cursor: None,
745        };
746        
747        serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
748    }
749    
750    /// Handle get prompt request
751    async fn handle_get_prompt(&self, params: Option<Value>) -> Result<Value, JsonRpcError> {
752        let params: GetPromptParams = if let Some(p) = params {
753            serde_json::from_value(p).map_err(|e| JsonRpcError::invalid_params(e.to_string()))?
754        } else {
755            return Err(JsonRpcError::invalid_params("Missing params"));
756        };
757        
758        let result = match params.name.as_str() {
759            "code_review" => {
760                let file = params.arguments.get("file")
761                    .and_then(|v| v.as_str())
762                    .unwrap_or("file.rs");
763                
764                GetPromptResult {
765                    description: Some("Code review prompt".to_string()),
766                    messages: vec![
767                        PromptMessage {
768                            role: PromptRole::User,
769                            content: PromptContent::Text {
770                                text: format!(
771                                    "Please review the following code for:\n\
772                                     - Bugs and potential issues\n\
773                                     - Performance concerns\n\
774                                     - Code style and best practices\n\
775                                     - Security vulnerabilities\n\n\
776                                     File: {}", file
777                                ),
778                            },
779                        },
780                    ],
781                }
782            }
783            "explain_code" => {
784                let file = params.arguments.get("file")
785                    .and_then(|v| v.as_str())
786                    .unwrap_or("file.rs");
787                
788                GetPromptResult {
789                    description: Some("Code explanation prompt".to_string()),
790                    messages: vec![
791                        PromptMessage {
792                            role: PromptRole::User,
793                            content: PromptContent::Text {
794                                text: format!(
795                                    "Please explain what this code does, including:\n\
796                                     - Overall purpose\n\
797                                     - Key functions and their roles\n\
798                                     - Data flow\n\
799                                     - Important algorithms used\n\n\
800                                     File: {}", file
801                                ),
802                            },
803                        },
804                    ],
805                }
806            }
807            _ => return Err(JsonRpcError::method_not_found(&params.name)),
808        };
809        
810        serde_json::to_value(result).map_err(|e| JsonRpcError::internal_error(e.to_string()))
811    }
812}