fix mcp
This commit is contained in:
		| @@ -42,7 +42,8 @@ | |||||||
|       "Bash(echo:*)", |       "Bash(echo:*)", | ||||||
|       "Bash(aigpt shell:*)", |       "Bash(aigpt shell:*)", | ||||||
|       "Bash(aigpt maintenance)", |       "Bash(aigpt maintenance)", | ||||||
|       "Bash(aigpt status syui)" |       "Bash(aigpt status syui)", | ||||||
|  |       "Bash(cp:*)" | ||||||
|     ], |     ], | ||||||
|     "deny": [] |     "deny": [] | ||||||
|   } |   } | ||||||
|   | |||||||
							
								
								
									
										91
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										91
									
								
								README.md
									
									
									
									
									
								
							| @@ -546,6 +546,97 @@ aigpt maintenance  # AI要約を自動実行 | |||||||
| aigpt chat syui "記憶システムについて" --provider ollama --model qwen3:latest | aigpt chat syui "記憶システムについて" --provider ollama --model qwen3:latest | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | ## 🎉 **TODAY: MCP統合とサーバー表示改善完了** (2025/01/06) | ||||||
|  |  | ||||||
|  | ### ✅ **本日の主要な改善** | ||||||
|  |  | ||||||
|  | #### 🚀 **サーバー起動表示の大幅改善** | ||||||
|  | 従来のシンプルな表示から、プロフェッショナルな情報表示に刷新: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | aigpt server | ||||||
|  | ``` | ||||||
|  | **改善前:** | ||||||
|  | ``` | ||||||
|  | Starting ai.gpt MCP Server | ||||||
|  | Host: localhost:8001 | ||||||
|  | Endpoints: 27 MCP tools | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | **改善後:** | ||||||
|  | ``` | ||||||
|  | 🚀 ai.gpt MCP Server | ||||||
|  |  | ||||||
|  | Server Configuration: | ||||||
|  | 🌐 Address: http://localhost:8001 | ||||||
|  | 📋 API Docs: http://localhost:8001/docs | ||||||
|  | 💾 Data Directory: /Users/syui/.config/syui/ai/gpt/data | ||||||
|  |  | ||||||
|  | AI Provider Configuration: | ||||||
|  | 🤖 Provider: ollama ✅ http://192.168.11.95:11434 | ||||||
|  | 🧩 Model: qwen3 | ||||||
|  |  | ||||||
|  | MCP Tools Available (27 total): | ||||||
|  | 🧠 Memory System: 5 tools | ||||||
|  | 🤝 Relationships: 4 tools   | ||||||
|  | ⚙️  System State: 3 tools | ||||||
|  | 💻 Shell Integration: 5 tools | ||||||
|  | 🔒 Remote Execution: 4 tools | ||||||
|  |  | ||||||
|  | Integration Status: | ||||||
|  | ✅ MCP Client Ready | ||||||
|  | 🔗 Config: /Users/syui/.config/syui/ai/gpt/config.json | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### 🔧 **OpenAI Function Calling + MCP統合の実証** | ||||||
|  | OpenAI GPT-4o-miniでMCP function callingが完全動作: | ||||||
|  |  | ||||||
|  | ```bash | ||||||
|  | aigpt conv test_user --provider openai --model gpt-4o-mini | ||||||
|  | ``` | ||||||
|  | **動作フロー:** | ||||||
|  | 1. **自然言語入力**: 「覚えていることはある?」 | ||||||
|  | 2. **自動ツール選択**: OpenAIが`get_memories`を自動呼び出し | ||||||
|  | 3. **MCP通信**: `http://localhost:8001/get_memories`にHTTPリクエスト | ||||||
|  | 4. **記憶取得**: 実際の過去の会話データを取得 | ||||||
|  | 5. **文脈回答**: 記憶に基づく具体的な内容で回答 | ||||||
|  |  | ||||||
|  | **技術的実証:** | ||||||
|  | ```sh | ||||||
|  | 🔧 [OpenAI] 1 tools called: | ||||||
|  |   - get_memories({"limit":5}) | ||||||
|  | 🌐 [MCP] Executing get_memories... | ||||||
|  | ✅ [MCP] Result: [{'id': '5ce8f7d0-c078-43f1... | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | #### 📊 **統合アーキテクチャの完成** | ||||||
|  | ``` | ||||||
|  | OpenAI GPT-4o-mini | ||||||
|  |     ↓ (Function Calling) | ||||||
|  | MCP Client (aigpt conv) | ||||||
|  |     ↓ (HTTP API) | ||||||
|  | MCP Server (aigpt server:8001) | ||||||
|  |     ↓ (Direct Access)   | ||||||
|  | Memory/Relationship Systems | ||||||
|  |     ↓ | ||||||
|  | JSON/SQLite Data | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ### 🎯 **技術的成果** | ||||||
|  | - ✅ **分散型AIシステム**: プロセス間MCP通信で複数AIアプリが記憶共有 | ||||||
|  | - ✅ **OpenAI統合**: GPT-4o-miniのfunction callingが記憶システムと完全連携 | ||||||
|  | - ✅ **プロフェッショナルUI**: enterprise-grade開発ツール風の情報表示 | ||||||
|  | - ✅ **設定統合**: config.jsonからの自動設定読み込み | ||||||
|  | - ✅ **エラーハンドリング**: graceful shutdown、設定チェック、接続状態表示 | ||||||
|  |  | ||||||
|  | ### 📈 **ユーザー体験の向上** | ||||||
|  | - **開発者体験**: サーバー状況が一目で把握可能 | ||||||
|  | - **デバッグ効率**: 詳細なログと状態表示 | ||||||
|  | - **設定管理**: 設定ファイルパス、プロバイダー状態の明確化 | ||||||
|  | - **AI連携**: OpenAI + MCP + 記憶システムのシームレス統合 | ||||||
|  |  | ||||||
|  | **ai.gptの基盤アーキテクチャが完成し、実用的なAI記憶システムとして動作開始!** 🚀 | ||||||
|  |  | ||||||
| ## 🔥 **NEW: Claude Code的継続開発機能** (2025/06/03 完成) | ## 🔥 **NEW: Claude Code的継続開発機能** (2025/06/03 完成) | ||||||
|  |  | ||||||
| ### 🚀 **プロジェクト管理システム完全実装** | ### 🚀 **プロジェクト管理システム完全実装** | ||||||
|   | |||||||
							
								
								
									
										36
									
								
								config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								config.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | { | ||||||
|  |   "providers": { | ||||||
|  |     "openai": { | ||||||
|  |       "api_key": "", | ||||||
|  |       "default_model": "gpt-4o-mini" | ||||||
|  |     }, | ||||||
|  |     "ollama": { | ||||||
|  |       "host": "http://127.0.0.1:11434", | ||||||
|  |       "default_model": "qwen3" | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "atproto": { | ||||||
|  |     "handle": null, | ||||||
|  |     "password": null, | ||||||
|  |     "host": "https://bsky.social" | ||||||
|  |   }, | ||||||
|  |   "default_provider": "openai", | ||||||
|  |   "mcp": { | ||||||
|  |     "servers": { | ||||||
|  |       "ai_gpt": { | ||||||
|  |         "base_url": "http://localhost:8001", | ||||||
|  |         "name": "ai.gpt MCP Server", | ||||||
|  |         "timeout": "10.0", | ||||||
|  |         "endpoints": { | ||||||
|  |           "get_memories": "/get_memories", | ||||||
|  |           "search_memories": "/search_memories", | ||||||
|  |           "get_contextual_memories": "/get_contextual_memories", | ||||||
|  |           "get_relationship": "/get_relationship", | ||||||
|  |           "process_interaction": "/process_interaction" | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |     "enabled": "true", | ||||||
|  |     "auto_detect": "true" | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,6 +1,7 @@ | |||||||
| """AI Provider integration for response generation""" | """AI Provider integration for response generation""" | ||||||
|  |  | ||||||
| import os | import os | ||||||
|  | import json | ||||||
| from typing import Optional, Dict, List, Any, Protocol | from typing import Optional, Dict, List, Any, Protocol | ||||||
| from abc import abstractmethod | from abc import abstractmethod | ||||||
| import logging | import logging | ||||||
| @@ -128,9 +129,9 @@ Recent memories: | |||||||
|  |  | ||||||
|  |  | ||||||
| class OpenAIProvider: | class OpenAIProvider: | ||||||
|     """OpenAI API provider""" |     """OpenAI API provider with MCP function calling support""" | ||||||
|      |      | ||||||
|     def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None): |     def __init__(self, model: str = "gpt-4o-mini", api_key: Optional[str] = None, mcp_client=None): | ||||||
|         self.model = model |         self.model = model | ||||||
|         # Try to get API key from config first |         # Try to get API key from config first | ||||||
|         config = Config() |         config = Config() | ||||||
| @@ -139,6 +140,90 @@ class OpenAIProvider: | |||||||
|             raise ValueError("OpenAI API key not provided. Set it with: aigpt config set providers.openai.api_key YOUR_KEY") |             raise ValueError("OpenAI API key not provided. Set it with: aigpt config set providers.openai.api_key YOUR_KEY") | ||||||
|         self.client = OpenAI(api_key=self.api_key) |         self.client = OpenAI(api_key=self.api_key) | ||||||
|         self.logger = logging.getLogger(__name__) |         self.logger = logging.getLogger(__name__) | ||||||
|  |         self.mcp_client = mcp_client  # For MCP function calling | ||||||
|  |      | ||||||
|  |     def _get_mcp_tools(self) -> List[Dict[str, Any]]: | ||||||
|  |         """Generate OpenAI tools from MCP endpoints""" | ||||||
|  |         if not self.mcp_client or not self.mcp_client.available: | ||||||
|  |             return [] | ||||||
|  |          | ||||||
|  |         tools = [ | ||||||
|  |             { | ||||||
|  |                 "type": "function", | ||||||
|  |                 "function": { | ||||||
|  |                     "name": "get_memories", | ||||||
|  |                     "description": "過去の会話記憶を取得します。「覚えている」「前回」「以前」などの質問で必ず使用してください", | ||||||
|  |                     "parameters": { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "properties": { | ||||||
|  |                             "limit": { | ||||||
|  |                                 "type": "integer", | ||||||
|  |                                 "description": "取得する記憶の数", | ||||||
|  |                                 "default": 5 | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "type": "function",  | ||||||
|  |                 "function": { | ||||||
|  |                     "name": "search_memories", | ||||||
|  |                     "description": "特定のトピックについて話した記憶を検索します。「プログラミングについて」「○○について話した」などの質問で使用してください", | ||||||
|  |                     "parameters": { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "properties": { | ||||||
|  |                             "keywords": { | ||||||
|  |                                 "type": "array", | ||||||
|  |                                 "items": {"type": "string"}, | ||||||
|  |                                 "description": "検索キーワードの配列" | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         "required": ["keywords"] | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "type": "function", | ||||||
|  |                 "function": { | ||||||
|  |                     "name": "get_contextual_memories",  | ||||||
|  |                     "description": "クエリに関連する文脈的記憶を取得します", | ||||||
|  |                     "parameters": { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "properties": { | ||||||
|  |                             "query": { | ||||||
|  |                                 "type": "string", | ||||||
|  |                                 "description": "検索クエリ" | ||||||
|  |                             }, | ||||||
|  |                             "limit": { | ||||||
|  |                                 "type": "integer",  | ||||||
|  |                                 "description": "取得する記憶の数", | ||||||
|  |                                 "default": 5 | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         "required": ["query"] | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             }, | ||||||
|  |             { | ||||||
|  |                 "type": "function", | ||||||
|  |                 "function": { | ||||||
|  |                     "name": "get_relationship", | ||||||
|  |                     "description": "特定ユーザーとの関係性情報を取得します", | ||||||
|  |                     "parameters": { | ||||||
|  |                         "type": "object", | ||||||
|  |                         "properties": { | ||||||
|  |                             "user_id": { | ||||||
|  |                                 "type": "string", | ||||||
|  |                                 "description": "ユーザーID" | ||||||
|  |                             } | ||||||
|  |                         }, | ||||||
|  |                         "required": ["user_id"] | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         ] | ||||||
|  |         return tools | ||||||
|      |      | ||||||
|     async def generate_response( |     async def generate_response( | ||||||
|         self, |         self, | ||||||
| @@ -184,6 +269,127 @@ Recent memories: | |||||||
|             self.logger.error(f"OpenAI generation failed: {e}") |             self.logger.error(f"OpenAI generation failed: {e}") | ||||||
|             return self._fallback_response(persona_state) |             return self._fallback_response(persona_state) | ||||||
|      |      | ||||||
|  |     async def chat_with_mcp(self, prompt: str, max_tokens: int = 2000, user_id: str = "user") -> str: | ||||||
|  |         """Chat interface with MCP function calling support""" | ||||||
|  |         if not self.mcp_client or not self.mcp_client.available: | ||||||
|  |             return self.chat(prompt, max_tokens) | ||||||
|  |          | ||||||
|  |         try: | ||||||
|  |             # Prepare tools | ||||||
|  |             tools = self._get_mcp_tools() | ||||||
|  |              | ||||||
|  |             # Initial request with tools | ||||||
|  |             response = self.client.chat.completions.create( | ||||||
|  |                 model=self.model, | ||||||
|  |                 messages=[ | ||||||
|  |                     {"role": "system", "content": "あなたは記憶システムと関係性データにアクセスできます。過去の会話、記憶、関係性について質問された時は、必ずツールを使用して正確な情報を取得してください。「覚えている」「前回」「以前」「について話した」「関係」などのキーワードがあれば積極的にツールを使用してください。"}, | ||||||
|  |                     {"role": "user", "content": prompt} | ||||||
|  |                 ], | ||||||
|  |                 tools=tools, | ||||||
|  |                 tool_choice="auto", | ||||||
|  |                 max_tokens=max_tokens, | ||||||
|  |                 temperature=0.7 | ||||||
|  |             ) | ||||||
|  |              | ||||||
|  |             message = response.choices[0].message | ||||||
|  |              | ||||||
|  |             # Handle tool calls | ||||||
|  |             if message.tool_calls: | ||||||
|  |                 print(f"🔧 [OpenAI] {len(message.tool_calls)} tools called:") | ||||||
|  |                 for tc in message.tool_calls: | ||||||
|  |                     print(f"  - {tc.function.name}({tc.function.arguments})") | ||||||
|  |                  | ||||||
|  |                 messages = [ | ||||||
|  |                     {"role": "system", "content": "必要に応じて利用可能なツールを使って、より正確で詳細な回答を提供してください。"}, | ||||||
|  |                     {"role": "user", "content": prompt}, | ||||||
|  |                     { | ||||||
|  |                         "role": "assistant",  | ||||||
|  |                         "content": message.content, | ||||||
|  |                         "tool_calls": [tc.model_dump() for tc in message.tool_calls] | ||||||
|  |                     } | ||||||
|  |                 ] | ||||||
|  |                  | ||||||
|  |                 # Execute each tool call | ||||||
|  |                 for tool_call in message.tool_calls: | ||||||
|  |                     print(f"🌐 [MCP] Executing {tool_call.function.name}...") | ||||||
|  |                     tool_result = await self._execute_mcp_tool(tool_call, user_id) | ||||||
|  |                     print(f"✅ [MCP] Result: {str(tool_result)[:100]}...") | ||||||
|  |                     messages.append({ | ||||||
|  |                         "role": "tool", | ||||||
|  |                         "tool_call_id": tool_call.id, | ||||||
|  |                         "name": tool_call.function.name, | ||||||
|  |                         "content": json.dumps(tool_result, ensure_ascii=False) | ||||||
|  |                     }) | ||||||
|  |                  | ||||||
|  |                 # Get final response with tool outputs | ||||||
|  |                 final_response = self.client.chat.completions.create( | ||||||
|  |                     model=self.model, | ||||||
|  |                     messages=messages, | ||||||
|  |                     max_tokens=max_tokens, | ||||||
|  |                     temperature=0.7 | ||||||
|  |                 ) | ||||||
|  |                  | ||||||
|  |                 return final_response.choices[0].message.content | ||||||
|  |             else: | ||||||
|  |                 return message.content | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             self.logger.error(f"OpenAI MCP chat failed: {e}") | ||||||
|  |             return f"申し訳ありません。エラーが発生しました: {e}" | ||||||
|  |      | ||||||
|  |     async def _execute_mcp_tool(self, tool_call, context_user_id: str = "user") -> Dict[str, Any]: | ||||||
|  |         """Execute MCP tool call""" | ||||||
|  |         try: | ||||||
|  |             import json | ||||||
|  |             function_name = tool_call.function.name | ||||||
|  |             arguments = json.loads(tool_call.function.arguments) | ||||||
|  |              | ||||||
|  |             if function_name == "get_memories": | ||||||
|  |                 limit = arguments.get("limit", 5) | ||||||
|  |                 return await self.mcp_client.get_memories(limit) or {"error": "記憶の取得に失敗しました"} | ||||||
|  |              | ||||||
|  |             elif function_name == "search_memories": | ||||||
|  |                 keywords = arguments.get("keywords", []) | ||||||
|  |                 return await self.mcp_client.search_memories(keywords) or {"error": "記憶の検索に失敗しました"} | ||||||
|  |              | ||||||
|  |             elif function_name == "get_contextual_memories": | ||||||
|  |                 query = arguments.get("query", "") | ||||||
|  |                 limit = arguments.get("limit", 5) | ||||||
|  |                 return await self.mcp_client.get_contextual_memories(query, limit) or {"error": "文脈記憶の取得に失敗しました"} | ||||||
|  |              | ||||||
|  |             elif function_name == "get_relationship": | ||||||
|  |                 # 引数のuser_idがない場合はコンテキストから取得 | ||||||
|  |                 user_id = arguments.get("user_id", context_user_id) | ||||||
|  |                 if not user_id or user_id == "user": | ||||||
|  |                     user_id = context_user_id | ||||||
|  |                 # デバッグ用ログ | ||||||
|  |                 print(f"🔍 [DEBUG] get_relationship called with user_id: '{user_id}' (context: '{context_user_id}')") | ||||||
|  |                 result = await self.mcp_client.get_relationship(user_id) | ||||||
|  |                 print(f"🔍 [DEBUG] MCP result: {result}") | ||||||
|  |                 return result or {"error": "関係性の取得に失敗しました"} | ||||||
|  |              | ||||||
|  |             else: | ||||||
|  |                 return {"error": f"未知のツール: {function_name}"} | ||||||
|  |                  | ||||||
|  |         except Exception as e: | ||||||
|  |             return {"error": f"ツール実行エラー: {str(e)}"} | ||||||
|  |  | ||||||
|  |     def chat(self, prompt: str, max_tokens: int = 2000) -> str: | ||||||
|  |         """Simple chat interface without MCP tools""" | ||||||
|  |         try: | ||||||
|  |             response = self.client.chat.completions.create( | ||||||
|  |                 model=self.model, | ||||||
|  |                 messages=[ | ||||||
|  |                     {"role": "user", "content": prompt} | ||||||
|  |                 ], | ||||||
|  |                 max_tokens=max_tokens, | ||||||
|  |                 temperature=0.7 | ||||||
|  |             ) | ||||||
|  |             return response.choices[0].message.content | ||||||
|  |         except Exception as e: | ||||||
|  |             self.logger.error(f"OpenAI chat failed: {e}") | ||||||
|  |             return "I'm having trouble connecting to the AI model." | ||||||
|  |      | ||||||
|     def _fallback_response(self, persona_state: PersonaState) -> str: |     def _fallback_response(self, persona_state: PersonaState) -> str: | ||||||
|         """Fallback response based on mood""" |         """Fallback response based on mood""" | ||||||
|         mood_responses = { |         mood_responses = { | ||||||
| @@ -196,7 +402,7 @@ Recent memories: | |||||||
|         return mood_responses.get(persona_state.current_mood, "I see.") |         return mood_responses.get(persona_state.current_mood, "I see.") | ||||||
|  |  | ||||||
|  |  | ||||||
| def create_ai_provider(provider: str = "ollama", model: Optional[str] = None, **kwargs) -> AIProvider: | def create_ai_provider(provider: str = "ollama", model: Optional[str] = None, mcp_client=None, **kwargs) -> AIProvider: | ||||||
|     """Factory function to create AI providers""" |     """Factory function to create AI providers""" | ||||||
|     if provider == "ollama": |     if provider == "ollama": | ||||||
|         # Get model from config if not provided |         # Get model from config if not provided | ||||||
| @@ -228,6 +434,6 @@ def create_ai_provider(provider: str = "ollama", model: Optional[str] = None, ** | |||||||
|                 model = config.get('providers.openai.default_model', 'gpt-4o-mini') |                 model = config.get('providers.openai.default_model', 'gpt-4o-mini') | ||||||
|             except: |             except: | ||||||
|                 model = 'gpt-4o-mini'  # Fallback to default |                 model = 'gpt-4o-mini'  # Fallback to default | ||||||
|         return OpenAIProvider(model=model, **kwargs) |         return OpenAIProvider(model=model, mcp_client=mcp_client, **kwargs) | ||||||
|     else: |     else: | ||||||
|         raise ValueError(f"Unknown provider: {provider}") |         raise ValueError(f"Unknown provider: {provider}") | ||||||
|   | |||||||
							
								
								
									
										409
									
								
								src/aigpt/cli.py
									
									
									
									
									
								
							
							
						
						
									
										409
									
								
								src/aigpt/cli.py
									
									
									
									
									
								
							| @@ -2,13 +2,15 @@ | |||||||
|  |  | ||||||
| import typer | import typer | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import Optional | from typing import Optional, Dict, Any | ||||||
| from rich.console import Console | from rich.console import Console | ||||||
| from rich.table import Table | from rich.table import Table | ||||||
| from rich.panel import Panel | from rich.panel import Panel | ||||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||||
| import subprocess | import subprocess | ||||||
| import shlex | import shlex | ||||||
|  | import httpx | ||||||
|  | import asyncio | ||||||
| from prompt_toolkit import prompt as ptk_prompt | from prompt_toolkit import prompt as ptk_prompt | ||||||
| from prompt_toolkit.completion import WordCompleter, Completer, Completion | from prompt_toolkit.completion import WordCompleter, Completer, Completion | ||||||
| from prompt_toolkit.history import FileHistory | from prompt_toolkit.history import FileHistory | ||||||
| @@ -30,6 +32,202 @@ config = Config() | |||||||
| DEFAULT_DATA_DIR = config.data_dir | DEFAULT_DATA_DIR = config.data_dir | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class MCPClient: | ||||||
|  |     """Client for communicating with MCP server using config settings""" | ||||||
|  |      | ||||||
|  |     def __init__(self, config: Optional[Config] = None): | ||||||
|  |         self.config = config or Config() | ||||||
|  |         self.enabled = self.config.get("mcp.enabled", True) | ||||||
|  |         self.auto_detect = self.config.get("mcp.auto_detect", True) | ||||||
|  |         self.servers = self.config.get("mcp.servers", {}) | ||||||
|  |         self.available = False | ||||||
|  |          | ||||||
|  |         if self.enabled: | ||||||
|  |             self._check_availability() | ||||||
|  |      | ||||||
|  |     def _check_availability(self): | ||||||
|  |         """Check if any MCP server is available""" | ||||||
|  |         self.available = False | ||||||
|  |         if not self.enabled: | ||||||
|  |             print(f"🚨 [MCP Client] MCP disabled in config") | ||||||
|  |             return | ||||||
|  |              | ||||||
|  |         print(f"🔍 [MCP Client] Checking availability...") | ||||||
|  |         print(f"🔍 [MCP Client] Available servers: {list(self.servers.keys())}") | ||||||
|  |          | ||||||
|  |         # Check ai.gpt server first (primary) | ||||||
|  |         ai_gpt_config = self.servers.get("ai_gpt", {}) | ||||||
|  |         if ai_gpt_config: | ||||||
|  |             base_url = ai_gpt_config.get("base_url", "http://localhost:8001") | ||||||
|  |             timeout = ai_gpt_config.get("timeout", 5.0) | ||||||
|  |              | ||||||
|  |             # Convert timeout to float if it's a string | ||||||
|  |             if isinstance(timeout, str): | ||||||
|  |                 timeout = float(timeout) | ||||||
|  |              | ||||||
|  |             print(f"🔍 [MCP Client] Testing ai_gpt server: {base_url} (timeout: {timeout})") | ||||||
|  |             try: | ||||||
|  |                 import httpx | ||||||
|  |                 with httpx.Client(timeout=timeout) as client: | ||||||
|  |                     response = client.get(f"{base_url}/docs") | ||||||
|  |                     print(f"🔍 [MCP Client] ai_gpt response: {response.status_code}") | ||||||
|  |                     if response.status_code == 200: | ||||||
|  |                         self.available = True | ||||||
|  |                         self.active_server = "ai_gpt" | ||||||
|  |                         print(f"✅ [MCP Client] ai_gpt server connected successfully") | ||||||
|  |                         return | ||||||
|  |             except Exception as e: | ||||||
|  |                 print(f"🚨 [MCP Client] ai_gpt connection failed: {e}") | ||||||
|  |         else: | ||||||
|  |             print(f"🚨 [MCP Client] No ai_gpt config found") | ||||||
|  |          | ||||||
|  |         # If auto_detect is enabled, try to find any available server | ||||||
|  |         if self.auto_detect: | ||||||
|  |             print(f"🔍 [MCP Client] Auto-detect enabled, trying other servers...") | ||||||
|  |             for server_name, server_config in self.servers.items(): | ||||||
|  |                 base_url = server_config.get("base_url", "") | ||||||
|  |                 timeout = server_config.get("timeout", 5.0) | ||||||
|  |                  | ||||||
|  |                 # Convert timeout to float if it's a string | ||||||
|  |                 if isinstance(timeout, str): | ||||||
|  |                     timeout = float(timeout) | ||||||
|  |                  | ||||||
|  |                 print(f"🔍 [MCP Client] Testing {server_name}: {base_url} (timeout: {timeout})") | ||||||
|  |                 try: | ||||||
|  |                     import httpx | ||||||
|  |                     with httpx.Client(timeout=timeout) as client: | ||||||
|  |                         response = client.get(f"{base_url}/docs") | ||||||
|  |                         print(f"🔍 [MCP Client] {server_name} response: {response.status_code}") | ||||||
|  |                         if response.status_code == 200: | ||||||
|  |                             self.available = True | ||||||
|  |                             self.active_server = server_name | ||||||
|  |                             print(f"✅ [MCP Client] {server_name} server connected successfully") | ||||||
|  |                             return | ||||||
|  |                 except Exception as e: | ||||||
|  |                     print(f"🚨 [MCP Client] {server_name} connection failed: {e}") | ||||||
|  |          | ||||||
|  |         print(f"🚨 [MCP Client] No MCP servers available") | ||||||
|  |      | ||||||
|  |     def _get_url(self, endpoint_name: str) -> Optional[str]: | ||||||
|  |         """Get full URL for an endpoint""" | ||||||
|  |         if not self.available or not hasattr(self, 'active_server'): | ||||||
|  |             print(f"🚨 [MCP Client] Not available or no active server") | ||||||
|  |             return None | ||||||
|  |              | ||||||
|  |         server_config = self.servers.get(self.active_server, {}) | ||||||
|  |         base_url = server_config.get("base_url", "") | ||||||
|  |         endpoints = server_config.get("endpoints", {}) | ||||||
|  |         endpoint_path = endpoints.get(endpoint_name, "") | ||||||
|  |          | ||||||
|  |         print(f"🔍 [MCP Client] Server: {self.active_server}") | ||||||
|  |         print(f"🔍 [MCP Client] Base URL: {base_url}") | ||||||
|  |         print(f"🔍 [MCP Client] Endpoints: {list(endpoints.keys())}") | ||||||
|  |         print(f"🔍 [MCP Client] Looking for: {endpoint_name}") | ||||||
|  |         print(f"🔍 [MCP Client] Found path: {endpoint_path}") | ||||||
|  |          | ||||||
|  |         if base_url and endpoint_path: | ||||||
|  |             return f"{base_url}{endpoint_path}" | ||||||
|  |         return None | ||||||
|  |      | ||||||
|  |     def _get_timeout(self) -> float: | ||||||
|  |         """Get timeout for the active server""" | ||||||
|  |         if not hasattr(self, 'active_server'): | ||||||
|  |             return 5.0 | ||||||
|  |         server_config = self.servers.get(self.active_server, {}) | ||||||
|  |         timeout = server_config.get("timeout", 5.0) | ||||||
|  |          | ||||||
|  |         # Convert timeout to float if it's a string | ||||||
|  |         if isinstance(timeout, str): | ||||||
|  |             timeout = float(timeout) | ||||||
|  |          | ||||||
|  |         return timeout | ||||||
|  |      | ||||||
|  |     async def get_memories(self, limit: int = 5) -> Optional[Dict[str, Any]]: | ||||||
|  |         """Get memories via MCP""" | ||||||
|  |         url = self._get_url("get_memories") | ||||||
|  |         if not url: | ||||||
|  |             return None | ||||||
|  |         try: | ||||||
|  |             async with httpx.AsyncClient(timeout=self._get_timeout()) as client: | ||||||
|  |                 response = await client.get(f"{url}?limit={limit}") | ||||||
|  |                 return response.json() if response.status_code == 200 else None | ||||||
|  |         except Exception: | ||||||
|  |             return None | ||||||
|  |      | ||||||
|  |     async def search_memories(self, keywords: list) -> Optional[Dict[str, Any]]: | ||||||
|  |         """Search memories via MCP""" | ||||||
|  |         url = self._get_url("search_memories") | ||||||
|  |         if not url: | ||||||
|  |             return None | ||||||
|  |         try: | ||||||
|  |             async with httpx.AsyncClient(timeout=self._get_timeout()) as client: | ||||||
|  |                 response = await client.post(url, json={"keywords": keywords}) | ||||||
|  |                 return response.json() if response.status_code == 200 else None | ||||||
|  |         except Exception: | ||||||
|  |             return None | ||||||
|  |      | ||||||
|  |     async def get_contextual_memories(self, query: str, limit: int = 5) -> Optional[Dict[str, Any]]: | ||||||
|  |         """Get contextual memories via MCP""" | ||||||
|  |         url = self._get_url("get_contextual_memories") | ||||||
|  |         if not url: | ||||||
|  |             return None | ||||||
|  |         try: | ||||||
|  |             async with httpx.AsyncClient(timeout=self._get_timeout()) as client: | ||||||
|  |                 response = await client.get(f"{url}?query={query}&limit={limit}") | ||||||
|  |                 return response.json() if response.status_code == 200 else None | ||||||
|  |         except Exception: | ||||||
|  |             return None | ||||||
|  |      | ||||||
|  |     async def process_interaction(self, user_id: str, message: str) -> Optional[Dict[str, Any]]: | ||||||
|  |         """Process interaction via MCP""" | ||||||
|  |         url = self._get_url("process_interaction") | ||||||
|  |         if not url: | ||||||
|  |             return None | ||||||
|  |         try: | ||||||
|  |             async with httpx.AsyncClient(timeout=self._get_timeout()) as client: | ||||||
|  |                 response = await client.post(url, json={"user_id": user_id, "message": message}) | ||||||
|  |                 return response.json() if response.status_code == 200 else None | ||||||
|  |         except Exception: | ||||||
|  |             return None | ||||||
|  |      | ||||||
|  |     async def get_relationship(self, user_id: str) -> Optional[Dict[str, Any]]: | ||||||
|  |         """Get relationship via MCP""" | ||||||
|  |         url = self._get_url("get_relationship") | ||||||
|  |         print(f"🔍 [MCP Client] get_relationship URL: {url}") | ||||||
|  |         if not url: | ||||||
|  |             print(f"🚨 [MCP Client] No URL found for get_relationship") | ||||||
|  |             return None | ||||||
|  |         try: | ||||||
|  |             async with httpx.AsyncClient(timeout=self._get_timeout()) as client: | ||||||
|  |                 response = await client.get(f"{url}?user_id={user_id}") | ||||||
|  |                 print(f"🔍 [MCP Client] Response status: {response.status_code}") | ||||||
|  |                 if response.status_code == 200: | ||||||
|  |                     result = response.json() | ||||||
|  |                     print(f"🔍 [MCP Client] Response data: {result}") | ||||||
|  |                     return result | ||||||
|  |                 else: | ||||||
|  |                     print(f"🚨 [MCP Client] HTTP error: {response.status_code}") | ||||||
|  |                     return None | ||||||
|  |         except Exception as e: | ||||||
|  |             print(f"🚨 [MCP Client] Exception: {e}") | ||||||
|  |             return None | ||||||
|  |      | ||||||
|  |     def get_server_info(self) -> Dict[str, Any]: | ||||||
|  |         """Get information about the active MCP server""" | ||||||
|  |         if not self.available or not hasattr(self, 'active_server'): | ||||||
|  |             return {"available": False} | ||||||
|  |          | ||||||
|  |         server_config = self.servers.get(self.active_server, {}) | ||||||
|  |         return { | ||||||
|  |             "available": True, | ||||||
|  |             "server_name": self.active_server, | ||||||
|  |             "display_name": server_config.get("name", self.active_server), | ||||||
|  |             "base_url": server_config.get("base_url", ""), | ||||||
|  |             "timeout": server_config.get("timeout", 5.0), | ||||||
|  |             "endpoints": len(server_config.get("endpoints", {})) | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |  | ||||||
| def get_persona(data_dir: Optional[Path] = None) -> Persona: | def get_persona(data_dir: Optional[Path] = None) -> Persona: | ||||||
|     """Get or create persona instance""" |     """Get or create persona instance""" | ||||||
|     if data_dir is None: |     if data_dir is None: | ||||||
| @@ -226,10 +424,10 @@ def relationships( | |||||||
| @app.command() | @app.command() | ||||||
| def server( | def server( | ||||||
|     host: str = typer.Option("localhost", "--host", "-h", help="Server host"), |     host: str = typer.Option("localhost", "--host", "-h", help="Server host"), | ||||||
|     port: int = typer.Option(8000, "--port", "-p", help="Server port"), |     port: int = typer.Option(8001, "--port", "-p", help="Server port"), | ||||||
|     data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"), |     data_dir: Optional[Path] = typer.Option(None, "--data-dir", "-d", help="Data directory"), | ||||||
|     model: str = typer.Option("qwen2.5", "--model", "-m", help="AI model to use"), |     model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"), | ||||||
|     provider: str = typer.Option("ollama", "--provider", help="AI provider (ollama/openai)") |     provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)") | ||||||
| ): | ): | ||||||
|     """Run MCP server for AI integration""" |     """Run MCP server for AI integration""" | ||||||
|     import uvicorn |     import uvicorn | ||||||
| @@ -239,26 +437,94 @@ def server( | |||||||
|      |      | ||||||
|     data_dir.mkdir(parents=True, exist_ok=True) |     data_dir.mkdir(parents=True, exist_ok=True) | ||||||
|      |      | ||||||
|  |     # Get configuration | ||||||
|  |     config_instance = Config() | ||||||
|  |      | ||||||
|  |     # Get defaults from config if not provided | ||||||
|  |     if not provider: | ||||||
|  |         provider = config_instance.get("default_provider", "ollama") | ||||||
|  |     if not model: | ||||||
|  |         if provider == "ollama": | ||||||
|  |             model = config_instance.get("providers.ollama.default_model", "qwen3:latest") | ||||||
|  |         elif provider == "openai": | ||||||
|  |             model = config_instance.get("providers.openai.default_model", "gpt-4o-mini") | ||||||
|  |         else: | ||||||
|  |             model = "qwen3:latest" | ||||||
|  |      | ||||||
|     # Create MCP server |     # Create MCP server | ||||||
|     mcp_server = AIGptMcpServer(data_dir) |     mcp_server = AIGptMcpServer(data_dir) | ||||||
|     app_instance = mcp_server.app |     app_instance = mcp_server.app | ||||||
|      |      | ||||||
|  |     # Get endpoint categories and count | ||||||
|  |     total_routes = len(mcp_server.app.routes) | ||||||
|  |     mcp_tools = total_routes - 2  # Exclude docs and openapi | ||||||
|  |      | ||||||
|  |     # Categorize endpoints | ||||||
|  |     memory_endpoints = ["get_memories", "search_memories", "get_contextual_memories", "create_summary", "create_core_memory"] | ||||||
|  |     relationship_endpoints = ["get_relationship", "get_all_relationships", "process_interaction", "check_transmission_eligibility"] | ||||||
|  |     system_endpoints = ["get_persona_state", "get_fortune", "run_maintenance"] | ||||||
|  |     shell_endpoints = ["execute_command", "analyze_file", "write_file", "list_files", "read_project_file"] | ||||||
|  |     remote_endpoints = ["remote_shell", "ai_bot_status", "isolated_python", "isolated_analysis"] | ||||||
|  |      | ||||||
|  |     # Build endpoint summary | ||||||
|  |     endpoint_summary = f"""🧠 Memory System: {len(memory_endpoints)} tools | ||||||
|  | 🤝 Relationships: {len(relationship_endpoints)} tools   | ||||||
|  | ⚙️  System State: {len(system_endpoints)} tools | ||||||
|  | 💻 Shell Integration: {len(shell_endpoints)} tools | ||||||
|  | 🔒 Remote Execution: {len(remote_endpoints)} tools""" | ||||||
|  |      | ||||||
|  |     # Check MCP client connectivity | ||||||
|  |     mcp_client = MCPClient(config_instance) | ||||||
|  |     mcp_status = "✅ MCP Client Ready" if mcp_client.available else "⚠️  MCP Client Disabled" | ||||||
|  |      | ||||||
|  |     # Provider configuration check | ||||||
|  |     provider_status = "✅ Ready" | ||||||
|  |     if provider == "openai": | ||||||
|  |         api_key = config_instance.get_api_key("openai") | ||||||
|  |         if not api_key: | ||||||
|  |             provider_status = "⚠️  No API Key" | ||||||
|  |     elif provider == "ollama": | ||||||
|  |         ollama_host = config_instance.get("providers.ollama.host", "http://localhost:11434") | ||||||
|  |         provider_status = f"✅ {ollama_host}" | ||||||
|  |      | ||||||
|     console.print(Panel( |     console.print(Panel( | ||||||
|         f"[cyan]Starting ai.gpt MCP Server[/cyan]\n\n" |         f"[bold cyan]🚀 ai.gpt MCP Server[/bold cyan]\n\n" | ||||||
|         f"Host: {host}:{port}\n" |         f"[green]Server Configuration:[/green]\n" | ||||||
|         f"Provider: {provider}\n" |         f"🌐 Address: http://{host}:{port}\n" | ||||||
|         f"Model: {model}\n" |         f"📋 API Docs: http://{host}:{port}/docs\n" | ||||||
|         f"Data: {data_dir}", |         f"💾 Data Directory: {data_dir}\n\n" | ||||||
|         title="MCP Server", |         f"[green]AI Provider Configuration:[/green]\n" | ||||||
|         border_style="green" |         f"🤖 Provider: {provider} {provider_status}\n" | ||||||
|  |         f"🧩 Model: {model}\n\n" | ||||||
|  |         f"[green]MCP Tools Available ({mcp_tools} total):[/green]\n" | ||||||
|  |         f"{endpoint_summary}\n\n" | ||||||
|  |         f"[green]Integration Status:[/green]\n" | ||||||
|  |         f"{mcp_status}\n" | ||||||
|  |         f"🔗 Config: {config_instance.config_file}\n\n" | ||||||
|  |         f"[dim]Press Ctrl+C to stop server[/dim]", | ||||||
|  |         title="🔧 MCP Server Startup", | ||||||
|  |         border_style="green", | ||||||
|  |         expand=True | ||||||
|     )) |     )) | ||||||
|      |      | ||||||
|     # Store provider info in app state for later use |     # Store provider info in app state for later use | ||||||
|     app_instance.state.ai_provider = provider |     app_instance.state.ai_provider = provider | ||||||
|     app_instance.state.ai_model = model |     app_instance.state.ai_model = model | ||||||
|  |     app_instance.state.config = config_instance | ||||||
|      |      | ||||||
|     # Run server |     # Run server with better logging | ||||||
|     uvicorn.run(app_instance, host=host, port=port) |     try: | ||||||
|  |         uvicorn.run( | ||||||
|  |             app_instance,  | ||||||
|  |             host=host,  | ||||||
|  |             port=port, | ||||||
|  |             log_level="info", | ||||||
|  |             access_log=False  # Reduce noise | ||||||
|  |         ) | ||||||
|  |     except KeyboardInterrupt: | ||||||
|  |         console.print("\n[yellow]🛑 MCP Server stopped[/yellow]") | ||||||
|  |     except Exception as e: | ||||||
|  |         console.print(f"\n[red]❌ Server error: {e}[/red]") | ||||||
|  |  | ||||||
|  |  | ||||||
| @app.command() | @app.command() | ||||||
| @@ -869,7 +1135,8 @@ def config( | |||||||
|             console.print("[red]Error: key required for get action[/red]") |             console.print("[red]Error: key required for get action[/red]") | ||||||
|             return |             return | ||||||
|          |          | ||||||
|         val = config.get(key) |         config_instance = Config() | ||||||
|  |         val = config_instance.get(key) | ||||||
|         if val is None: |         if val is None: | ||||||
|             console.print(f"[yellow]Key '{key}' not found[/yellow]") |             console.print(f"[yellow]Key '{key}' not found[/yellow]") | ||||||
|         else: |         else: | ||||||
| @@ -880,13 +1147,14 @@ def config( | |||||||
|             console.print("[red]Error: key and value required for set action[/red]") |             console.print("[red]Error: key and value required for set action[/red]") | ||||||
|             return |             return | ||||||
|          |          | ||||||
|  |         config_instance = Config() | ||||||
|         # Special handling for sensitive keys |         # Special handling for sensitive keys | ||||||
|         if "password" in key or "api_key" in key: |         if "password" in key or "api_key" in key: | ||||||
|             console.print(f"[cyan]Setting {key}[/cyan] = [dim]***hidden***[/dim]") |             console.print(f"[cyan]Setting {key}[/cyan] = [dim]***hidden***[/dim]") | ||||||
|         else: |         else: | ||||||
|             console.print(f"[cyan]Setting {key}[/cyan] = [green]{value}[/green]") |             console.print(f"[cyan]Setting {key}[/cyan] = [green]{value}[/green]") | ||||||
|          |          | ||||||
|         config.set(key, value) |         config_instance.set(key, value) | ||||||
|         console.print("[green]✓ Configuration saved[/green]") |         console.print("[green]✓ Configuration saved[/green]") | ||||||
|      |      | ||||||
|     elif action == "delete": |     elif action == "delete": | ||||||
| @@ -894,7 +1162,8 @@ def config( | |||||||
|             console.print("[red]Error: key required for delete action[/red]") |             console.print("[red]Error: key required for delete action[/red]") | ||||||
|             return |             return | ||||||
|          |          | ||||||
|         if config.delete(key): |         config_instance = Config() | ||||||
|  |         if config_instance.delete(key): | ||||||
|             console.print(f"[green]✓ Deleted {key}[/green]") |             console.print(f"[green]✓ Deleted {key}[/green]") | ||||||
|         else: |         else: | ||||||
|             console.print(f"[yellow]Key '{key}' not found[/yellow]") |             console.print(f"[yellow]Key '{key}' not found[/yellow]") | ||||||
| @@ -986,7 +1255,9 @@ def conversation( | |||||||
|     model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"), |     model: Optional[str] = typer.Option(None, "--model", "-m", help="AI model to use"), | ||||||
|     provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)") |     provider: Optional[str] = typer.Option(None, "--provider", help="AI provider (ollama/openai)") | ||||||
| ): | ): | ||||||
|     """Simple continuous conversation mode""" |     """Simple continuous conversation mode with MCP support""" | ||||||
|  |     # Initialize MCP client | ||||||
|  |     mcp_client = MCPClient() | ||||||
|     persona = get_persona(data_dir) |     persona = get_persona(data_dir) | ||||||
|      |      | ||||||
|     # Get defaults from config if not provided |     # Get defaults from config if not provided | ||||||
| @@ -1001,35 +1272,49 @@ def conversation( | |||||||
|         else: |         else: | ||||||
|             model = "qwen3:latest"  # fallback |             model = "qwen3:latest"  # fallback | ||||||
|      |      | ||||||
|     # Create AI provider |     # Create AI provider with MCP client | ||||||
|     ai_provider = None |     ai_provider = None | ||||||
|     try: |     try: | ||||||
|         ai_provider = create_ai_provider(provider=provider, model=model) |         ai_provider = create_ai_provider(provider=provider, model=model, mcp_client=mcp_client) | ||||||
|         console.print(f"[dim]Using {provider} with model {model}[/dim]") |         console.print(f"[dim]Using {provider} with model {model}[/dim]") | ||||||
|     except Exception as e: |     except Exception as e: | ||||||
|         console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]") |         console.print(f"[yellow]Warning: Could not create AI provider: {e}[/yellow]") | ||||||
|      |      | ||||||
|  |     # MCP status | ||||||
|  |     server_info = mcp_client.get_server_info() | ||||||
|  |     if server_info["available"]: | ||||||
|  |         console.print(f"[green]✓ MCP Server connected: {server_info['display_name']}[/green]") | ||||||
|  |         console.print(f"[dim]  URL: {server_info['base_url']} | Endpoints: {server_info['endpoints']}[/dim]") | ||||||
|  |     else: | ||||||
|  |         console.print(f"[yellow]⚠ MCP Server unavailable (running in local mode)[/yellow]") | ||||||
|  |      | ||||||
|     # Welcome message |     # Welcome message | ||||||
|     console.print(f"[cyan]Conversation with AI started. Type 'exit' or 'quit' to end.[/cyan]\n") |     console.print(f"[cyan]Conversation with AI started. Type 'exit' or 'quit' to end.[/cyan]") | ||||||
|  |     if server_info["available"]: | ||||||
|  |         console.print(f"[dim]MCP commands: /memories, /search, /context, /relationship[/dim]\n") | ||||||
|  |     else: | ||||||
|  |         console.print() | ||||||
|      |      | ||||||
|     # History for conversation mode |     # History for conversation mode | ||||||
|     actual_data_dir = data_dir if data_dir else DEFAULT_DATA_DIR |     actual_data_dir = data_dir if data_dir else DEFAULT_DATA_DIR | ||||||
|     history_file = actual_data_dir / "conversation_history.txt" |     history_file = actual_data_dir / "conversation_history.txt" | ||||||
|     history = FileHistory(str(history_file)) |     history = FileHistory(str(history_file)) | ||||||
|      |      | ||||||
|     # Custom completer for slash commands and phrases |     # Custom completer for slash commands and phrases with MCP support | ||||||
|     class ConversationCompleter(Completer): |     class ConversationCompleter(Completer): | ||||||
|         def __init__(self): |         def __init__(self, mcp_available: bool = False): | ||||||
|             self.slash_commands = ['/status', '/help', '/clear', '/exit', '/quit'] |             self.basic_commands = ['/status', '/help', '/clear', '/exit', '/quit'] | ||||||
|  |             self.mcp_commands = ['/memories', '/search', '/context', '/relationship'] if mcp_available else [] | ||||||
|             self.phrases = ['こんにちは', '今日は', 'ありがとう', 'お疲れ様', |             self.phrases = ['こんにちは', '今日は', 'ありがとう', 'お疲れ様', | ||||||
|                            'どう思う?', 'どうですか?', '教えて', 'わかりました'] |                            'どう思う?', 'どうですか?', '教えて', 'わかりました'] | ||||||
|  |             self.all_commands = self.basic_commands + self.mcp_commands | ||||||
|          |          | ||||||
|         def get_completions(self, document, complete_event): |         def get_completions(self, document, complete_event): | ||||||
|             text = document.text_before_cursor |             text = document.text_before_cursor | ||||||
|              |              | ||||||
|             # If text starts with '/', complete slash commands |             # If text starts with '/', complete slash commands | ||||||
|             if text.startswith('/'): |             if text.startswith('/'): | ||||||
|                 for cmd in self.slash_commands: |                 for cmd in self.all_commands: | ||||||
|                     if cmd.startswith(text): |                     if cmd.startswith(text): | ||||||
|                         yield Completion(cmd, start_position=-len(text)) |                         yield Completion(cmd, start_position=-len(text)) | ||||||
|             # For other text, complete phrases |             # For other text, complete phrases | ||||||
| @@ -1038,7 +1323,7 @@ def conversation( | |||||||
|                     if phrase.startswith(text): |                     if phrase.startswith(text): | ||||||
|                         yield Completion(phrase, start_position=-len(text)) |                         yield Completion(phrase, start_position=-len(text)) | ||||||
|      |      | ||||||
|     completer = ConversationCompleter() |     completer = ConversationCompleter(mcp_client.available) | ||||||
|      |      | ||||||
|     while True: |     while True: | ||||||
|         try: |         try: | ||||||
| @@ -1076,6 +1361,12 @@ def conversation( | |||||||
|                 console.print("  /clear   - Clear screen") |                 console.print("  /clear   - Clear screen") | ||||||
|                 console.print("  /exit    - End conversation") |                 console.print("  /exit    - End conversation") | ||||||
|                 console.print("  /        - Show commands (same as /help)") |                 console.print("  /        - Show commands (same as /help)") | ||||||
|  |                 if mcp_client.available: | ||||||
|  |                     console.print(f"\n[cyan]MCP Commands:[/cyan]") | ||||||
|  |                     console.print("  /memories     - Show recent memories") | ||||||
|  |                     console.print("  /search <keywords> - Search memories") | ||||||
|  |                     console.print("  /context <query>   - Get contextual memories") | ||||||
|  |                     console.print("  /relationship      - Show relationship via MCP") | ||||||
|                 console.print("  <message> - Chat with AI\n") |                 console.print("  <message> - Chat with AI\n") | ||||||
|                 continue |                 continue | ||||||
|                  |                  | ||||||
| @@ -1083,7 +1374,73 @@ def conversation( | |||||||
|                 console.clear() |                 console.clear() | ||||||
|                 continue |                 continue | ||||||
|              |              | ||||||
|             # Process interaction |             # MCP Commands | ||||||
|  |             elif user_input.lower() == '/memories' and mcp_client.available: | ||||||
|  |                 memories = asyncio.run(mcp_client.get_memories(limit=5)) | ||||||
|  |                 if memories: | ||||||
|  |                     console.print(f"\n[cyan]Recent Memories (via MCP):[/cyan]") | ||||||
|  |                     for i, mem in enumerate(memories[:5], 1): | ||||||
|  |                         console.print(f"  {i}. [{mem.get('level', 'unknown')}] {mem.get('content', '')[:100]}...") | ||||||
|  |                     console.print("") | ||||||
|  |                 else: | ||||||
|  |                     console.print("[yellow]No memories found[/yellow]") | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             elif user_input.lower().startswith('/search ') and mcp_client.available: | ||||||
|  |                 query = user_input[8:].strip() | ||||||
|  |                 if query: | ||||||
|  |                     keywords = query.split() | ||||||
|  |                     results = asyncio.run(mcp_client.search_memories(keywords)) | ||||||
|  |                     if results: | ||||||
|  |                         console.print(f"\n[cyan]Memory Search Results for '{query}' (via MCP):[/cyan]") | ||||||
|  |                         for i, mem in enumerate(results[:5], 1): | ||||||
|  |                             console.print(f"  {i}. {mem.get('content', '')[:100]}...") | ||||||
|  |                         console.print("") | ||||||
|  |                     else: | ||||||
|  |                         console.print(f"[yellow]No memories found for '{query}'[/yellow]") | ||||||
|  |                 else: | ||||||
|  |                     console.print("[red]Usage: /search <keywords>[/red]") | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             elif user_input.lower().startswith('/context ') and mcp_client.available: | ||||||
|  |                 query = user_input[9:].strip() | ||||||
|  |                 if query: | ||||||
|  |                     results = asyncio.run(mcp_client.get_contextual_memories(query, limit=5)) | ||||||
|  |                     if results: | ||||||
|  |                         console.print(f"\n[cyan]Contextual Memories for '{query}' (via MCP):[/cyan]") | ||||||
|  |                         for i, mem in enumerate(results[:5], 1): | ||||||
|  |                             console.print(f"  {i}. {mem.get('content', '')[:100]}...") | ||||||
|  |                         console.print("") | ||||||
|  |                     else: | ||||||
|  |                         console.print(f"[yellow]No contextual memories found for '{query}'[/yellow]") | ||||||
|  |                 else: | ||||||
|  |                     console.print("[red]Usage: /context <query>[/red]") | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             elif user_input.lower() == '/relationship' and mcp_client.available: | ||||||
|  |                 rel_data = asyncio.run(mcp_client.get_relationship(user_id)) | ||||||
|  |                 if rel_data: | ||||||
|  |                     console.print(f"\n[cyan]Relationship (via MCP):[/cyan]") | ||||||
|  |                     console.print(f"Status: {rel_data.get('status', 'unknown')}") | ||||||
|  |                     console.print(f"Score: {rel_data.get('score', 0):.2f}") | ||||||
|  |                     console.print(f"Interactions: {rel_data.get('total_interactions', 0)}") | ||||||
|  |                     console.print("") | ||||||
|  |                 else: | ||||||
|  |                     console.print("[yellow]No relationship data found[/yellow]") | ||||||
|  |                 continue | ||||||
|  |              | ||||||
|  |             # Process interaction - try MCP first, fallback to local | ||||||
|  |             if mcp_client.available: | ||||||
|  |                 try: | ||||||
|  |                     mcp_result = asyncio.run(mcp_client.process_interaction(user_id, user_input)) | ||||||
|  |                     if mcp_result and 'response' in mcp_result: | ||||||
|  |                         response = mcp_result['response'] | ||||||
|  |                         console.print(f"AI> {response} [dim](via MCP)[/dim]\n") | ||||||
|  |                         continue | ||||||
|  |                 except Exception as e: | ||||||
|  |                     console.print(f"[yellow]MCP failed, using local: {e}[/yellow]") | ||||||
|  |              | ||||||
|  |             # Fallback to local processing | ||||||
|             response, relationship_delta = persona.process_interaction(user_id, user_input, ai_provider) |             response, relationship_delta = persona.process_interaction(user_id, user_input, ai_provider) | ||||||
|              |              | ||||||
|             # Simple AI response display (no Panel, no extra info) |             # Simple AI response display (no Panel, no extra info) | ||||||
|   | |||||||
| @@ -45,7 +45,44 @@ class Config: | |||||||
|                     }, |                     }, | ||||||
|                     "ollama": { |                     "ollama": { | ||||||
|                         "host": "http://localhost:11434", |                         "host": "http://localhost:11434", | ||||||
|                         "default_model": "qwen2.5" |                         "default_model": "qwen3:latest" | ||||||
|  |                     } | ||||||
|  |                 }, | ||||||
|  |                 "mcp": { | ||||||
|  |                     "enabled": True, | ||||||
|  |                     "auto_detect": True, | ||||||
|  |                     "servers": { | ||||||
|  |                         "ai_gpt": { | ||||||
|  |                             "name": "ai.gpt MCP Server", | ||||||
|  |                             "base_url": "http://localhost:8001", | ||||||
|  |                             "endpoints": { | ||||||
|  |                                 "get_memories": "/get_memories", | ||||||
|  |                                 "search_memories": "/search_memories", | ||||||
|  |                                 "get_contextual_memories": "/get_contextual_memories", | ||||||
|  |                                 "process_interaction": "/process_interaction", | ||||||
|  |                                 "get_relationship": "/get_relationship", | ||||||
|  |                                 "get_all_relationships": "/get_all_relationships", | ||||||
|  |                                 "get_persona_state": "/get_persona_state", | ||||||
|  |                                 "get_fortune": "/get_fortune", | ||||||
|  |                                 "run_maintenance": "/run_maintenance", | ||||||
|  |                                 "execute_command": "/execute_command", | ||||||
|  |                                 "analyze_file": "/analyze_file", | ||||||
|  |                                 "remote_shell": "/remote_shell", | ||||||
|  |                                 "ai_bot_status": "/ai_bot_status" | ||||||
|  |                             }, | ||||||
|  |                             "timeout": 10.0 | ||||||
|  |                         }, | ||||||
|  |                         "ai_card": { | ||||||
|  |                             "name": "ai.card MCP Server", | ||||||
|  |                             "base_url": "http://localhost:8000", | ||||||
|  |                             "endpoints": { | ||||||
|  |                                 "health": "/health", | ||||||
|  |                                 "get_user_cards": "/api/cards/user", | ||||||
|  |                                 "gacha": "/api/gacha", | ||||||
|  |                                 "sync_atproto": "/api/sync" | ||||||
|  |                             }, | ||||||
|  |                             "timeout": 5.0 | ||||||
|  |                         } | ||||||
|                     } |                     } | ||||||
|                 }, |                 }, | ||||||
|                 "atproto": { |                 "atproto": { | ||||||
|   | |||||||
| @@ -160,6 +160,11 @@ AI:""" | |||||||
|                  |                  | ||||||
|                 # Generate response using AI with full context |                 # Generate response using AI with full context | ||||||
|                 try: |                 try: | ||||||
|  |                     # Check if AI provider supports MCP | ||||||
|  |                     if hasattr(ai_provider, 'chat_with_mcp'): | ||||||
|  |                         import asyncio | ||||||
|  |                         response = asyncio.run(ai_provider.chat_with_mcp(context_prompt, max_tokens=2000, user_id=user_id)) | ||||||
|  |                     else: | ||||||
|                         response = ai_provider.chat(context_prompt, max_tokens=2000) |                         response = ai_provider.chat(context_prompt, max_tokens=2000) | ||||||
|                      |                      | ||||||
|                     # Clean up response if it includes the prompt echo |                     # Clean up response if it includes the prompt echo | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user