cheeeaaat commited on
Commit
d317445
·
1 Parent(s): 38c39af

Long Term Memory MCP Server

Browse files
Files changed (10) hide show
  1. README.md +147 -6
  2. app.py +58 -0
  3. demo_script.md +121 -0
  4. dockerfile +30 -0
  5. gradio_demo.py +359 -0
  6. langchain_memory_tools.py +176 -0
  7. local_run.py +61 -0
  8. ltm_mcp_server.py +353 -0
  9. requirements.txt +18 -0
  10. run_mcp_server.py +18 -0
README.md CHANGED
@@ -1,14 +1,155 @@
1
  ---
2
  title: Long Term Memory MCP Server
3
- emoji: 🏃
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 5.33.0
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
- short_description: Long Term Memory MCP Server
 
 
 
 
 
 
 
12
  ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: Long Term Memory MCP Server
3
+ emoji: 🧠
4
+ colorFrom: blue
5
+ colorTo: purple
6
  sdk: gradio
7
+ sdk_version: "4.0.0"
8
  app_file: app.py
9
  pinned: false
10
  license: mit
11
+ tags:
12
+ - mcp-server-track
13
+ - mcp
14
+ - memory
15
+ - rag
16
+ - llm
17
+ - conversation
18
+ short_description: MCP Server providing long-term memory for LLM conversations
19
  ---
20
 
