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,298 @@
1
+ """
2
+ Neo4j backend implementation for the Claude Code Memory Server.
3
+
4
+ This module provides the Neo4j-specific implementation of the GraphBackend interface,
5
+ wrapping the existing Neo4j connection and query logic.
6
+ """
7
+
8
+ import logging
9
+ import os
10
+ from typing import Any, Optional
11
+
12
+ from neo4j import AsyncGraphDatabase, AsyncDriver
13
+ from neo4j.exceptions import ServiceUnavailable, AuthError, Neo4jError
14
+ from contextlib import asynccontextmanager
15
+
16
+ from .base import GraphBackend
17
+ from ..models import DatabaseConnectionError, SchemaError
18
+ from ..config import Config
19
+
20
+ logger = logging.getLogger(__name__)
21
+
22
+
23
+ class Neo4jBackend(GraphBackend):
24
+ """Neo4j implementation of the GraphBackend interface."""
25
+
26
+ def __init__(
27
+ self,
28
+ uri: Optional[str] = None,
29
+ user: Optional[str] = None,
30
+ password: Optional[str] = None,
31
+ database: str = "neo4j"
32
+ ):
33
+ """
34
+ Initialize Neo4j backend.
35
+
36
+ Args:
37
+ uri: Neo4j database URI (defaults to MEMORY_NEO4J_URI or NEO4J_URI env var)
38
+ user: Database username (defaults to MEMORY_NEO4J_USER or NEO4J_USER env var)
39
+ password: Database password (defaults to MEMORY_NEO4J_PASSWORD or NEO4J_PASSWORD env var)
40
+ database: Database name (defaults to 'neo4j')
41
+
42
+ Raises:
43
+ DatabaseConnectionError: If password is not provided
44
+ """
45
+ self.uri = uri or os.getenv("MEMORY_NEO4J_URI") or os.getenv("NEO4J_URI", "bolt://localhost:7687")
46
+ self.user = user or os.getenv("MEMORY_NEO4J_USER") or os.getenv("NEO4J_USER", "neo4j")
47
+ self.password = password or os.getenv("MEMORY_NEO4J_PASSWORD") or os.getenv("NEO4J_PASSWORD")
48
+ self.database = database
49
+ self.driver: Optional[AsyncDriver] = None
50
+ self._connected = False
51
+
52
+ if not self.password:
53
+ raise DatabaseConnectionError(
54
+ "Neo4j password must be provided via parameter or MEMORY_NEO4J_PASSWORD/NEO4J_PASSWORD env var"
55
+ )
56
+
57
+ async def connect(self) -> bool:
58
+ """
59
+ Establish async connection to Neo4j database.
60
+
61
+ Returns:
62
+ True if connection successful
63
+
64
+ Raises:
65
+ DatabaseConnectionError: If connection fails
66
+ """
67
+ try:
68
+ self.driver = AsyncGraphDatabase.driver(
69
+ self.uri,
70
+ auth=(self.user, self.password),
71
+ max_connection_lifetime=30 * 60, # 30 minutes
72
+ max_connection_pool_size=50,
73
+ connection_acquisition_timeout=30.0
74
+ )
75
+
76
+ # Verify connectivity
77
+ await self.driver.verify_connectivity()
78
+ self._connected = True
79
+ logger.info(f"Successfully connected to Neo4j at {self.uri}")
80
+ return True
81
+
82
+ except ServiceUnavailable as e:
83
+ logger.error(f"Failed to connect to Neo4j: {e}")
84
+ raise DatabaseConnectionError(f"Failed to connect to Neo4j: {e}")
85
+ except AuthError as e:
86
+ logger.error(f"Authentication failed for Neo4j: {e}")
87
+ raise DatabaseConnectionError(f"Authentication failed for Neo4j: {e}")
88
+ except Exception as e:
89
+ logger.error(f"Unexpected error connecting to Neo4j: {e}")
90
+ raise DatabaseConnectionError(f"Unexpected error connecting to Neo4j: {e}")
91
+
92
+ async def disconnect(self) -> None:
93
+ """Close the database connection."""
94
+ if self.driver:
95
+ await self.driver.close()
96
+ self.driver = None
97
+ self._connected = False
98
+ logger.info("Neo4j connection closed")
99
+
100
+ async def execute_query(
101
+ self,
102
+ query: str,
103
+ parameters: Optional[dict[str, Any]] = None,
104
+ write: bool = False
105
+ ) -> list[dict[str, Any]]:
106
+ """
107
+ Execute a Cypher query and return results.
108
+
109
+ Args:
110
+ query: The Cypher query string
111
+ parameters: Query parameters for parameterized queries
112
+ write: Whether this is a write operation (default: False)
113
+
114
+ Returns:
115
+ List of result records as dictionaries
116
+
117
+ Raises:
118
+ DatabaseConnectionError: If not connected or query fails
119
+ """
120
+ if not self._connected or not self.driver:
121
+ raise DatabaseConnectionError("Not connected to Neo4j. Call connect() first.")
122
+
123
+ params = parameters or {}
124
+
125
+ try:
126
+ async with self._session() as session:
127
+ if write:
128
+ result = await session.execute_write(self._run_query_async, query, params)
129
+ else:
130
+ result = await session.execute_read(self._run_query_async, query, params)
131
+ return result
132
+ except Neo4jError as e:
133
+ logger.error(f"Query execution failed: {e}")
134
+ raise DatabaseConnectionError(f"Query execution failed: {e}")
135
+
136
+ @asynccontextmanager
137
+ async def _session(self):
138
+ """Async context manager for Neo4j session."""
139
+ if not self.driver:
140
+ raise DatabaseConnectionError("Not connected to Neo4j. Call connect() first.")
141
+
142
+ session = self.driver.session(database=self.database)
143
+ try:
144
+ yield session
145
+ finally:
146
+ await session.close()
147
+
148
+ @staticmethod
149
+ async def _run_query_async(tx, query: str, parameters: dict[str, Any]) -> list[dict[str, Any]]:
150
+ """
151
+ Helper method to run a query within an async transaction.
152
+
153
+ Args:
154
+ tx: Transaction object
155
+ query: Cypher query string
156
+ parameters: Query parameters
157
+
158
+ Returns:
159
+ List of result records as dictionaries
160
+ """
161
+ result = await tx.run(query, parameters)
162
+ records = await result.data()
163
+ return records
164
+
165
+ async def initialize_schema(self) -> None:
166
+ """
167
+ Initialize database schema including indexes and constraints.
168
+
169
+ Raises:
170
+ SchemaError: If schema initialization fails
171
+ """
172
+ logger.info("Initializing Neo4j schema for Claude Memory...")
173
+
174
+ # Create constraints
175
+ constraints = [
176
+ "CREATE CONSTRAINT memory_id_unique IF NOT EXISTS FOR (m:Memory) REQUIRE m.id IS UNIQUE",
177
+ "CREATE CONSTRAINT relationship_id_unique IF NOT EXISTS FOR (r:RELATIONSHIP) REQUIRE r.id IS UNIQUE",
178
+ ]
179
+
180
+ # Create indexes for performance
181
+ indexes = [
182
+ "CREATE INDEX memory_type_index IF NOT EXISTS FOR (m:Memory) ON (m.type)",
183
+ "CREATE INDEX memory_created_at_index IF NOT EXISTS FOR (m:Memory) ON (m.created_at)",
184
+ "CREATE INDEX memory_tags_index IF NOT EXISTS FOR (m:Memory) ON (m.tags)",
185
+ "CREATE FULLTEXT INDEX memory_content_index IF NOT EXISTS FOR (m:Memory) ON EACH [m.title, m.content, m.summary]",
186
+ "CREATE INDEX memory_importance_index IF NOT EXISTS FOR (m:Memory) ON (m.importance)",
187
+ "CREATE INDEX memory_confidence_index IF NOT EXISTS FOR (m:Memory) ON (m.confidence)",
188
+ "CREATE INDEX memory_project_path_index IF NOT EXISTS FOR (m:Memory) ON (m.context_project_path)",
189
+ ]
190
+
191
+ # Conditional multi-tenant indexes (Phase 1)
192
+ if Config.is_multi_tenant_mode():
193
+ multitenant_indexes = [
194
+ "CREATE INDEX memory_tenant_index IF NOT EXISTS FOR (m:Memory) ON (m.context_tenant_id)",
195
+ "CREATE INDEX memory_team_index IF NOT EXISTS FOR (m:Memory) ON (m.context_team_id)",
196
+ "CREATE INDEX memory_visibility_index IF NOT EXISTS FOR (m:Memory) ON (m.context_visibility)",
197
+ "CREATE INDEX memory_created_by_index IF NOT EXISTS FOR (m:Memory) ON (m.context_created_by)",
198
+ "CREATE INDEX memory_version_index IF NOT EXISTS FOR (m:Memory) ON (m.version)",
199
+ ]
200
+ indexes.extend(multitenant_indexes)
201
+ logger.info("Multi-tenant mode enabled, adding tenant indexes")
202
+
203
+ # Execute schema creation
204
+ for constraint in constraints:
205
+ try:
206
+ await self.execute_query(constraint, write=True)
207
+ logger.debug(f"Created constraint: {constraint}")
208
+ except DatabaseConnectionError as e:
209
+ if "already exists" not in str(e).lower():
210
+ raise SchemaError(f"Failed to create constraint: {e}")
211
+
212
+ for index in indexes:
213
+ try:
214
+ await self.execute_query(index, write=True)
215
+ logger.debug(f"Created index: {index}")
216
+ except DatabaseConnectionError as e:
217
+ if "already exists" not in str(e).lower():
218
+ raise SchemaError(f"Failed to create index: {e}")
219
+
220
+ logger.info("Schema initialization completed")
221
+
222
+ async def health_check(self) -> dict[str, Any]:
223
+ """
224
+ Check backend health and return status information.
225
+
226
+ Returns:
227
+ Dictionary with health check results
228
+ """
229
+ health_info = {
230
+ "connected": self._connected,
231
+ "backend_type": "neo4j",
232
+ "uri": self.uri,
233
+ "database": self.database
234
+ }
235
+
236
+ if self._connected:
237
+ try:
238
+ # Try to get version and basic statistics
239
+ query = """
240
+ CALL dbms.components() YIELD name, versions, edition
241
+ RETURN name, versions[0] as version, edition
242
+ """
243
+ result = await self.execute_query(query, write=False)
244
+ if result:
245
+ health_info["version"] = result[0].get("version", "unknown")
246
+ health_info["edition"] = result[0].get("edition", "unknown")
247
+
248
+ # Get basic node count
249
+ count_query = "MATCH (m:Memory) RETURN count(m) as count"
250
+ count_result = await self.execute_query(count_query, write=False)
251
+ if count_result:
252
+ health_info["statistics"] = {
253
+ "memory_count": count_result[0].get("count", 0)
254
+ }
255
+ except Exception as e:
256
+ logger.warning(f"Could not get detailed health info: {e}")
257
+ health_info["warning"] = str(e)
258
+
259
+ return health_info
260
+
261
+ def backend_name(self) -> str:
262
+ """Return the name of this backend implementation."""
263
+ return "neo4j"
264
+
265
+ def supports_fulltext_search(self) -> bool:
266
+ """Check if this backend supports full-text search."""
267
+ return True
268
+
269
+ def supports_transactions(self) -> bool:
270
+ """Check if this backend supports ACID transactions."""
271
+ return True
272
+
273
+ @classmethod
274
+ async def create(
275
+ cls,
276
+ uri: Optional[str] = None,
277
+ user: Optional[str] = None,
278
+ password: Optional[str] = None,
279
+ database: str = "neo4j"
280
+ ) -> "Neo4jBackend":
281
+ """
282
+ Factory method to create and connect to a Neo4j backend.
283
+
284
+ Args:
285
+ uri: Neo4j database URI
286
+ user: Database username
287
+ password: Database password
288
+ database: Database name
289
+
290
+ Returns:
291
+ Connected Neo4jBackend instance
292
+
293
+ Raises:
294
+ DatabaseConnectionError: If connection fails
295
+ """
296
+ backend = cls(uri, user, password, database)
297
+ await backend.connect()
298
+ return backend