forked from pierre-cheneau/vector-memory-mcp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
313 lines (257 loc) · 10 KB
/
main.py
File metadata and controls
313 lines (257 loc) · 10 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#!/usr/bin/env -S uv run --script
# -*- coding: utf-8 -*-
# /// script
# dependencies = [
# "mcp>=0.3.0",
# "sqlite-vec>=0.1.6",
# "sentence-transformers>=2.2.2"
# ]
# requires-python = ">=3.8"
# ///
"""
Vector Memory MCP Server - Main Entry Point
===========================================
A secure, vector-based memory server using sqlite-vec for semantic search.
Stores and retrieves coding memories, experiences, and knowledge using
384-dimensional embeddings generated by sentence-transformers.
Usage:
python main.py --working-dir /path/to/project
Memory files stored in: {working_dir}/memory/vector_memory.db
"""
import sys
from pathlib import Path
from typing import Dict, Any
# Add src to path for imports
sys.path.insert(0, str(Path(__file__).parent / "src"))
from mcp.server.fastmcp import FastMCP
# Import our modules
from src.models import Config
from src.security import validate_working_dir, SecurityError
from src.memory_store import VectorMemoryStore
def get_working_dir() -> Path:
"""Get working directory from command line arguments"""
if len(sys.argv) >= 3 and sys.argv[1] == "--working-dir":
return validate_working_dir(sys.argv[2])
else:
# Default to current directory
return validate_working_dir(".")
def create_server() -> FastMCP:
"""Create and configure the MCP server"""
# Initialize global memory store
try:
memory_dir = get_working_dir()
db_path = memory_dir / Config.DB_NAME
memory_store = VectorMemoryStore(db_path)
print(f"Memory database initialized: {db_path}", file=sys.stderr)
except Exception as e:
print(f"Failed to initialize memory store: {e}", file=sys.stderr)
sys.exit(1)
# Create FastMCP server
mcp = FastMCP(Config.SERVER_NAME)
# ===============================================================================
# MCP TOOLS IMPLEMENTATION
# ===============================================================================
@mcp.tool()
def store_memory(
content: str,
category: str = "other",
tags: list[str] = None
) -> dict[str, Any]:
"""
Store a coding memory, experience, or knowledge with automatic vector embedding.
Use this to remember:
- Code solutions and patterns you've implemented
- Bug fixes and debugging approaches
- Architecture decisions and rationale
- Tool usage and configuration tips
- Performance optimizations
- Security considerations
- Learning insights
Args:
content: The memory content (max 10,000 characters)
category: Memory category - one of: code-solution, bug-fix, architecture,
learning, tool-usage, debugging, performance, security, other
tags: List of relevant tags for organization (max 10 tags)
Returns:
Dict with success status, memory_id, and metadata
"""
try:
if tags is None:
tags = []
result = memory_store.store_memory(content, category, tags)
return result
except SecurityError as e:
return {
"success": False,
"error": "Security validation failed",
"message": str(e)
}
except Exception as e:
return {
"success": False,
"error": "Storage failed",
"message": str(e)
}
@mcp.tool()
def search_memories(
query: str,
limit: int = 10,
category: str = None
) -> dict[str, Any]:
"""
Search stored memories using semantic similarity.
This performs vector-based semantic search to find memories most relevant
to your query, even if they don't contain exact keyword matches.
Examples:
- "authentication patterns" -> finds JWT, OAuth, login implementations
- "async error handling" -> finds Promise, async/await, error catching
- "database optimization" -> finds indexing, query performance, caching
Args:
query: Search query describing what you're looking for
limit: Maximum number of results to return (1-50, default: 10)
category: Optional category filter (code-solution, bug-fix, etc.)
Returns:
Dict with search results including content, similarity scores, and metadata
"""
try:
search_results = memory_store.search_memories(query, limit, category)
if not search_results:
return {
"success": True,
"results": [],
"message": "No matching memories found. Try different keywords or broader terms."
}
# Convert SearchResult objects to dictionaries
results = [result.to_dict() for result in search_results]
return {
"success": True,
"query": query,
"results": results,
"count": len(results),
"message": f"Found {len(results)} relevant memories"
}
except SecurityError as e:
return {
"success": False,
"error": "Security validation failed",
"message": str(e)
}
except Exception as e:
return {
"success": False,
"error": "Search failed",
"message": str(e)
}
@mcp.tool()
def list_recent_memories(limit: int = 10) -> dict[str, Any]:
"""
List recently stored memories in chronological order.
Useful for reviewing what you've recently learned or stored,
and for getting familiar with the type of content in your memory.
Args:
limit: Maximum number of recent memories to return (1-50, default: 10)
Returns:
Dict with list of recent memories and their metadata
"""
try:
limit = min(max(1, limit), Config.MAX_MEMORIES_PER_SEARCH)
memories = memory_store.get_recent_memories(limit)
# Convert MemoryEntry objects to dictionaries
memory_dicts = [memory.to_dict() for memory in memories]
return {
"success": True,
"memories": memory_dicts,
"count": len(memory_dicts),
"message": f"Retrieved {len(memory_dicts)} recent memories"
}
except Exception as e:
return {
"success": False,
"error": "Failed to get recent memories",
"message": str(e)
}
@mcp.tool()
def get_memory_stats() -> dict[str, Any]:
"""
Get comprehensive statistics about the memory database.
Provides insights into:
- Total number of stored memories
- Memory usage and limits
- Category breakdown
- Recent activity
- Most accessed memories
- Database size and health
Returns:
Dict with detailed statistics and health information
"""
try:
stats = memory_store.get_stats()
result = stats.to_dict()
result["success"] = True
return result
except Exception as e:
return {
"success": False,
"error": "Failed to get statistics",
"message": str(e)
}
@mcp.tool()
def clear_old_memories(
days_old: int = 30,
max_to_keep: int = 1000
) -> dict[str, Any]:
"""
Clear old, less frequently accessed memories to free space.
This tool helps maintain database performance by removing older memories
that haven't been accessed recently. It prioritizes keeping frequently
accessed and recent memories.
Args:
days_old: Only consider memories older than this many days (default: 30)
max_to_keep: Maximum total memories to keep in database (default: 1000)
Returns:
Dict with cleanup results and statistics
"""
try:
if days_old < 1:
return {
"success": False,
"error": "Invalid parameter",
"message": "days_old must be at least 1"
}
result = memory_store.clear_old_memories(days_old, max_to_keep)
return result
except SecurityError as e:
return {
"success": False,
"error": "Security validation failed",
"message": str(e)
}
except Exception as e:
return {
"success": False,
"error": "Cleanup failed",
"message": str(e)
}
return mcp
def main():
"""Main entry point"""
print(f"Starting {Config.SERVER_NAME} v{Config.SERVER_VERSION}", file=sys.stderr)
try:
# Get working directory info
memory_dir = get_working_dir()
db_path = memory_dir / Config.DB_NAME
print(f"Working directory: {memory_dir.parent}", file=sys.stderr)
print(f"Memory database: {db_path}", file=sys.stderr)
print(f"Embedding model: {Config.EMBEDDING_MODEL}", file=sys.stderr)
print("=" * 50, file=sys.stderr)
# Create and run server
server = create_server()
print("Server ready for connections...", file=sys.stderr)
server.run()
except KeyboardInterrupt:
print("\nServer stopped by user", file=sys.stderr)
except Exception as e:
print(f"Server failed to start: {e}", file=sys.stderr)
sys.exit(1)
if __name__ == "__main__":
main()