mcp-sqlite-memory-bank 1.4.2__py3-none-any.whl → 1.5.0__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.
@@ -0,0 +1,68 @@
1
+ """
2
+ Tools module for SQLite Memory Bank MCP server.
3
+
4
+ This module organizes the various MCP tools into logical categories:
5
+ - analytics: Content analysis and health assessment tools
6
+ - search: Intelligent search and discovery tools
7
+ - basic: Core CRUD operations and table management
8
+ """
9
+
10
+ # Import all tools to make them available at the package level
11
+ from .analytics import (
12
+ analyze_memory_patterns,
13
+ get_content_health_score,
14
+ )
15
+ from .search import (
16
+ search_content,
17
+ explore_tables,
18
+ add_embeddings,
19
+ semantic_search,
20
+ find_related,
21
+ smart_search,
22
+ embedding_stats,
23
+ auto_semantic_search,
24
+ auto_smart_search,
25
+ )
26
+ from .basic import (
27
+ create_table,
28
+ list_tables,
29
+ describe_table,
30
+ drop_table,
31
+ rename_table,
32
+ create_row,
33
+ read_rows,
34
+ update_rows,
35
+ delete_rows,
36
+ run_select_query,
37
+ list_all_columns,
38
+ )
39
+
40
+ __all__ = [
41
+ # Analytics tools
42
+ 'analyze_memory_patterns',
43
+ 'get_content_health_score',
44
+
45
+ # Search tools
46
+ 'search_content',
47
+ 'explore_tables',
48
+ 'add_embeddings',
49
+ 'semantic_search',
50
+ 'find_related',
51
+ 'smart_search',
52
+ 'embedding_stats',
53
+ 'auto_semantic_search',
54
+ 'auto_smart_search',
55
+
56
+ # Basic tools
57
+ 'create_table',
58
+ 'list_tables',
59
+ 'describe_table',
60
+ 'drop_table',
61
+ 'rename_table',
62
+ 'create_row',
63
+ 'read_rows',
64
+ 'update_rows',
65
+ 'delete_rows',
66
+ 'run_select_query',
67
+ 'list_all_columns',
68
+ ]
@@ -0,0 +1,419 @@
1
+ """
2
+ Analytics and Content Analysis Tools for SQLite Memory Bank
3
+ ===========================================================
4
+
5
+ This module contains tools for analyzing memory bank content, assessing health,
6
+ and providing insights for better knowledge organization.
7
+ """
8
+
9
+ import logging
10
+ from typing import Dict, Optional, List, cast, Any
11
+ from fastmcp import FastMCP
12
+
13
+ from ..database import get_database
14
+ from ..semantic import is_semantic_search_available
15
+ from ..types import ToolResponse
16
+ from ..utils import catch_errors
17
+
18
+
19
+ @catch_errors
20
+ def analyze_memory_patterns() -> ToolResponse:
21
+ """
22
+ 🔍 **MEMORY PATTERN ANALYSIS** - Discover insights in your memory bank!
23
+
24
+ Analyzes content patterns, usage statistics, and identifies opportunities
25
+ for better organization and knowledge discovery.
26
+
27
+ Returns:
28
+ ToolResponse: On success: {"success": True, "analysis": dict}
29
+ On error: {"success": False, "error": str, "category": str, "details": dict}
30
+
31
+ Examples:
32
+ >>> analyze_memory_patterns()
33
+ {"success": True, "analysis": {
34
+ "content_distribution": {"technical_decisions": 15, "project_notes": 8},
35
+ "text_density": {"high": ["documentation"], "low": ["metadata"]},
36
+ "semantic_readiness": {"ready": 2, "needs_setup": 1},
37
+ "recommendations": ["Consider embedding setup for 'notes' table"]
38
+ }}
39
+
40
+ FastMCP Tool Info:
41
+ - **CONTENT INSIGHTS**: Analyzes distribution and quality of stored content
42
+ - **SEMANTIC READINESS**: Shows which tables are ready for semantic search
43
+ - **ORGANIZATION TIPS**: Suggests improvements for better knowledge discovery
44
+ - **USAGE PATTERNS**: Identifies most and least used content areas
45
+ """
46
+ try:
47
+ from .. import server
48
+ db = get_database(server.DB_PATH)
49
+
50
+ # Get all tables
51
+ tables_result = db.list_tables()
52
+ if not tables_result.get("success"):
53
+ return cast(ToolResponse, tables_result)
54
+
55
+ tables = tables_result.get("tables", [])
56
+ analysis = {
57
+ "content_distribution": {},
58
+ "text_density": {"high": [], "medium": [], "low": []},
59
+ "semantic_readiness": {"ready": [], "partial": [], "needs_setup": []},
60
+ "schema_analysis": {},
61
+ "recommendations": [],
62
+ "total_tables": len(tables),
63
+ "total_content_rows": 0
64
+ }
65
+
66
+ for table_name in tables:
67
+ try:
68
+ # Get basic table info
69
+ rows_result = db.read_rows(table_name)
70
+ if not rows_result.get("success"):
71
+ continue
72
+
73
+ rows = rows_result.get("rows", [])
74
+ row_count = len(rows)
75
+ analysis["content_distribution"][table_name] = row_count
76
+ analysis["total_content_rows"] += row_count
77
+
78
+ # Analyze schema
79
+ schema_result = db.describe_table(table_name)
80
+ if schema_result.get("success"):
81
+ columns = schema_result.get("columns", [])
82
+ text_columns = [col for col in columns if "TEXT" in col.get("type", "").upper()]
83
+
84
+ analysis["schema_analysis"][table_name] = {
85
+ "total_columns": len(columns),
86
+ "text_columns": len(text_columns),
87
+ "has_id_column": any(col.get("name") == "id" for col in columns),
88
+ "has_timestamp": any("timestamp" in col.get("name", "").lower() for col in columns)
89
+ }
90
+
91
+ # Analyze text density
92
+ if rows and text_columns:
93
+ text_content_lengths = []
94
+ for row in rows[:10]: # Sample first 10 rows
95
+ for col in text_columns:
96
+ content = row.get(col["name"], "")
97
+ if content:
98
+ text_content_lengths.append(len(str(content)))
99
+
100
+ if text_content_lengths:
101
+ avg_length = sum(text_content_lengths) / len(text_content_lengths)
102
+ if avg_length > 500:
103
+ analysis["text_density"]["high"].append(table_name)
104
+ elif avg_length > 100:
105
+ analysis["text_density"]["medium"].append(table_name)
106
+ else:
107
+ analysis["text_density"]["low"].append(table_name)
108
+
109
+ # Check semantic readiness
110
+ if is_semantic_search_available():
111
+ embedding_stats = db.get_embedding_stats(table_name)
112
+ if embedding_stats.get("success"):
113
+ coverage = embedding_stats.get("coverage_percent", 0)
114
+ if coverage >= 80:
115
+ analysis["semantic_readiness"]["ready"].append(table_name)
116
+ elif coverage > 0:
117
+ analysis["semantic_readiness"]["partial"].append(table_name)
118
+ else:
119
+ analysis["semantic_readiness"]["needs_setup"].append(table_name)
120
+
121
+ except Exception as e:
122
+ logging.warning(f"Error analyzing table {table_name}: {e}")
123
+ continue
124
+
125
+ # Generate recommendations
126
+ recommendations = []
127
+
128
+ # Semantic search recommendations
129
+ if len(analysis["semantic_readiness"]["needs_setup"]) > 0:
130
+ high_value_tables = [t for t in analysis["semantic_readiness"]["needs_setup"]
131
+ if t in analysis["text_density"]["high"] + analysis["text_density"]["medium"]]
132
+ if high_value_tables:
133
+ recommendations.append(f"Consider setting up semantic search for high-value tables: {', '.join(high_value_tables[:3])}")
134
+
135
+ # Content organization recommendations
136
+ large_tables = [t for t, count in analysis["content_distribution"].items() if count > 50]
137
+ if large_tables:
138
+ recommendations.append(f"Large tables detected: {', '.join(large_tables)}. Consider organizing with categories or tags.")
139
+
140
+ # Empty or sparse tables
141
+ sparse_tables = [t for t, count in analysis["content_distribution"].items() if count < 5 and count > 0]
142
+ if sparse_tables:
143
+ recommendations.append(f"Sparse tables found: {', '.join(sparse_tables)}. Consider consolidating or adding more content.")
144
+
145
+ # Schema improvements
146
+ tables_without_timestamps = [t for t, schema in analysis["schema_analysis"].items()
147
+ if not schema.get("has_timestamp")]
148
+ if len(tables_without_timestamps) > 2:
149
+ recommendations.append("Consider adding timestamp columns to track when content was created/modified.")
150
+
151
+ analysis["recommendations"] = recommendations
152
+
153
+ return cast(ToolResponse, {
154
+ "success": True,
155
+ "analysis": analysis,
156
+ "summary": {
157
+ "tables_analyzed": len(tables),
158
+ "total_rows": analysis["total_content_rows"],
159
+ "semantic_ready": len(analysis["semantic_readiness"]["ready"]),
160
+ "high_value_content": len(analysis["text_density"]["high"]),
161
+ "recommendations_count": len(recommendations)
162
+ }
163
+ })
164
+
165
+ except Exception as e:
166
+ return cast(ToolResponse, {
167
+ "success": False,
168
+ "error": f"Memory pattern analysis failed: {str(e)}",
169
+ "category": "ANALYSIS",
170
+ "details": {"exception": str(e)}
171
+ })
172
+
173
+
174
+ @catch_errors
175
+ def get_content_health_score() -> ToolResponse:
176
+ """
177
+ 📊 **CONTENT HEALTH ASSESSMENT** - Rate the quality of your memory bank!
178
+
179
+ Provides a comprehensive health score based on content quality, organization,
180
+ semantic search readiness, and usage patterns.
181
+
182
+ Returns:
183
+ ToolResponse: On success: {"success": True, "health_score": float, "metrics": dict}
184
+ On error: {"success": False, "error": str, "category": str, "details": dict}
185
+
186
+ Examples:
187
+ >>> get_content_health_score()
188
+ {"success": True, "health_score": 8.5, "metrics": {
189
+ "content_quality": 9.0, "organization": 7.5, "semantic_readiness": 8.0,
190
+ "accessibility": 9.0, "recommendations": [...]
191
+ }}
192
+
193
+ FastMCP Tool Info:
194
+ - **OVERALL SCORE**: Single metric (0-10) indicating memory bank health
195
+ - **DETAILED METRICS**: Breakdown by quality, organization, and readiness
196
+ - **ACTIONABLE INSIGHTS**: Specific recommendations for improvement
197
+ - **TREND TRACKING**: Compare health over time (if run regularly)
198
+ """
199
+ try:
200
+ # Get the pattern analysis first - call database methods directly
201
+ from .. import server
202
+ db = get_database(server.DB_PATH)
203
+
204
+ # Get all tables
205
+ tables_result = db.list_tables()
206
+ if not tables_result.get("success"):
207
+ return cast(ToolResponse, {
208
+ "success": False,
209
+ "error": "Failed to get tables for health analysis",
210
+ "category": "DATABASE",
211
+ "details": tables_result
212
+ })
213
+
214
+ tables = tables_result.get("tables", [])
215
+
216
+ # Build basic analysis for health scoring
217
+ analysis = {
218
+ "content_distribution": {},
219
+ "text_density": {"high": [], "medium": [], "low": []},
220
+ "semantic_readiness": {"ready": [], "partial": [], "needs_setup": []},
221
+ "schema_analysis": {},
222
+ "total_tables": len(tables),
223
+ "total_content_rows": 0
224
+ }
225
+
226
+ for table_name in tables:
227
+ try:
228
+ # Get basic table info
229
+ rows_result = db.read_rows(table_name)
230
+ if not rows_result.get("success"):
231
+ continue
232
+
233
+ rows = rows_result.get("rows", [])
234
+ row_count = len(rows)
235
+ analysis["content_distribution"][table_name] = row_count
236
+ analysis["total_content_rows"] += row_count
237
+
238
+ # Analyze schema
239
+ schema_result = db.describe_table(table_name)
240
+ if schema_result.get("success"):
241
+ columns = schema_result.get("columns", [])
242
+ text_columns = [col for col in columns if "TEXT" in col.get("type", "").upper()]
243
+
244
+ analysis["schema_analysis"][table_name] = {
245
+ "total_columns": len(columns),
246
+ "text_columns": len(text_columns),
247
+ "has_id_column": any(col.get("name") == "id" for col in columns),
248
+ "has_timestamp": any("timestamp" in col.get("name", "").lower() for col in columns)
249
+ }
250
+
251
+ # Analyze text density
252
+ if rows and text_columns:
253
+ text_content_lengths = []
254
+ for row in rows[:10]: # Sample first 10 rows
255
+ for col in text_columns:
256
+ content = row.get(col["name"], "")
257
+ if content:
258
+ text_content_lengths.append(len(str(content)))
259
+
260
+ if text_content_lengths:
261
+ avg_length = sum(text_content_lengths) / len(text_content_lengths)
262
+ if avg_length > 500:
263
+ analysis["text_density"]["high"].append(table_name)
264
+ elif avg_length > 100:
265
+ analysis["text_density"]["medium"].append(table_name)
266
+ else:
267
+ analysis["text_density"]["low"].append(table_name)
268
+
269
+ # Check semantic readiness
270
+ if is_semantic_search_available():
271
+ embedding_stats = db.get_embedding_stats(table_name)
272
+ if embedding_stats.get("success"):
273
+ coverage = embedding_stats.get("coverage_percent", 0)
274
+ if coverage >= 80:
275
+ analysis["semantic_readiness"]["ready"].append(table_name)
276
+ elif coverage > 0:
277
+ analysis["semantic_readiness"]["partial"].append(table_name)
278
+ else:
279
+ analysis["semantic_readiness"]["needs_setup"].append(table_name)
280
+
281
+ except Exception as e:
282
+ logging.warning(f"Error analyzing table {table_name}: {e}")
283
+ continue
284
+
285
+ summary = {
286
+ "tables_analyzed": len(tables),
287
+ "total_rows": analysis["total_content_rows"],
288
+ "semantic_ready": len(analysis["semantic_readiness"]["ready"]),
289
+ "high_value_content": len(analysis["text_density"]["high"])
290
+ }
291
+
292
+ # Calculate health metrics (0-10 scale)
293
+ metrics = {}
294
+
295
+ # 1. Content Quality Score (based on text density and volume)
296
+ total_rows = summary.get("total_rows", 0)
297
+ high_quality_tables = len(analysis.get("text_density", {}).get("high", []))
298
+ total_tables = summary.get("tables_analyzed", 1)
299
+
300
+ if total_rows == 0:
301
+ metrics["content_volume"] = 0.0
302
+ elif total_rows < 10:
303
+ metrics["content_volume"] = 3.0
304
+ elif total_rows < 50:
305
+ metrics["content_volume"] = 6.0
306
+ elif total_rows < 200:
307
+ metrics["content_volume"] = 8.0
308
+ else:
309
+ metrics["content_volume"] = 10.0
310
+
311
+ metrics["content_quality"] = min(10.0, (high_quality_tables / total_tables) * 10 + 3)
312
+
313
+ # 2. Organization Score (based on schema quality)
314
+ schema_analysis = analysis.get("schema_analysis", {})
315
+ organization_factors = []
316
+
317
+ for table_name, schema in schema_analysis.items():
318
+ table_score = 0
319
+ if schema.get("has_id_column"):
320
+ table_score += 2
321
+ if schema.get("has_timestamp"):
322
+ table_score += 2
323
+ if schema.get("text_columns", 0) > 0:
324
+ table_score += 3
325
+ if 2 <= schema.get("total_columns", 0) <= 10: # Good column count
326
+ table_score += 3
327
+ organization_factors.append(table_score)
328
+
329
+ metrics["organization"] = (sum(organization_factors) / len(organization_factors)) if organization_factors else 5.0
330
+
331
+ # 3. Semantic Readiness Score
332
+ semantic_ready = len(analysis.get("semantic_readiness", {}).get("ready", []))
333
+ semantic_partial = len(analysis.get("semantic_readiness", {}).get("partial", []))
334
+ semantic_needed = len(analysis.get("semantic_readiness", {}).get("needs_setup", []))
335
+
336
+ if not is_semantic_search_available():
337
+ metrics["semantic_readiness"] = 5.0 # Neutral score if not available
338
+ metrics["semantic_note"] = "Semantic search dependencies not available"
339
+ else:
340
+ semantic_score = ((semantic_ready * 2 + semantic_partial) / (total_tables * 2)) * 10
341
+ metrics["semantic_readiness"] = min(10.0, semantic_score)
342
+
343
+ # 4. Accessibility Score (how easy it is to find and use content)
344
+ medium_density = len(analysis.get("text_density", {}).get("medium", []))
345
+ low_density = len(analysis.get("text_density", {}).get("low", []))
346
+
347
+ # Prefer medium density (not too verbose, not too sparse)
348
+ if total_tables == 0:
349
+ metrics["accessibility"] = 5.0
350
+ else:
351
+ accessibility_score = ((high_quality_tables + medium_density * 1.5) / total_tables) * 8 + 2
352
+ metrics["accessibility"] = min(10.0, accessibility_score)
353
+
354
+ # 5. Overall Health Score (weighted average)
355
+ weights = {
356
+ "content_volume": 0.2,
357
+ "content_quality": 0.3,
358
+ "organization": 0.2,
359
+ "semantic_readiness": 0.15,
360
+ "accessibility": 0.15
361
+ }
362
+
363
+ health_score = sum(metrics[key] * weights[key] for key in weights.keys())
364
+
365
+ # Generate health-specific recommendations
366
+ health_recommendations = []
367
+
368
+ if metrics["content_volume"] < 5:
369
+ health_recommendations.append("🔴 LOW CONTENT: Add more valuable content to your memory bank")
370
+ elif metrics["content_volume"] < 7:
371
+ health_recommendations.append("🟡 MODERATE CONTENT: Consider expanding your knowledge base")
372
+
373
+ if metrics["content_quality"] < 6:
374
+ health_recommendations.append("🔴 CONTENT QUALITY: Focus on adding more detailed, rich content")
375
+
376
+ if metrics["organization"] < 6:
377
+ health_recommendations.append("🔴 ORGANIZATION: Improve table schemas with timestamps and proper columns")
378
+
379
+ if metrics["semantic_readiness"] < 5 and is_semantic_search_available():
380
+ health_recommendations.append("🟡 SEMANTIC SEARCH: Set up embeddings for better content discovery")
381
+
382
+ if metrics["accessibility"] < 6:
383
+ health_recommendations.append("🔴 ACCESSIBILITY: Improve content structure for easier discovery")
384
+
385
+ # Health grade
386
+ if health_score >= 9:
387
+ grade = "A+ (Excellent)"
388
+ elif health_score >= 8:
389
+ grade = "A (Great)"
390
+ elif health_score >= 7:
391
+ grade = "B+ (Good)"
392
+ elif health_score >= 6:
393
+ grade = "B (Adequate)"
394
+ elif health_score >= 5:
395
+ grade = "C (Needs Improvement)"
396
+ else:
397
+ grade = "D (Poor - Needs Attention)"
398
+
399
+ return cast(ToolResponse, {
400
+ "success": True,
401
+ "health_score": round(health_score, 1),
402
+ "grade": grade,
403
+ "metrics": {k: round(v, 1) for k, v in metrics.items()},
404
+ "recommendations": health_recommendations,
405
+ "detailed_analysis": analysis,
406
+ "improvement_priority": {
407
+ "highest": [k for k, v in metrics.items() if v < 5],
408
+ "medium": [k for k, v in metrics.items() if 5 <= v < 7],
409
+ "good": [k for k, v in metrics.items() if v >= 7]
410
+ }
411
+ })
412
+
413
+ except Exception as e:
414
+ return cast(ToolResponse, {
415
+ "success": False,
416
+ "error": f"Content health assessment failed: {str(e)}",
417
+ "category": "ANALYSIS",
418
+ "details": {"exception": str(e)}
419
+ })
@@ -0,0 +1,112 @@
1
+ """
2
+ Basic tools module for SQLite Memory Bank.
3
+
4
+ This module contains all basic CRUD and utility MCP tools including table management,
5
+ data operations, and core functionality.
6
+ """
7
+
8
+ from typing import Any, Dict, List, Optional, cast
9
+
10
+ from ..database import get_database
11
+ from ..types import MemoryBankError, DatabaseError, ToolResponse
12
+ from ..utils import catch_errors
13
+
14
+
15
+ @catch_errors
16
+ def create_table(
17
+ table_name: str,
18
+ columns: List[Dict[str, str]],
19
+ ) -> ToolResponse:
20
+ """Create a new table in the SQLite memory bank."""
21
+ from .. import server
22
+ return cast(ToolResponse, get_database(server.DB_PATH).create_table(table_name, columns))
23
+
24
+
25
+ @catch_errors
26
+ def list_tables() -> ToolResponse:
27
+ """List all tables in the SQLite memory bank."""
28
+ from .. import server
29
+ return cast(ToolResponse, get_database(server.DB_PATH).list_tables())
30
+
31
+
32
+ @catch_errors
33
+ def describe_table(table_name: str) -> ToolResponse:
34
+ """Get detailed schema information for a table."""
35
+ from .. import server
36
+ return cast(ToolResponse, get_database(server.DB_PATH).describe_table(table_name))
37
+
38
+
39
+ @catch_errors
40
+ def drop_table(table_name: str) -> ToolResponse:
41
+ """Drop (delete) a table from the SQLite memory bank."""
42
+ from .. import server
43
+ return cast(ToolResponse, get_database(server.DB_PATH).drop_table(table_name))
44
+
45
+
46
+ @catch_errors
47
+ def rename_table(old_name: str, new_name: str) -> ToolResponse:
48
+ """Rename a table in the SQLite memory bank."""
49
+ from .. import server
50
+ return cast(ToolResponse, get_database(server.DB_PATH).rename_table(old_name, new_name))
51
+
52
+
53
+ @catch_errors
54
+ def create_row(
55
+ table_name: str,
56
+ data: Dict[str, Any],
57
+ ) -> ToolResponse:
58
+ """Insert a new row into any table in the SQLite Memory Bank."""
59
+ from .. import server
60
+ return cast(ToolResponse, get_database(server.DB_PATH).insert_row(table_name, data))
61
+
62
+
63
+ @catch_errors
64
+ def read_rows(
65
+ table_name: str,
66
+ where: Optional[Dict[str, Any]] = None,
67
+ ) -> ToolResponse:
68
+ """Read rows from any table in the SQLite memory bank, with optional filtering."""
69
+ from .. import server
70
+ return cast(ToolResponse, get_database(server.DB_PATH).read_rows(table_name, where))
71
+
72
+
73
+ @catch_errors
74
+ def update_rows(
75
+ table_name: str,
76
+ data: Dict[str, Any],
77
+ where: Optional[Dict[str, Any]] = None,
78
+ ) -> ToolResponse:
79
+ """Update rows in any table in the SQLite Memory Bank, matching the WHERE clause."""
80
+ from .. import server
81
+ return cast(ToolResponse, get_database(server.DB_PATH).update_rows(table_name, data, where))
82
+
83
+
84
+ @catch_errors
85
+ def delete_rows(
86
+ table_name: str,
87
+ where: Optional[Dict[str, Any]] = None,
88
+ ) -> ToolResponse:
89
+ """Delete rows from any table in the SQLite Memory Bank, matching the WHERE clause."""
90
+ from .. import server
91
+ return cast(ToolResponse, get_database(server.DB_PATH).delete_rows(table_name, where))
92
+
93
+
94
+ @catch_errors
95
+ def run_select_query(
96
+ table_name: str,
97
+ columns: Optional[List[str]] = None,
98
+ where: Optional[Dict[str, Any]] = None,
99
+ limit: int = 100,
100
+ ) -> ToolResponse:
101
+ """Run a safe SELECT query on a table in the SQLite memory bank."""
102
+ from .. import server
103
+ return cast(ToolResponse, get_database(server.DB_PATH).select_query(
104
+ table_name, columns, where, limit
105
+ ))
106
+
107
+
108
+ @catch_errors
109
+ def list_all_columns() -> ToolResponse:
110
+ """List all columns for all tables in the SQLite memory bank."""
111
+ from .. import server
112
+ return cast(ToolResponse, get_database(server.DB_PATH).list_all_columns())