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,242 @@
1
+ """
2
+ LadybugDB backend implementation for the Claude Code Memory Server.
3
+
4
+ This module provides the LadybugDB-specific implementation of the GraphBackend interface.
5
+ LadybugDB is a graph database that uses Cypher queries, similar to Kuzu.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ from typing import Any, Optional, List, Tuple, Dict
11
+ from pathlib import Path
12
+
13
+ try:
14
+ import real_ladybug as lb
15
+ LADYBUGDB_AVAILABLE = True
16
+ except ImportError:
17
+ lb = None # type: ignore
18
+ LADYBUGDB_AVAILABLE = False
19
+
20
+ from .base import GraphBackend
21
+ from ..models import (
22
+ Memory,
23
+ MemoryType,
24
+ Relationship,
25
+ RelationshipType,
26
+ RelationshipProperties,
27
+ SearchQuery,
28
+ MemoryContext,
29
+ MemoryNode,
30
+ DatabaseConnectionError,
31
+ SchemaError,
32
+ ValidationError,
33
+ RelationshipError,
34
+ )
35
+ from ..config import Config
36
+ from datetime import datetime, timezone
37
+ import uuid
38
+ import json
39
+
40
+ logger = logging.getLogger(__name__)
41
+
42
+
43
+ class LadybugDBBackend(GraphBackend):
44
+ """LadybugDB implementation of the GraphBackend interface."""
45
+
46
+ def __init__(self, db_path: Optional[str] = None, graph_name: str = "memorygraph"):
47
+ """
48
+ Initialize LadybugDB backend.
49
+
50
+ Args:
51
+ db_path: Path to database file (defaults to LADYBUGDB_PATH env var or ~/.memorygraph/ladybugdb.db)
52
+ graph_name: Name of the graph database (defaults to 'memorygraph')
53
+
54
+ Raises:
55
+ ImportError: If real_ladybug package is not installed.
56
+ """
57
+ if not LADYBUGDB_AVAILABLE:
58
+ raise ImportError(
59
+ "LadybugDB backend requires real_ladybug package. "
60
+ "Install it with: pip install real-ladybug"
61
+ )
62
+ if db_path is None:
63
+ db_path = os.getenv("LADYBUGDB_PATH")
64
+ if db_path is None:
65
+ # Default to ~/.memorygraph/ladybugdb.db
66
+ home = Path.home()
67
+ db_dir = home / ".memorygraph"
68
+ db_dir.mkdir(parents=True, exist_ok=True)
69
+ db_path = str(db_dir / "ladybugdb.db")
70
+
71
+ self.db_path = db_path
72
+ self.graph_name = graph_name
73
+ self.client = None
74
+ self.graph = None
75
+ self._connected = False
76
+
77
+ async def connect(self) -> bool:
78
+ """
79
+ Establish connection to LadybugDB database.
80
+
81
+ Returns:
82
+ True if connection successful
83
+
84
+ Raises:
85
+ DatabaseConnectionError: If connection fails
86
+ """
87
+ try:
88
+ # Create LadybugDB database
89
+ self.client = lb.Database(self.db_path)
90
+
91
+ # Create connection for executing queries
92
+ self.graph = lb.Connection(self.client)
93
+ self._connected = True
94
+
95
+ logger.info(f"Successfully connected to LadybugDB at {self.db_path}")
96
+ return True
97
+
98
+ except Exception as e:
99
+ logger.error(f"Failed to connect to LadybugDB: {e}")
100
+ raise DatabaseConnectionError(f"Failed to connect to LadybugDB: {e}")
101
+
102
+ async def disconnect(self) -> None:
103
+ """
104
+ Close the LadybugDB connection and clean up resources.
105
+ """
106
+ if self.graph:
107
+ self.graph.close()
108
+ self.graph = None
109
+ if self.client:
110
+ self.client.close()
111
+ self.client = None
112
+ self._connected = False
113
+ logger.info("Disconnected from LadybugDB")
114
+
115
+ async def execute_query(
116
+ self,
117
+ query: str,
118
+ parameters: Optional[dict[str, Any]] = None,
119
+ write: bool = False,
120
+ ) -> list[dict[str, Any]]:
121
+ """
122
+ Execute a Cypher query and return results.
123
+
124
+ Args:
125
+ query: Cypher query string
126
+ parameters: Query parameters
127
+ write: Whether this is a write operation
128
+
129
+ Returns:
130
+ List of result dictionaries
131
+ """
132
+ if not self._connected or not self.graph:
133
+ raise DatabaseConnectionError("Not connected to LadybugDB")
134
+
135
+ try:
136
+ # Execute query using LadybugDB's connection
137
+ result = self.graph.execute(query)
138
+
139
+ # Convert result to list of dictionaries
140
+ # LadybugDB returns QueryResult with has_next()/get_next() methods
141
+ # get_next() returns the row data as a dictionary
142
+ rows = []
143
+ while result.has_next():
144
+ row_data = result.get_next()
145
+ rows.append(row_data)
146
+
147
+ return rows
148
+
149
+ except Exception as e:
150
+ logger.error(f"Query execution failed: {e}")
151
+ raise SchemaError(f"Query execution failed: {e}")
152
+
153
+ async def initialize_schema(self) -> None:
154
+ """
155
+ Initialize database schema including indexes and constraints.
156
+
157
+ This should be idempotent and safe to call multiple times.
158
+
159
+ Raises:
160
+ SchemaError: If schema initialization fails
161
+ """
162
+ if not self._connected or not self.graph:
163
+ raise DatabaseConnectionError("Not connected to LadybugDB")
164
+
165
+ try:
166
+ # Create basic schema - indexes and constraints
167
+ # Note: LadybugDB Cypher syntax may vary, adjust as needed
168
+ schema_queries = [
169
+ "CREATE INDEX IF NOT EXISTS FOR (n:Memory) ON (n.id)",
170
+ "CREATE INDEX IF NOT EXISTS FOR (n:Memory) ON (n.type)",
171
+ "CREATE INDEX IF NOT EXISTS FOR (n:Memory) ON (n.created_at)",
172
+ "CREATE CONSTRAINT IF NOT EXISTS FOR (n:Memory) REQUIRE n.id IS UNIQUE",
173
+ ]
174
+
175
+ for query in schema_queries:
176
+ await self.execute_query(query, write=True)
177
+
178
+ except Exception as e:
179
+ logger.error(f"Schema initialization failed: {e}")
180
+ raise SchemaError(f"Schema initialization failed: {e}")
181
+
182
+ async def health_check(self) -> dict[str, Any]:
183
+ """
184
+ Check backend health and return status information.
185
+
186
+ Returns:
187
+ Dictionary with health check results
188
+ """
189
+ health_info = {
190
+ "connected": self._connected,
191
+ "backend_type": "ladybugdb",
192
+ "backend_name": self.backend_name(),
193
+ "supports_fulltext_search": self.supports_fulltext_search(),
194
+ "supports_transactions": self.supports_transactions(),
195
+ }
196
+
197
+ if self._connected and self.graph:
198
+ try:
199
+ # Simple health check query
200
+ result = await self.execute_query("RETURN 'healthy' as status")
201
+ health_info["status"] = result[0]["status"] if result else "unknown"
202
+ health_info["healthy"] = True
203
+ except Exception as e:
204
+ health_info["healthy"] = False
205
+ health_info["error"] = str(e)
206
+ else:
207
+ health_info["healthy"] = False
208
+ health_info["error"] = "Not connected"
209
+
210
+ return health_info
211
+
212
+ def backend_name(self) -> str:
213
+ """
214
+ Return the name of this backend implementation.
215
+
216
+ Returns:
217
+ Backend name
218
+ """
219
+ return "ladybugdb"
220
+
221
+ def supports_fulltext_search(self) -> bool:
222
+ """
223
+ Check if this backend supports full-text search.
224
+
225
+ Returns:
226
+ True if full-text search is supported
227
+ """
228
+ # LadybugDB may support full-text search, but we'll be conservative
229
+ return False
230
+
231
+ def supports_transactions(self) -> bool:
232
+ """
233
+ Check if this backend supports ACID transactions.
234
+
235
+ Returns:
236
+ True if transactions are supported
237
+ """
238
+ # LadybugDB likely supports transactions
239
+ return True
240
+
241
+ # Additional methods would be implemented here following the GraphBackend interface
242
+ # For brevity, only the core methods are shown
@@ -0,0 +1,327 @@
1
+ """
2
+ Memgraph backend implementation for the Claude Code Memory Server.
3
+
4
+ This module provides Memgraph-specific implementation of the GraphBackend interface.
5
+ Memgraph uses the Bolt protocol and Cypher, so it can use the same driver as Neo4j
6
+ with some Cypher dialect adaptations.
7
+ """
8
+
9
+ import logging
10
+ import os
11
+ from typing import Any, Optional
12
+
13
+ from neo4j import AsyncGraphDatabase, AsyncDriver
14
+ from neo4j.exceptions import ServiceUnavailable, AuthError, Neo4jError
15
+ from contextlib import asynccontextmanager
16
+
17
+ from .base import GraphBackend
18
+ from ..models import DatabaseConnectionError, SchemaError
19
+ from ..config import Config
20
+
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ class MemgraphBackend(GraphBackend):
25
+ """Memgraph implementation of the GraphBackend interface."""
26
+
27
+ def __init__(
28
+ self,
29
+ uri: Optional[str] = None,
30
+ user: str = "",
31
+ password: str = "",
32
+ database: str = "memgraph"
33
+ ):
34
+ """
35
+ Initialize Memgraph backend.
36
+
37
+ Args:
38
+ uri: Memgraph database URI (defaults to MEMORY_MEMGRAPH_URI env var)
39
+ user: Database username (Memgraph Community has no auth by default)
40
+ password: Database password (empty for Community Edition)
41
+ database: Database name (default: 'memgraph')
42
+
43
+ Note:
44
+ Memgraph Community Edition has no authentication by default.
45
+ Enterprise Edition supports authentication.
46
+ """
47
+ self.uri = uri or os.getenv("MEMORY_MEMGRAPH_URI", "bolt://localhost:7687")
48
+ self.user = user or os.getenv("MEMORY_MEMGRAPH_USER", "")
49
+ self.password = password or os.getenv("MEMORY_MEMGRAPH_PASSWORD", "")
50
+ self.database = database
51
+ self.driver: Optional[AsyncDriver] = None
52
+ self._connected = False
53
+
54
+ async def connect(self) -> bool:
55
+ """
56
+ Establish async connection to Memgraph database.
57
+
58
+ Returns:
59
+ True if connection successful
60
+
61
+ Raises:
62
+ DatabaseConnectionError: If connection fails
63
+ """
64
+ try:
65
+ # Memgraph uses same Bolt protocol as Neo4j
66
+ # Community Edition: auth is typically empty tuple or ("", "")
67
+ auth = (self.user, self.password) if self.user or self.password else None
68
+
69
+ self.driver = AsyncGraphDatabase.driver(
70
+ self.uri,
71
+ auth=auth,
72
+ max_connection_lifetime=30 * 60,
73
+ max_connection_pool_size=50,
74
+ connection_acquisition_timeout=30.0
75
+ )
76
+
77
+ # Verify connectivity
78
+ await self.driver.verify_connectivity()
79
+ self._connected = True
80
+ logger.info(f"Successfully connected to Memgraph at {self.uri}")
81
+ return True
82
+
83
+ except ServiceUnavailable as e:
84
+ logger.error(f"Failed to connect to Memgraph: {e}")
85
+ raise DatabaseConnectionError(f"Failed to connect to Memgraph: {e}")
86
+ except AuthError as e:
87
+ logger.error(f"Authentication failed for Memgraph: {e}")
88
+ raise DatabaseConnectionError(f"Authentication failed for Memgraph: {e}")
89
+ except Exception as e:
90
+ logger.error(f"Unexpected error connecting to Memgraph: {e}")
91
+ raise DatabaseConnectionError(f"Unexpected error connecting to Memgraph: {e}")
92
+
93
+ async def disconnect(self) -> None:
94
+ """Close the database connection."""
95
+ if self.driver:
96
+ await self.driver.close()
97
+ self.driver = None
98
+ self._connected = False
99
+ logger.info("Memgraph connection closed")
100
+
101
+ async def execute_query(
102
+ self,
103
+ query: str,
104
+ parameters: Optional[dict[str, Any]] = None,
105
+ write: bool = False
106
+ ) -> list[dict[str, Any]]:
107
+ """
108
+ Execute a Cypher query and return results.
109
+
110
+ Args:
111
+ query: The Cypher query string
112
+ parameters: Query parameters for parameterized queries
113
+ write: Whether this is a write operation (default: False)
114
+
115
+ Returns:
116
+ List of result records as dictionaries
117
+
118
+ Raises:
119
+ DatabaseConnectionError: If not connected or query fails
120
+ """
121
+ if not self._connected or not self.driver:
122
+ raise DatabaseConnectionError("Not connected to Memgraph. Call connect() first.")
123
+
124
+ params = parameters or {}
125
+
126
+ # Adapt Cypher for Memgraph dialect differences
127
+ adapted_query = self._adapt_cypher(query)
128
+
129
+ try:
130
+ async with self._session() as session:
131
+ # Memgraph doesn't distinguish between read/write transactions in the same way
132
+ # We'll use execute_write for both to ensure consistency
133
+ result = await session.execute_write(self._run_query_async, adapted_query, params)
134
+ return result
135
+ except Neo4jError as e:
136
+ logger.error(f"Query execution failed: {e}")
137
+ raise DatabaseConnectionError(f"Query execution failed: {e}")
138
+
139
+ @asynccontextmanager
140
+ async def _session(self):
141
+ """Async context manager for Memgraph session."""
142
+ if not self.driver:
143
+ raise DatabaseConnectionError("Not connected to Memgraph. Call connect() first.")
144
+
145
+ session = self.driver.session()
146
+ try:
147
+ yield session
148
+ finally:
149
+ await session.close()
150
+
151
+ @staticmethod
152
+ async def _run_query_async(tx, query: str, parameters: dict[str, Any]) -> list[dict[str, Any]]:
153
+ """
154
+ Helper method to run a query within an async transaction.
155
+
156
+ Args:
157
+ tx: Transaction object
158
+ query: Cypher query string
159
+ parameters: Query parameters
160
+
161
+ Returns:
162
+ List of result records as dictionaries
163
+ """
164
+ result = await tx.run(query, parameters)
165
+ records = await result.data()
166
+ return records
167
+
168
+ def _adapt_cypher(self, query: str) -> str:
169
+ """
170
+ Adapt Cypher query for Memgraph dialect differences.
171
+
172
+ Args:
173
+ query: Original Cypher query
174
+
175
+ Returns:
176
+ Adapted query for Memgraph
177
+
178
+ Note:
179
+ Main differences:
180
+ - FULLTEXT INDEX syntax is different
181
+ - Some constraint syntax differs
182
+ - CALL dbms.* procedures may not be available
183
+ """
184
+ # Memgraph uses CREATE TEXT INDEX instead of CREATE FULLTEXT INDEX
185
+ # But it doesn't support fulltext the same way, so we skip it
186
+ if "CREATE FULLTEXT INDEX" in query:
187
+ logger.debug(f"Skipping fulltext index creation for Memgraph (not fully supported)")
188
+ return "RETURN 1" # No-op query
189
+
190
+ # Memgraph uses different constraint syntax pre-v2.11
191
+ # But modern Memgraph should support standard syntax
192
+ return query
193
+
194
+ async def initialize_schema(self) -> None:
195
+ """
196
+ Initialize database schema including indexes and constraints.
197
+
198
+ Raises:
199
+ SchemaError: If schema initialization fails
200
+ """
201
+ logger.info("Initializing Memgraph schema for Claude Memory...")
202
+
203
+ # Create constraints (Memgraph syntax)
204
+ constraints = [
205
+ "CREATE CONSTRAINT ON (m:Memory) ASSERT m.id IS UNIQUE",
206
+ # Note: Relationship constraints may not be supported in all Memgraph versions
207
+ ]
208
+
209
+ # Create indexes for performance
210
+ indexes = [
211
+ "CREATE INDEX ON :Memory(type)",
212
+ "CREATE INDEX ON :Memory(created_at)",
213
+ "CREATE INDEX ON :Memory(tags)",
214
+ "CREATE INDEX ON :Memory(importance)",
215
+ "CREATE INDEX ON :Memory(confidence)",
216
+ # Note: Memgraph doesn't support multi-property indexes the same way
217
+ ]
218
+
219
+ # Conditional multi-tenant indexes (Phase 1)
220
+ if Config.is_multi_tenant_mode():
221
+ multitenant_indexes = [
222
+ "CREATE INDEX ON :Memory(context_tenant_id)",
223
+ "CREATE INDEX ON :Memory(context_team_id)",
224
+ "CREATE INDEX ON :Memory(context_visibility)",
225
+ "CREATE INDEX ON :Memory(context_created_by)",
226
+ "CREATE INDEX ON :Memory(version)",
227
+ ]
228
+ indexes.extend(multitenant_indexes)
229
+ logger.info("Multi-tenant mode enabled, adding tenant indexes")
230
+
231
+ # Execute schema creation
232
+ for constraint in constraints:
233
+ try:
234
+ await self.execute_query(constraint, write=True)
235
+ logger.debug(f"Created constraint: {constraint}")
236
+ except DatabaseConnectionError as e:
237
+ # Memgraph may not support all constraint types
238
+ if "already exists" not in str(e).lower() and "not supported" not in str(e).lower():
239
+ logger.warning(f"Failed to create constraint (may not be supported): {e}")
240
+
241
+ for index in indexes:
242
+ try:
243
+ await self.execute_query(index, write=True)
244
+ logger.debug(f"Created index: {index}")
245
+ except DatabaseConnectionError as e:
246
+ if "already exists" not in str(e).lower():
247
+ logger.warning(f"Failed to create index: {e}")
248
+
249
+ logger.info("Schema initialization completed")
250
+
251
+ async def health_check(self) -> dict[str, Any]:
252
+ """
253
+ Check backend health and return status information.
254
+
255
+ Returns:
256
+ Dictionary with health check results
257
+ """
258
+ health_info = {
259
+ "connected": self._connected,
260
+ "backend_type": "memgraph",
261
+ "uri": self.uri,
262
+ "database": self.database
263
+ }
264
+
265
+ if self._connected:
266
+ try:
267
+ # Get basic node count
268
+ count_query = "MATCH (m:Memory) RETURN count(m) as count"
269
+ count_result = await self.execute_query(count_query, write=False)
270
+ if count_result:
271
+ health_info["statistics"] = {
272
+ "memory_count": count_result[0].get("count", 0)
273
+ }
274
+
275
+ # Try to get Memgraph version (if available)
276
+ # Note: Memgraph may not have dbms.components()
277
+ health_info["version"] = "unknown"
278
+ except Exception as e:
279
+ logger.warning(f"Could not get detailed health info: {e}")
280
+ health_info["warning"] = str(e)
281
+
282
+ return health_info
283
+
284
+ def backend_name(self) -> str:
285
+ """Return the name of this backend implementation."""
286
+ return "memgraph"
287
+
288
+ def supports_fulltext_search(self) -> bool:
289
+ """
290
+ Check if this backend supports full-text search.
291
+
292
+ Note:
293
+ Memgraph has limited full-text search support compared to Neo4j.
294
+ Text indexing is available but not full FULLTEXT INDEX functionality.
295
+ """
296
+ return False # Limited support
297
+
298
+ def supports_transactions(self) -> bool:
299
+ """Check if this backend supports ACID transactions."""
300
+ return True
301
+
302
+ @classmethod
303
+ async def create(
304
+ cls,
305
+ uri: Optional[str] = None,
306
+ user: str = "",
307
+ password: str = "",
308
+ database: str = "memgraph"
309
+ ) -> "MemgraphBackend":
310
+ """
311
+ Factory method to create and connect to a Memgraph backend.
312
+
313
+ Args:
314
+ uri: Memgraph database URI
315
+ user: Database username
316
+ password: Database password
317
+ database: Database name
318
+
319
+ Returns:
320
+ Connected MemgraphBackend instance
321
+
322
+ Raises:
323
+ DatabaseConnectionError: If connection fails
324
+ """
325
+ backend = cls(uri, user, password, database)
326
+ await backend.connect()
327
+ return backend