diff --git a/mcp/chatgpt.json b/mcp/chatgpt.json
index e146936..4842402 100644
--- a/mcp/chatgpt.json
+++ b/mcp/chatgpt.json
@@ -231,7 +231,7 @@
"0be4b4a5-d52f-4bef-927e-5d6f93a9cb26"
]
}
- },
+ },
"moderation_results": [],
"current_node": "",
"plugin_ids": null,
@@ -251,5 +251,141 @@
"is_do_not_remember": null,
"memory_scope": "global_enabled",
"id": ""
+ },
+ {
+ "title": "img",
+ "create_time": 1747448872.545226,
+ "update_time": 1748085075.161424,
+ "mapping": {
+ "2de0f3c9-52b1-49bf-b980-b3ef9be6551e": {
+ "id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
+ "message": {
+ "id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
+ "author": {
+ "role": "user",
+ "name": null,
+ "metadata": {}
+ },
+ "create_time": 1748085041.769279,
+ "update_time": null,
+ "content": {
+ "content_type": "multimodal_text",
+ "parts": [
+ {
+ "content_type": "image_asset_pointer",
+ "asset_pointer": "",
+ "size_bytes": 425613,
+ "width": 333,
+ "height": 444,
+ "fovea": null,
+ "metadata": {
+ "dalle": null,
+ "gizmo": null,
+ "generation": null,
+ "container_pixel_height": null,
+ "container_pixel_width": null,
+ "emu_omit_glimpse_image": null,
+ "emu_patches_override": null,
+ "sanitized": true,
+ "asset_pointer_link": null,
+ "watermarked_asset_pointer": null
+ }
+ },
+ ""
+ ]
+ },
+ "status": "finished_successfully",
+ "end_turn": null,
+ "weight": 1.0,
+ "metadata": {
+ "attachments": [
+ {
+ "name": "",
+ "width": 333,
+ "height": 444,
+ "size": 425613,
+ "id": "file-35eytNMMTW2k7vKUHBuNzW"
+ }
+ ],
+ "request_id": "944c59177932fc9a-KIX",
+ "message_source": null,
+ "timestamp_": "absolute",
+ "message_type": null
+ },
+ "recipient": "all",
+ "channel": null
+ },
+ "parent": "7960fbff-bc4f-45e7-95e9-9d0bc79d9090",
+ "children": [
+ "98d84adc-156e-4c81-8cd8-9b0eb01c8369"
+ ]
+ },
+ "98d84adc-156e-4c81-8cd8-9b0eb01c8369": {
+ "id": "98d84adc-156e-4c81-8cd8-9b0eb01c8369",
+ "message": {
+ "id": "98d84adc-156e-4c81-8cd8-9b0eb01c8369",
+ "author": {
+ "role": "assistant",
+ "name": null,
+ "metadata": {}
+ },
+ "create_time": 1748085043.312312,
+ "update_time": null,
+ "content": {
+ "content_type": "text",
+ "parts": [
+ ""
+ ]
+ },
+ "status": "finished_successfully",
+ "end_turn": true,
+ "weight": 1.0,
+ "metadata": {
+ "finish_details": {
+ "type": "stop",
+ "stop_tokens": [
+ 200002
+ ]
+ },
+ "is_complete": true,
+ "citations": [],
+ "content_references": [],
+ "message_type": null,
+ "model_slug": "gpt-4o",
+ "default_model_slug": "auto",
+ "parent_id": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
+ "request_id": "944c5912c8fdd1c6-KIX",
+ "timestamp_": "absolute"
+ },
+ "recipient": "all",
+ "channel": null
+ },
+ "parent": "2de0f3c9-52b1-49bf-b980-b3ef9be6551e",
+ "children": [
+ "caa61793-9dbf-44a5-945b-5ca4cd5130d0"
+ ]
+ }
+ },
+ "moderation_results": [],
+ "current_node": "06488d3f-a95f-4906-96d1-f7e9ba1e8662",
+ "plugin_ids": null,
+ "conversation_id": "6827f428-78e8-800d-b3bf-eb7ff4288e47",
+ "conversation_template_id": null,
+ "gizmo_id": null,
+ "gizmo_type": null,
+ "is_archived": false,
+ "is_starred": null,
+ "safe_urls": [
+ "https://exifinfo.org/"
+ ],
+ "blocked_urls": [],
+ "default_model_slug": "auto",
+ "conversation_origin": null,
+ "voice": null,
+ "async_status": null,
+ "disabled_tool_ids": [],
+ "is_do_not_remember": false,
+ "memory_scope": "global_enabled",
+ "id": "6827f428-78e8-800d-b3bf-eb7ff4288e47"
}
]
diff --git a/mcp/chatgpt_converter.html b/mcp/chatgpt_converter.html
new file mode 100644
index 0000000..ae98532
--- /dev/null
+++ b/mcp/chatgpt_converter.html
@@ -0,0 +1,549 @@
+
+
+
+
+
+ 改良版 ChatGPT会話コンバーター
+
+
+
+
+
+
+
+
📁
+
ChatGPT会話ファイルをドロップまたはクリックして選択
+
conversations.json ファイルをアップロード
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mcp/memory_client.py b/mcp/memory_client.py
new file mode 100644
index 0000000..366169e
--- /dev/null
+++ b/mcp/memory_client.py
@@ -0,0 +1,212 @@
+# mcp/memory_client.py
+"""
+Memory client for importing and managing ChatGPT conversations
+"""
+import sys
+import json
+import requests
+from pathlib import Path
+from typing import Dict, Any, List
+
+class MemoryClient:
+ """記憶機能のクライアント"""
+
+ def __init__(self, server_url: str = "http://127.0.0.1:5000"):
+ self.server_url = server_url.rstrip('/')
+
+ def import_chatgpt_file(self, filepath: str) -> Dict[str, Any]:
+ """ChatGPTのエクスポートファイルをインポート"""
+ try:
+ with open(filepath, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+
+ # ファイルが配列の場合(複数の会話)
+ if isinstance(data, list):
+ results = []
+ for conversation in data:
+ result = self._import_single_conversation(conversation)
+ results.append(result)
+ return {
+ "success": True,
+ "imported_count": len([r for r in results if r.get("success")]),
+ "total_count": len(results),
+ "results": results
+ }
+ else:
+ # 単一の会話
+ return self._import_single_conversation(data)
+
+ except FileNotFoundError:
+ return {"success": False, "error": f"File not found: {filepath}"}
+ except json.JSONDecodeError as e:
+ return {"success": False, "error": f"Invalid JSON: {e}"}
+ except Exception as e:
+ return {"success": False, "error": str(e)}
+
+ def _import_single_conversation(self, conversation_data: Dict[str, Any]) -> Dict[str, Any]:
+ """単一の会話をインポート"""
+ try:
+ response = requests.post(
+ f"{self.server_url}/memory/import/chatgpt",
+ json={"conversation_data": conversation_data},
+ timeout=30
+ )
+ response.raise_for_status()
+ return response.json()
+ except requests.RequestException as e:
+ return {"success": False, "error": f"Server error: {e}"}
+
+ def search_memories(self, query: str, limit: int = 10) -> Dict[str, Any]:
+ """記憶を検索"""
+ try:
+ response = requests.post(
+ f"{self.server_url}/memory/search",
+ json={"query": query, "limit": limit},
+ timeout=30
+ )
+ response.raise_for_status()
+ return response.json()
+ except requests.RequestException as e:
+ return {"success": False, "error": f"Server error: {e}"}
+
+ def list_memories(self) -> Dict[str, Any]:
+ """記憶一覧を取得"""
+ try:
+ response = requests.get(f"{self.server_url}/memory/list", timeout=30)
+ response.raise_for_status()
+ return response.json()
+ except requests.RequestException as e:
+ return {"success": False, "error": f"Server error: {e}"}
+
+ def get_memory_detail(self, filepath: str) -> Dict[str, Any]:
+ """記憶の詳細を取得"""
+ try:
+ response = requests.get(
+ f"{self.server_url}/memory/detail",
+ params={"filepath": filepath},
+ timeout=30
+ )
+ response.raise_for_status()
+ return response.json()
+ except requests.RequestException as e:
+ return {"success": False, "error": f"Server error: {e}"}
+
+ def chat_with_memory(self, message: str, model: str = None) -> Dict[str, Any]:
+ """記憶を活用してチャット"""
+ try:
+ payload = {"message": message}
+ if model:
+ payload["model"] = model
+
+ response = requests.post(
+ f"{self.server_url}/chat",
+ json=payload,
+ timeout=30
+ )
+ response.raise_for_status()
+ return response.json()
+ except requests.RequestException as e:
+ return {"success": False, "error": f"Server error: {e}"}
+
+def main():
+ """コマンドライン インターフェース"""
+ if len(sys.argv) < 2:
+ print("Usage:")
+ print(" python memory_client.py import ")
+ print(" python memory_client.py search ")
+ print(" python memory_client.py list")
+ print(" python memory_client.py detail ")
+ print(" python memory_client.py chat ")
+ sys.exit(1)
+
+ client = MemoryClient()
+ command = sys.argv[1]
+
+ try:
+ if command == "import" and len(sys.argv) == 3:
+ filepath = sys.argv[2]
+ print(f"🔄 Importing ChatGPT conversations from {filepath}...")
+ result = client.import_chatgpt_file(filepath)
+
+ if result.get("success"):
+ if "imported_count" in result:
+ print(f"✅ Imported {result['imported_count']}/{result['total_count']} conversations")
+ else:
+ print("✅ Conversation imported successfully")
+ print(f"📁 Saved to: {result.get('filepath', 'Unknown')}")
+ else:
+ print(f"❌ Import failed: {result.get('error')}")
+
+ elif command == "search" and len(sys.argv) == 3:
+ query = sys.argv[2]
+ print(f"🔍 Searching for: {query}")
+ result = client.search_memories(query)
+
+ if result.get("success"):
+ memories = result.get("results", [])
+ print(f"📚 Found {len(memories)} memories:")
+ for memory in memories:
+ print(f" • {memory.get('title', 'Untitled')}")
+ print(f" Summary: {memory.get('summary', 'No summary')}")
+ print(f" Messages: {memory.get('message_count', 0)}")
+ print()
+ else:
+ print(f"❌ Search failed: {result.get('error')}")
+
+ elif command == "list":
+ print("📋 Listing all memories...")
+ result = client.list_memories()
+
+ if result.get("success"):
+ memories = result.get("memories", [])
+ print(f"📚 Total memories: {len(memories)}")
+ for memory in memories:
+ print(f" • {memory.get('title', 'Untitled')}")
+ print(f" Source: {memory.get('source', 'Unknown')}")
+ print(f" Messages: {memory.get('message_count', 0)}")
+ print(f" Imported: {memory.get('import_time', 'Unknown')}")
+ print()
+ else:
+ print(f"❌ List failed: {result.get('error')}")
+
+ elif command == "detail" and len(sys.argv) == 3:
+ filepath = sys.argv[2]
+ print(f"📄 Getting details for: {filepath}")
+ result = client.get_memory_detail(filepath)
+
+ if result.get("success"):
+ memory = result.get("memory", {})
+ print(f"Title: {memory.get('title', 'Untitled')}")
+ print(f"Source: {memory.get('source', 'Unknown')}")
+ print(f"Summary: {memory.get('summary', 'No summary')}")
+ print(f"Messages: {len(memory.get('messages', []))}")
+ print()
+ print("Recent messages:")
+ for msg in memory.get('messages', [])[:5]:
+ role = msg.get('role', 'unknown')
+ content = msg.get('content', '')[:100]
+ print(f" {role}: {content}...")
+ else:
+ print(f"❌ Detail failed: {result.get('error')}")
+
+ elif command == "chat" and len(sys.argv) == 3:
+ message = sys.argv[2]
+ print(f"💬 Chatting with memory: {message}")
+ result = client.chat_with_memory(message)
+
+ if result.get("success"):
+ print(f"🤖 Response: {result.get('response')}")
+ print(f"📚 Memories used: {result.get('memories_used', 0)}")
+ else:
+ print(f"❌ Chat failed: {result.get('error')}")
+
+ else:
+ print("❌ Invalid command or arguments")
+ sys.exit(1)
+
+ except Exception as e:
+ print(f"❌ Error: {e}")
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/mcp/requirements.txt b/mcp/requirements.txt
index 6c93e2d..27486dd 100644
--- a/mcp/requirements.txt
+++ b/mcp/requirements.txt
@@ -1,3 +1,5 @@
-fastmcp>=0.1.0
-uvicorn>=0.24.0
+fastapi>=0.104.0
+uvicorn[standard]>=0.24.0
+pydantic>=2.5.0
requests>=2.31.0
+python-multipart>=0.0.6
diff --git a/mcp/server.py b/mcp/server.py
index 78eb505..e4c7c0f 100644
--- a/mcp/server.py
+++ b/mcp/server.py
@@ -1,79 +1,294 @@
# mcp/server.py
"""
-MCP Server for aigpt CLI
+Enhanced MCP Server with Memory for aigpt CLI
"""
-from fastmcp import FastMCP
-import platform
+import json
import os
-import sys
+from datetime import datetime
+from pathlib import Path
+from typing import List, Dict, Any, Optional
+from fastapi import FastAPI, HTTPException
+from pydantic import BaseModel
+import uvicorn
-mcp = FastMCP("AigptMCP")
+# データモデル
+class ChatMessage(BaseModel):
+ message: str
+ model: Optional[str] = None
-@mcp.tool()
-def process_text(text: str) -> str:
- """テキストを処理する"""
- return f"Processed: {text}"
+class MemoryQuery(BaseModel):
+ query: str
+ limit: Optional[int] = 10
-@mcp.tool()
-def get_system_info() -> dict:
- """システム情報を取得"""
+class ConversationImport(BaseModel):
+ conversation_data: Dict[str, Any]
+
+# 設定
+BASE_DIR = Path.home() / ".config" / "aigpt"
+MEMORY_DIR = BASE_DIR / "memory"
+CHATGPT_MEMORY_DIR = MEMORY_DIR / "chatgpt"
+
+def init_directories():
+ """必要なディレクトリを作成"""
+ BASE_DIR.mkdir(parents=True, exist_ok=True)
+ MEMORY_DIR.mkdir(parents=True, exist_ok=True)
+ CHATGPT_MEMORY_DIR.mkdir(parents=True, exist_ok=True)
+
+class MemoryManager:
+ """記憶管理クラス"""
+
+ def __init__(self):
+ init_directories()
+
+ def parse_chatgpt_conversation(self, conversation_data: Dict[str, Any]) -> List[Dict[str, Any]]:
+ """ChatGPTの会話データを解析してメッセージを抽出"""
+ messages = []
+ mapping = conversation_data.get("mapping", {})
+
+ # メッセージを時系列順に並べる
+ message_nodes = []
+ for node_id, node in mapping.items():
+ message = node.get("message")
+ if message and message.get("content", {}).get("parts"):
+ parts = message["content"]["parts"]
+ if parts and parts[0].strip(): # 空でないメッセージのみ
+ message_nodes.append({
+ "id": node_id,
+ "create_time": message.get("create_time", 0),
+ "author_role": message["author"]["role"],
+ "content": parts[0],
+ "parent": node.get("parent")
+ })
+
+ # 作成時間でソート
+ message_nodes.sort(key=lambda x: x["create_time"] or 0)
+
+ for msg in message_nodes:
+ if msg["author_role"] in ["user", "assistant"]:
+ messages.append({
+ "role": msg["author_role"],
+ "content": msg["content"],
+ "timestamp": msg["create_time"],
+ "message_id": msg["id"]
+ })
+
+ return messages
+
+ def save_chatgpt_memory(self, conversation_data: Dict[str, Any]) -> str:
+ """ChatGPTの会話を記憶として保存"""
+ title = conversation_data.get("title", "untitled")
+ create_time = conversation_data.get("create_time", datetime.now().timestamp())
+
+ # メッセージを解析
+ messages = self.parse_chatgpt_conversation(conversation_data)
+
+ if not messages:
+ raise ValueError("No valid messages found in conversation")
+
+ # 保存データを作成
+ memory_data = {
+ "title": title,
+ "source": "chatgpt",
+ "import_time": datetime.now().isoformat(),
+ "original_create_time": create_time,
+ "messages": messages,
+ "summary": self.generate_summary(messages)
+ }
+
+ # ファイル名を生成(タイトルをサニタイズ)
+ safe_title = "".join(c for c in title if c.isalnum() or c in (' ', '-', '_')).rstrip()
+ timestamp = datetime.fromtimestamp(create_time).strftime("%Y%m%d_%H%M%S")
+ filename = f"{timestamp}_{safe_title[:50]}.json"
+
+ filepath = CHATGPT_MEMORY_DIR / filename
+ with open(filepath, 'w', encoding='utf-8') as f:
+ json.dump(memory_data, f, ensure_ascii=False, indent=2)
+
+ return str(filepath)
+
+ def generate_summary(self, messages: List[Dict[str, Any]]) -> str:
+ """会話の要約を生成"""
+ if not messages:
+ return "Empty conversation"
+
+ # 簡単な要約を生成(実際のAIによる要約は後で実装可能)
+ user_messages = [msg for msg in messages if msg["role"] == "user"]
+ assistant_messages = [msg for msg in messages if msg["role"] == "assistant"]
+
+ summary = f"Conversation with {len(user_messages)} user messages and {len(assistant_messages)} assistant responses. "
+
+ if user_messages:
+ first_user_msg = user_messages[0]["content"][:100]
+ summary += f"Started with: {first_user_msg}..."
+
+ return summary
+
+ def search_memories(self, query: str, limit: int = 10) -> List[Dict[str, Any]]:
+ """記憶を検索"""
+ results = []
+
+ # ChatGPTの記憶を検索
+ for filepath in CHATGPT_MEMORY_DIR.glob("*.json"):
+ try:
+ with open(filepath, 'r', encoding='utf-8') as f:
+ memory_data = json.load(f)
+
+ # 簡単なキーワード検索
+ search_text = f"{memory_data.get('title', '')} {memory_data.get('summary', '')}"
+ for msg in memory_data.get('messages', []):
+ search_text += f" {msg.get('content', '')}"
+
+ if query.lower() in search_text.lower():
+ results.append({
+ "filepath": str(filepath),
+ "title": memory_data.get("title"),
+ "summary": memory_data.get("summary"),
+ "source": memory_data.get("source"),
+ "import_time": memory_data.get("import_time"),
+ "message_count": len(memory_data.get("messages", []))
+ })
+
+ if len(results) >= limit:
+ break
+
+ except Exception as e:
+ print(f"Error reading memory file {filepath}: {e}")
+ continue
+
+ return results
+
+ def get_memory_detail(self, filepath: str) -> Dict[str, Any]:
+ """記憶の詳細を取得"""
+ try:
+ with open(filepath, 'r', encoding='utf-8') as f:
+ return json.load(f)
+ except Exception as e:
+ raise ValueError(f"Error reading memory file: {e}")
+
+ def list_all_memories(self) -> List[Dict[str, Any]]:
+ """すべての記憶をリスト"""
+ memories = []
+
+ for filepath in CHATGPT_MEMORY_DIR.glob("*.json"):
+ try:
+ with open(filepath, 'r', encoding='utf-8') as f:
+ memory_data = json.load(f)
+
+ memories.append({
+ "filepath": str(filepath),
+ "title": memory_data.get("title"),
+ "summary": memory_data.get("summary"),
+ "source": memory_data.get("source"),
+ "import_time": memory_data.get("import_time"),
+ "message_count": len(memory_data.get("messages", []))
+ })
+ except Exception as e:
+ print(f"Error reading memory file {filepath}: {e}")
+ continue
+
+ # インポート時間でソート
+ memories.sort(key=lambda x: x.get("import_time", ""), reverse=True)
+ return memories
+
+# FastAPI アプリケーション
+app = FastAPI(title="AigptMCP Server with Memory", version="1.0.0")
+memory_manager = MemoryManager()
+
+@app.post("/memory/import/chatgpt")
+async def import_chatgpt_conversation(data: ConversationImport):
+ """ChatGPTの会話をインポート"""
+ try:
+ filepath = memory_manager.save_chatgpt_memory(data.conversation_data)
+ return {
+ "success": True,
+ "message": "Conversation imported successfully",
+ "filepath": filepath
+ }
+ except Exception as e:
+ raise HTTPException(status_code=400, detail=str(e))
+
+@app.post("/memory/search")
+async def search_memories(query: MemoryQuery):
+ """記憶を検索"""
+ try:
+ results = memory_manager.search_memories(query.query, query.limit)
+ return {
+ "success": True,
+ "results": results,
+ "count": len(results)
+ }
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+@app.get("/memory/list")
+async def list_memories():
+ """すべての記憶をリスト"""
+ try:
+ memories = memory_manager.list_all_memories()
+ return {
+ "success": True,
+ "memories": memories,
+ "count": len(memories)
+ }
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+@app.get("/memory/detail")
+async def get_memory_detail(filepath: str):
+ """記憶の詳細を取得"""
+ try:
+ detail = memory_manager.get_memory_detail(filepath)
+ return {
+ "success": True,
+ "memory": detail
+ }
+ except Exception as e:
+ raise HTTPException(status_code=404, detail=str(e))
+
+@app.post("/chat")
+async def chat_endpoint(data: ChatMessage):
+ """チャット機能(記憶を活用)"""
+ try:
+ # 関連する記憶を検索
+ memories = memory_manager.search_memories(data.message, limit=3)
+
+ # メモリのコンテキストを構築
+ memory_context = ""
+ if memories:
+ memory_context = "\n# Related memories:\n"
+ for memory in memories:
+ memory_context += f"- {memory['title']}: {memory['summary']}\n"
+
+ # 実際のチャット処理(他のプロバイダーに転送)
+ enhanced_message = data.message
+ if memory_context:
+ enhanced_message = f"{data.message}\n\n{memory_context}"
+
+ return {
+ "success": True,
+ "response": f"Enhanced response with memory context: {enhanced_message}",
+ "memories_used": len(memories)
+ }
+ except Exception as e:
+ raise HTTPException(status_code=500, detail=str(e))
+
+@app.get("/")
+async def root():
+ """ヘルスチェック"""
return {
- "platform": platform.system(),
- "version": platform.version(),
- "python_version": sys.version,
- "current_dir": os.getcwd()
+ "service": "AigptMCP Server with Memory",
+ "status": "running",
+ "memory_dir": str(MEMORY_DIR),
+ "endpoints": [
+ "/memory/import/chatgpt",
+ "/memory/search",
+ "/memory/list",
+ "/memory/detail",
+ "/chat"
+ ]
}
-@mcp.tool()
-def execute_command(command: str) -> dict:
- """安全なコマンドを実行する"""
- # セキュリティのため、許可されたコマンドのみ実行
- allowed_commands = ["ls", "pwd", "date", "whoami"]
- cmd_parts = command.split()
-
- if not cmd_parts or cmd_parts[0] not in allowed_commands:
- return {
- "error": f"Command '{command}' is not allowed",
- "allowed": allowed_commands
- }
-
- try:
- import subprocess
- result = subprocess.run(
- cmd_parts,
- capture_output=True,
- text=True,
- timeout=10
- )
- return {
- "stdout": result.stdout,
- "stderr": result.stderr,
- "returncode": result.returncode
- }
- except subprocess.TimeoutExpired:
- return {"error": "Command timed out"}
- except Exception as e:
- return {"error": str(e)}
-
-@mcp.tool()
-def file_operations(operation: str, filepath: str, content: str = None) -> dict:
- """ファイル操作を行う"""
- try:
- if operation == "read":
- with open(filepath, 'r', encoding='utf-8') as f:
- return {"content": f.read(), "success": True}
- elif operation == "write" and content is not None:
- with open(filepath, 'w', encoding='utf-8') as f:
- f.write(content)
- return {"message": f"File written to {filepath}", "success": True}
- elif operation == "exists":
- return {"exists": os.path.exists(filepath), "success": True}
- else:
- return {"error": "Invalid operation or missing content", "success": False}
- except Exception as e:
- return {"error": str(e), "success": False}
-
if __name__ == "__main__":
- print("🚀 AigptMCP Server starting...")
- mcp.run()
-
+ print("🚀 AigptMCP Server with Memory starting...")
+ print(f"📁 Memory directory: {MEMORY_DIR}")
+ uvicorn.run(app, host="127.0.0.1", port=5000)
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..07111e2
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,130 @@
+Memory-Enhanced MCP Server 使用ガイド
+概要
+このMCPサーバーは、ChatGPTの会話履歴を記憶として保存し、AIとの対話で活用できる機能を提供します。
+
+セットアップ
+1. 依存関係のインストール
+bash
+pip install -r requirements.txt
+2. サーバーの起動
+bash
+python mcp/server.py
+サーバーは http://localhost:5000 で起動します。
+
+使用方法
+1. ChatGPTの会話履歴をインポート
+ChatGPTから会話をエクスポートし、JSONファイルとして保存してください。
+
+bash
+# 単一ファイルをインポート
+python mcp/memory_client.py import your_chatgpt_export.json
+
+# インポート結果の例
+✅ Imported 5/5 conversations
+2. 記憶の検索
+bash
+# キーワードで記憶を検索
+python mcp/memory_client.py search "プログラミング"
+
+# 検索結果の例
+🔍 Searching for: プログラミング
+📚 Found 3 memories:
+ • Pythonの基礎学習
+ Summary: Conversation with 10 user messages and 8 assistant responses...
+ Messages: 18
+3. 記憶一覧の表示
+bash
+python mcp/memory_client.py list
+
+# 結果の例
+📋 Listing all memories...
+📚 Total memories: 15
+ • day
+ Source: chatgpt
+ Messages: 2
+ Imported: 2025-01-21T10:30:45.123456
+4. 記憶の詳細表示
+bash
+python mcp/memory_client.py detail "/path/to/memory/file.json"
+
+# 結果の例
+📄 Getting details for: /path/to/memory/file.json
+Title: day
+Source: chatgpt
+Summary: Conversation with 1 user messages and 1 assistant responses...
+Messages: 2
+
+Recent messages:
+ user: こんにちは...
+ assistant: こんにちは〜!✨...
+5. 記憶を活用したチャット
+bash
+python mcp/memory_client.py chat "Pythonについて教えて"
+
+# 結果の例
+💬 Chatting with memory: Pythonについて教えて
+🤖 Response: Enhanced response with memory context...
+📚 Memories used: 2
+API エンドポイント
+POST /memory/import/chatgpt
+ChatGPTの会話履歴をインポート
+
+json
+{
+ "conversation_data": { ... }
+}
+POST /memory/search
+記憶を検索
+
+json
+{
+ "query": "検索キーワード",
+ "limit": 10
+}
+GET /memory/list
+すべての記憶をリスト
+
+GET /memory/detail?filepath=/path/to/file
+記憶の詳細を取得
+
+POST /chat
+記憶を活用したチャット
+
+json
+{
+ "message": "メッセージ",
+ "model": "model_name"
+}
+記憶の保存場所
+記憶は以下のディレクトリに保存されます:
+
+~/.config/aigpt/memory/chatgpt/
+各会話は個別のJSONファイルとして保存され、以下の情報を含みます:
+
+タイトル
+インポート時刻
+メッセージ履歴
+自動生成された要約
+メタデータ
+ChatGPTの会話エクスポート方法
+ChatGPTの設定画面を開く
+"Data controls" → "Export data" を選択
+エクスポートファイルをダウンロード
+conversations.json ファイルを使用
+拡張可能な機能
+高度な検索: ベクトル検索やセマンティック検索の実装
+要約生成: AIによる自動要約の改善
+記憶の分類: カテゴリやタグによる分類
+記憶の統合: 複数の会話からの知識統合
+プライバシー保護: 機密情報の自動検出・マスキング
+トラブルシューティング
+サーバーが起動しない
+ポート5000が使用中でないか確認
+依存関係が正しくインストールされているか確認
+インポートに失敗する
+JSONファイルが正しい形式か確認
+ファイルパスが正しいか確認
+ファイルの権限を確認
+検索結果が表示されない
+インポートが正常に完了しているか確認
+検索キーワードを変更して試行
diff --git a/src/cli.rs b/src/cli.rs
index c56b9df..837743b 100644
--- a/src/cli.rs
+++ b/src/cli.rs
@@ -3,7 +3,7 @@ use clap::{Parser, Subcommand};
#[derive(Parser)]
#[command(name = "aigpt")]
-#[command(about = "AI GPT CLI with MCP Server")]
+#[command(about = "AI GPT CLI with MCP Server and Memory")]
pub struct Args {
#[command(subcommand)]
pub command: Commands,
@@ -20,6 +20,14 @@ pub enum Commands {
Chat {
/// Message to send
message: String,
+ /// Use memory context
+ #[arg(long)]
+ with_memory: bool,
+ },
+ /// Memory management
+ Memory {
+ #[command(subcommand)]
+ command: MemoryCommands,
},
}
@@ -30,3 +38,27 @@ pub enum ServerCommands {
/// Run the MCP server
Run,
}
+
+#[derive(Subcommand)]
+pub enum MemoryCommands {
+ /// Import ChatGPT conversation export file
+ Import {
+ /// Path to ChatGPT export JSON file
+ file: String,
+ },
+ /// Search memories
+ Search {
+ /// Search query
+ query: String,
+ /// Maximum number of results
+ #[arg(short, long, default_value = "10")]
+ limit: usize,
+ },
+ /// List all memories
+ List,
+ /// Show memory details
+ Detail {
+ /// Path to memory file
+ filepath: String,
+ },
+}
diff --git a/src/main.rs b/src/main.rs
index adfbdf3..ca96094 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -3,7 +3,7 @@ mod cli;
mod config;
mod mcp;
-use cli::{Args, Commands, ServerCommands};
+use cli::{Args, Commands, ServerCommands, MemoryCommands};
use clap::Parser;
#[tokio::main]
@@ -21,8 +21,38 @@ async fn main() {
}
}
}
- Commands::Chat { message } => {
- mcp::server::chat(&message).await;
+ Commands::Chat { message, with_memory } => {
+ if with_memory {
+ if let Err(e) = mcp::memory::handle_chat_with_memory(&message).await {
+ eprintln!("❌ 記憶チャットエラー: {}", e);
+ }
+ } else {
+ mcp::server::chat(&message).await;
+ }
+ }
+ Commands::Memory { command } => {
+ match command {
+ MemoryCommands::Import { file } => {
+ if let Err(e) = mcp::memory::handle_import(&file).await {
+ eprintln!("❌ インポートエラー: {}", e);
+ }
+ }
+ MemoryCommands::Search { query, limit } => {
+ if let Err(e) = mcp::memory::handle_search(&query, limit).await {
+ eprintln!("❌ 検索エラー: {}", e);
+ }
+ }
+ MemoryCommands::List => {
+ if let Err(e) = mcp::memory::handle_list().await {
+ eprintln!("❌ 一覧取得エラー: {}", e);
+ }
+ }
+ MemoryCommands::Detail { filepath } => {
+ if let Err(e) = mcp::memory::handle_detail(&filepath).await {
+ eprintln!("❌ 詳細取得エラー: {}", e);
+ }
+ }
+ }
}
}
}
diff --git a/src/mcp/memory.rs b/src/mcp/memory.rs
new file mode 100644
index 0000000..e3e7df2
--- /dev/null
+++ b/src/mcp/memory.rs
@@ -0,0 +1,393 @@
+// src/mcp/memory.rs
+use reqwest;
+use serde::{Deserialize, Serialize};
+use serde_json::{self, Value};
+use std::fs;
+use std::path::Path;
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct MemorySearchRequest {
+ pub query: String,
+ pub limit: usize,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ChatRequest {
+ pub message: String,
+ pub model: Option,
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+pub struct ConversationImportRequest {
+ pub conversation_data: Value,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct ApiResponse {
+ pub success: bool,
+ pub error: Option,
+ #[allow(dead_code)]
+ pub message: Option,
+ pub filepath: Option,
+ pub results: Option>,
+ pub memories: Option>,
+ #[allow(dead_code)]
+ pub count: Option,
+ pub memory: Option,
+ pub response: Option,
+ pub memories_used: Option,
+ pub imported_count: Option,
+ pub total_count: Option,
+}
+
+#[derive(Debug, Deserialize)]
+pub struct MemoryResult {
+ #[allow(dead_code)]
+ pub filepath: String,
+ pub title: Option,
+ pub summary: Option,
+ pub source: Option,
+ pub import_time: Option,
+ pub message_count: Option,
+}
+
+pub struct MemoryClient {
+ base_url: String,
+ client: reqwest::Client,
+}
+
+impl MemoryClient {
+ pub fn new(base_url: Option) -> Self {
+ let url = base_url.unwrap_or_else(|| "http://127.0.0.1:5000".to_string());
+ Self {
+ base_url: url,
+ client: reqwest::Client::new(),
+ }
+ }
+
+ pub async fn import_chatgpt_file(&self, filepath: &str) -> Result> {
+ // ファイルを読み込み
+ let content = fs::read_to_string(filepath)?;
+ let json_data: Value = serde_json::from_str(&content)?;
+
+ // 配列かどうかチェック
+ match json_data.as_array() {
+ Some(conversations) => {
+ // 複数の会話をインポート
+ let mut imported_count = 0;
+ let total_count = conversations.len();
+
+ for conversation in conversations {
+ match self.import_single_conversation(conversation.clone()).await {
+ Ok(response) => {
+ if response.success {
+ imported_count += 1;
+ }
+ }
+ Err(e) => {
+ eprintln!("❌ インポートエラー: {}", e);
+ }
+ }
+ }
+
+ Ok(ApiResponse {
+ success: true,
+ imported_count: Some(imported_count),
+ total_count: Some(total_count),
+ error: None,
+ message: Some(format!("{}個中{}個の会話をインポートしました", total_count, imported_count)),
+ filepath: None,
+ results: None,
+ memories: None,
+ count: None,
+ memory: None,
+ response: None,
+ memories_used: None,
+ })
+ }
+ None => {
+ // 単一の会話をインポート
+ self.import_single_conversation(json_data).await
+ }
+ }
+ }
+
+ async fn import_single_conversation(&self, conversation_data: Value) -> Result> {
+ let request = ConversationImportRequest { conversation_data };
+
+ let response = self.client
+ .post(&format!("{}/memory/import/chatgpt", self.base_url))
+ .json(&request)
+ .send()
+ .await?;
+
+ let result: ApiResponse = response.json().await?;
+ Ok(result)
+ }
+
+ pub async fn search_memories(&self, query: &str, limit: usize) -> Result> {
+ let request = MemorySearchRequest {
+ query: query.to_string(),
+ limit,
+ };
+
+ let response = self.client
+ .post(&format!("{}/memory/search", self.base_url))
+ .json(&request)
+ .send()
+ .await?;
+
+ let result: ApiResponse = response.json().await?;
+ Ok(result)
+ }
+
+ pub async fn list_memories(&self) -> Result> {
+ let response = self.client
+ .get(&format!("{}/memory/list", self.base_url))
+ .send()
+ .await?;
+
+ let result: ApiResponse = response.json().await?;
+ Ok(result)
+ }
+
+ pub async fn get_memory_detail(&self, filepath: &str) -> Result> {
+ let response = self.client
+ .get(&format!("{}/memory/detail", self.base_url))
+ .query(&[("filepath", filepath)])
+ .send()
+ .await?;
+
+ let result: ApiResponse = response.json().await?;
+ Ok(result)
+ }
+
+ pub async fn chat_with_memory(&self, message: &str) -> Result> {
+ let request = ChatRequest {
+ message: message.to_string(),
+ model: None,
+ };
+
+ let response = self.client
+ .post(&format!("{}/chat", self.base_url))
+ .json(&request)
+ .send()
+ .await?;
+
+ let result: ApiResponse = response.json().await?;
+ Ok(result)
+ }
+
+ pub async fn is_server_running(&self) -> bool {
+ match self.client.get(&self.base_url).send().await {
+ Ok(response) => response.status().is_success(),
+ Err(_) => false,
+ }
+ }
+}
+
+pub async fn handle_import(filepath: &str) -> Result<(), Box> {
+ if !Path::new(filepath).exists() {
+ eprintln!("❌ ファイルが見つかりません: {}", filepath);
+ return Ok(());
+ }
+
+ let client = MemoryClient::new(None);
+
+ // サーバーが起動しているかチェック
+ if !client.is_server_running().await {
+ eprintln!("❌ MCP Serverが起動していません。先に 'aigpt server run' を実行してください。");
+ return Ok(());
+ }
+
+ println!("🔄 ChatGPT会話をインポートしています: {}", filepath);
+
+ match client.import_chatgpt_file(filepath).await {
+ Ok(response) => {
+ if response.success {
+ if let (Some(imported), Some(total)) = (response.imported_count, response.total_count) {
+ println!("✅ {}個中{}個の会話をインポートしました", total, imported);
+ } else {
+ println!("✅ 会話をインポートしました");
+ if let Some(path) = response.filepath {
+ println!("📁 保存先: {}", path);
+ }
+ }
+ } else {
+ eprintln!("❌ インポートに失敗: {:?}", response.error);
+ }
+ }
+ Err(e) => {
+ eprintln!("❌ インポートエラー: {}", e);
+ }
+ }
+
+ Ok(())
+}
+
+pub async fn handle_search(query: &str, limit: usize) -> Result<(), Box> {
+ let client = MemoryClient::new(None);
+
+ if !client.is_server_running().await {
+ eprintln!("❌ MCP Serverが起動していません。先に 'aigpt server run' を実行してください。");
+ return Ok(());
+ }
+
+ println!("🔍 記憶を検索しています: {}", query);
+
+ match client.search_memories(query, limit).await {
+ Ok(response) => {
+ if response.success {
+ if let Some(results) = response.results {
+ println!("📚 {}個の記憶が見つかりました:", results.len());
+ for memory in results {
+ println!(" • {}", memory.title.unwrap_or_else(|| "タイトルなし".to_string()));
+ if let Some(summary) = memory.summary {
+ println!(" 概要: {}", summary);
+ }
+ if let Some(count) = memory.message_count {
+ println!(" メッセージ数: {}", count);
+ }
+ println!();
+ }
+ } else {
+ println!("📚 記憶が見つかりませんでした");
+ }
+ } else {
+ eprintln!("❌ 検索に失敗: {:?}", response.error);
+ }
+ }
+ Err(e) => {
+ eprintln!("❌ 検索エラー: {}", e);
+ }
+ }
+
+ Ok(())
+}
+
+pub async fn handle_list() -> Result<(), Box> {
+ let client = MemoryClient::new(None);
+
+ if !client.is_server_running().await {
+ eprintln!("❌ MCP Serverが起動していません。先に 'aigpt server run' を実行してください。");
+ return Ok(());
+ }
+
+ println!("📋 記憶一覧を取得しています...");
+
+ match client.list_memories().await {
+ Ok(response) => {
+ if response.success {
+ if let Some(memories) = response.memories {
+ println!("📚 総記憶数: {}", memories.len());
+ for memory in memories {
+ println!(" • {}", memory.title.unwrap_or_else(|| "タイトルなし".to_string()));
+ if let Some(source) = memory.source {
+ println!(" ソース: {}", source);
+ }
+ if let Some(count) = memory.message_count {
+ println!(" メッセージ数: {}", count);
+ }
+ if let Some(import_time) = memory.import_time {
+ println!(" インポート時刻: {}", import_time);
+ }
+ println!();
+ }
+ } else {
+ println!("📚 記憶がありません");
+ }
+ } else {
+ eprintln!("❌ 一覧取得に失敗: {:?}", response.error);
+ }
+ }
+ Err(e) => {
+ eprintln!("❌ 一覧取得エラー: {}", e);
+ }
+ }
+
+ Ok(())
+}
+
+pub async fn handle_detail(filepath: &str) -> Result<(), Box> {
+ let client = MemoryClient::new(None);
+
+ if !client.is_server_running().await {
+ eprintln!("❌ MCP Serverが起動していません。先に 'aigpt server run' を実行してください。");
+ return Ok(());
+ }
+
+ println!("📄 記憶の詳細を取得しています: {}", filepath);
+
+ match client.get_memory_detail(filepath).await {
+ Ok(response) => {
+ if response.success {
+ if let Some(memory) = response.memory {
+ if let Some(title) = memory.get("title").and_then(|v| v.as_str()) {
+ println!("タイトル: {}", title);
+ }
+ if let Some(source) = memory.get("source").and_then(|v| v.as_str()) {
+ println!("ソース: {}", source);
+ }
+ if let Some(summary) = memory.get("summary").and_then(|v| v.as_str()) {
+ println!("概要: {}", summary);
+ }
+ if let Some(messages) = memory.get("messages").and_then(|v| v.as_array()) {
+ println!("メッセージ数: {}", messages.len());
+ println!("\n最近のメッセージ:");
+ for msg in messages.iter().take(5) {
+ if let (Some(role), Some(content)) = (
+ msg.get("role").and_then(|v| v.as_str()),
+ msg.get("content").and_then(|v| v.as_str())
+ ) {
+ let content_preview = if content.len() > 100 {
+ format!("{}...", &content[..100])
+ } else {
+ content.to_string()
+ };
+ println!(" {}: {}", role, content_preview);
+ }
+ }
+ }
+ }
+ } else {
+ eprintln!("❌ 詳細取得に失敗: {:?}", response.error);
+ }
+ }
+ Err(e) => {
+ eprintln!("❌ 詳細取得エラー: {}", e);
+ }
+ }
+
+ Ok(())
+}
+
+pub async fn handle_chat_with_memory(message: &str) -> Result<(), Box> {
+ let client = MemoryClient::new(None);
+
+ if !client.is_server_running().await {
+ eprintln!("❌ MCP Serverが起動していません。先に 'aigpt server run' を実行してください。");
+ return Ok(());
+ }
+
+ println!("💬 記憶を活用してチャットしています...");
+
+ match client.chat_with_memory(message).await {
+ Ok(response) => {
+ if response.success {
+ if let Some(reply) = response.response {
+ println!("🤖 {}", reply);
+ }
+ if let Some(memories_used) = response.memories_used {
+ println!("📚 使用した記憶数: {}", memories_used);
+ }
+ } else {
+ eprintln!("❌ チャットに失敗: {:?}", response.error);
+ }
+ }
+ Err(e) => {
+ eprintln!("❌ チャットエラー: {}", e);
+ }
+ }
+
+ Ok(())
+}
diff --git a/src/mcp/mod.rs b/src/mcp/mod.rs
index 078d630..e023caf 100644
--- a/src/mcp/mod.rs
+++ b/src/mcp/mod.rs
@@ -1,2 +1,3 @@
// src/mcp/mod.rs
pub mod server;
+pub mod memory;