use serde::{Deserialize, Serialize}; use anyhow::{Result, Context}; use serde_json::{json, Value}; use std::path::Path; use std::sync::Arc; use tokio::sync::Mutex; use axum::{ extract::{Path as AxumPath, State}, http::Method, response::Json, routing::{get, post}, Router, }; use tower::ServiceBuilder; use tower_http::cors::{CorsLayer, Any}; use crate::config::Config; use crate::persona::Persona; use crate::transmission::TransmissionController; use crate::scheduler::AIScheduler; use crate::http_client::{ServiceClient, ServiceDetector}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MCPTool { pub name: String, pub description: String, pub input_schema: Value, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MCPRequest { pub method: String, pub params: Value, pub id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MCPResponse { pub result: Option, pub error: Option, pub id: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MCPError { pub code: i32, pub message: String, pub data: Option, } // HTTP MCP Server state pub type AppState = Arc>; // MCP HTTP request types for REST-style endpoints #[derive(Debug, Serialize, Deserialize)] pub struct MCPHttpRequest { pub user_id: Option, pub message: Option, pub provider: Option, pub model: Option, pub query: Option, pub keywords: Option>, pub limit: Option, pub content: Option, pub file_path: Option, pub command: Option, pub pattern: Option, } #[derive(Debug, Serialize)] pub struct MCPHttpResponse { pub success: bool, pub result: Option, pub error: Option, } #[allow(dead_code)] pub struct MCPServer { config: Config, persona: Persona, transmission_controller: TransmissionController, scheduler: AIScheduler, service_client: ServiceClient, service_detector: ServiceDetector, user_id: String, data_dir: Option, } impl MCPServer { pub fn new(config: Config, user_id: String, data_dir: Option) -> Result { let persona = Persona::new(&config)?; let transmission_controller = TransmissionController::new(config.clone())?; let scheduler = AIScheduler::new(&config)?; let service_client = ServiceClient::new(); let service_detector = ServiceDetector::new(); Ok(MCPServer { config, persona, transmission_controller, scheduler, service_client, service_detector, user_id, data_dir, }) } pub fn get_tools(&self) -> Vec { vec![ MCPTool { name: "get_status".to_string(), description: "Get AI status including mood, fortune, and personality".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID to get relationship status for (optional)" } } }), }, MCPTool { name: "chat_with_ai".to_string(), description: "Send a message to the AI and get a response".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID for the conversation" }, "message": { "type": "string", "description": "Message to send to the AI" }, "provider": { "type": "string", "description": "AI provider to use (ollama/openai) - optional" }, "model": { "type": "string", "description": "AI model to use - optional" } }, "required": ["user_id", "message"] }), }, MCPTool { name: "get_relationships".to_string(), description: "Get all relationships and their statuses".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": {} }), }, MCPTool { name: "get_memories".to_string(), description: "Get memories for a specific user".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID to get memories for" }, "limit": { "type": "integer", "description": "Maximum number of memories to return (default: 10)" } }, "required": ["user_id"] }), }, MCPTool { name: "get_contextual_memories".to_string(), description: "Get memories organized by priority with contextual relevance".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID to get memories for" }, "query": { "type": "string", "description": "Query text for contextual relevance matching" }, "limit": { "type": "integer", "description": "Maximum number of memories to return (default: 10)" } }, "required": ["user_id", "query"] }), }, MCPTool { name: "search_memories".to_string(), description: "Search memories by keywords and types".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID to search memories for" }, "keywords": { "type": "array", "items": {"type": "string"}, "description": "Keywords to search for in memory content" }, "memory_types": { "type": "array", "items": {"type": "string"}, "description": "Memory types to filter by (conversation, core, summary, experience)" } }, "required": ["user_id", "keywords"] }), }, MCPTool { name: "create_summary".to_string(), description: "Create AI-powered summary of recent memories".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID to create summary for" } }, "required": ["user_id"] }), }, MCPTool { name: "create_core_memory".to_string(), description: "Create core memory by analyzing existing memories".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_id": { "type": "string", "description": "User ID to create core memory for" } }, "required": ["user_id"] }), }, MCPTool { name: "execute_command".to_string(), description: "Execute shell commands with safety checks".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "command": { "type": "string", "description": "Shell command to execute" }, "working_dir": { "type": "string", "description": "Working directory for command execution (optional)" }, "timeout": { "type": "integer", "description": "Timeout in seconds (default: 30)" } }, "required": ["command"] }), }, MCPTool { name: "analyze_file".to_string(), description: "Analyze files using AI provider".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to file to analyze" }, "analysis_type": { "type": "string", "description": "Type of analysis (code, text, structure, security)", "default": "general" } }, "required": ["file_path"] }), }, MCPTool { name: "write_file".to_string(), description: "Write content to files with backup functionality".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "file_path": { "type": "string", "description": "Path to file to write" }, "content": { "type": "string", "description": "Content to write to file" }, "create_backup": { "type": "boolean", "description": "Create backup before writing (default: true)" } }, "required": ["file_path", "content"] }), }, MCPTool { name: "list_files".to_string(), description: "List files in directory with pattern matching".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "directory": { "type": "string", "description": "Directory to list files from" }, "pattern": { "type": "string", "description": "Glob pattern for file filtering (optional)" }, "recursive": { "type": "boolean", "description": "Recursive directory traversal (default: false)" } }, "required": ["directory"] }), }, MCPTool { name: "check_transmissions".to_string(), description: "Check and execute autonomous transmissions".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": {} }), }, MCPTool { name: "run_maintenance".to_string(), description: "Run daily maintenance tasks".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": {} }), }, MCPTool { name: "run_scheduler".to_string(), description: "Run scheduled tasks".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": {} }), }, MCPTool { name: "get_scheduler_status".to_string(), description: "Get scheduler statistics and upcoming tasks".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": {} }), }, MCPTool { name: "get_transmission_history".to_string(), description: "Get recent transmission history".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "limit": { "type": "integer", "description": "Maximum number of transmissions to return (default: 10)" } } }), }, MCPTool { name: "get_user_cards".to_string(), description: "Get user's card collection from ai.card service".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_did": { "type": "string", "description": "User DID to get cards for" } }, "required": ["user_did"] }), }, MCPTool { name: "draw_card".to_string(), description: "Draw a card from ai.card gacha system".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_did": { "type": "string", "description": "User DID to draw card for" }, "is_paid": { "type": "boolean", "description": "Whether this is a premium draw (default: false)" } }, "required": ["user_did"] }), }, MCPTool { name: "get_draw_status".to_string(), description: "Check if user can draw cards (daily limit check)".to_string(), input_schema: serde_json::json!({ "type": "object", "properties": { "user_did": { "type": "string", "description": "User DID to check draw status for" } }, "required": ["user_did"] }), }, ] } pub async fn handle_request(&mut self, request: MCPRequest) -> MCPResponse { let result = match request.method.as_str() { "tools/list" => self.handle_list_tools().await, "tools/call" => self.handle_tool_call(request.params).await, // HTTP endpoint直接呼び出し対応 method => self.handle_direct_tool_call(method, request.params).await, }; match result { Ok(value) => MCPResponse { result: Some(value), error: None, id: request.id, }, Err(e) => MCPResponse { result: None, error: Some(MCPError { code: -32603, message: e.to_string(), data: None, }), id: request.id, }, } } async fn handle_list_tools(&self) -> Result { let tools = self.get_tools(); Ok(serde_json::json!({ "tools": tools })) } async fn handle_tool_call(&mut self, params: Value) -> Result { let tool_name = params["name"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing tool name"))?; let arguments = params["arguments"].clone(); match tool_name { "get_status" => self.tool_get_status(arguments).await, "chat_with_ai" => self.tool_chat_with_ai(arguments).await, "get_relationships" => self.tool_get_relationships(arguments).await, "get_contextual_memories" => self.tool_get_contextual_memories(arguments).await, "search_memories" => self.tool_search_memories(arguments).await, "create_summary" => self.tool_create_summary(arguments).await, "create_core_memory" => self.tool_create_core_memory(arguments).await, "execute_command" => self.tool_execute_command(arguments).await, "analyze_file" => self.tool_analyze_file(arguments).await, "write_file" => self.tool_write_file(arguments).await, "list_files" => self.tool_list_files(arguments).await, "get_memories" => self.tool_get_memories(arguments).await, "check_transmissions" => self.tool_check_transmissions(arguments).await, "run_maintenance" => self.tool_run_maintenance(arguments).await, "run_scheduler" => self.tool_run_scheduler(arguments).await, "get_scheduler_status" => self.tool_get_scheduler_status(arguments).await, "get_transmission_history" => self.tool_get_transmission_history(arguments).await, "get_user_cards" => self.tool_get_user_cards(arguments).await, "draw_card" => self.tool_draw_card(arguments).await, "get_draw_status" => self.tool_get_draw_status(arguments).await, _ => Err(anyhow::anyhow!("Unknown tool: {}", tool_name)), } } async fn handle_direct_tool_call(&mut self, tool_name: &str, params: Value) -> Result { // HTTP endpointからの直接呼び出し用(パラメータ構造が異なる) match tool_name { "get_status" => self.tool_get_status(params).await, "chat_with_ai" => self.tool_chat_with_ai(params).await, "get_relationships" => self.tool_get_relationships(params).await, "get_memories" => self.tool_get_memories(params).await, "get_contextual_memories" => self.tool_get_contextual_memories(params).await, "search_memories" => self.tool_search_memories(params).await, "create_summary" => self.tool_create_summary(params).await, "create_core_memory" => self.tool_create_core_memory(params).await, "execute_command" => self.tool_execute_command(params).await, "analyze_file" => self.tool_analyze_file(params).await, "write_file" => self.tool_write_file(params).await, "list_files" => self.tool_list_files(params).await, "check_transmissions" => self.tool_check_transmissions(params).await, "run_maintenance" => self.tool_run_maintenance(params).await, "run_scheduler" => self.tool_run_scheduler(params).await, "get_scheduler_status" => self.tool_get_scheduler_status(params).await, "get_transmission_history" => self.tool_get_transmission_history(params).await, "get_user_cards" => self.tool_get_user_cards(params).await, "draw_card" => self.tool_draw_card(params).await, "get_draw_status" => self.tool_get_draw_status(params).await, _ => Err(anyhow::anyhow!("Unknown tool: {}", tool_name)), } } async fn tool_get_status(&self, args: Value) -> Result { let user_id = args["user_id"].as_str(); let state = self.persona.get_current_state()?; let mut result = serde_json::json!({ "mood": state.current_mood, "fortune": state.fortune_value, "breakthrough_triggered": state.breakthrough_triggered, "personality": state.base_personality }); if let Some(user_id) = user_id { if let Some(relationship) = self.persona.get_relationship(user_id) { result["relationship"] = serde_json::json!({ "status": relationship.status.to_string(), "score": relationship.score, "threshold": relationship.threshold, "transmission_enabled": relationship.transmission_enabled, "total_interactions": relationship.total_interactions, "is_broken": relationship.is_broken }); } } Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&result)? } ] })) } async fn tool_chat_with_ai(&mut self, args: Value) -> Result { let user_id = args["user_id"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_id"))?; let message = args["message"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing message"))?; let provider = args["provider"].as_str().map(|s| s.to_string()); let model = args["model"].as_str().map(|s| s.to_string()); let (response, relationship_delta) = if provider.is_some() || model.is_some() { self.persona.process_ai_interaction(user_id, message, provider, model).await? } else { self.persona.process_interaction(user_id, message)? }; let relationship_status = self.persona.get_relationship(user_id) .map(|r| serde_json::json!({ "status": r.status.to_string(), "score": r.score, "transmission_enabled": r.transmission_enabled })); Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("AI Response: {}\n\nRelationship Change: {:+.2}\n\nRelationship Status: {}", response, relationship_delta, relationship_status.map(|r| r.to_string()).unwrap_or_else(|| "None".to_string())) } ] })) } async fn tool_get_relationships(&self, _args: Value) -> Result { let relationships = self.persona.list_all_relationships(); let relationships_json: Vec = relationships.iter() .map(|(user_id, rel)| { serde_json::json!({ "user_id": user_id, "status": rel.status.to_string(), "score": rel.score, "threshold": rel.threshold, "transmission_enabled": rel.transmission_enabled, "total_interactions": rel.total_interactions, "is_broken": rel.is_broken, "last_interaction": rel.last_interaction }) }) .collect(); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&relationships_json)? } ] })) } async fn tool_get_memories(&mut self, args: Value) -> Result { let user_id = args["user_id"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_id"))?; let limit = args["limit"].as_u64().unwrap_or(10) as usize; let memories = self.persona.get_memories(user_id, limit); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&memories)? } ] })) } async fn tool_check_transmissions(&mut self, _args: Value) -> Result { let autonomous = self.transmission_controller.check_autonomous_transmissions(&mut self.persona).await?; let breakthrough = self.transmission_controller.check_breakthrough_transmissions(&mut self.persona).await?; let maintenance = self.transmission_controller.check_maintenance_transmissions(&mut self.persona).await?; let total = autonomous.len() + breakthrough.len() + maintenance.len(); let result = serde_json::json!({ "autonomous_transmissions": autonomous.len(), "breakthrough_transmissions": breakthrough.len(), "maintenance_transmissions": maintenance.len(), "total_transmissions": total, "transmissions": { "autonomous": autonomous, "breakthrough": breakthrough, "maintenance": maintenance } }); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&result)? } ] })) } async fn tool_get_contextual_memories(&self, args: Value) -> Result { let user_id = args["user_id"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_id"))?; let query = args["query"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing query"))?; let limit = args["limit"].as_u64().unwrap_or(10) as usize; // For now, use search_memories as a placeholder for contextual memories let keywords = query.split_whitespace().map(|s| s.to_string()).collect::>(); let memory_results = self.persona.search_memories(user_id, &keywords); let memories = memory_results.into_iter().take(limit).collect::>(); let memories_json: Vec = memories.iter() .enumerate() .map(|(id, content)| { serde_json::json!({ "id": id, "content": content, "level": "conversation", "importance": 0.5, "is_core": false, "timestamp": chrono::Utc::now().to_rfc3339(), "summary": None::, "metadata": {} }) }) .collect(); Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Found {} contextual memories for query: '{}'\n\n{}", memories.len(), query, serde_json::to_string_pretty(&memories_json)?) } ] })) } async fn tool_search_memories(&self, args: Value) -> Result { let user_id = args["user_id"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_id"))?; let keywords: Vec = args["keywords"].as_array() .ok_or_else(|| anyhow::anyhow!("Missing keywords"))? .iter() .filter_map(|v| v.as_str().map(|s| s.to_string())) .collect(); if keywords.is_empty() { return Err(anyhow::anyhow!("At least one keyword is required")); } let memories = self.persona.search_memories(user_id, &keywords); let memories_json: Vec = memories.iter() .enumerate() .map(|(id, content)| { serde_json::json!({ "id": id, "content": content, "level": "conversation", "importance": 0.5, "is_core": false, "timestamp": chrono::Utc::now().to_rfc3339(), "summary": None::, "metadata": {} }) }) .collect(); Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Found {} memories matching keywords: {}\n\n{}", memories.len(), keywords.join(", "), serde_json::to_string_pretty(&memories_json)?) } ] })) } async fn tool_create_summary(&mut self, args: Value) -> Result { let user_id = args["user_id"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_id"))?; // Placeholder implementation - in full version this would use AI let result = format!("Summary created for user: {}", user_id); Ok(serde_json::json!({ "content": [ { "type": "text", "text": result } ] })) } async fn tool_create_core_memory(&mut self, args: Value) -> Result { let user_id = args["user_id"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_id"))?; // Placeholder implementation - in full version this would use AI let result = format!("Core memory created for user: {}", user_id); Ok(serde_json::json!({ "content": [ { "type": "text", "text": result } ] })) } async fn tool_execute_command(&self, args: Value) -> Result { let command = args["command"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing command"))?; let working_dir = args["working_dir"].as_str(); let _timeout = args["timeout"].as_u64().unwrap_or(30); // Security check - block dangerous commands if self.is_dangerous_command(command) { return Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Command blocked for security reasons: {}", command) } ] })); } use std::process::Command; let mut cmd = if cfg!(target_os = "windows") { let mut c = Command::new("cmd"); c.args(["/C", command]); c } else { let mut c = Command::new("sh"); c.args(["-c", command]); c }; if let Some(dir) = working_dir { cmd.current_dir(dir); } match cmd.output() { Ok(output) => { let stdout = String::from_utf8_lossy(&output.stdout); let stderr = String::from_utf8_lossy(&output.stderr); let status = output.status; Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Command: {}\nStatus: {}\n\nSTDOUT:\n{}\n\nSTDERR:\n{}", command, status, stdout, stderr) } ] })) } Err(e) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Failed to execute command '{}': {}", command, e) } ] })) } } } fn is_dangerous_command(&self, command: &str) -> bool { let dangerous_patterns = [ "rm -rf", "rmdir", "del /q", "format", "fdisk", "dd if=", "mkfs", "shutdown", "reboot", "halt", "sudo rm", "sudo dd", "chmod 777", "chown root", "> /dev/", "curl", "wget", "nc ", "netcat", ]; dangerous_patterns.iter().any(|pattern| command.contains(pattern)) } async fn tool_analyze_file(&self, args: Value) -> Result { let file_path = args["file_path"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing file_path"))?; let analysis_type = args["analysis_type"].as_str().unwrap_or("general"); use std::fs; use std::path::Path; let path = Path::new(file_path); if !path.exists() { return Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("File not found: {}", file_path) } ] })); } match fs::read_to_string(path) { Ok(content) => { let file_size = content.len(); let line_count = content.lines().count(); let analysis = match analysis_type { "code" => self.analyze_code_content(&content), "structure" => self.analyze_file_structure(&content), "security" => self.analyze_security(&content), _ => self.analyze_general_content(&content), }; Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("File Analysis: {}\nType: {}\nSize: {} bytes\nLines: {}\n\n{}", file_path, analysis_type, file_size, line_count, analysis) } ] })) } Err(e) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Failed to read file '{}': {}", file_path, e) } ] })) } } } fn analyze_code_content(&self, content: &str) -> String { let mut analysis = String::new(); // Basic code analysis if content.contains("fn ") || content.contains("function ") { analysis.push_str("Language: Likely Rust or JavaScript\n"); } if content.contains("def ") { analysis.push_str("Language: Likely Python\n"); } if content.contains("class ") { analysis.push_str("Contains class definitions\n"); } if content.contains("import ") || content.contains("use ") { analysis.push_str("Contains import/use statements\n"); } analysis.push_str(&format!("Functions/methods found: {}\n", content.matches("fn ").count() + content.matches("def ").count() + content.matches("function ").count())); analysis } fn analyze_file_structure(&self, content: &str) -> String { format!("Structure analysis:\n- Characters: {}\n- Words: {}\n- Lines: {}\n- Paragraphs: {}", content.len(), content.split_whitespace().count(), content.lines().count(), content.split("\n\n").count()) } fn analyze_security(&self, content: &str) -> String { let mut issues = Vec::new(); if content.contains("password") || content.contains("secret") { issues.push("Contains potential password/secret references"); } if content.contains("api_key") || content.contains("token") { issues.push("Contains potential API keys or tokens"); } if content.contains("eval(") || content.contains("exec(") { issues.push("Contains potentially dangerous eval/exec calls"); } if issues.is_empty() { "No obvious security issues found".to_string() } else { format!("Security concerns:\n- {}", issues.join("\n- ")) } } fn analyze_general_content(&self, content: &str) -> String { format!("General analysis:\n- File appears to be text-based\n- Contains {} characters\n- {} lines total", content.len(), content.lines().count()) } async fn tool_write_file(&self, args: Value) -> Result { let file_path = args["file_path"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing file_path"))?; let content = args["content"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing content"))?; let create_backup = args["create_backup"].as_bool().unwrap_or(true); use std::fs; use std::path::Path; let path = Path::new(file_path); // Create backup if file exists and backup is requested if create_backup && path.exists() { let backup_path = format!("{}.backup", file_path); if let Err(e) = fs::copy(file_path, &backup_path) { return Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Failed to create backup: {}", e) } ] })); } } match fs::write(path, content) { Ok(()) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Successfully wrote {} bytes to: {}", content.len(), file_path) } ] })) } Err(e) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Failed to write file '{}': {}", file_path, e) } ] })) } } } async fn tool_list_files(&self, args: Value) -> Result { let directory = args["directory"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing directory"))?; let pattern = args["pattern"].as_str(); let recursive = args["recursive"].as_bool().unwrap_or(false); use std::fs; use std::path::Path; let dir_path = Path::new(directory); if !dir_path.exists() || !dir_path.is_dir() { return Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("Directory not found or not a directory: {}", directory) } ] })); } let mut files = Vec::new(); if recursive { self.collect_files_recursive(dir_path, pattern, &mut files); } else { if let Ok(entries) = fs::read_dir(dir_path) { for entry in entries.flatten() { let path = entry.path(); let file_name = path.file_name().unwrap_or_default().to_string_lossy(); if let Some(pat) = pattern { if !file_name.contains(pat) { continue; } } files.push(format!("{} ({})", path.display(), if path.is_dir() { "directory" } else { "file" })); } } } files.sort(); let result = if files.is_empty() { "No files found matching criteria".to_string() } else { format!("Found {} items:\n{}", files.len(), files.join("\n")) }; Ok(serde_json::json!({ "content": [ { "type": "text", "text": result } ] })) } fn collect_files_recursive(&self, dir: &Path, pattern: Option<&str>, files: &mut Vec) { if let Ok(entries) = std::fs::read_dir(dir) { for entry in entries.flatten() { let path = entry.path(); let file_name = path.file_name().unwrap_or_default().to_string_lossy(); if path.is_dir() { self.collect_files_recursive(&path, pattern, files); } else { if let Some(pat) = pattern { if !file_name.contains(pat) { continue; } } files.push(path.display().to_string()); } } } } async fn tool_run_maintenance(&mut self, _args: Value) -> Result { self.persona.daily_maintenance()?; let maintenance_transmissions = self.transmission_controller.check_maintenance_transmissions(&mut self.persona).await?; let stats = self.persona.get_relationship_stats(); let result = serde_json::json!({ "maintenance_completed": true, "maintenance_transmissions_sent": maintenance_transmissions.len(), "relationship_stats": stats }); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&result)? } ] })) } async fn tool_run_scheduler(&mut self, _args: Value) -> Result { let executions = self.scheduler.run_scheduled_tasks(&mut self.persona, &mut self.transmission_controller).await?; let stats = self.scheduler.get_scheduler_stats(); let result = serde_json::json!({ "tasks_executed": executions.len(), "executions": executions, "scheduler_stats": { "total_tasks": stats.total_tasks, "enabled_tasks": stats.enabled_tasks, "due_tasks": stats.due_tasks, "total_executions": stats.total_executions, "today_executions": stats.today_executions, "success_rate": stats.success_rate, "avg_duration_ms": stats.avg_duration_ms } }); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&result)? } ] })) } async fn tool_get_scheduler_status(&self, _args: Value) -> Result { let stats = self.scheduler.get_scheduler_stats(); let upcoming_tasks: Vec<_> = self.scheduler.get_tasks() .values() .filter(|task| task.enabled) .take(10) .map(|task| { serde_json::json!({ "id": task.id, "task_type": task.task_type.to_string(), "next_run": task.next_run, "interval_hours": task.interval_hours, "run_count": task.run_count }) }) .collect(); let recent_executions = self.scheduler.get_execution_history(Some(5)); let result = serde_json::json!({ "scheduler_stats": { "total_tasks": stats.total_tasks, "enabled_tasks": stats.enabled_tasks, "due_tasks": stats.due_tasks, "total_executions": stats.total_executions, "today_executions": stats.today_executions, "success_rate": stats.success_rate, "avg_duration_ms": stats.avg_duration_ms }, "upcoming_tasks": upcoming_tasks, "recent_executions": recent_executions }); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&result)? } ] })) } async fn tool_get_transmission_history(&self, args: Value) -> Result { let limit = args["limit"].as_u64().unwrap_or(10) as usize; let transmissions = self.transmission_controller.get_recent_transmissions(limit); Ok(serde_json::json!({ "content": [ { "type": "text", "text": serde_json::to_string_pretty(&transmissions)? } ] })) } // MARK: - ai.card Integration Tools async fn tool_get_user_cards(&self, args: Value) -> Result { let user_did = args["user_did"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_did"))?; // Use ServiceClient to call ai.card API match self.service_client.get_user_cards(user_did).await { Ok(cards) => { let card_count = cards.as_array().map(|arr| arr.len()).unwrap_or(0); Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("ユーザー {} のカード一覧 ({}枚):\n\n{}", user_did, card_count, serde_json::to_string_pretty(&cards)?) } ] })) } Err(e) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("カード取得エラー: {}. ai.cardサーバーが起動していることを確認してください。", e) } ] })) } } } async fn tool_draw_card(&self, args: Value) -> Result { let user_did = args["user_did"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_did"))?; let is_paid = args["is_paid"].as_bool().unwrap_or(false); // Use ServiceClient to call ai.card API match self.service_client.draw_card(user_did, is_paid).await { Ok(draw_result) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("🎉 カードドロー結果:\n\n{}", serde_json::to_string_pretty(&draw_result)?) } ] })) } Err(e) => { let error_msg = e.to_string(); if error_msg.contains("429") { Ok(serde_json::json!({ "content": [ { "type": "text", "text": "⏰ カードドロー制限中です。日別制限により、現在カードを引くことができません。時間を置いてから再度お試しください。" } ] })) } else { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("カードドローエラー: {}. ai.cardサーバーが起動していることを確認してください。", e) } ] })) } } } } async fn tool_get_draw_status(&self, args: Value) -> Result { let user_did = args["user_did"].as_str() .ok_or_else(|| anyhow::anyhow!("Missing user_did"))?; // Use ServiceClient to call ai.card API match self.service_client.get_request(&format!("http://localhost:8000/api/v1/cards/draw-status/{}", user_did)).await { Ok(status) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("ユーザー {} のドロー状況:\n\n{}", user_did, serde_json::to_string_pretty(&status)?) } ] })) } Err(e) => { Ok(serde_json::json!({ "content": [ { "type": "text", "text": format!("ドロー状況取得エラー: {}. ai.cardサーバーが起動していることを確認してください。", e) } ] })) } } } pub async fn start_server(&mut self, port: u16) -> Result<()> { println!("🚀 Starting MCP Server on port {}", port); println!("📋 Available tools: {}", self.get_tools().len()); // Print available tools for tool in self.get_tools() { println!(" - {}: {}", ColorExt::cyan(tool.name.as_str()), tool.description); } println!("✅ MCP Server ready for requests"); // Create shared state let app_state: AppState = Arc::new(Mutex::new( MCPServer::new( self.config.clone(), self.user_id.clone(), self.data_dir.clone(), )? )); // Create router with CORS let app = Router::new() // MCP Core endpoints .route("/mcp/tools", get(list_tools)) .route("/mcp/call/:tool_name", post(call_tool)) // AI Chat endpoints .route("/chat", post(chat_with_ai_handler)) .route("/status", get(get_status_handler)) .route("/status/:user_id", get(get_status_with_user_handler)) // Memory endpoints .route("/memories/:user_id", get(get_memories_handler)) .route("/memories/:user_id/search", post(search_memories_handler)) .route("/memories/:user_id/contextual", post(get_contextual_memories_handler)) .route("/memories/:user_id/summary", post(create_summary_handler)) .route("/memories/:user_id/core", post(create_core_memory_handler)) // Relationship endpoints .route("/relationships", get(get_relationships_handler)) // System endpoints .route("/transmissions", get(check_transmissions_handler)) .route("/maintenance", post(run_maintenance_handler)) .route("/scheduler", post(run_scheduler_handler)) .route("/scheduler/status", get(get_scheduler_status_handler)) .route("/scheduler/history", get(get_transmission_history_handler)) // File operations .route("/files", get(list_files_handler)) .route("/files/analyze", post(analyze_file_handler)) .route("/files/write", post(write_file_handler)) // Shell execution .route("/execute", post(execute_command_handler)) // AI Card and AI Log proxy endpoints .route("/card/user_cards/:user_id", get(get_user_cards_handler)) .route("/card/draw", post(draw_card_handler)) .route("/card/stats", get(get_card_stats_handler)) .route("/log/posts", get(get_blog_posts_handler)) .route("/log/posts", post(create_blog_post_handler)) .route("/log/build", post(build_blog_handler)) .layer( ServiceBuilder::new() .layer( CorsLayer::new() .allow_origin(Any) .allow_methods([Method::GET, Method::POST, Method::PUT, Method::DELETE]) .allow_headers(Any), ) ) .with_state(app_state); // Start the server let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)) .await .context("Failed to bind to address")?; println!("🌐 HTTP MCP Server listening on http://0.0.0.0:{}", port); axum::serve(listener, app) .await .context("Server error")?; Ok(()) } pub async fn run(&mut self, port: u16) -> Result<()> { self.start_server(port).await } } // Helper trait for colored output (placeholder) trait ColorExt { fn cyan(&self) -> String; } impl ColorExt for str { fn cyan(&self) -> String { self.to_string() // In real implementation, would add ANSI color codes } } // HTTP Handlers for MCP endpoints // MCP Core handlers async fn list_tools(State(state): State) -> Json { let server = state.lock().await; let tools = server.get_tools(); Json(MCPHttpResponse { success: true, result: Some(json!({ "tools": tools })), error: None, }) } async fn call_tool( State(state): State, AxumPath(tool_name): AxumPath, Json(request): Json, ) -> Json { let mut server = state.lock().await; // Create MCP request from HTTP request let mcp_request = MCPRequest { method: tool_name, params: json!(request), id: Some(json!("http_request")), }; let response = server.handle_request(mcp_request).await; Json(MCPHttpResponse { success: response.error.is_none(), result: response.result, error: response.error.map(|e| e.message), }) } // AI Chat handlers async fn chat_with_ai_handler( State(state): State, Json(request): Json, ) -> Json { let mut server = state.lock().await; let user_id = request.user_id.unwrap_or_else(|| "default_user".to_string()); let message = request.message.unwrap_or_default(); let args = json!({ "user_id": user_id, "message": message, "provider": request.provider, "model": request.model }); match server.tool_chat_with_ai(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn get_status_handler(State(state): State) -> Json { let server = state.lock().await; match server.tool_get_status(json!({})).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn get_status_with_user_handler( State(state): State, AxumPath(user_id): AxumPath, ) -> Json { let server = state.lock().await; let args = json!({ "user_id": user_id }); match server.tool_get_status(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } // Memory handlers async fn get_memories_handler( State(state): State, AxumPath(user_id): AxumPath, ) -> Json { let mut server = state.lock().await; let args = json!({ "user_id": user_id, "limit": 10 }); match server.tool_get_memories(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn search_memories_handler( State(state): State, AxumPath(user_id): AxumPath, Json(request): Json, ) -> Json { let server = state.lock().await; let args = json!({ "user_id": user_id, "query": request.query.unwrap_or_default(), "keywords": request.keywords.unwrap_or_default() }); match server.tool_search_memories(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn get_contextual_memories_handler( State(state): State, AxumPath(user_id): AxumPath, Json(request): Json, ) -> Json { let server = state.lock().await; let args = json!({ "user_id": user_id, "query": request.query.unwrap_or_default(), "limit": request.limit.unwrap_or(10) }); match server.tool_get_contextual_memories(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn create_summary_handler( State(state): State, AxumPath(user_id): AxumPath, Json(request): Json, ) -> Json { let mut server = state.lock().await; let args = json!({ "user_id": user_id, "content": request.content.unwrap_or_default() }); match server.tool_create_summary(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn create_core_memory_handler( State(state): State, AxumPath(user_id): AxumPath, Json(request): Json, ) -> Json { let mut server = state.lock().await; let args = json!({ "user_id": user_id, "content": request.content.unwrap_or_default() }); match server.tool_create_core_memory(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } // Relationship handlers async fn get_relationships_handler(State(state): State) -> Json { let server = state.lock().await; match server.tool_get_relationships(json!({})).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } // System handlers async fn check_transmissions_handler(State(state): State) -> Json { let mut server = state.lock().await; match server.tool_check_transmissions(json!({})).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn run_maintenance_handler(State(state): State) -> Json { let mut server = state.lock().await; match server.tool_run_maintenance(json!({})).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn run_scheduler_handler(State(state): State) -> Json { let mut server = state.lock().await; match server.tool_run_scheduler(json!({})).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn get_scheduler_status_handler(State(state): State) -> Json { let server = state.lock().await; match server.tool_get_scheduler_status(json!({})).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn get_transmission_history_handler(State(state): State) -> Json { let server = state.lock().await; let args = json!({ "limit": 10 }); match server.tool_get_transmission_history(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } // File operation handlers async fn list_files_handler(State(state): State) -> Json { let server = state.lock().await; let args = json!({ "path": ".", "pattern": "*" }); match server.tool_list_files(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn analyze_file_handler( State(state): State, Json(request): Json, ) -> Json { let server = state.lock().await; let args = json!({ "file_path": request.file_path.unwrap_or_default() }); match server.tool_analyze_file(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } async fn write_file_handler( State(state): State, Json(request): Json, ) -> Json { let server = state.lock().await; let args = json!({ "file_path": request.file_path.unwrap_or_default(), "content": request.content.unwrap_or_default() }); match server.tool_write_file(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } // Shell execution handler async fn execute_command_handler( State(state): State, Json(request): Json, ) -> Json { let server = state.lock().await; let args = json!({ "command": request.command.unwrap_or_default() }); match server.tool_execute_command(args).await { Ok(result) => Json(MCPHttpResponse { success: true, result: Some(result), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(e.to_string()), }), } } // AI Card proxy handlers async fn get_user_cards_handler( State(state): State, AxumPath(user_id): AxumPath, ) -> Json { let server = state.lock().await; match server.service_client.get_user_cards(&user_id).await { Ok(cards) => Json(MCPHttpResponse { success: true, result: Some(cards), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(format!("Failed to get user cards: {}", e)), }), } } async fn draw_card_handler( State(state): State, Json(request): Json, ) -> Json { // Extract user_did from user_id field, default is_paid to false for now let user_did = request.user_id.as_deref().unwrap_or("unknown"); let is_paid = false; // TODO: Add is_paid field to MCPHttpRequest if needed let server = state.lock().await; match server.service_client.draw_card(user_did, is_paid).await { Ok(card) => Json(MCPHttpResponse { success: true, result: Some(card), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(format!("Failed to draw card: {}", e)), }), } } async fn get_card_stats_handler(State(state): State) -> Json { let server = state.lock().await; match server.service_client.get_card_stats().await { Ok(stats) => Json(MCPHttpResponse { success: true, result: Some(stats), error: None, }), Err(e) => Json(MCPHttpResponse { success: false, result: None, error: Some(format!("Failed to get card stats: {}", e)), }), } } // AI Log proxy handlers (placeholder - these would need to be implemented) async fn get_blog_posts_handler(State(_state): State) -> Json { // TODO: Implement ai.log service integration Json(MCPHttpResponse { success: false, result: None, error: Some("AI Log service integration not yet implemented".to_string()), }) } async fn create_blog_post_handler( State(_state): State, Json(_request): Json, ) -> Json { // TODO: Implement ai.log service integration Json(MCPHttpResponse { success: false, result: None, error: Some("AI Log service integration not yet implemented".to_string()), }) } async fn build_blog_handler(State(_state): State) -> Json { // TODO: Implement ai.log service integration Json(MCPHttpResponse { success: false, result: None, error: Some("AI Log service integration not yet implemented".to_string()), }) }