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