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.
Files changed (65) hide show
  1. memorygraph/__init__.py +50 -0
  2. memorygraph/__main__.py +12 -0
  3. memorygraph/advanced_tools.py +509 -0
  4. memorygraph/analytics/__init__.py +46 -0
  5. memorygraph/analytics/advanced_queries.py +727 -0
  6. memorygraph/backends/__init__.py +21 -0
  7. memorygraph/backends/base.py +179 -0
  8. memorygraph/backends/cloud.py +75 -0
  9. memorygraph/backends/cloud_backend.py +858 -0
  10. memorygraph/backends/factory.py +577 -0
  11. memorygraph/backends/falkordb_backend.py +749 -0
  12. memorygraph/backends/falkordblite_backend.py +746 -0
  13. memorygraph/backends/ladybugdb_backend.py +242 -0
  14. memorygraph/backends/memgraph_backend.py +327 -0
  15. memorygraph/backends/neo4j_backend.py +298 -0
  16. memorygraph/backends/sqlite_fallback.py +463 -0
  17. memorygraph/backends/turso.py +448 -0
  18. memorygraph/cli.py +743 -0
  19. memorygraph/cloud_database.py +297 -0
  20. memorygraph/config.py +295 -0
  21. memorygraph/database.py +933 -0
  22. memorygraph/graph_analytics.py +631 -0
  23. memorygraph/integration/__init__.py +69 -0
  24. memorygraph/integration/context_capture.py +426 -0
  25. memorygraph/integration/project_analysis.py +583 -0
  26. memorygraph/integration/workflow_tracking.py +492 -0
  27. memorygraph/intelligence/__init__.py +59 -0
  28. memorygraph/intelligence/context_retrieval.py +447 -0
  29. memorygraph/intelligence/entity_extraction.py +386 -0
  30. memorygraph/intelligence/pattern_recognition.py +420 -0
  31. memorygraph/intelligence/temporal.py +374 -0
  32. memorygraph/migration/__init__.py +27 -0
  33. memorygraph/migration/manager.py +579 -0
  34. memorygraph/migration/models.py +142 -0
  35. memorygraph/migration/scripts/__init__.py +17 -0
  36. memorygraph/migration/scripts/bitemporal_migration.py +595 -0
  37. memorygraph/migration/scripts/multitenancy_migration.py +452 -0
  38. memorygraph/migration_tools_module.py +146 -0
  39. memorygraph/models.py +684 -0
  40. memorygraph/proactive/__init__.py +46 -0
  41. memorygraph/proactive/outcome_learning.py +444 -0
  42. memorygraph/proactive/predictive.py +410 -0
  43. memorygraph/proactive/session_briefing.py +399 -0
  44. memorygraph/relationships.py +668 -0
  45. memorygraph/server.py +883 -0
  46. memorygraph/sqlite_database.py +1876 -0
  47. memorygraph/tools/__init__.py +59 -0
  48. memorygraph/tools/activity_tools.py +262 -0
  49. memorygraph/tools/memory_tools.py +315 -0
  50. memorygraph/tools/migration_tools.py +181 -0
  51. memorygraph/tools/relationship_tools.py +147 -0
  52. memorygraph/tools/search_tools.py +406 -0
  53. memorygraph/tools/temporal_tools.py +339 -0
  54. memorygraph/utils/__init__.py +10 -0
  55. memorygraph/utils/context_extractor.py +429 -0
  56. memorygraph/utils/error_handling.py +151 -0
  57. memorygraph/utils/export_import.py +425 -0
  58. memorygraph/utils/graph_algorithms.py +200 -0
  59. memorygraph/utils/pagination.py +149 -0
  60. memorygraph/utils/project_detection.py +133 -0
  61. memorygraphmcp-0.11.7.dist-info/METADATA +970 -0
  62. memorygraphmcp-0.11.7.dist-info/RECORD +65 -0
  63. memorygraphmcp-0.11.7.dist-info/WHEEL +4 -0
  64. memorygraphmcp-0.11.7.dist-info/entry_points.txt +2 -0
  65. 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
+ )