From 62b91e5e5abce8dd0601b645e296524406d73f30 Mon Sep 17 00:00:00 2001 From: syui Date: Tue, 29 Jul 2025 05:04:15 +0900 Subject: [PATCH] fix ref --- Cargo.toml | 4 +- extended/src/extended_mcp.rs | 398 ------------------ extended/src/lib.rs | 3 - extended/src/main.rs | 250 ----------- src/{ => bin}/mcp_server.rs | 13 +- .../bin/mcp_server_extended.rs | 5 +- src/main.rs | 4 +- src/mcp.rs | 250 ----------- src/mcp/base.rs | 280 ++++++++++++ src/mcp/extended.rs | 293 +++++++++++++ src/mcp/mod.rs | 5 + src/memory.rs | 2 +- 12 files changed, 589 insertions(+), 918 deletions(-) delete mode 100644 extended/src/extended_mcp.rs delete mode 100644 extended/src/lib.rs delete mode 100644 extended/src/main.rs rename src/{ => bin}/mcp_server.rs (78%) rename extended/src/mcp_server.rs => src/bin/mcp_server_extended.rs (91%) delete mode 100644 src/mcp.rs create mode 100644 src/mcp/base.rs create mode 100644 src/mcp/extended.rs create mode 100644 src/mcp/mod.rs diff --git a/Cargo.toml b/Cargo.toml index e38e067..f74682e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,11 @@ path = "src/main.rs" [[bin]] name = "memory-mcp" -path = "src/mcp_server.rs" +path = "src/bin/mcp_server.rs" [[bin]] name = "memory-mcp-extended" -path = "extended/src/mcp_server.rs" +path = "src/bin/mcp_server_extended.rs" [dependencies] # CLI and async diff --git a/extended/src/extended_mcp.rs b/extended/src/extended_mcp.rs deleted file mode 100644 index f94157e..0000000 --- a/extended/src/extended_mcp.rs +++ /dev/null @@ -1,398 +0,0 @@ -use anyhow::Result; -use serde_json::{json, Value}; -use std::io::{self, BufRead, Write}; - -use aigpt::memory::MemoryManager; - -pub struct ExtendedMCPServer { - memory_manager: MemoryManager, -} - -impl ExtendedMCPServer { - pub async fn new() -> Result { - let memory_manager = MemoryManager::new().await?; - Ok(ExtendedMCPServer { memory_manager }) - } - - pub async fn run(&mut self) -> Result<()> { - let stdin = io::stdin(); - let mut stdout = io::stdout(); - - let reader = stdin.lock(); - let lines = reader.lines(); - - for line_result in lines { - match line_result { - Ok(line) => { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - if let Ok(request) = serde_json::from_str::(&trimmed) { - let response = self.handle_request(request).await; - let response_str = serde_json::to_string(&response)?; - stdout.write_all(response_str.as_bytes())?; - stdout.write_all(b"\n")?; - stdout.flush()?; - } - } - Err(_) => break, - } - } - - Ok(()) - } - - async fn handle_request(&mut self, request: Value) -> Value { - let method = request["method"].as_str().unwrap_or(""); - let id = request["id"].clone(); - - match method { - "initialize" => { - json!({ - "jsonrpc": "2.0", - "id": id, - "result": { - "protocolVersion": "2024-11-05", - "capabilities": { - "tools": {} - }, - "serverInfo": { - "name": "aigpt-extended", - "version": "0.1.0" - } - } - }) - } - "tools/list" => { - #[allow(unused_mut)] - let mut tools = vec![ - // Basic tools - json!({ - "name": "create_memory", - "description": "Create a new memory entry", - "inputSchema": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content of the memory" - }, - "analyze": { - "type": "boolean", - "description": "Enable AI analysis for this memory" - } - }, - "required": ["content"] - } - }), - json!({ - "name": "search_memories", - "description": "Search memories with advanced options", - "inputSchema": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Search query" - }, - "semantic": { - "type": "boolean", - "description": "Use semantic search" - }, - "category": { - "type": "string", - "description": "Filter by category" - }, - "time_range": { - "type": "string", - "description": "Filter by time range (e.g., '1week', '1month')" - } - }, - "required": ["query"] - } - }), - json!({ - "name": "update_memory", - "description": "Update an existing memory entry", - "inputSchema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the memory to update" - }, - "content": { - "type": "string", - "description": "New content for the memory" - } - }, - "required": ["id", "content"] - } - }), - json!({ - "name": "delete_memory", - "description": "Delete a memory entry", - "inputSchema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the memory to delete" - } - }, - "required": ["id"] - } - }) - ]; - - // Add extended tools based on features - #[cfg(feature = "web-integration")] - { - tools.push(json!({ - "name": "import_webpage", - "description": "Import content from a webpage", - "inputSchema": { - "type": "object", - "properties": { - "url": { - "type": "string", - "description": "URL to import from" - } - }, - "required": ["url"] - } - })); - } - - #[cfg(feature = "ai-analysis")] - { - tools.push(json!({ - "name": "analyze_sentiment", - "description": "Analyze sentiment of memories", - "inputSchema": { - "type": "object", - "properties": { - "period": { - "type": "string", - "description": "Time period to analyze" - } - } - } - })); - - tools.push(json!({ - "name": "extract_insights", - "description": "Extract insights and patterns from memories", - "inputSchema": { - "type": "object", - "properties": { - "category": { - "type": "string", - "description": "Category to analyze" - } - } - } - })); - } - - json!({ - "jsonrpc": "2.0", - "id": id, - "result": { - "tools": tools - } - }) - } - "tools/call" => { - let tool_name = request["params"]["name"].as_str().unwrap_or(""); - let arguments = &request["params"]["arguments"]; - - let result = match tool_name { - "create_memory" => { - let content = arguments["content"].as_str().unwrap_or(""); - let analyze = arguments["analyze"].as_bool().unwrap_or(false); - - let final_content = if analyze { - #[cfg(feature = "ai-analysis")] - { - format!("[AI分析] 感情: neutral, カテゴリ: general\n{}", content) - } - #[cfg(not(feature = "ai-analysis"))] - { - content.to_string() - } - } else { - content.to_string() - }; - - match self.memory_manager.create_memory(&final_content) { - Ok(id) => json!({ - "success": true, - "id": id, - "message": if analyze { "Memory created with AI analysis" } else { "Memory created successfully" } - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - "search_memories" => { - let query = arguments["query"].as_str().unwrap_or(""); - let semantic = arguments["semantic"].as_bool().unwrap_or(false); - - let memories = if semantic { - #[cfg(feature = "semantic-search")] - { - // Mock semantic search for now - self.memory_manager.search_memories(query) - } - #[cfg(not(feature = "semantic-search"))] - { - self.memory_manager.search_memories(query) - } - } else { - self.memory_manager.search_memories(query) - }; - - json!({ - "success": true, - "memories": memories.into_iter().map(|m| json!({ - "id": m.id, - "content": m.content, - "created_at": m.created_at, - "updated_at": m.updated_at - })).collect::>(), - "search_type": if semantic { "semantic" } else { "keyword" } - }) - } - "update_memory" => { - let id = arguments["id"].as_str().unwrap_or(""); - let content = arguments["content"].as_str().unwrap_or(""); - match self.memory_manager.update_memory(id, content) { - Ok(()) => json!({ - "success": true, - "message": "Memory updated successfully" - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - "delete_memory" => { - let id = arguments["id"].as_str().unwrap_or(""); - match self.memory_manager.delete_memory(id) { - Ok(()) => json!({ - "success": true, - "message": "Memory deleted successfully" - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - #[cfg(feature = "web-integration")] - "import_webpage" => { - let url = arguments["url"].as_str().unwrap_or(""); - match self.import_from_web(url).await { - Ok(content) => { - match self.memory_manager.create_memory(&content) { - Ok(id) => json!({ - "success": true, - "id": id, - "message": format!("Webpage imported successfully from {}", url) - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - Err(e) => json!({ - "success": false, - "error": format!("Failed to import webpage: {}", e) - }) - } - } - #[cfg(feature = "ai-analysis")] - "analyze_sentiment" => { - json!({ - "success": true, - "analysis": { - "positive": 60, - "neutral": 30, - "negative": 10, - "dominant_sentiment": "positive" - }, - "message": "Sentiment analysis completed" - }) - } - #[cfg(feature = "ai-analysis")] - "extract_insights" => { - json!({ - "success": true, - "insights": { - "most_frequent_topics": ["programming", "ai", "productivity"], - "learning_frequency": "5 times per week", - "growth_trend": "increasing", - "recommendations": ["Focus more on advanced topics", "Consider practical applications"] - }, - "message": "Insights extracted successfully" - }) - } - _ => json!({ - "success": false, - "error": format!("Unknown tool: {}", tool_name) - }) - }; - - json!({ - "jsonrpc": "2.0", - "id": id, - "result": { - "content": [{ - "type": "text", - "text": result.to_string() - }] - } - }) - } - _ => { - json!({ - "jsonrpc": "2.0", - "id": id, - "error": { - "code": -32601, - "message": "Method not found" - } - }) - } - } - } - - #[cfg(feature = "web-integration")] - async fn import_from_web(&self, url: &str) -> Result { - let response = reqwest::get(url).await?; - let content = response.text().await?; - - let document = scraper::Html::parse_document(&content); - let title_selector = scraper::Selector::parse("title").unwrap(); - let body_selector = scraper::Selector::parse("p").unwrap(); - - let title = document.select(&title_selector) - .next() - .map(|el| el.inner_html()) - .unwrap_or_else(|| "Untitled".to_string()); - - let paragraphs: Vec = document.select(&body_selector) - .map(|el| el.inner_html()) - .take(5) - .collect(); - - Ok(format!("# {}\nURL: {}\n\n{}", title, url, paragraphs.join("\n\n"))) - } -} \ No newline at end of file diff --git a/extended/src/lib.rs b/extended/src/lib.rs deleted file mode 100644 index 2d650ac..0000000 --- a/extended/src/lib.rs +++ /dev/null @@ -1,3 +0,0 @@ -// Re-export core modules to make them available to extended version -pub use aigpt::memory; -pub use aigpt::mcp; \ No newline at end of file diff --git a/extended/src/main.rs b/extended/src/main.rs deleted file mode 100644 index 6242931..0000000 --- a/extended/src/main.rs +++ /dev/null @@ -1,250 +0,0 @@ -use anyhow::Result; -use clap::{Parser, Subcommand}; -use std::path::PathBuf; - -// Re-use core modules from parent -use aigpt::memory::MemoryManager; - -#[derive(Parser)] -#[command(name = "aigpt-extended")] -#[command(about = "Extended Claude Memory Tool with AI analysis and web integration")] -struct Cli { - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Create a new memory entry - Create { - content: String, - #[arg(long)] - analyze: bool, - }, - /// Search memories with advanced options - Search { - query: String, - #[arg(long)] - semantic: bool, - #[arg(long)] - category: Option, - #[arg(long)] - time_range: Option, - }, - /// Import content from web - Import { - #[arg(long)] - url: Option, - #[arg(long)] - file: Option, - }, - /// Analyze memories for insights - Analyze { - #[arg(long)] - sentiment: bool, - #[arg(long)] - patterns: bool, - #[arg(long)] - period: Option, - }, - /// Sync with external services - Sync { - service: String, - }, - /// Run in standard mode (fallback to simple) - Simple { - #[command(subcommand)] - command: SimpleCommands, - }, -} - -#[derive(Subcommand)] -enum SimpleCommands { - Create { content: String }, - Search { query: String }, - List, - Delete { id: String }, -} - -#[tokio::main] -async fn main() -> Result<()> { - let cli = Cli::parse(); - let mut memory_manager = MemoryManager::new().await?; - - match cli.command { - Commands::Create { content, analyze } => { - if analyze { - println!("🧠 AI分析付きでメモリーを作成中..."); - #[cfg(feature = "ai-analysis")] - { - let analyzed_content = ai_analyze(&content).await?; - let id = memory_manager.create_memory(&analyzed_content)?; - println!("✅ 分析済みメモリーを作成: {}", id); - } - #[cfg(not(feature = "ai-analysis"))] - { - println!("⚠️ AI分析機能が無効です。通常のメモリーとして保存します。"); - let id = memory_manager.create_memory(&content)?; - println!("✅ メモリーを作成: {}", id); - } - } else { - let id = memory_manager.create_memory(&content)?; - println!("✅ メモリーを作成: {}", id); - } - } - Commands::Search { query, semantic, category, time_range } => { - if semantic { - #[cfg(feature = "semantic-search")] - { - println!("🔍 セマンティック検索を実行中..."); - let results = semantic_search(&memory_manager, &query).await?; - print_search_results(results); - } - #[cfg(not(feature = "semantic-search"))] - { - println!("⚠️ セマンティック検索機能が無効です。通常検索を実行します。"); - let results = memory_manager.search_memories(&query); - print_search_results(results); - } - } else { - let results = memory_manager.search_memories(&query); - print_search_results(results); - } - } - Commands::Import { url, file } => { - #[cfg(feature = "web-integration")] - { - if let Some(url) = url { - println!("🌐 Webページをインポート中: {}", url); - let content = import_from_web(&url).await?; - let id = memory_manager.create_memory(&content)?; - println!("✅ Webコンテンツをメモリーに保存: {}", id); - } else if let Some(file) = file { - println!("📄 ファイルをインポート中: {}", file.display()); - let content = std::fs::read_to_string(file)?; - let id = memory_manager.create_memory(&content)?; - println!("✅ ファイルをメモリーに保存: {}", id); - } - } - #[cfg(not(feature = "web-integration"))] - { - println!("⚠️ Web統合機能が無効です。"); - } - } - Commands::Analyze { sentiment, patterns, period } => { - #[cfg(feature = "ai-analysis")] - { - println!("📊 メモリー分析を実行中..."); - if sentiment { - analyze_sentiment(&memory_manager).await?; - } - if patterns { - analyze_patterns(&memory_manager, period).await?; - } - } - #[cfg(not(feature = "ai-analysis"))] - { - println!("⚠️ AI分析機能が無効です。"); - } - } - Commands::Sync { service } => { - println!("🔄 {}との同期機能は開発中です", service); - } - Commands::Simple { command } => { - // Fallback to simple mode - match command { - SimpleCommands::Create { content } => { - let id = memory_manager.create_memory(&content)?; - println!("✅ メモリーを作成: {}", id); - } - SimpleCommands::Search { query } => { - let results = memory_manager.search_memories(&query); - print_search_results(results); - } - SimpleCommands::List => { - // List all memories (simplified) - let results = memory_manager.search_memories(""); - print_search_results(results); - } - SimpleCommands::Delete { id } => { - memory_manager.delete_memory(&id)?; - println!("🗑️ メモリーを削除: {}", id); - } - } - } - } - - Ok(()) -} - -fn print_search_results(results: Vec) { - if results.is_empty() { - println!("🔍 検索結果が見つかりませんでした"); - return; - } - - println!("🔍 {} 件の結果が見つかりました:", results.len()); - for memory in results { - println!("📝 [{}] {} ({})", - memory.id, - memory.content.chars().take(50).collect::(), - memory.created_at.format("%Y-%m-%d %H:%M") - ); - } -} - -// Extended features (only compiled when features are enabled) - -#[cfg(feature = "ai-analysis")] -async fn ai_analyze(content: &str) -> Result { - // Mock AI analysis for now - Ok(format!("[AI分析] 感情: neutral, カテゴリ: general\n{}", content)) -} - -#[cfg(feature = "semantic-search")] -async fn semantic_search(memory_manager: &MemoryManager, query: &str) -> Result> { - // Mock semantic search - in reality would use embeddings - Ok(memory_manager.search_memories(query)) -} - -#[cfg(feature = "web-integration")] -async fn import_from_web(url: &str) -> Result { - let response = reqwest::get(url).await?; - let content = response.text().await?; - - // Basic HTML parsing - let document = scraper::Html::parse_document(&content); - let title_selector = scraper::Selector::parse("title").unwrap(); - let body_selector = scraper::Selector::parse("p").unwrap(); - - let title = document.select(&title_selector) - .next() - .map(|el| el.inner_html()) - .unwrap_or_else(|| "Untitled".to_string()); - - let paragraphs: Vec = document.select(&body_selector) - .map(|el| el.inner_html()) - .take(5) // First 5 paragraphs - .collect(); - - Ok(format!("# {}\nURL: {}\n\n{}", title, url, paragraphs.join("\n\n"))) -} - -#[cfg(feature = "ai-analysis")] -async fn analyze_sentiment(memory_manager: &MemoryManager) -> Result<()> { - println!("📊 センチメント分析結果:"); - println!(" - ポジティブ: 60%"); - println!(" - ニュートラル: 30%"); - println!(" - ネガティブ: 10%"); - Ok(()) -} - -#[cfg(feature = "ai-analysis")] -async fn analyze_patterns(memory_manager: &MemoryManager, period: Option) -> Result<()> { - let period_str = period.unwrap_or_else(|| "1week".to_string()); - println!("📈 学習パターン分析 ({})", period_str); - println!(" - 最多トピック: プログラミング"); - println!(" - 学習頻度: 週5回"); - println!(" - 成長傾向: 上昇"); - Ok(()) -} \ No newline at end of file diff --git a/src/mcp_server.rs b/src/bin/mcp_server.rs similarity index 78% rename from src/mcp_server.rs rename to src/bin/mcp_server.rs index 7679211..e50d789 100644 --- a/src/mcp_server.rs +++ b/src/bin/mcp_server.rs @@ -1,14 +1,11 @@ use anyhow::Result; use std::env; -mod memory; -mod mcp; - -use mcp::MCPServer; +use aigpt::mcp::BaseMCPServer; #[tokio::main] async fn main() -> Result<()> { - // 環境変数から自動実行設定を読み込み + // 環境変数から設定を読み込み let auto_execute = env::var("MEMORY_AUTO_EXECUTE") .unwrap_or_else(|_| "false".to_string()) .parse::() @@ -27,14 +24,14 @@ async fn main() -> Result<()> { let trigger_sensitivity = env::var("TRIGGER_SENSITIVITY") .unwrap_or_else(|_| "medium".to_string()); - // 設定をログ出力(デバッグ用) - eprintln!("Memory MCP Server starting with config:"); + // 設定をログ出力 + eprintln!("Memory MCP Server (Standard) starting with config:"); eprintln!(" AUTO_EXECUTE: {}", auto_execute); eprintln!(" AUTO_SAVE: {}", auto_save); eprintln!(" AUTO_SEARCH: {}", auto_search); eprintln!(" TRIGGER_SENSITIVITY: {}", trigger_sensitivity); - let mut server = MCPServer::new().await?; + let mut server = BaseMCPServer::new().await?; server.run().await?; Ok(()) diff --git a/extended/src/mcp_server.rs b/src/bin/mcp_server_extended.rs similarity index 91% rename from extended/src/mcp_server.rs rename to src/bin/mcp_server_extended.rs index 0696007..c38a0e8 100644 --- a/extended/src/mcp_server.rs +++ b/src/bin/mcp_server_extended.rs @@ -1,10 +1,7 @@ use anyhow::Result; use std::env; -// Re-use core modules from parent (these imports removed as they're unused) - -mod extended_mcp; -use extended_mcp::ExtendedMCPServer; +use aigpt::mcp::ExtendedMCPServer; #[tokio::main] async fn main() -> Result<()> { diff --git a/src/main.rs b/src/main.rs index b607abf..68477a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ pub mod memory; pub mod mcp; use memory::MemoryManager; -use mcp::MCPServer; +use mcp::BaseMCPServer; #[derive(Parser)] #[command(name = "aigpt")] @@ -35,7 +35,7 @@ async fn main() -> Result<()> { match cli.command { Commands::Server | Commands::Serve => { - let mut server = MCPServer::new().await?; + let mut server = BaseMCPServer::new().await?; server.run().await?; } Commands::Import { file } => { diff --git a/src/mcp.rs b/src/mcp.rs deleted file mode 100644 index d8877cb..0000000 --- a/src/mcp.rs +++ /dev/null @@ -1,250 +0,0 @@ -use anyhow::Result; -use serde_json::{json, Value}; -use std::io::{self, BufRead, Write}; - -use crate::memory::MemoryManager; - -pub struct MCPServer { - memory_manager: MemoryManager, -} - -impl MCPServer { - pub async fn new() -> Result { - let memory_manager = MemoryManager::new().await?; - Ok(MCPServer { memory_manager }) - } - - pub async fn run(&mut self) -> Result<()> { - let stdin = io::stdin(); - let mut stdout = io::stdout(); - - // Set up line-based reading - let reader = stdin.lock(); - let lines = reader.lines(); - - for line_result in lines { - match line_result { - Ok(line) => { - let trimmed = line.trim(); - if trimmed.is_empty() { - continue; - } - - if let Ok(request) = serde_json::from_str::(&trimmed) { - let response = self.handle_request(request).await; - let response_str = serde_json::to_string(&response)?; - stdout.write_all(response_str.as_bytes())?; - stdout.write_all(b"\n")?; - stdout.flush()?; - } - } - Err(_) => { - // EOF or error, exit gracefully - break; - } - } - } - - Ok(()) - } - - async fn handle_request(&mut self, request: Value) -> Value { - let method = request["method"].as_str().unwrap_or(""); - let id = request["id"].clone(); - - match method { - "initialize" => { - json!({ - "jsonrpc": "2.0", - "id": id, - "result": { - "protocolVersion": "2024-11-05", - "capabilities": { - "tools": {} - }, - "serverInfo": { - "name": "aigpt", - "version": "0.1.0" - } - } - }) - } - "tools/list" => { - json!({ - "jsonrpc": "2.0", - "id": id, - "result": { - "tools": [ - { - "name": "create_memory", - "description": "Create a new memory entry", - "inputSchema": { - "type": "object", - "properties": { - "content": { - "type": "string", - "description": "Content of the memory" - } - }, - "required": ["content"] - } - }, - { - "name": "update_memory", - "description": "Update an existing memory entry", - "inputSchema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the memory to update" - }, - "content": { - "type": "string", - "description": "New content for the memory" - } - }, - "required": ["id", "content"] - } - }, - { - "name": "delete_memory", - "description": "Delete a memory entry", - "inputSchema": { - "type": "object", - "properties": { - "id": { - "type": "string", - "description": "ID of the memory to delete" - } - }, - "required": ["id"] - } - }, - { - "name": "search_memories", - "description": "Search memories by content", - "inputSchema": { - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "Search query" - } - }, - "required": ["query"] - } - }, - { - "name": "list_conversations", - "description": "List all imported conversations", - "inputSchema": { - "type": "object", - "properties": {} - } - } - ] - } - }) - } - "tools/call" => { - let tool_name = request["params"]["name"].as_str().unwrap_or(""); - let arguments = &request["params"]["arguments"]; - - let result = match tool_name { - "create_memory" => { - let content = arguments["content"].as_str().unwrap_or(""); - match self.memory_manager.create_memory(content) { - Ok(id) => json!({ - "success": true, - "id": id, - "message": "Memory created successfully" - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - "update_memory" => { - let id = arguments["id"].as_str().unwrap_or(""); - let content = arguments["content"].as_str().unwrap_or(""); - match self.memory_manager.update_memory(id, content) { - Ok(()) => json!({ - "success": true, - "message": "Memory updated successfully" - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - "delete_memory" => { - let id = arguments["id"].as_str().unwrap_or(""); - match self.memory_manager.delete_memory(id) { - Ok(()) => json!({ - "success": true, - "message": "Memory deleted successfully" - }), - Err(e) => json!({ - "success": false, - "error": e.to_string() - }) - } - } - "search_memories" => { - let query = arguments["query"].as_str().unwrap_or(""); - let memories = self.memory_manager.search_memories(query); - json!({ - "success": true, - "memories": memories.into_iter().map(|m| json!({ - "id": m.id, - "content": m.content, - "created_at": m.created_at, - "updated_at": m.updated_at - })).collect::>() - }) - } - "list_conversations" => { - let conversations = self.memory_manager.list_conversations(); - json!({ - "success": true, - "conversations": conversations.into_iter().map(|c| json!({ - "id": c.id, - "title": c.title, - "created_at": c.created_at, - "message_count": c.message_count - })).collect::>() - }) - } - _ => json!({ - "success": false, - "error": format!("Unknown tool: {}", tool_name) - }) - }; - - json!({ - "jsonrpc": "2.0", - "id": id, - "result": { - "content": [{ - "type": "text", - "text": result.to_string() - }] - } - }) - } - _ => { - json!({ - "jsonrpc": "2.0", - "id": id, - "error": { - "code": -32601, - "message": "Method not found" - } - }) - } - } - } -} \ No newline at end of file diff --git a/src/mcp/base.rs b/src/mcp/base.rs new file mode 100644 index 0000000..491e390 --- /dev/null +++ b/src/mcp/base.rs @@ -0,0 +1,280 @@ +use anyhow::Result; +use serde_json::{json, Value}; +use std::io::{self, BufRead, Write}; + +use crate::memory::MemoryManager; + +pub struct BaseMCPServer { + pub memory_manager: MemoryManager, +} + +impl BaseMCPServer { + pub async fn new() -> Result { + let memory_manager = MemoryManager::new().await?; + Ok(BaseMCPServer { memory_manager }) + } + + pub async fn run(&mut self) -> Result<()> { + let stdin = io::stdin(); + let mut stdout = io::stdout(); + + let reader = stdin.lock(); + let lines = reader.lines(); + + for line_result in lines { + match line_result { + Ok(line) => { + let trimmed = line.trim(); + if trimmed.is_empty() { + continue; + } + + if let Ok(request) = serde_json::from_str::(&trimmed) { + let response = self.handle_request(request).await; + let response_str = serde_json::to_string(&response)?; + stdout.write_all(response_str.as_bytes())?; + stdout.write_all(b"\n")?; + stdout.flush()?; + } + } + Err(_) => break, + } + } + + Ok(()) + } + + pub async fn handle_request(&mut self, request: Value) -> Value { + let method = request["method"].as_str().unwrap_or(""); + let id = request["id"].clone(); + + match method { + "initialize" => self.handle_initialize(id), + "tools/list" => self.handle_tools_list(id), + "tools/call" => self.handle_tools_call(request, id).await, + _ => self.handle_unknown_method(id), + } + } + + // 初期化ハンドラ + fn handle_initialize(&self, id: Value) -> Value { + json!({ + "jsonrpc": "2.0", + "id": id, + "result": { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {} + }, + "serverInfo": { + "name": "aigpt", + "version": "0.1.0" + } + } + }) + } + + // ツールリストハンドラ (拡張可能) + pub fn handle_tools_list(&self, id: Value) -> Value { + let tools = self.get_available_tools(); + json!({ + "jsonrpc": "2.0", + "id": id, + "result": { + "tools": tools + } + }) + } + + // 基本ツール定義 (拡張で上書き可能) + pub fn get_available_tools(&self) -> Vec { + vec![ + json!({ + "name": "create_memory", + "description": "Create a new memory entry", + "inputSchema": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content of the memory" + } + }, + "required": ["content"] + } + }), + json!({ + "name": "search_memories", + "description": "Search memories by content", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query" + } + }, + "required": ["query"] + } + }), + json!({ + "name": "update_memory", + "description": "Update an existing memory entry", + "inputSchema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the memory to update" + }, + "content": { + "type": "string", + "description": "New content for the memory" + } + }, + "required": ["id", "content"] + } + }), + json!({ + "name": "delete_memory", + "description": "Delete a memory entry", + "inputSchema": { + "type": "object", + "properties": { + "id": { + "type": "string", + "description": "ID of the memory to delete" + } + }, + "required": ["id"] + } + }), + json!({ + "name": "list_conversations", + "description": "List all imported conversations", + "inputSchema": { + "type": "object", + "properties": {} + } + }) + ] + } + + // ツール呼び出しハンドラ + async fn handle_tools_call(&mut self, request: Value, id: Value) -> Value { + let tool_name = request["params"]["name"].as_str().unwrap_or(""); + let arguments = &request["params"]["arguments"]; + + let result = self.execute_tool(tool_name, arguments).await; + + json!({ + "jsonrpc": "2.0", + "id": id, + "result": { + "content": [{ + "type": "text", + "text": result.to_string() + }] + } + }) + } + + // ツール実行 (拡張で上書き可能) + pub async fn execute_tool(&mut self, tool_name: &str, arguments: &Value) -> Value { + match tool_name { + "create_memory" => self.tool_create_memory(arguments), + "search_memories" => self.tool_search_memories(arguments), + "update_memory" => self.tool_update_memory(arguments), + "delete_memory" => self.tool_delete_memory(arguments), + "list_conversations" => self.tool_list_conversations(), + _ => json!({ + "success": false, + "error": format!("Unknown tool: {}", tool_name) + }) + } + } + + // 基本ツール実装 + fn tool_create_memory(&mut self, arguments: &Value) -> Value { + let content = arguments["content"].as_str().unwrap_or(""); + match self.memory_manager.create_memory(content) { + Ok(id) => json!({ + "success": true, + "id": id, + "message": "Memory created successfully" + }), + Err(e) => json!({ + "success": false, + "error": e.to_string() + }) + } + } + + fn tool_search_memories(&self, arguments: &Value) -> Value { + let query = arguments["query"].as_str().unwrap_or(""); + let memories = self.memory_manager.search_memories(query); + json!({ + "success": true, + "memories": memories.into_iter().map(|m| json!({ + "id": m.id, + "content": m.content, + "created_at": m.created_at, + "updated_at": m.updated_at + })).collect::>() + }) + } + + fn tool_update_memory(&mut self, arguments: &Value) -> Value { + let id = arguments["id"].as_str().unwrap_or(""); + let content = arguments["content"].as_str().unwrap_or(""); + match self.memory_manager.update_memory(id, content) { + Ok(()) => json!({ + "success": true, + "message": "Memory updated successfully" + }), + Err(e) => json!({ + "success": false, + "error": e.to_string() + }) + } + } + + fn tool_delete_memory(&mut self, arguments: &Value) -> Value { + let id = arguments["id"].as_str().unwrap_or(""); + match self.memory_manager.delete_memory(id) { + Ok(()) => json!({ + "success": true, + "message": "Memory deleted successfully" + }), + Err(e) => json!({ + "success": false, + "error": e.to_string() + }) + } + } + + fn tool_list_conversations(&self) -> Value { + let conversations = self.memory_manager.list_conversations(); + json!({ + "success": true, + "conversations": conversations.into_iter().map(|c| json!({ + "id": c.id, + "title": c.title, + "created_at": c.created_at, + "message_count": c.message_count + })).collect::>() + }) + } + + // 不明なメソッドハンドラ + fn handle_unknown_method(&self, id: Value) -> Value { + json!({ + "jsonrpc": "2.0", + "id": id, + "error": { + "code": -32601, + "message": "Method not found" + } + }) + } +} \ No newline at end of file diff --git a/src/mcp/extended.rs b/src/mcp/extended.rs new file mode 100644 index 0000000..604e761 --- /dev/null +++ b/src/mcp/extended.rs @@ -0,0 +1,293 @@ +use anyhow::Result; +use serde_json::{json, Value}; + +use super::base::BaseMCPServer; + +pub struct ExtendedMCPServer { + base: BaseMCPServer, +} + +impl ExtendedMCPServer { + pub async fn new() -> Result { + let base = BaseMCPServer::new().await?; + Ok(ExtendedMCPServer { base }) + } + + pub async fn run(&mut self) -> Result<()> { + self.base.run().await + } + + pub async fn handle_request(&mut self, request: Value) -> Value { + self.base.handle_request(request).await + } + + // 拡張ツールを追加 + pub fn get_available_tools(&self) -> Vec { + #[allow(unused_mut)] + let mut tools = self.base.get_available_tools(); + + // AI分析ツールを追加 + #[cfg(feature = "ai-analysis")] + { + tools.push(json!({ + "name": "analyze_sentiment", + "description": "Analyze sentiment of memories", + "inputSchema": { + "type": "object", + "properties": { + "period": { + "type": "string", + "description": "Time period to analyze" + } + } + } + })); + + tools.push(json!({ + "name": "extract_insights", + "description": "Extract insights and patterns from memories", + "inputSchema": { + "type": "object", + "properties": { + "category": { + "type": "string", + "description": "Category to analyze" + } + } + } + })); + } + + // Web統合ツールを追加 + #[cfg(feature = "web-integration")] + { + tools.push(json!({ + "name": "import_webpage", + "description": "Import content from a webpage", + "inputSchema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL to import from" + } + }, + "required": ["url"] + } + })); + } + + // セマンティック検索強化 + #[cfg(feature = "semantic-search")] + { + // create_memoryを拡張版で上書き + if let Some(pos) = tools.iter().position(|tool| tool["name"] == "create_memory") { + tools[pos] = json!({ + "name": "create_memory", + "description": "Create a new memory entry with optional AI analysis", + "inputSchema": { + "type": "object", + "properties": { + "content": { + "type": "string", + "description": "Content of the memory" + }, + "analyze": { + "type": "boolean", + "description": "Enable AI analysis for this memory" + } + }, + "required": ["content"] + } + }); + } + + // search_memoriesを拡張版で上書き + if let Some(pos) = tools.iter().position(|tool| tool["name"] == "search_memories") { + tools[pos] = json!({ + "name": "search_memories", + "description": "Search memories with advanced options", + "inputSchema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search query" + }, + "semantic": { + "type": "boolean", + "description": "Use semantic search" + }, + "category": { + "type": "string", + "description": "Filter by category" + }, + "time_range": { + "type": "string", + "description": "Filter by time range (e.g., '1week', '1month')" + } + }, + "required": ["query"] + } + }); + } + } + + tools + } + + // 拡張ツール実行 + pub async fn execute_tool(&mut self, tool_name: &str, arguments: &Value) -> Value { + match tool_name { + // 拡張機能 + #[cfg(feature = "ai-analysis")] + "analyze_sentiment" => self.tool_analyze_sentiment(arguments).await, + #[cfg(feature = "ai-analysis")] + "extract_insights" => self.tool_extract_insights(arguments).await, + #[cfg(feature = "web-integration")] + "import_webpage" => self.tool_import_webpage(arguments).await, + + // 拡張版の基本ツール (AI分析付き) + "create_memory" => self.tool_create_memory_extended(arguments).await, + "search_memories" => self.tool_search_memories_extended(arguments).await, + + // 基本ツールにフォールバック + _ => self.base.execute_tool(tool_name, arguments).await, + } + } + + // 拡張ツール実装 + async fn tool_create_memory_extended(&mut self, arguments: &Value) -> Value { + let content = arguments["content"].as_str().unwrap_or(""); + let analyze = arguments["analyze"].as_bool().unwrap_or(false); + + let final_content = if analyze { + #[cfg(feature = "ai-analysis")] + { + format!("[AI分析] 感情: neutral, カテゴリ: general\n{}", content) + } + #[cfg(not(feature = "ai-analysis"))] + { + content.to_string() + } + } else { + content.to_string() + }; + + match self.base.memory_manager.create_memory(&final_content) { + Ok(id) => json!({ + "success": true, + "id": id, + "message": if analyze { "Memory created with AI analysis" } else { "Memory created successfully" } + }), + Err(e) => json!({ + "success": false, + "error": e.to_string() + }) + } + } + + async fn tool_search_memories_extended(&mut self, arguments: &Value) -> Value { + let query = arguments["query"].as_str().unwrap_or(""); + let semantic = arguments["semantic"].as_bool().unwrap_or(false); + + let memories = if semantic { + #[cfg(feature = "semantic-search")] + { + // モックセマンティック検索 + self.base.memory_manager.search_memories(query) + } + #[cfg(not(feature = "semantic-search"))] + { + self.base.memory_manager.search_memories(query) + } + } else { + self.base.memory_manager.search_memories(query) + }; + + json!({ + "success": true, + "memories": memories.into_iter().map(|m| json!({ + "id": m.id, + "content": m.content, + "created_at": m.created_at, + "updated_at": m.updated_at + })).collect::>(), + "search_type": if semantic { "semantic" } else { "keyword" } + }) + } + + #[cfg(feature = "ai-analysis")] + async fn tool_analyze_sentiment(&mut self, _arguments: &Value) -> Value { + json!({ + "success": true, + "analysis": { + "positive": 60, + "neutral": 30, + "negative": 10, + "dominant_sentiment": "positive" + }, + "message": "Sentiment analysis completed" + }) + } + + #[cfg(feature = "ai-analysis")] + async fn tool_extract_insights(&mut self, _arguments: &Value) -> Value { + json!({ + "success": true, + "insights": { + "most_frequent_topics": ["programming", "ai", "productivity"], + "learning_frequency": "5 times per week", + "growth_trend": "increasing", + "recommendations": ["Focus more on advanced topics", "Consider practical applications"] + }, + "message": "Insights extracted successfully" + }) + } + + #[cfg(feature = "web-integration")] + async fn tool_import_webpage(&mut self, arguments: &Value) -> Value { + let url = arguments["url"].as_str().unwrap_or(""); + match self.import_from_web(url).await { + Ok(content) => { + match self.base.memory_manager.create_memory(&content) { + Ok(id) => json!({ + "success": true, + "id": id, + "message": format!("Webpage imported successfully from {}", url) + }), + Err(e) => json!({ + "success": false, + "error": e.to_string() + }) + } + } + Err(e) => json!({ + "success": false, + "error": format!("Failed to import webpage: {}", e) + }) + } + } + + #[cfg(feature = "web-integration")] + async fn import_from_web(&self, url: &str) -> Result { + let response = reqwest::get(url).await?; + let content = response.text().await?; + + let document = scraper::Html::parse_document(&content); + let title_selector = scraper::Selector::parse("title").unwrap(); + let body_selector = scraper::Selector::parse("p").unwrap(); + + let title = document.select(&title_selector) + .next() + .map(|el| el.inner_html()) + .unwrap_or_else(|| "Untitled".to_string()); + + let paragraphs: Vec = document.select(&body_selector) + .map(|el| el.inner_html()) + .take(5) + .collect(); + + Ok(format!("# {}\nURL: {}\n\n{}", title, url, paragraphs.join("\n\n"))) + } +} \ No newline at end of file diff --git a/src/mcp/mod.rs b/src/mcp/mod.rs new file mode 100644 index 0000000..8ce42df --- /dev/null +++ b/src/mcp/mod.rs @@ -0,0 +1,5 @@ +pub mod base; +pub mod extended; + +pub use base::BaseMCPServer; +pub use extended::ExtendedMCPServer; \ No newline at end of file diff --git a/src/memory.rs b/src/memory.rs index e598da2..d879a78 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -238,4 +238,4 @@ impl MemoryManager { Ok(()) } -} \ No newline at end of file +}