memorygraphMCP 0.11.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memorygraph/__init__.py +50 -0
- memorygraph/__main__.py +12 -0
- memorygraph/advanced_tools.py +509 -0
- memorygraph/analytics/__init__.py +46 -0
- memorygraph/analytics/advanced_queries.py +727 -0
- memorygraph/backends/__init__.py +21 -0
- memorygraph/backends/base.py +179 -0
- memorygraph/backends/cloud.py +75 -0
- memorygraph/backends/cloud_backend.py +858 -0
- memorygraph/backends/factory.py +577 -0
- memorygraph/backends/falkordb_backend.py +749 -0
- memorygraph/backends/falkordblite_backend.py +746 -0
- memorygraph/backends/ladybugdb_backend.py +242 -0
- memorygraph/backends/memgraph_backend.py +327 -0
- memorygraph/backends/neo4j_backend.py +298 -0
- memorygraph/backends/sqlite_fallback.py +463 -0
- memorygraph/backends/turso.py +448 -0
- memorygraph/cli.py +743 -0
- memorygraph/cloud_database.py +297 -0
- memorygraph/config.py +295 -0
- memorygraph/database.py +933 -0
- memorygraph/graph_analytics.py +631 -0
- memorygraph/integration/__init__.py +69 -0
- memorygraph/integration/context_capture.py +426 -0
- memorygraph/integration/project_analysis.py +583 -0
- memorygraph/integration/workflow_tracking.py +492 -0
- memorygraph/intelligence/__init__.py +59 -0
- memorygraph/intelligence/context_retrieval.py +447 -0
- memorygraph/intelligence/entity_extraction.py +386 -0
- memorygraph/intelligence/pattern_recognition.py +420 -0
- memorygraph/intelligence/temporal.py +374 -0
- memorygraph/migration/__init__.py +27 -0
- memorygraph/migration/manager.py +579 -0
- memorygraph/migration/models.py +142 -0
- memorygraph/migration/scripts/__init__.py +17 -0
- memorygraph/migration/scripts/bitemporal_migration.py +595 -0
- memorygraph/migration/scripts/multitenancy_migration.py +452 -0
- memorygraph/migration_tools_module.py +146 -0
- memorygraph/models.py +684 -0
- memorygraph/proactive/__init__.py +46 -0
- memorygraph/proactive/outcome_learning.py +444 -0
- memorygraph/proactive/predictive.py +410 -0
- memorygraph/proactive/session_briefing.py +399 -0
- memorygraph/relationships.py +668 -0
- memorygraph/server.py +883 -0
- memorygraph/sqlite_database.py +1876 -0
- memorygraph/tools/__init__.py +59 -0
- memorygraph/tools/activity_tools.py +262 -0
- memorygraph/tools/memory_tools.py +315 -0
- memorygraph/tools/migration_tools.py +181 -0
- memorygraph/tools/relationship_tools.py +147 -0
- memorygraph/tools/search_tools.py +406 -0
- memorygraph/tools/temporal_tools.py +339 -0
- memorygraph/utils/__init__.py +10 -0
- memorygraph/utils/context_extractor.py +429 -0
- memorygraph/utils/error_handling.py +151 -0
- memorygraph/utils/export_import.py +425 -0
- memorygraph/utils/graph_algorithms.py +200 -0
- memorygraph/utils/pagination.py +149 -0
- memorygraph/utils/project_detection.py +133 -0
- memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
- memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
- memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
- memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
- memorygraphmcp-0.11.7.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Search tool handlers for the MCP server.
|
|
3
|
+
|
|
4
|
+
This module contains handlers for search operations:
|
|
5
|
+
- search_memories: Advanced search with filtering and tolerance modes
|
|
6
|
+
- recall_memories: Simplified search with optimal defaults for natural language queries
|
|
7
|
+
- contextual_search: Search only within related memories (scoped search)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import logging
|
|
11
|
+
from typing import Any, Dict, Set, List
|
|
12
|
+
|
|
13
|
+
from mcp.types import CallToolResult, TextContent
|
|
14
|
+
from pydantic import ValidationError
|
|
15
|
+
|
|
16
|
+
from ..database import MemoryDatabase
|
|
17
|
+
from ..models import MemoryType, SearchQuery
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
async def handle_search_memories(
|
|
23
|
+
memory_db: MemoryDatabase,
|
|
24
|
+
arguments: Dict[str, Any]
|
|
25
|
+
) -> CallToolResult:
|
|
26
|
+
"""Handle search_memories tool call.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
memory_db: Database instance for memory operations
|
|
30
|
+
arguments: Tool arguments from MCP call containing:
|
|
31
|
+
- query: Text search query (optional)
|
|
32
|
+
- terms: Multiple search terms (optional)
|
|
33
|
+
- memory_types: Filter by memory types (optional)
|
|
34
|
+
- tags: Filter by tags (optional)
|
|
35
|
+
- project_path: Filter by project path (optional)
|
|
36
|
+
- min_importance: Minimum importance threshold (optional)
|
|
37
|
+
- limit: Maximum results per page (default: 50)
|
|
38
|
+
- offset: Number of results to skip for pagination (default: 0)
|
|
39
|
+
- search_tolerance: Search mode (strict/normal/fuzzy, default: normal)
|
|
40
|
+
- match_mode: Match mode for terms (any/all, default: any)
|
|
41
|
+
- relationship_filter: Filter by relationship types (optional)
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
CallToolResult with formatted search results or error message
|
|
45
|
+
"""
|
|
46
|
+
try:
|
|
47
|
+
# Build search query
|
|
48
|
+
search_query: SearchQuery = SearchQuery(
|
|
49
|
+
query=arguments.get("query"),
|
|
50
|
+
terms=arguments.get("terms", []),
|
|
51
|
+
memory_types=[MemoryType(t) for t in arguments.get("memory_types", [])],
|
|
52
|
+
tags=arguments.get("tags", []),
|
|
53
|
+
project_path=arguments.get("project_path"),
|
|
54
|
+
min_importance=arguments.get("min_importance"),
|
|
55
|
+
limit=arguments.get("limit", 50),
|
|
56
|
+
offset=arguments.get("offset", 0),
|
|
57
|
+
search_tolerance=arguments.get("search_tolerance", "normal"),
|
|
58
|
+
match_mode=arguments.get("match_mode", "any"),
|
|
59
|
+
relationship_filter=arguments.get("relationship_filter")
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
memories = await memory_db.search_memories(search_query)
|
|
63
|
+
|
|
64
|
+
if not memories:
|
|
65
|
+
return CallToolResult(
|
|
66
|
+
content=[TextContent(
|
|
67
|
+
type="text",
|
|
68
|
+
text="No memories found matching the search criteria."
|
|
69
|
+
)]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Format results
|
|
73
|
+
results_text: str = f"Found {len(memories)} memories:\n\n"
|
|
74
|
+
for i, memory in enumerate(memories, 1):
|
|
75
|
+
results_text += f"**{i}. {memory.title}** (ID: {memory.id})\n"
|
|
76
|
+
results_text += f"Type: {memory.type.value} | Importance: {memory.importance}\n"
|
|
77
|
+
results_text += f"Tags: {', '.join(memory.tags) if memory.tags else 'None'}\n"
|
|
78
|
+
if memory.summary:
|
|
79
|
+
results_text += f"Summary: {memory.summary}\n"
|
|
80
|
+
results_text += "\n"
|
|
81
|
+
|
|
82
|
+
return CallToolResult(
|
|
83
|
+
content=[TextContent(type="text", text=results_text)]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
logger.error(f"Invalid value in search memories: {e}")
|
|
88
|
+
return CallToolResult(
|
|
89
|
+
content=[TextContent(
|
|
90
|
+
type="text",
|
|
91
|
+
text=f"Invalid value: {e}"
|
|
92
|
+
)],
|
|
93
|
+
isError=True
|
|
94
|
+
)
|
|
95
|
+
except KeyError as e:
|
|
96
|
+
logger.error(f"Missing required field in search memories: {e}")
|
|
97
|
+
return CallToolResult(
|
|
98
|
+
content=[TextContent(
|
|
99
|
+
type="text",
|
|
100
|
+
text=f"Missing required field: {e}"
|
|
101
|
+
)],
|
|
102
|
+
isError=True
|
|
103
|
+
)
|
|
104
|
+
except ValidationError as e:
|
|
105
|
+
logger.error(f"Invalid search parameters: {e}")
|
|
106
|
+
return CallToolResult(
|
|
107
|
+
content=[TextContent(
|
|
108
|
+
type="text",
|
|
109
|
+
text=f"Invalid search parameters: {e}"
|
|
110
|
+
)],
|
|
111
|
+
isError=True
|
|
112
|
+
)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.error(f"Failed to search memories: {e}")
|
|
115
|
+
return CallToolResult(
|
|
116
|
+
content=[TextContent(
|
|
117
|
+
type="text",
|
|
118
|
+
text=f"Failed to search memories: {e}"
|
|
119
|
+
)],
|
|
120
|
+
isError=True
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
async def handle_recall_memories(
|
|
125
|
+
memory_db: MemoryDatabase,
|
|
126
|
+
arguments: Dict[str, Any]
|
|
127
|
+
) -> CallToolResult:
|
|
128
|
+
"""Handle recall_memories tool call - convenience wrapper around search_memories.
|
|
129
|
+
|
|
130
|
+
This provides a simplified interface optimized for natural language queries with
|
|
131
|
+
best-practice defaults (fuzzy matching, relationship inclusion).
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
memory_db: Database instance for memory operations
|
|
135
|
+
arguments: Tool arguments from MCP call containing:
|
|
136
|
+
- query: Natural language search query (optional)
|
|
137
|
+
- memory_types: Filter by memory types (optional)
|
|
138
|
+
- project_path: Filter by project path (optional)
|
|
139
|
+
- limit: Maximum results per page (default: 20)
|
|
140
|
+
- offset: Number of results to skip for pagination (default: 0)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
CallToolResult with enhanced formatted results or error message
|
|
144
|
+
"""
|
|
145
|
+
try:
|
|
146
|
+
# Build search query with optimal defaults
|
|
147
|
+
search_query: SearchQuery = SearchQuery(
|
|
148
|
+
query=arguments.get("query"),
|
|
149
|
+
memory_types=[MemoryType(t) for t in arguments.get("memory_types", [])],
|
|
150
|
+
project_path=arguments.get("project_path"),
|
|
151
|
+
limit=arguments.get("limit", 20),
|
|
152
|
+
offset=arguments.get("offset", 0),
|
|
153
|
+
search_tolerance="normal", # Always use fuzzy matching
|
|
154
|
+
include_relationships=True # Always include relationships
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Use the existing search_memories implementation
|
|
158
|
+
memories = await memory_db.search_memories(search_query)
|
|
159
|
+
|
|
160
|
+
if not memories:
|
|
161
|
+
return CallToolResult(
|
|
162
|
+
content=[TextContent(
|
|
163
|
+
type="text",
|
|
164
|
+
text="No memories found matching your query. Try:\n- Using different search terms\n- Removing filters to broaden the search\n- Checking if memories have been stored for this topic"
|
|
165
|
+
)]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Format results with enhanced context
|
|
169
|
+
results_text: str = f"**Found {len(memories)} relevant memories:**\n\n"
|
|
170
|
+
|
|
171
|
+
for i, memory in enumerate(memories, 1):
|
|
172
|
+
results_text += f"**{i}. {memory.title}** (ID: {memory.id})\n"
|
|
173
|
+
results_text += f"Type: {memory.type.value} | Importance: {memory.importance}\n"
|
|
174
|
+
|
|
175
|
+
# Add match quality if available
|
|
176
|
+
if hasattr(memory, 'match_info') and memory.match_info:
|
|
177
|
+
match_info = memory.match_info
|
|
178
|
+
if isinstance(match_info, dict):
|
|
179
|
+
quality = match_info.get('match_quality', 'unknown')
|
|
180
|
+
matched_fields = match_info.get('matched_fields', [])
|
|
181
|
+
results_text += f"Match: {quality} quality"
|
|
182
|
+
if matched_fields:
|
|
183
|
+
results_text += f" (in {', '.join(matched_fields)})"
|
|
184
|
+
results_text += "\n"
|
|
185
|
+
|
|
186
|
+
# Add context summary if available
|
|
187
|
+
if hasattr(memory, 'context_summary') and memory.context_summary:
|
|
188
|
+
results_text += f"Context: {memory.context_summary}\n"
|
|
189
|
+
|
|
190
|
+
# Add summary or content snippet
|
|
191
|
+
if memory.summary:
|
|
192
|
+
results_text += f"Summary: {memory.summary}\n"
|
|
193
|
+
elif memory.content:
|
|
194
|
+
# Show first 150 chars of content
|
|
195
|
+
snippet = memory.content[:150]
|
|
196
|
+
if len(memory.content) > 150:
|
|
197
|
+
snippet += "..."
|
|
198
|
+
results_text += f"Content: {snippet}\n"
|
|
199
|
+
|
|
200
|
+
# Add tags
|
|
201
|
+
if memory.tags:
|
|
202
|
+
results_text += f"Tags: {', '.join(memory.tags)}\n"
|
|
203
|
+
|
|
204
|
+
# Add relationships if available
|
|
205
|
+
if hasattr(memory, 'relationships') and memory.relationships:
|
|
206
|
+
rel_summary = []
|
|
207
|
+
for rel_type, related_titles in memory.relationships.items():
|
|
208
|
+
if related_titles:
|
|
209
|
+
rel_summary.append(f"{rel_type}: {len(related_titles)} memories")
|
|
210
|
+
if rel_summary:
|
|
211
|
+
results_text += f"Relationships: {', '.join(rel_summary)}\n"
|
|
212
|
+
|
|
213
|
+
results_text += "\n"
|
|
214
|
+
|
|
215
|
+
# Add helpful tip at the end
|
|
216
|
+
results_text += "\n💡 **Next steps:**\n"
|
|
217
|
+
results_text += "- Use `get_memory(memory_id=\"...\")` to see full details\n"
|
|
218
|
+
results_text += "- Use `get_related_memories(memory_id=\"...\")` to explore connections\n"
|
|
219
|
+
|
|
220
|
+
return CallToolResult(
|
|
221
|
+
content=[TextContent(type="text", text=results_text)]
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
except ValueError as e:
|
|
225
|
+
logger.error(f"Invalid value in recall memories: {e}")
|
|
226
|
+
return CallToolResult(
|
|
227
|
+
content=[TextContent(
|
|
228
|
+
type="text",
|
|
229
|
+
text=f"Invalid value: {e}"
|
|
230
|
+
)],
|
|
231
|
+
isError=True
|
|
232
|
+
)
|
|
233
|
+
except KeyError as e:
|
|
234
|
+
logger.error(f"Missing required field in recall memories: {e}")
|
|
235
|
+
return CallToolResult(
|
|
236
|
+
content=[TextContent(
|
|
237
|
+
type="text",
|
|
238
|
+
text=f"Missing required field: {e}"
|
|
239
|
+
)],
|
|
240
|
+
isError=True
|
|
241
|
+
)
|
|
242
|
+
except ValidationError as e:
|
|
243
|
+
logger.error(f"Invalid search parameters: {e}")
|
|
244
|
+
return CallToolResult(
|
|
245
|
+
content=[TextContent(
|
|
246
|
+
type="text",
|
|
247
|
+
text=f"Invalid search parameters: {e}"
|
|
248
|
+
)],
|
|
249
|
+
isError=True
|
|
250
|
+
)
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.error(f"Failed to recall memories: {e}")
|
|
253
|
+
return CallToolResult(
|
|
254
|
+
content=[TextContent(
|
|
255
|
+
type="text",
|
|
256
|
+
text=f"Failed to recall memories: {e}"
|
|
257
|
+
)],
|
|
258
|
+
isError=True
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
async def handle_contextual_search(
|
|
263
|
+
memory_db: MemoryDatabase,
|
|
264
|
+
arguments: Dict[str, Any]
|
|
265
|
+
) -> CallToolResult:
|
|
266
|
+
"""Handle contextual_search tool call.
|
|
267
|
+
|
|
268
|
+
Search only within the context of a given memory by first finding
|
|
269
|
+
all related memories, then searching only within that related set.
|
|
270
|
+
This provides semantic scoping without embeddings.
|
|
271
|
+
|
|
272
|
+
Args:
|
|
273
|
+
memory_db: Database instance for memory operations
|
|
274
|
+
arguments: Tool arguments from MCP call containing:
|
|
275
|
+
- memory_id: ID of memory to use as context root (required)
|
|
276
|
+
- query: Text search query (required)
|
|
277
|
+
- max_depth: Maximum relationship traversal depth (default: 2)
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
CallToolResult with scoped search results or error message
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
# Validate required parameters
|
|
284
|
+
if "memory_id" not in arguments:
|
|
285
|
+
return CallToolResult(
|
|
286
|
+
content=[TextContent(
|
|
287
|
+
type="text",
|
|
288
|
+
text="Error: 'memory_id' parameter is required"
|
|
289
|
+
)],
|
|
290
|
+
isError=True
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
if "query" not in arguments:
|
|
294
|
+
return CallToolResult(
|
|
295
|
+
content=[TextContent(
|
|
296
|
+
type="text",
|
|
297
|
+
text="Error: 'query' parameter is required"
|
|
298
|
+
)],
|
|
299
|
+
isError=True
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
memory_id: str = arguments["memory_id"]
|
|
303
|
+
query: str = arguments["query"]
|
|
304
|
+
max_depth: int = arguments.get("max_depth", 2)
|
|
305
|
+
|
|
306
|
+
# Phase 1: Find all memories related to the context memory
|
|
307
|
+
related = await memory_db.get_related_memories(
|
|
308
|
+
memory_id=memory_id,
|
|
309
|
+
relationship_types=None, # All relationship types
|
|
310
|
+
max_depth=max_depth
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
if not related:
|
|
314
|
+
return CallToolResult(
|
|
315
|
+
content=[TextContent(
|
|
316
|
+
type="text",
|
|
317
|
+
text=f"No related memories found for context: {memory_id}"
|
|
318
|
+
)]
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Extract IDs of related memories to scope the search
|
|
322
|
+
related_ids: Set[str] = {mem.id for mem, _ in related}
|
|
323
|
+
|
|
324
|
+
# Phase 2: Search only within the related memories
|
|
325
|
+
# Get all memories from search, then filter by related IDs
|
|
326
|
+
search_query: SearchQuery = SearchQuery(
|
|
327
|
+
query=query,
|
|
328
|
+
limit=100, # Get more results to filter
|
|
329
|
+
search_tolerance="normal"
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
all_matches: List = await memory_db.search_memories(search_query)
|
|
333
|
+
|
|
334
|
+
# Filter to only include memories that are in the related set
|
|
335
|
+
contextual_matches: List = [
|
|
336
|
+
mem for mem in all_matches
|
|
337
|
+
if mem.id in related_ids
|
|
338
|
+
]
|
|
339
|
+
|
|
340
|
+
if not contextual_matches:
|
|
341
|
+
return CallToolResult(
|
|
342
|
+
content=[TextContent(
|
|
343
|
+
type="text",
|
|
344
|
+
text=f"No matches found for '{query}' within the context of {memory_id}"
|
|
345
|
+
)]
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
# Format results
|
|
349
|
+
results_text: str = f"**Contextual Search Results:**\n\n"
|
|
350
|
+
results_text += f"Context: {memory_id}\n"
|
|
351
|
+
results_text += f"Query: '{query}'\n"
|
|
352
|
+
results_text += f"Searched within {len(related_ids)} related memories\n"
|
|
353
|
+
results_text += f"Found {len(contextual_matches)} matches:\n\n"
|
|
354
|
+
|
|
355
|
+
for i, memory in enumerate(contextual_matches, 1):
|
|
356
|
+
results_text += f"{i}. **{memory.title}** (ID: {memory.id})\n"
|
|
357
|
+
results_text += f" Type: {memory.type.value} | Importance: {memory.importance}\n"
|
|
358
|
+
|
|
359
|
+
# Add summary or content snippet
|
|
360
|
+
if memory.summary:
|
|
361
|
+
results_text += f" Summary: {memory.summary}\n"
|
|
362
|
+
elif memory.content:
|
|
363
|
+
snippet = memory.content[:150]
|
|
364
|
+
if len(memory.content) > 150:
|
|
365
|
+
snippet += "..."
|
|
366
|
+
results_text += f" Content: {snippet}\n"
|
|
367
|
+
|
|
368
|
+
# Add tags
|
|
369
|
+
if memory.tags:
|
|
370
|
+
results_text += f" Tags: {', '.join(memory.tags)}\n"
|
|
371
|
+
|
|
372
|
+
results_text += "\n"
|
|
373
|
+
|
|
374
|
+
results_text += f"\n💡 Use `get_memory(memory_id=\"...\")` to see full details\n"
|
|
375
|
+
|
|
376
|
+
return CallToolResult(
|
|
377
|
+
content=[TextContent(type="text", text=results_text)]
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
except ValueError as e:
|
|
381
|
+
logger.error(f"Invalid value in contextual search: {e}")
|
|
382
|
+
return CallToolResult(
|
|
383
|
+
content=[TextContent(
|
|
384
|
+
type="text",
|
|
385
|
+
text=f"Invalid value: {e}"
|
|
386
|
+
)],
|
|
387
|
+
isError=True
|
|
388
|
+
)
|
|
389
|
+
except KeyError as e:
|
|
390
|
+
logger.error(f"Missing required field in contextual search: {e}")
|
|
391
|
+
return CallToolResult(
|
|
392
|
+
content=[TextContent(
|
|
393
|
+
type="text",
|
|
394
|
+
text=f"Missing required field: {e}"
|
|
395
|
+
)],
|
|
396
|
+
isError=True
|
|
397
|
+
)
|
|
398
|
+
except Exception as e:
|
|
399
|
+
logger.error(f"Failed to perform contextual search: {e}")
|
|
400
|
+
return CallToolResult(
|
|
401
|
+
content=[TextContent(
|
|
402
|
+
type="text",
|
|
403
|
+
text=f"Failed to perform contextual search: {e}"
|
|
404
|
+
)],
|
|
405
|
+
isError=True
|
|
406
|
+
)
|