From 4620d0862a7ba031049bf8da3e0b9d11dfbb681b Mon Sep 17 00:00:00 2001 From: syui Date: Tue, 29 Jul 2025 04:08:29 +0900 Subject: [PATCH] add extended --- .gitignore | 1 + Cargo.toml | 21 ++ claude.md | 98 ++++-- docs/README_CONFIG.md | 125 +++++++ docs/claude_code_config.json | 58 ++++ docs/claude_code_config_extended.json | 81 +++++ docs/claude_desktop_config.json | 34 ++ docs/claude_desktop_config_extended.json | 45 +++ extended/src/extended_mcp.rs | 398 +++++++++++++++++++++++ extended/src/lib.rs | 3 + extended/src/main.rs | 250 ++++++++++++++ extended/src/mcp_server.rs | 48 +++ src/lib.rs | 2 + src/main.rs | 4 +- src/mcp_server.rs | 41 +++ src/memory.rs | 1 + 16 files changed, 1189 insertions(+), 21 deletions(-) create mode 100644 docs/README_CONFIG.md create mode 100644 docs/claude_code_config.json create mode 100644 docs/claude_code_config_extended.json create mode 100644 docs/claude_desktop_config.json create mode 100644 docs/claude_desktop_config_extended.json create mode 100644 extended/src/extended_mcp.rs create mode 100644 extended/src/lib.rs create mode 100644 extended/src/main.rs create mode 100644 extended/src/mcp_server.rs create mode 100644 src/lib.rs create mode 100644 src/mcp_server.rs diff --git a/.gitignore b/.gitignore index 2ae6464..66f9a9a 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ Thumbs.db *.log json gpt +.claude diff --git a/Cargo.toml b/Cargo.toml index 0733506..e38e067 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,19 @@ edition = "2021" authors = ["syui"] description = "Simple memory storage for Claude with MCP" + [[bin]] name = "aigpt" path = "src/main.rs" +[[bin]] +name = "memory-mcp" +path = "src/mcp_server.rs" + +[[bin]] +name = "memory-mcp-extended" +path = "extended/src/mcp_server.rs" + [dependencies] # CLI and async clap = { version = "4.5", features = ["derive"] } @@ -25,3 +34,15 @@ uuid = { version = "1.10", features = ["v4"] } # Error handling and utilities anyhow = "1.0" dirs = "5.0" + +# Extended features (optional) +reqwest = { version = "0.11", features = ["json"], optional = true } +scraper = { version = "0.18", optional = true } +openai = { version = "1.1", optional = true } + +[features] +default = [] +extended = ["semantic-search", "ai-analysis", "web-integration"] +semantic-search = ["openai"] +ai-analysis = ["openai"] +web-integration = ["reqwest", "scraper"] diff --git a/claude.md b/claude.md index fe83fb2..51663a3 100644 --- a/claude.md +++ b/claude.md @@ -6,30 +6,90 @@ claude desktop, claude codeで使用できるmemory機能をmcpで作ります 2. chatgptのjsonをimportできる @json/ 3. rustで作る -## 自動メモリー保存のルール +## 自動実行設定 +```json +{ + "mcp_auto_execute": true, + "memory_auto_save": true, + "memory_auto_search": true, + "trigger_sensitivity": "high" +} +``` -以下の情報が会話に現れた場合、自動的にcreate_memory MCPツールを使用して保存してください: +## 自動メモリー保存のルール(強化版) -1. **ユーザーの個人情報** - - 名前、誕生日、住所など - - 好きなもの、嫌いなもの - - 習慣や予定 +**即座に自動実行** - 以下の情報が会話に現れた瞬間、create_memory MCPツールを自動実行: -2. **重要な決定事項** - - プロジェクトの決定 - - 設定の変更 - - 今後の計画 +### 1. **ユーザーの個人情報** +**トリガーワード**: 名前、誕生日、住所、年齢、職業、家族、出身 +- 基本情報: 名前、誕生日、住所、年齢、性別 +- 関係性: 家族構成、恋人、友人関係 +- 好み: 好きなもの、嫌いなもの、趣味、音楽、映画、本 +- 習慣: 日課、スケジュール、ルーティン +- 価値観: 信念、考え方、人生観 -3. **技術的な解決策** - - 問題の解決方法 - - 有用なコマンドやコード - - 設定手順 +### 2. **重要な決定事項** +**トリガーワード**: 決めた、決定、方針、計画、予定、目標 +- プロジェクト方針の決定 +- 技術選択の決定 +- 設定・環境の変更 +- 今後のロードマップ +- 作業分担・役割 -## 自動メモリー検索のルール +### 3. **技術的な解決策** +**トリガーワード**: 解決、修正、対処、設定、インストール、手順 +- エラーの解決方法 +- 有用なコマンド・スクリプト +- 設定手順・インストール方法 +- デバッグテクニック +- 最適化手法 -以下の場合、自動的にsearch_memories MCPツールを使用してください: +### 4. **学習・発見事項** +**トリガーワード**: 学んだ、わかった、発見、理解、気づき +- 新しい知識・概念の理解 +- ツール・ライブラリの使い方 +- ベストプラクティス +- 失敗から得た教訓 -1. ユーザーが「前に話した」「以前の」などと言及した場合 -2. 過去の会話や情報を参照する必要がある場合 -3. ユーザーの好みや設定を確認する必要がある場合 +## 自動メモリー検索のルール(強化版) + +**会話開始時に自動実行** - search_memories を実行してコンテキストを取得 + +**即座に自動実行** - 以下の場合、search_memories MCPツールを自動実行: + +### 1. **過去参照キーワード検出** +**トリガーワード**: 前に、以前、昔、過去、先ほど、さっき、この間 +- 「前に話した〜」 +- 「以前設定した〜」 +- 「昔やった〜」 + +### 2. **記憶呼び出しキーワード** +**トリガーワード**: 覚えている、記録、メモ、保存、履歴 +- 「覚えていますか?」 +- 「記録していた〜」 +- 「メモした〜」 + +### 3. **設定・好み確認** +**トリガーワード**: 好み、設定、環境、構成、preferences +- ユーザーの好みを確認する必要がある場合 +- 過去の設定を参照する必要がある場合 +- 環境構成を確認する必要がある場合 + +### 4. **不明な参照** +- ユーザーが具体的でない参照をした場合 +- 「あれ」「それ」「例のやつ」などの曖昧な表現 +- 文脈から過去の情報が必要と判断される場合 + +## 自動実行タイミング + +1. **会話開始時**: search_memories を実行してコンテキスト取得 +2. **リアルタイム**: トリガーワード検出後、即座にMCPツール実行 +3. **会話終了時**: 重要な情報があれば create_memory で保存 +4. **定期的**: 長い会話では中間地点でメモリー整理 + +## エラーハンドリング + +- MCPツールが利用できない場合は通常の会話を継続 +- メモリー保存失敗時はユーザーに通知 +- 検索結果が空の場合も適切に対応 diff --git a/docs/README_CONFIG.md b/docs/README_CONFIG.md new file mode 100644 index 0000000..ac25d94 --- /dev/null +++ b/docs/README_CONFIG.md @@ -0,0 +1,125 @@ +# Claude Memory MCP 設定ガイド + +## モード選択 + +### 標準モード (Simple Mode) +- 基本的なメモリー機能のみ +- 軽量で高速 +- 最小限の依存関係 + +### 拡張モード (Extended Mode) +- AI分析機能 +- セマンティック検索 +- Web統合機能 +- 高度なインサイト抽出 + +## ビルド・実行方法 + +### 標準モード +```bash +# MCPサーバー起動 +cargo run --bin memory-mcp + +# CLI実行 +cargo run --bin aigpt -- create "メモリー内容" +``` + +### 拡張モード +```bash +# MCPサーバー起動 +cargo run --bin memory-mcp-extended --features extended + +# CLI実行 +cargo run --bin aigpt-extended --features extended -- create "メモリー内容" --analyze +``` + +## 設定ファイルの配置 + +### 標準モード + +#### Claude Desktop +```bash +# macOS +cp claude_desktop_config.json ~/.config/claude-desktop/claude_desktop_config.json + +# Windows +cp claude_desktop_config.json %APPDATA%\Claude\claude_desktop_config.json +``` + +#### Claude Code +```bash +# プロジェクトルートまたはグローバル設定 +cp claude_code_config.json .claude/config.json +# または +cp claude_code_config.json ~/.claude/config.json +``` + +### 拡張モード + +#### Claude Desktop +```bash +# macOS +cp claude_desktop_config_extended.json ~/.config/claude-desktop/claude_desktop_config.json + +# Windows +cp claude_desktop_config_extended.json %APPDATA%\Claude\claude_desktop_config.json +``` + +#### Claude Code +```bash +# プロジェクトルートまたはグローバル設定 +cp claude_code_config_extended.json .claude/config.json +# または +cp claude_code_config_extended.json ~/.claude/config.json +``` + +## 環境変数設定 + +```bash +export MEMORY_AUTO_EXECUTE=true +export MEMORY_AUTO_SAVE=true +export MEMORY_AUTO_SEARCH=true +export TRIGGER_SENSITIVITY=high +export MEMORY_DB_PATH=~/.claude/memory.db +``` + +## 設定オプション + +### auto_execute +- `true`: 自動でMCPツールを実行 +- `false`: 手動実行のみ + +### trigger_sensitivity +- `high`: 多くのキーワードで反応 +- `medium`: 適度な反応 +- `low`: 明確なキーワードのみ + +### max_memories +メモリーの最大保存数 + +### search_limit +検索結果の最大表示数 + +## カスタマイズ + +`trigger_words`セクションでトリガーワードをカスタマイズ可能: + +```json +"trigger_words": { + "custom_category": ["カスタム", "キーワード", "リスト"] +} +``` + +## トラブルシューティング + +1. MCPサーバーが起動しない場合: + - Rustがインストールされているか確認 + - `cargo build --release`でビルド確認 + +2. 自動実行されない場合: + - 環境変数が正しく設定されているか確認 + - トリガーワードが含まれているか確認 + +3. メモリーが保存されない場合: + - データベースファイルのパスが正しいか確認 + - 書き込み権限があるか確認 \ No newline at end of file diff --git a/docs/claude_code_config.json b/docs/claude_code_config.json new file mode 100644 index 0000000..c2dca42 --- /dev/null +++ b/docs/claude_code_config.json @@ -0,0 +1,58 @@ +{ + "mcpServers": { + "memory": { + "command": "cargo", + "args": ["run", "--release", "--bin", "memory-mcp"], + "cwd": "/Users/syui/ai/ai/gpt", + "env": { + "MEMORY_AUTO_EXECUTE": "true", + "MEMORY_AUTO_SAVE": "true", + "MEMORY_AUTO_SEARCH": "true", + "TRIGGER_SENSITIVITY": "high", + "MEMORY_DB_PATH": "~/.claude/memory.db" + } + } + }, + "tools": { + "memory": { + "enabled": true, + "auto_execute": true + } + }, + "workspace": { + "memory_integration": true, + "auto_save_on_file_change": true, + "auto_search_on_context_switch": true + }, + "memory": { + "auto_execute": true, + "auto_save": true, + "auto_search": true, + "trigger_sensitivity": "high", + "max_memories": 10000, + "search_limit": 50, + "session_memory": true, + "cross_session_memory": true, + "trigger_words": { + "personal_info": ["名前", "誕生日", "住所", "年齢", "職業", "家族", "出身", "好き", "嫌い", "趣味"], + "decisions": ["決めた", "決定", "方針", "計画", "予定", "目標"], + "solutions": ["解決", "修正", "対処", "設定", "インストール", "手順"], + "learning": ["学んだ", "わかった", "発見", "理解", "気づき"], + "past_reference": ["前に", "以前", "昔", "過去", "先ほど", "さっき", "この間"], + "memory_recall": ["覚えている", "記録", "メモ", "保存", "履歴"], + "preferences": ["好み", "設定", "環境", "構成", "preferences"], + "vague_reference": ["あれ", "それ", "例のやつ"] + } + }, + "hooks": { + "on_conversation_start": [ + "search_memories --limit 10 --recent" + ], + "on_trigger_word": [ + "auto_execute_memory_tools" + ], + "on_conversation_end": [ + "save_important_memories" + ] + } +} \ No newline at end of file diff --git a/docs/claude_code_config_extended.json b/docs/claude_code_config_extended.json new file mode 100644 index 0000000..afc0d58 --- /dev/null +++ b/docs/claude_code_config_extended.json @@ -0,0 +1,81 @@ +{ + "mcpServers": { + "memory-extended": { + "command": "cargo", + "args": ["run", "--bin", "memory-mcp-extended", "--features", "extended"], + "cwd": "/Users/syui/ai/ai/gpt", + "env": { + "MEMORY_AUTO_EXECUTE": "true", + "MEMORY_AUTO_SAVE": "true", + "MEMORY_AUTO_SEARCH": "true", + "TRIGGER_SENSITIVITY": "high", + "MEMORY_DB_PATH": "~/.claude/memory.db", + "OPENAI_API_KEY": "${OPENAI_API_KEY}" + } + } + }, + "tools": { + "memory": { + "enabled": true, + "auto_execute": true, + "mode": "extended" + } + }, + "workspace": { + "memory_integration": true, + "auto_save_on_file_change": true, + "auto_search_on_context_switch": true, + "ai_analysis_on_code_review": true, + "web_integration_for_docs": true + }, + "memory": { + "mode": "extended", + "auto_execute": true, + "auto_save": true, + "auto_search": true, + "trigger_sensitivity": "high", + "max_memories": 10000, + "search_limit": 50, + "session_memory": true, + "cross_session_memory": true, + "features": { + "ai_analysis": true, + "semantic_search": true, + "web_integration": true, + "sentiment_analysis": true, + "pattern_recognition": true, + "code_analysis": true, + "documentation_import": true + }, + "trigger_words": { + "personal_info": ["名前", "誕生日", "住所", "年齢", "職業", "家族", "出身", "好き", "嫌い", "趣味"], + "decisions": ["決めた", "決定", "方針", "計画", "予定", "目標"], + "solutions": ["解決", "修正", "対処", "設定", "インストール", "手順"], + "learning": ["学んだ", "わかった", "発見", "理解", "気づき"], + "past_reference": ["前に", "以前", "昔", "過去", "先ほど", "さっき", "この間"], + "memory_recall": ["覚えている", "記録", "メモ", "保存", "履歴"], + "preferences": ["好み", "設定", "環境", "構成", "preferences"], + "vague_reference": ["あれ", "それ", "例のやつ"], + "web_content": ["URL", "リンク", "サイト", "ページ", "記事", "ドキュメント"], + "analysis_request": ["分析", "パターン", "傾向", "インサイト", "統計", "レビュー"], + "code_related": ["関数", "クラス", "メソッド", "変数", "バグ", "リファクタリング"] + } + }, + "hooks": { + "on_conversation_start": [ + "search_memories --limit 10 --recent --semantic" + ], + "on_trigger_word": [ + "auto_execute_memory_tools --with-analysis" + ], + "on_conversation_end": [ + "save_important_memories --with-insights" + ], + "on_code_change": [ + "analyze_code_patterns --auto-save" + ], + "on_web_reference": [ + "import_webpage --auto-categorize" + ] + } +} \ No newline at end of file diff --git a/docs/claude_desktop_config.json b/docs/claude_desktop_config.json new file mode 100644 index 0000000..6ed0b35 --- /dev/null +++ b/docs/claude_desktop_config.json @@ -0,0 +1,34 @@ +{ + "mcpServers": { + "memory": { + "command": "cargo", + "args": ["run", "--release", "--bin", "memory-mcp"], + "cwd": "/Users/syui/ai/ai/gpt", + "env": { + "MEMORY_AUTO_EXECUTE": "true", + "MEMORY_AUTO_SAVE": "true", + "MEMORY_AUTO_SEARCH": "true", + "TRIGGER_SENSITIVITY": "high", + "MEMORY_DB_PATH": "~/.claude/memory.db" + } + } + }, + "memory": { + "auto_execute": true, + "auto_save": true, + "auto_search": true, + "trigger_sensitivity": "high", + "max_memories": 10000, + "search_limit": 50, + "trigger_words": { + "personal_info": ["名前", "誕生日", "住所", "年齢", "職業", "家族", "出身", "好き", "嫌い", "趣味"], + "decisions": ["決めた", "決定", "方針", "計画", "予定", "目標"], + "solutions": ["解決", "修正", "対処", "設定", "インストール", "手順"], + "learning": ["学んだ", "わかった", "発見", "理解", "気づき"], + "past_reference": ["前に", "以前", "昔", "過去", "先ほど", "さっき", "この間"], + "memory_recall": ["覚えている", "記録", "メモ", "保存", "履歴"], + "preferences": ["好み", "設定", "環境", "構成", "preferences"], + "vague_reference": ["あれ", "それ", "例のやつ"] + } + } +} \ No newline at end of file diff --git a/docs/claude_desktop_config_extended.json b/docs/claude_desktop_config_extended.json new file mode 100644 index 0000000..1beabb1 --- /dev/null +++ b/docs/claude_desktop_config_extended.json @@ -0,0 +1,45 @@ +{ + "mcpServers": { + "memory-extended": { + "command": "cargo", + "args": ["run", "--bin", "memory-mcp-extended", "--features", "extended"], + "cwd": "/Users/syui/ai/ai/gpt", + "env": { + "MEMORY_AUTO_EXECUTE": "true", + "MEMORY_AUTO_SAVE": "true", + "MEMORY_AUTO_SEARCH": "true", + "TRIGGER_SENSITIVITY": "high", + "MEMORY_DB_PATH": "~/.claude/memory.db", + "OPENAI_API_KEY": "${OPENAI_API_KEY}" + } + } + }, + "memory": { + "mode": "extended", + "auto_execute": true, + "auto_save": true, + "auto_search": true, + "trigger_sensitivity": "high", + "max_memories": 10000, + "search_limit": 50, + "features": { + "ai_analysis": true, + "semantic_search": true, + "web_integration": true, + "sentiment_analysis": true, + "pattern_recognition": true + }, + "trigger_words": { + "personal_info": ["名前", "誕生日", "住所", "年齢", "職業", "家族", "出身", "好き", "嫌い", "趣味"], + "decisions": ["決めた", "決定", "方針", "計画", "予定", "目標"], + "solutions": ["解決", "修正", "対処", "設定", "インストール", "手順"], + "learning": ["学んだ", "わかった", "発見", "理解", "気づき"], + "past_reference": ["前に", "以前", "昔", "過去", "先ほど", "さっき", "この間"], + "memory_recall": ["覚えている", "記録", "メモ", "保存", "履歴"], + "preferences": ["好み", "設定", "環境", "構成", "preferences"], + "vague_reference": ["あれ", "それ", "例のやつ"], + "web_content": ["URL", "リンク", "サイト", "ページ", "記事"], + "analysis_request": ["分析", "パターン", "傾向", "インサイト", "統計"] + } + } +} \ No newline at end of file diff --git a/extended/src/extended_mcp.rs b/extended/src/extended_mcp.rs new file mode 100644 index 0000000..f94157e --- /dev/null +++ b/extended/src/extended_mcp.rs @@ -0,0 +1,398 @@ +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 new file mode 100644 index 0000000..2d650ac --- /dev/null +++ b/extended/src/lib.rs @@ -0,0 +1,3 @@ +// 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 new file mode 100644 index 0000000..6242931 --- /dev/null +++ b/extended/src/main.rs @@ -0,0 +1,250 @@ +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/extended/src/mcp_server.rs b/extended/src/mcp_server.rs new file mode 100644 index 0000000..0696007 --- /dev/null +++ b/extended/src/mcp_server.rs @@ -0,0 +1,48 @@ +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; + +#[tokio::main] +async fn main() -> Result<()> { + // 環境変数から拡張機能の設定を読み込み + let auto_execute = env::var("MEMORY_AUTO_EXECUTE") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap_or(false); + + let auto_save = env::var("MEMORY_AUTO_SAVE") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap_or(false); + + let auto_search = env::var("MEMORY_AUTO_SEARCH") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap_or(false); + + let trigger_sensitivity = env::var("TRIGGER_SENSITIVITY") + .unwrap_or_else(|_| "medium".to_string()); + + let enable_ai_analysis = cfg!(feature = "ai-analysis"); + let enable_semantic_search = cfg!(feature = "semantic-search"); + let enable_web_integration = cfg!(feature = "web-integration"); + + // 拡張設定をログ出力 + eprintln!("Memory MCP Server (Extended) starting with config:"); + eprintln!(" AUTO_EXECUTE: {}", auto_execute); + eprintln!(" AUTO_SAVE: {}", auto_save); + eprintln!(" AUTO_SEARCH: {}", auto_search); + eprintln!(" TRIGGER_SENSITIVITY: {}", trigger_sensitivity); + eprintln!(" AI_ANALYSIS: {}", enable_ai_analysis); + eprintln!(" SEMANTIC_SEARCH: {}", enable_semantic_search); + eprintln!(" WEB_INTEGRATION: {}", enable_web_integration); + + let mut server = ExtendedMCPServer::new().await?; + server.run().await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..da25a77 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,2 @@ +pub mod memory; +pub mod mcp; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index f733317..b607abf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,8 @@ use anyhow::Result; use clap::{Parser, Subcommand}; use std::path::PathBuf; -mod memory; -mod mcp; +pub mod memory; +pub mod mcp; use memory::MemoryManager; use mcp::MCPServer; diff --git a/src/mcp_server.rs b/src/mcp_server.rs new file mode 100644 index 0000000..7679211 --- /dev/null +++ b/src/mcp_server.rs @@ -0,0 +1,41 @@ +use anyhow::Result; +use std::env; + +mod memory; +mod mcp; + +use mcp::MCPServer; + +#[tokio::main] +async fn main() -> Result<()> { + // 環境変数から自動実行設定を読み込み + let auto_execute = env::var("MEMORY_AUTO_EXECUTE") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap_or(false); + + let auto_save = env::var("MEMORY_AUTO_SAVE") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap_or(false); + + let auto_search = env::var("MEMORY_AUTO_SEARCH") + .unwrap_or_else(|_| "false".to_string()) + .parse::() + .unwrap_or(false); + + let trigger_sensitivity = env::var("TRIGGER_SENSITIVITY") + .unwrap_or_else(|_| "medium".to_string()); + + // 設定をログ出力(デバッグ用) + eprintln!("Memory MCP Server 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?; + server.run().await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/memory.rs b/src/memory.rs index 03fd506..e598da2 100644 --- a/src/memory.rs +++ b/src/memory.rs @@ -148,6 +148,7 @@ impl MemoryManager { conversations } + #[allow(dead_code)] pub async fn import_chatgpt_conversations(&mut self, file_path: &PathBuf) -> Result<()> { let content = std::fs::read_to_string(file_path) .context("Failed to read conversations file")?;