21
+ # 🧠 Long Term Memory MCP Server
22
+
23
+ **Tags**: mcp-server-track
24
+
25
+ A Model Context Protocol (MCP) server that provides long-term memory capabilities for LLM conversations. This allows users to save important insights, conclusions, and context from conversations and retrieve them in future interactions.
26
+
27
+ ## 🎯 Problem Solved
28
+
29
+ Current LLM interactions are stateless - they don't remember previous conversations or insights you've shared. This MCP server solves that by providing:
30
+
31
+ - **Persistent Memory**: Save important insights and context from conversations
32
+ - **Semantic Search**: Find relevant memories using natural language queries
33
+ - **Context Continuity**: Build upon previous conversations and learnings
34
+ - **Knowledge Accumulation**: Build a personal knowledge base over time
35
+
36
+ ## 🚀 Features
37
+
38
+ ### MCP Server Tools
39
+ - `save_memory` - Save insights, conclusions, or context to long-term memory
40
+ - `search_memory` - Search through memories using semantic similarity
41
+ - `list_memories` - Browse all stored memories
42
+ - `delete_memory` - Remove specific memories
43
+
44
+ ### Gradio Demo Interface
45
+ - Interactive web interface for testing all MCP tools
46
+ - Real-time memory statistics
47
+ - Semantic search with adjustable similarity thresholds
48
+ - Memory browsing and management
49
+
50
+ ## 🛠️ Technical Architecture
51
+
52
+ - **MCP Protocol**: Standards-compliant MCP server
53
+ - **Vector Storage**: ChromaDB for efficient semantic search
54
+ - **Embeddings**: SentenceTransformers (all-MiniLM-L6-v2) for semantic understanding
55
+ - **Interface**: Gradio web app for demonstration and testing
56
+ - **Storage**: Persistent local database
57
+
58
+ ## 📦 Installation & Usage
59
+
60
+ ### Local Development
61
+ ```bash
62
+ # Clone and install dependencies
63
+ pip install -r requirements.txt
64
+
65
+ # Run the application
66
+ python app.py
67
+ ```
68
+
69
+ ### Hugging Face Spaces
70
+ This Space runs both the MCP server and Gradio demo simultaneously.
71
+
72
+ ## 🎮 Demo Video
73
+
74
+ [Demo Video Link](https://your-demo-video-link.com) - *Recording of the MCP server working with Claude Desktop*
75
+
76
+ ## 💡 Use Cases
77
+
78
+ ### Example Scenario
79
+ 1. **Initial Conversation**: You discuss quantum consciousness theories with an LLM
80
+ 2. **Save Insight**: Use `save_memory` to store key conclusions
81
+ 3. **Future Conversation**: LLM can `search_memory` to find relevant context
82
+ 4. **Continuity**: Build upon previous insights in new discussions
83
+
84
+ ### Sample Usage with Claude Desktop
85
+
86
+ **Saving a memory:**
87
+ ```
88
+ User: "Save this insight to memory: 'Consciousness might emerge from quantum processes in microtubules, as proposed by Penrose-Hameroff theory. This could explain the hard problem of consciousness.' Title: 'Quantum Consciousness Theory', Tags: 'consciousness, quantum, penrose, microtubules'"
89
+
90
+ LLM: *Uses save_memory tool*
91
+ ✅ Memory saved successfully! ID: abc123...
92
+ ```
93
+
94
+ **Searching memories:**
95
+ ```
96
+ User: "What did we previously discuss about consciousness and quantum physics?"
97
+
98
+ LLM: *Uses search_memory tool*
99
+ 🔍 Found relevant memory: "Quantum Consciousness Theory" - discusses how consciousness might emerge from quantum processes in microtubules...
100
+ ```
101
+
102
+ ## 🔧 MCP Client Configuration
103
+
104
+ ### Claude Desktop
105
+ Add to your `claude_desktop_config.json`:
106
+ ```json
107
+ {
108
+ "mcpServers": {
109
+ "long-term-memory": {
110
+ "command": "python",
111
+ "args": ["path/to/mcp_server.py"],
112
+ "env": {}
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Cursor IDE
119
+ Configure in your MCP settings to connect to the server.
120
+
121
+ ## 📊 Memory Statistics
122
+
123
+ The system tracks:
124
+ - Total memories stored
125
+ - Content length statistics
126
+ - Tag usage patterns
127
+ - Timestamp-based organization
128
+
129
+ ## 🔐 Privacy & Data
130
+
131
+ - All data stored locally in ChromaDB
132
+ - No external API calls for embeddings (uses local SentenceTransformers)
133
+ - Full control over your memory data
134
+ - Easy export/import capabilities
135
+
136
+ ## 🚧 Future Enhancements
137
+
138
+ - [ ] Memory categorization and hierarchical organization
139
+ - [ ] Conversation threading and context linking
140
+ - [ ] Export/import functionality
141
+ - [ ] Advanced search filters (date, tags, content type)
142
+ - [ ] Memory summarization and consolidation
143
+ - [ ] Integration with external knowledge bases
144
+
145
+ ## 🤝 Contributing
146
+
147
+ This project was created for the [Hugging Face MCP Hackathon](https://huggingface.co/Agents-MCP-Hackathon). Contributions welcome!
148
+
149
+ ## 📝 License
150
+
151
+ MIT License - Feel free to use and modify!
152
+
153
+ ---
154
+
155
+ *Built with ❤️ for the Hugging Face MCP Hackathon - Track 1: MCP Server/Tool*
app.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Long Term Memory MCP Server & Gradio Demo
4
+ A Model Context Protocol server that provides long-term memory capabilities for LLM conversations.
5
+ """
6
+
7
+ import os
8
+ import sys
9
+ import threading
10
+ import time
11
+ import subprocess
12
+ from pathlib import Path
13
+
14
+ # Add the current directory to the Python path
15
+ sys.path.insert(0, str(Path(__file__).parent))
16
+
17
+ from gradio_demo import demo
18
+
19
+ def run_mcp_server():
20
+ """Run the MCP server in a separate thread."""
21
+ try:
22
+ # Import and run the MCP server
23
+ from mcp_server import main
24
+ import asyncio
25
+
26
+ # Create new event loop for this thread
27
+ loop = asyncio.new_event_loop()
28
+ asyncio.set_event_loop(loop)
29
+
30
+ # Run the server
31
+ loop.run_until_complete(main())
32
+ except Exception as e:
33
+ print(f"MCP Server error: {e}")
34
+
35
+ def main():
36
+ """Main entry point."""
37
+ print("🧠 Starting Long Term Memory MCP Server & Demo...")
38
+
39
+ # Start MCP server in background thread
40
+ mcp_thread = threading.Thread(target=run_mcp_server, daemon=True)
41
+ mcp_thread.start()
42
+
43
+ # Give the MCP server a moment to start
44
+ time.sleep(2)
45
+
46
+ print("✅ MCP Server started!")
47
+ print("🚀 Launching Gradio demo...")
48
+
49
+ # Launch Gradio demo
50
+ demo.launch(
51
+ server_name="0.0.0.0",
52
+ server_port=7860,
53
+ share=True,
54
+ show_error=True
55
+ )
56
+
57
+ if __name__ == "__main__":
58
+ main()
demo_script.md ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🎬 Demo Script & Testing Guide
2
+
3
+ ## Quick Start Testing
4
+
5
+ ### 1. Test Gradio Interface
6
+ 1. Visit the Hugging Face Space
7
+ 2. Try saving a memory in the "Save Memory" tab
8
+ 3. Search for it in the "Search Memory" tab
9
+ 4. Browse all memories in the "Browse Memories" tab
10
+
11
+ ### 2. Test MCP Server with Claude Desktop
12
+
13
+ #### Setup:
14
+ 1. Add to your `claude_desktop_config.json`:
15
+ ```json
16
+ {
17
+ "mcpServers": {
18
+ "long-term-memory": {
19
+ "command": "python",
20
+ "args": ["run_mcp_server.py"],
21
+ "env": {},
22
+ "cwd": "/path/to/your/project"
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ 2. Restart Claude Desktop
29
+
30
+ #### Demo Script for Video:
31
+
32
+ **Scene 1: Introduction**
33
+ "Hi! I'm demonstrating a Long Term Memory MCP Server that solves a key problem with LLM conversations - they don't remember previous discussions. Let me show you how this works."
34
+
35
+ **Scene 2: Save a Memory**
36
+ ```
37
+ User: "I want to save an important insight to my long-term memory. Use the save_memory tool with this content: 'Consciousness might emerge from quantum processes in microtubules according to the Penrose-Hameroff theory. This could explain the binding problem and why we have unified conscious experience.' Use the title 'Quantum Consciousness Theory' and tags 'consciousness, quantum, penrose, microtubules, binding-problem'"
38
+ ```
39
+
40
+ **Scene 3: Save Another Memory**
41
+ ```
42
+ User: "Save another insight: 'Free will might be an illusion created by our brain's narrative construction. The feeling of choice comes after neural commitment to action.' Title: 'Free Will Illusion Theory', tags: 'free-will, neuroscience, consciousness, illusion'"
43
+ ```
44
+
45
+ **Scene 4: Search Memories**
46
+ ```
47
+ User: "Search my memories for information about consciousness and how it relates to quantum physics"
48
+ ```
49
+
50
+ **Scene 5: Demonstrate Context Continuity**
51
+ ```
52
+ User: "Based on what we previously discussed about consciousness, what are the implications for artificial intelligence? Use my memories to inform your response."
53
+ ```
54
+
55
+ **Scene 6: List All Memories**
56
+ ```
57
+ User: "Show me all the memories I have stored using the list_memories tool"
58
+ ```
59
+
60
+ ## Demo Scenarios
61
+
62
+ ### Scenario 1: Philosophy Student
63
+ 1. Save insights from reading different philosophers
64
+ 2. Search for connections between ideas
65
+ 3. Build upon previous understanding in new discussions
66
+
67
+ ### Scenario 2: Research Notes
68
+ 1. Save key findings from papers
69
+ 2. Find related research using semantic search
70
+ 3. Synthesize knowledge across sessions
71
+
72
+ ### Scenario 3: Personal Development
73
+ 1. Save insights from conversations about goals
74
+ 2. Track progress and learnings over time
75
+ 3. Reference past conclusions in future planning
76
+
77
+ ## Testing Checklist
78
+
79
+ ### Basic Functionality
80
+ - [ ] Save memory with all fields
81
+ - [ ] Save memory with minimal fields
82
+ - [ ] Search with various queries
83
+ - [ ] Search with different thresholds
84
+ - [ ] List memories with different limits
85
+ - [ ] View memory statistics
86
+
87
+ ### Edge Cases
88
+ - [ ] Empty search query
89
+ - [ ] Search with no results
90
+ - [ ] Search when no memories exist
91
+ - [ ] Very long content
92
+ - [ ] Special characters in content
93
+ - [ ] Multiple identical memories
94
+
95
+ ### MCP Integration
96
+ - [ ] Server starts correctly
97
+ - [ ] Tools are discovered by client
98
+ - [ ] All tools execute successfully
99
+ - [ ] Error handling works
100
+ - [ ] Multiple concurrent requests
101
+
102
+ ## Performance Benchmarks
103
+
104
+ ### Expected Performance
105
+ - **Save Memory**: < 1 second
106
+ - **Search 100 memories**: < 2 seconds
107
+ - **List memories**: < 0.5 seconds
108
+ - **Memory footprint**: ~50MB for 1000 memories
109
+
110
+ ### Scalability Limits
111
+ - **ChromaDB**: Handles 100K+ documents efficiently
112
+ - **Embeddings**: 384-dimensional vectors (all-MiniLM-L6-v2)
113
+ - **Storage**: ~1KB per memory average
114
+
115
+ ## Troubleshooting
116
+
117
+ ### Common Issues
118
+ 1. **"MCP not available"**: Install missing dependencies
119
+ 2. **Embedding model fails**: Check internet connection for initial download
120
+ 3. **ChromaDB errors**: Check write permissions for memory_db directory
121
+ 4. **Claude Desktop not connecting**: Verify config.json path an
dockerfile ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Copy requirements first for better caching
11
+ COPY requirements.txt .
12
+
13
+ # Install Python dependencies
14
+ RUN pip install --no-cache-dir -r requirements.txt
15
+
16
+ # Copy application code
17
+ COPY . .
18
+
19
+ # Create directory for database
20
+ RUN mkdir -p /app/memory_db
21
+
22
+ # Expose port
23
+ EXPOSE 7860
24
+
25
+ # Set environment variables
26
+ ENV GRADIO_SERVER_NAME="0.0.0.0"
27
+ ENV GRADIO_SERVER_PORT=7860
28
+
29
+ # Run the application
30
+ CMD ["python", "app.py"]
gradio_demo.py ADDED
@@ -0,0 +1,359 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ import asyncio
4
+ from datetime import datetime
5
+ from typing import List, Dict, Any
6
+ import uuid
7
+ import chromadb
8
+ from chromadb.config import Settings
9
+ from sentence_transformers import SentenceTransformer
10
+ from pathlib import Path
11
+
12
+ class LongTermMemoryDemo:
13
+ def __init__(self):
14
+ self.db_path = "./memory_db"
15
+ Path(self.db_path).mkdir(exist_ok=True)
16
+
17
+ # Initialize ChromaDB
18
+ self.chroma_client = chromadb.PersistentClient(
19
+ path=self.db_path,
20
+ settings=Settings(anonymized_telemetry=False)
21
+ )
22
+
23
+ # Get or create collection
24
+ try:
25
+ self.collection = self.chroma_client.get_collection("memories")
26
+ except:
27
+ self.collection = self.chroma_client.create_collection(
28
+ name="memories",
29
+ metadata={"description": "Long-term memory storage for conversations"}
30
+ )
31
+
32
+ # Initialize sentence transformer for embeddings
33
+ print("Loading SentenceTransformer model...")
34
+ self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
35
+ print("Model loaded successfully!")
36
+
37
+ def save_memory(self, content: str, title: str, tags: str = "", context: str = "") -> str:
38
+ """Save content to long-term memory."""
39
+ if not content or not title:
40
+ return "❌ Error: Content and title are required!"
41
+
42
+ try:
43
+ tags_list = [tag.strip() for tag in tags.split(',') if tag.strip()] if tags else []
44
+
45
+ memory_id = str(uuid.uuid4())
46
+ timestamp = datetime.now().isoformat()
47
+
48
+ # Create embedding
49
+ embedding = self.encoder.encode(f"{title} {content}").tolist()
50
+
51
+ # Prepare metadata
52
+ metadata = {
53
+ "title": title,
54
+ "timestamp": timestamp,
55
+ "tags": json.dumps(tags_list),
56
+ "context": context,
57
+ "content_length": len(content)
58
+ }
59
+
60
+ # Save to ChromaDB
61
+ self.collection.add(
62
+ documents=[content],
63
+ embeddings=[embedding],
64
+ metadatas=[metadata],
65
+ ids=[memory_id]
66
+ )
67
+
68
+ result = f"✅ **Memory saved successfully!**\n\n"
69
+ result += f"**ID**: `{memory_id}`\n"
70
+ result += f"**Title**: {title}\n"
71
+ result += f"**Timestamp**: {timestamp}\n"
72
+ if tags_list:
73
+ result += f"**Tags**: {', '.join(tags_list)}\n"
74
+ if context:
75
+ result += f"**Context**: {context}\n"
76
+ result += f"**Content Preview**: {content[:200]}{'...' if len(content) > 200 else ''}"
77
+
78
+ return result
79
+ except Exception as e:
80
+ return f"❌ Error saving memory: {str(e)}"
81
+
82
+ def search_memory(self, query: str, limit: int = 5, threshold: float = 0.3) -> str:
83
+ """Search through memories."""
84
+ if not query:
85
+ return "❌ Error: Search query is required!"
86
+
87
+ try:
88
+ if self.collection.count() == 0:
89
+ return "📭 No memories stored yet. Save some memories first!"
90
+
91
+ # Create query embedding
92
+ query_embedding = self.encoder.encode(query).tolist()
93
+
94
+ # Search in ChromaDB
95
+ results = self.collection.query(
96
+ query_embeddings=[query_embedding],
97
+ n_results=min(limit, self.collection.count())
98
+ )
99
+
100
+ if not results['documents'][0]:
101
+ return "🔍 No relevant memories found."
102
+
103
+ # Filter by threshold and format results
104
+ response = f"🔍 **Search Results for**: \"{query}\"\n\n"
105
+
106
+ found_relevant = False
107
+ for i, (doc, metadata, distance) in enumerate(zip(
108
+ results['documents'][0],
109
+ results['metadatas'][0],
110
+ results['distances'][0]
111
+ )):
112
+ similarity = 1 - distance
113
+ if similarity >= threshold:
114
+ found_relevant = True
115
+ tags = json.loads(metadata.get('tags', '[]'))
116
+
117
+ response += f"### {i+1}. {metadata['title']} (Similarity: {similarity:.2f})\n"
118
+ response += f"**Saved**: {metadata['timestamp']}\n"
119
+ if tags:
120
+ response += f"**Tags**: {', '.join(tags)}\n"
121
+ if metadata.get('context'):
122
+ response += f"**Context**: {metadata['context']}\n"
123
+ response += f"**Content**: {doc}\n\n"
124
+ response += "---\n\n"
125
+
126
+ if not found_relevant:
127
+ response += f"No memories found above similarity threshold of {threshold:.2f}"
128
+
129
+ return response
130
+ except Exception as e:
131
+ return f"❌ Error searching memories: {str(e)}"
132
+
133
+ def list_memories(self, limit: int = 10) -> str:
134
+ """List all memories."""
135
+ try:
136
+ if self.collection.count() == 0:
137
+ return "📭 No memories stored yet."
138
+
139
+ # Get all memories
140
+ results = self.collection.get()
141
+
142
+ if not results['documents']:
143
+ return "📭 No memories found."
144
+
145
+ response = f"📚 **All Memories** (showing up to {limit})\n\n"
146
+
147
+ # Sort by timestamp (newest first)
148
+ memories = list(zip(results['ids'], results['documents'], results['metadatas']))
149
+ memories.sort(key=lambda x: x[2]['timestamp'], reverse=True)
150
+
151
+ for i, (memory_id, doc, metadata) in enumerate(memories[:limit]):
152
+ tags = json.loads(metadata.get('tags', '[]'))
153
+
154
+ response += f"### {i+1}. {metadata['title']}\n"
155
+ response += f"**ID**: `{memory_id}`\n"
156
+ response += f"**Saved**: {metadata['timestamp']}\n"
157
+ if tags:
158
+ response += f"**Tags**: {', '.join(tags)}\n"
159
+ response += f"**Preview**: {doc[:150]}{'...' if len(doc) > 150 else ''}\n\n"
160
+ response += "---\n\n"
161
+
162
+ if len(memories) > limit:
163
+ response += f"... and {len(memories) - limit} more memories"
164
+
165
+ return response
166
+ except Exception as e:
167
+ return f"❌ Error listing memories: {str(e)}"
168
+
169
+ def get_memory_stats(self) -> str:
170
+ """Get statistics about stored memories."""
171
+ try:
172
+ count = self.collection.count()
173
+ if count == 0:
174
+ return "📊 **Memory Statistics**: No memories stored yet."
175
+
176
+ results = self.collection.get()
177
+
178
+ # Calculate stats
179
+ total_content_length = sum(metadata['content_length'] for metadata in results['metadatas'])
180
+ avg_content_length = total_content_length / count if count > 0 else 0
181
+
182
+ # Get all tags
183
+ all_tags = []
184
+ for metadata in results['metadatas']:
185
+ tags = json.loads(metadata.get('tags', '[]'))
186
+ all_tags.extend(tags)
187
+
188
+ unique_tags = list(set(all_tags))
189
+
190
+ stats = f"📊 **Memory Statistics**\n\n"
191
+ stats += f"**Total Memories**: {count}\n"
192
+ stats += f"**Total Content Length**: {total_content_length:,} characters\n"
193
+ stats += f"**Average Content Length**: {avg_content_length:.0f} characters\n"
194
+ stats += f"**Unique Tags**: {len(unique_tags)}\n"
195
+ if unique_tags:
196
+ stats += f"**Tags**: {', '.join(unique_tags[:10])}{'...' if len(unique_tags) > 10 else ''}\n"
197
+
198
+ return stats
199
+ except Exception as e:
200
+ return f"❌ Error getting statistics: {str(e)}"
201
+
202
+ # Initialize the demo
203
+ print("Initializing Long Term Memory Demo...")
204
+ ltm_demo = LongTermMemoryDemo()
205
+
206
+ # Create Gradio interface
207
+ with gr.Blocks(title="Long Term Memory MCP Server Demo", theme=gr.themes.Soft()) as demo:
208
+ gr.Markdown("""
209
+ # 🧠 Long Term Memory MCP Server Demo
210
+
211
+ This is a demonstration of an MCP (Model Context Protocol) Server that provides long-term memory capabilities for LLM conversations.
212
+
213
+ ## Features:
214
+ - 💾 **Save Memory**: Store important insights, conclusions, or context
215
+ - 🔍 **Search Memory**: Find relevant information using semantic search
216
+ - 📚 **List Memories**: Browse all stored memories
217
+ - 📊 **Statistics**: View memory usage statistics
218
+
219
+ ## How it works:
220
+ 1. **Embeddings**: Uses SentenceTransformers to create semantic embeddings
221
+ 2. **Vector Storage**: ChromaDB for efficient similarity search
222
+ 3. **MCP Protocol**: Exposes tools that any MCP-compatible client can use
223
+ """)
224
+
225
+ with gr.Tabs():
226
+ # Save Memory Tab
227
+ with gr.Tab("💾 Save Memory"):
228
+ gr.Markdown("### Save important insights or context to long-term memory")
229
+
230
+ with gr.Row():
231
+ with gr.Column():
232
+ save_title = gr.Textbox(
233
+ label="Title",
234
+ placeholder="Brief title for this memory...",
235
+ lines=1
236
+ )
237
+ save_content = gr.Textbox(
238
+ label="Content",
239
+ placeholder="The insight, conclusion, or context you want to remember...",
240
+ lines=5
241
+ )
242
+ save_tags = gr.Textbox(
243
+ label="Tags (optional)",
244
+ placeholder="quantum physics, consciousness, philosophy",
245
+ lines=1
246
+ )
247
+ save_context = gr.Textbox(
248
+ label="Context (optional)",
249
+ placeholder="Why is this important? When was it discussed?",
250
+ lines=2
251
+ )
252
+ save_btn = gr.Button("💾 Save Memory", variant="primary")
253
+
254
+ with gr.Column():
255
+ save_output = gr.Markdown()
256
+
257
+ save_btn.click(
258
+ ltm_demo.save_memory,
259
+ inputs=[save_content, save_title, save_tags, save_context],
260
+ outputs=[save_output]
261
+ )
262
+
263
+ # Search Memory Tab
264
+ with gr.Tab("🔍 Search Memory"):
265
+ gr.Markdown("### Search through your memories using semantic similarity")
266
+
267
+ with gr.Row():
268
+ with gr.Column():
269
+ search_query = gr.Textbox(
270
+ label="Search Query",
271
+ placeholder="quantum consciousness, reality nature, philosophical insights...",
272
+ lines=2
273
+ )
274
+ with gr.Row():
275
+ search_limit = gr.Slider(
276
+ label="Max Results",
277
+ minimum=1,
278
+ maximum=20,
279
+ value=5,
280
+ step=1
281
+ )
282
+ search_threshold = gr.Slider(
283
+ label="Similarity Threshold",
284
+ minimum=0.0,
285
+ maximum=1.0,
286
+ value=0.3,
287
+ step=0.05
288
+ )
289
+ search_btn = gr.Button("🔍 Search Memories", variant="primary")
290
+
291
+ with gr.Column():
292
+ search_output = gr.Markdown()
293
+
294
+ search_btn.click(
295
+ ltm_demo.search_memory,
296
+ inputs=[search_query, search_limit, search_threshold],
297
+ outputs=[search_output]
298
+ )
299
+
300
+ # List Memories Tab
301
+ with gr.Tab("📚 Browse Memories"):
302
+ gr.Markdown("### Browse all stored memories")
303
+
304
+ with gr.Row():
305
+ with gr.Column(scale=1):
306
+ list_limit = gr.Slider(
307
+ label="Number of memories to show",
308
+ minimum=5,
309
+ maximum=50,
310
+ value=10,
311
+ step=5
312
+ )
313
+ list_btn = gr.Button("📚 List Memories", variant="primary")
314
+ stats_btn = gr.Button("📊 Show Statistics", variant="secondary")
315
+
316
+ with gr.Column(scale=3):
317
+ list_output = gr.Markdown()
318
+
319
+ list_btn.click(
320
+ ltm_demo.list_memories,
321
+ inputs=[list_limit],
322
+ outputs=[list_output]
323
+ )
324
+
325
+ stats_btn.click(
326
+ ltm_demo.get_memory_stats,
327
+ outputs=[list_output]
328
+ )
329
+
330
+ gr.Markdown("""
331
+ ---
332
+
333
+ ## 🔧 MCP Server Usage
334
+
335
+ This Gradio app is also an MCP Server! You can connect to it from MCP-compatible clients like:
336
+ - Claude Desktop
337
+ - Cursor IDE
338
+ - Other MCP clients
339
+
340
+ ### Available MCP Tools:
341
+ - `save_memory` - Save content to long-term memory
342
+ - `search_memory` - Search through memories
343
+ - `list_memories` - List all memories
344
+ - `delete_memory` - Delete a specific memory
345
+
346
+ ### Example Usage in Claude Desktop:
347
+ ```
348
+ "Save this insight to memory: 'Consciousness might be a quantum phenomenon
349
+ that emerges from the collapse of wave functions in microtubules.'
350
+ Title: 'Quantum Consciousness Theory', Tags: 'quantum, consciousness, microtubules'"
351
+ ```
352
+
353
+ Then later:
354
+ ```
355
+ "Search my memories for information about consciousness and quantum physics"
356
+ ```
357
+ """)
358
+
359
+ print("Gradio interface created successfully!")
langchain_memory_tools.py ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ LangChain tools for Long Term Memory integration with Ollama
4
+ """
5
+
6
+ from langchain.tools import tool
7
+ from langchain_ollama import OllamaLLM
8
+ from langchain.agents import create_react_agent, AgentExecutor
9
+ from langchain import hub
10
+ from typing import Optional, Dict, Any, List
11
+ import requests
12
+ import json
13
+
14
+ # Import your existing LTM demo class
15
+ from gradio_demo import LongTermMemoryDemo
16
+
17
+ # Initialize shared memory instance
18
+ ltm = LongTermMemoryDemo()
19
+
20
+ @tool
21
+ def save_memory(content: str, title: str, tags: str = "", context: str = "") -> str:
22
+ """
23
+ Save important insights, conclusions, or context to long-term memory.
24
+ Use this to remember key information from conversations that might be useful later.
25
+
26
+ Args:
27
+ content: The insight or information to save
28
+ title: A brief descriptive title
29
+ tags: Optional comma-separated tags
30
+ context: Optional additional context
31
+ """
32
+ try:
33
+ return ltm.save_memory(content, title, tags, context)
34
+ except Exception as e:
35
+ return f"Error saving memory: {str(e)}"
36
+
37
+ @tool
38
+ def search_memory(query: str, limit: int = 5, threshold: float = 0.3) -> str:
39
+ """
40
+ Search through long-term memory for relevant information.
41
+ Use this to find previously saved insights or context related to current discussion.
42
+
43
+ Args:
44
+ query: What to search for
45
+ limit: Max number of results (default: 5)
46
+ threshold: Similarity threshold 0-1 (default: 0.3)
47
+ """
48
+ try:
49
+ return ltm.search_memory(query, limit, threshold)
50
+ except Exception as e:
51
+ return f"Error searching memory: {str(e)}"
52
+
53
+ @tool
54
+ def list_memories(limit: int = 10) -> str:
55
+ """
56
+ List all stored memories to see what information is available.
57
+ Useful for getting an overview of stored knowledge.
58
+
59
+ Args:
60
+ limit: Maximum number of memories to show (default: 10)
61
+ """
62
+ try:
63
+ return ltm.list_memories(limit)
64
+ except Exception as e:
65
+ return f"Error listing memories: {str(e)}"
66
+
67
+ @tool
68
+ def memory_stats() -> str:
69
+ """
70
+ Get statistics about stored memories.
71
+ Shows total count, tags, and other metadata.
72
+ """
73
+ try:
74
+ return ltm.get_memory_stats()
75
+ except Exception as e:
76
+ return f"Error getting stats: {str(e)}"
77
+
78
+ # Example usage with Ollama
79
+ def create_memory_enabled_agent(model_name: str = "llama3.2"):
80
+ """Create a LangChain agent with memory capabilities"""
81
+
82
+ # Initialize Ollama LLM
83
+ llm = OllamaLLM(model=model_name)
84
+
85
+ # Create tools list
86
+ tools = [save_memory, search_memory, list_memories, memory_stats]
87
+
88
+ # Get the react prompt from hub
89
+ try:
90
+ prompt = hub.pull("hwchase17/react")
91
+ except:
92
+ # Fallback prompt if hub is not available
93
+ from langchain.prompts import PromptTemplate
94
+
95
+ template = """Answer the following questions as best you can. You have access to the following tools:
96
+
97
+ {tools}
98
+
99
+ Use the following format:
100
+
101
+ Question: the input question you must answer
102
+ Thought: you should always think about what to do
103
+ Action: the action to take, should be one of [{tool_names}]
104
+ Action Input: the input to the action
105
+ Observation: the result of the action
106
+ ... (this Thought/Action/Action Input/Observation can repeat N times)
107
+ Thought: I now know the final answer
108
+ Final Answer: the final answer to the original input question
109
+
110
+ Begin!
111
+
112
+ Question: {input}
113
+ Thought:{agent_scratchpad}"""
114
+
115
+ prompt = PromptTemplate.from_template(template)
116
+
117
+ # Create agent
118
+ agent = create_react_agent(llm, tools, prompt)
119
+
120
+ # Create agent executor
121
+ agent_executor = AgentExecutor(
122
+ agent=agent,
123
+ tools=tools,
124
+ verbose=True,
125
+ handle_parsing_errors=True,
126
+ max_iterations=10
127
+ )
128
+
129
+ return agent_executor
130
+
131
+ # Example conversation loop
132
+ def main():
133
+ """Example usage"""
134
+ print("🧠 Initializing Memory-Enabled Agent with Ollama...")
135
+
136
+ try:
137
+ agent = create_memory_enabled_agent("llama3.2") # или любая другая модель в Ollama
138
+
139
+ print("✅ Agent ready! Type 'quit' to exit.")
140
+ print("💡 Try commands like:")
141
+ print(" - 'Save this insight: quantum computers might revolutionize AI with title Quantum AI and tags quantum,ai,future' ")
142
+ print(" - 'Search my memories for information about quantum computing'")
143
+ print(" - 'What memories do I have stored?'")
144
+ print(" - 'Show me memory statistics'")
145
+ print()
146
+
147
+ while True:
148
+ try:
149
+ user_input = input("You: ").strip()
150
+
151
+ if user_input.lower() in ['quit', 'exit', 'bye']:
152
+ print("Goodbye!")
153
+ break
154
+
155
+ if not user_input:
156
+ continue
157
+
158
+ # Run the agent
159
+ response = agent.invoke({"input": user_input})
160
+ print(f"Agent: {response['output']}")
161
+ print()
162
+
163
+ except KeyboardInterrupt:
164
+ print("\nGoodbye!")
165
+ break
166
+ except Exception as e:
167
+ print(f"Error: {e}")
168
+ continue
169
+
170
+ except Exception as e:
171
+ print(f"Failed to initialize agent: {e}")
172
+ print("Make sure Ollama is running and the model is available.")
173
+ print("Try: ollama pull llama3.2")
174
+
175
+ if __name__ == "__main__":
176
+ main()
local_run.py ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Local runner for Memory-Enabled LangChain Agent
4
+ """
5
+
6
+ from langchain_memory_tools import create_memory_enabled_agent
7
+
8
+
9
+ def main():
10
+ print("🧠 Starting Memory-Enabled Chat with Ollama...")
11
+
12
+ # Убедитесь, что Ollama запущен и модель доступна
13
+ model_name = "llama3.2:3b" # или другая модель в вашем Ollama
14
+
15
+ try:
16
+ agent = create_memory_enabled_agent(model_name)
17
+
18
+ print(f"✅ Agent with model '{model_name}' ready!")
19
+ print("💡 Example commands:")
20
+ print(
21
+ " - Save this insight: 'Quantum computers use qubits' with title 'Quantum Computing Basics' and tags 'quantum,computing,physics'")
22
+ print(" - Search my memories for quantum")
23
+ print(" - List all my memories")
24
+ print(" - Show memory statistics")
25
+ print(" - Type 'quit' to exit")
26
+ print()
27
+
28
+ while True:
29
+ try:
30
+ user_input = input("You: ").strip()
31
+
32
+ if user_input.lower() in ['quit', 'exit', 'bye']:
33
+ print("👋 Goodbye!")
34
+ break
35
+
36
+ if not user_input:
37
+ continue
38
+
39
+ print("🤔 Thinking...")
40
+ response = agent.invoke({"input": user_input})
41
+ print(f"🤖 Agent: {response['output']}")
42
+ print("-" * 50)
43
+
44
+ except KeyboardInterrupt:
45
+ print("\n👋 Goodbye!")
46
+ break
47
+ except Exception as e:
48
+ print(f"❌ Error: {e}")
49
+ print("Continuing...")
50
+ continue
51
+
52
+ except Exception as e:
53
+ print(f"❌ Failed to initialize agent: {e}")
54
+ print("\n🔧 Troubleshooting:")
55
+ print("1. Make sure Ollama is running: ollama serve")
56
+ print(f"2. Make sure model is available: ollama pull {model_name}")
57
+ print("3. Check if Ollama is accessible at http://localhost:11434")
58
+
59
+
60
+ if __name__ == "__main__":
61
+ main()
ltm_mcp_server.py ADDED
@@ -0,0 +1,353 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from typing import Any, Dict, List, Optional, Sequence
6
+ from pathlib import Path
7
+ import uuid
8
+
9
+ import chromadb
10
+ from chromadb.config import Settings
11
+ import numpy as np
12
+ from sentence_transformers import SentenceTransformer
13
+
14
+ # MCP imports
15
+ try:
16
+ from mcp.server import Server
17
+ from mcp.server.models import InitializationOptions
18
+ from mcp.server.stdio import stdio_server
19
+ from mcp.types import (
20
+ Resource,
21
+ Tool,
22
+ TextContent,
23
+ ImageContent,
24
+ EmbeddedResource,
25
+ LoggingLevel
26
+ )
27
+ MCP_AVAILABLE = True
28
+ except ImportError:
29
+ print("MCP library not available, running in demo mode only")
30
+ MCP_AVAILABLE = False
31
+
32
+ # Mock classes for demo mode
33
+ class Server:
34
+ def __init__(self, name):
35
+ self.name = name
36
+ def list_tools(self): return lambda: None
37
+ def call_tool(self): return lambda: None
38
+
39
+ class TextContent:
40
+ def __init__(self, type, text):
41
+ self.type = type
42
+ self.text = text
43
+
44
+ # Configure logging
45
+ logging.basicConfig(level=logging.INFO)
46
+ logger = logging.getLogger("ltm-mcp-server")
47
+
48
+ class LongTermMemoryServer:
49
+ def __init__(self):
50
+ self.server = Server("long-term-memory")
51
+ self.db_path = "./memory_db"
52
+ Path(self.db_path).mkdir(exist_ok=True)
53
+
54
+ # Initialize ChromaDB
55
+ self.chroma_client = chromadb.PersistentClient(
56
+ path=self.db_path,
57
+ settings=Settings(anonymized_telemetry=False)
58
+ )
59
+
60
+ # Get or create collection
61
+ try:
62
+ self.collection = self.chroma_client.get_collection("memories")
63
+ except:
64
+ self.collection = self.chroma_client.create_collection(
65
+ name="memories",
66
+ metadata={"description": "Long-term memory storage for conversations"}
67
+ )
68
+
69
+ # Initialize sentence transformer for embeddings
70
+ self.encoder = SentenceTransformer('all-MiniLM-L6-v2')
71
+
72
+ self.setup_handlers()
73
+
74
+ def setup_handlers(self):
75
+ @self.server.list_tools()
76
+ async def handle_list_tools() -> List[Tool]:
77
+ """List available tools."""
78
+ return [
79
+ Tool(
80
+ name="save_memory",
81
+ description="Save important insights, conclusions, or context from conversation to long-term memory",
82
+ inputSchema={
83
+ "type": "object",
84
+ "properties": {
85
+ "content": {
86
+ "type": "string",
87
+ "description": "The content/insight to save to memory"
88
+ },
89
+ "title": {
90
+ "type": "string",
91
+ "description": "A short title/summary for this memory"
92
+ },
93
+ "tags": {
94
+ "type": "array",
95
+ "items": {"type": "string"},
96
+ "description": "Optional tags to categorize this memory",
97
+ "default": []
98
+ },
99
+ "context": {
100
+ "type": "string",
101
+ "description": "Additional context about when/why this was saved",
102
+ "default": ""
103
+ }
104
+ },
105
+ "required": ["content", "title"]
106
+ }
107
+ ),
108
+ Tool(
109
+ name="search_memory",
110
+ description="Search through long-term memory for relevant information",
111
+ inputSchema={
112
+ "type": "object",
113
+ "properties": {
114
+ "query": {
115
+ "type": "string",
116
+ "description": "Search query to find relevant memories"
117
+ },
118
+ "limit": {
119
+ "type": "integer",
120
+ "description": "Maximum number of results to return",
121
+ "default": 5
122
+ },
123
+ "threshold": {
124
+ "type": "number",
125
+ "description": "Similarity threshold (0-1, higher = more similar)",
126
+ "default": 0.3
127
+ }
128
+ },
129
+ "required": ["query"]
130
+ }
131
+ ),
132
+ Tool(
133
+ name="list_memories",
134
+ description="List all stored memories with basic info",
135
+ inputSchema={
136
+ "type": "object",
137
+ "properties": {
138
+ "limit": {
139
+ "type": "integer",
140
+ "description": "Maximum number of memories to return",
141
+ "default": 10
142
+ }
143
+ }
144
+ }
145
+ ),
146
+ Tool(
147
+ name="delete_memory",
148
+ description="Delete a specific memory by ID",
149
+ inputSchema={
150
+ "type": "object",
151
+ "properties": {
152
+ "memory_id": {
153
+ "type": "string",
154
+ "description": "The ID of the memory to delete"
155
+ }
156
+ },
157
+ "required": ["memory_id"]
158
+ }
159
+ )
160
+ ]
161
+
162
+ @self.server.call_tool()
163
+ async def handle_call_tool(name: str, arguments: Dict[str, Any]) -> Sequence[TextContent]:
164
+ """Handle tool calls."""
165
+ try:
166
+ if name == "save_memory":
167
+ return await self._save_memory(**arguments)
168
+ elif name == "search_memory":
169
+ return await self._search_memory(**arguments)
170
+ elif name == "list_memories":
171
+ return await self._list_memories(**arguments)
172
+ elif name == "delete_memory":
173
+ return await self._delete_memory(**arguments)
174
+ else:
175
+ raise ValueError(f"Unknown tool: {name}")
176
+ except Exception as e:
177
+ logger.error(f"Error in tool {name}: {e}")
178
+ return [TextContent(type="text", text=f"Error: {str(e)}")]
179
+
180
+ async def _save_memory(self, content: str, title: str, tags: List[str] = None, context: str = "") -> Sequence[TextContent]:
181
+ """Save content to long-term memory."""
182
+ if tags is None:
183
+ tags = []
184
+
185
+ memory_id = str(uuid.uuid4())
186
+ timestamp = datetime.now().isoformat()
187
+
188
+ # Create embedding
189
+ embedding = self.encoder.encode(f"{title} {content}").tolist()
190
+
191
+ # Prepare metadata
192
+ metadata = {
193
+ "title": title,
194
+ "timestamp": timestamp,
195
+ "tags": json.dumps(tags),
196
+ "context": context,
197
+ "content_length": len(content)
198
+ }
199
+
200
+ # Save to ChromaDB
201
+ self.collection.add(
202
+ documents=[content],
203
+ embeddings=[embedding],
204
+ metadatas=[metadata],
205
+ ids=[memory_id]
206
+ )
207
+
208
+ logger.info(f"Saved memory: {title} (ID: {memory_id})")
209
+
210
+ result = f"✅ Memory saved successfully!\n\n"
211
+ result += f"**ID**: {memory_id}\n"
212
+ result += f"**Title**: {title}\n"
213
+ result += f"**Timestamp**: {timestamp}\n"
214
+ if tags:
215
+ result += f"**Tags**: {', '.join(tags)}\n"
216
+ if context:
217
+ result += f"**Context**: {context}\n"
218
+ result += f"**Content Preview**: {content[:200]}{'...' if len(content) > 200 else ''}"
219
+
220
+ return [TextContent(type="text", text=result)]
221
+
222
+ async def _search_memory(self, query: str, limit: int = 5, threshold: float = 0.3) -> Sequence[TextContent]:
223
+ """Search through memories."""
224
+ if self.collection.count() == 0:
225
+ return [TextContent(type="text", text="No memories stored yet.")]
226
+
227
+ # Create query embedding
228
+ query_embedding = self.encoder.encode(query).tolist()
229
+
230
+ # Search in ChromaDB
231
+ results = self.collection.query(
232
+ query_embeddings=[query_embedding],
233
+ n_results=min(limit, self.collection.count())
234
+ )
235
+
236
+ if not results['documents'][0]:
237
+ return [TextContent(type="text", text="No relevant memories found.")]
238
+
239
+ # Filter by threshold and format results
240
+ response = f"🔍 **Search Results for**: \"{query}\"\n\n"
241
+
242
+ found_relevant = False
243
+ for i, (doc, metadata, distance) in enumerate(zip(
244
+ results['documents'][0],
245
+ results['metadatas'][0],
246
+ results['distances'][0]
247
+ )):
248
+ similarity = 1 - distance
249
+ if similarity >= threshold:
250
+ found_relevant = True
251
+ tags = json.loads(metadata.get('tags', '[]'))
252
+
253
+ response += f"**{i+1}. {metadata['title']}** (Similarity: {similarity:.2f})\n"
254
+ response += f"*Saved*: {metadata['timestamp']}\n"
255
+ if tags:
256
+ response += f"*Tags*: {', '.join(tags)}\n"
257
+ if metadata.get('context'):
258
+ response += f"*Context*: {metadata['context']}\n"
259
+ response += f"*Content*: {doc}\n\n"
260
+ response += "---\n\n"
261
+
262
+ if not found_relevant:
263
+ response += f"No memories found above similarity threshold of {threshold:.2f}"
264
+
265
+ return [TextContent(type="text", text=response)]
266
+
267
+ async def _list_memories(self, limit: int = 10) -> Sequence[TextContent]:
268
+ """List all memories."""
269
+ if self.collection.count() == 0:
270
+ return [TextContent(type="text", text="No memories stored yet.")]
271
+
272
+ # Get all memories (ChromaDB doesn't have a direct "get all" with limit)
273
+ results = self.collection.get()
274
+
275
+ if not results['documents']:
276
+ return [TextContent(type="text", text="No memories found.")]
277
+
278
+ response = f"📚 **All Memories** (showing up to {limit})\n\n"
279
+
280
+ # Sort by timestamp (newest first)
281
+ memories = list(zip(results['ids'], results['documents'], results['metadatas']))
282
+ memories.sort(key=lambda x: x[2]['timestamp'], reverse=True)
283
+
284
+ for i, (memory_id, doc, metadata) in enumerate(memories[:limit]):
285
+ tags = json.loads(metadata.get('tags', '[]'))
286
+
287
+ response += f"**{i+1}. {metadata['title']}**\n"
288
+ response += f"*ID*: {memory_id}\n"
289
+ response += f"*Saved*: {metadata['timestamp']}\n"
290
+ if tags:
291
+ response += f"*Tags*: {', '.join(tags)}\n"
292
+ response += f"*Preview*: {doc[:150]}{'...' if len(doc) > 150 else ''}\n\n"
293
+ response += "---\n\n"
294
+
295
+ if len(memories) > limit:
296
+ response += f"... and {len(memories) - limit} more memories"
297
+
298
+ return [TextContent(type="text", text=response)]
299
+
300
+ async def _delete_memory(self, memory_id: str) -> Sequence[TextContent]:
301
+ """Delete a memory by ID."""
302
+ try:
303
+ # Check if memory exists
304
+ result = self.collection.get(ids=[memory_id])
305
+ if not result['documents']:
306
+ return [TextContent(type="text", text=f"❌ Memory with ID {memory_id} not found.")]
307
+
308
+ # Get memory info before deletion
309
+ metadata = result['metadatas'][0]
310
+ title = metadata['title']
311
+
312
+ # Delete from ChromaDB
313
+ self.collection.delete(ids=[memory_id])
314
+
315
+ logger.info(f"Deleted memory: {title} (ID: {memory_id})")
316
+
317
+ return [TextContent(type="text", text=f"✅ Memory deleted successfully!\n\n**Title**: {title}\n**ID**: {memory_id}")]
318
+
319
+ except Exception as e:
320
+ logger.error(f"Error deleting memory {memory_id}: {e}")
321
+ return [TextContent(type="text", text=f"❌ Error deleting memory: {str(e)}")]
322
+
323
+ async def run(self):
324
+ """Run the MCP server."""
325
+ if not MCP_AVAILABLE:
326
+ print("MCP not available, cannot run server")
327
+ return
328
+
329
+ async with stdio_server() as (read_stream, write_stream):
330
+ await self.server.run(
331
+ read_stream,
332
+ write_stream,
333
+ InitializationOptions(
334
+ server_name="long-term-memory",
335
+ server_version="1.0.0",
336
+ capabilities=self.server.get_capabilities(
337
+ notification_options=None,
338
+ experimental_capabilities=None,
339
+ ),
340
+ ),
341
+ )
342
+
343
+ # Main entry point
344
+ async def main():
345
+ if not MCP_AVAILABLE:
346
+ print("MCP server cannot run without MCP library")
347
+ return
348
+
349
+ server = LongTermMemoryServer()
350
+ await server.run()
351
+
352
+ if __name__ == "__main__":
353
+ asyncio.run(main())
requirements.txt ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Existing dependencies
2
+ gradio>=4.0.0
3
+ mcp>=1.0.0
4
+ chromadb>=0.4.0
5
+ sentence-transformers>=2.2.0
6
+ numpy>=1.24.0
7
+ asyncio-mqtt>=0.11.0
8
+ pydantic>=2.0.0
9
+ typing-extensions>=4.5.0
10
+
11
+ # New dependencies for Ollama integration
12
+ fastapi>=0.104.0
13
+ uvicorn>=0.24.0
14
+ langchain>=0.3.0
15
+ langchain-ollama>=0.2.0
16
+ langchain-community>=0.3.0
17
+ langchainhub>=0.1.0
18
+ requests>=2.31.0
run_mcp_server.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Standalone MCP Server runner
4
+ Use this script to run only the MCP server without Gradio
5
+ """
6
+
7
+ import sys
8
+ import asyncio
9
+ from pathlib import Path
10
+
11
+ # Add current directory to path
12
+ sys.path.insert(0, str(Path(__file__).parent))
13
+
14
+ from mcp_server import main
15
+
16
+ if __name__ == "__main__":
17
+ print("🧠 Starting Long Term Memory MCP Server...")
18
+ asyncio.run(main())