mcp-sqlite-memory-bank 1.5.1__py3-none-any.whl → 1.6.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.
@@ -20,63 +20,75 @@ import json
20
20
 
21
21
  class MemoryBankResources:
22
22
  """Manages MCP Resources for the SQLite Memory Bank."""
23
-
23
+
24
24
  def __init__(self, mcp_app: FastMCP, db_path: str):
25
25
  self.mcp = mcp_app
26
26
  self.db_path = db_path
27
27
  self._register_resources()
28
-
28
+
29
29
  def _register_resources(self):
30
30
  """Register MCP resources with the FastMCP app."""
31
-
31
+
32
32
  @self.mcp.resource("memory://tables/list")
33
33
  async def get_tables_list() -> str:
34
34
  """Provide a list of all available tables as an MCP resource."""
35
35
  db = get_database(self.db_path)
36
36
  result = cast(Dict[str, Any], db.list_tables())
37
-
37
+
38
38
  if not result.get("success"):
39
- return json.dumps({"error": "Failed to fetch tables", "details": result})
40
-
39
+ return json.dumps(
40
+ {"error": "Failed to fetch tables", "details": result}
41
+ )
42
+
41
43
  resource_content = {
42
44
  "resource_type": "table_list",
43
45
  "description": "List of all available tables in the memory bank",
44
46
  "tables": result.get("tables", []),
45
47
  "total_count": len(result.get("tables", [])),
46
- "last_updated": "dynamic"
48
+ "last_updated": "dynamic",
47
49
  }
48
-
50
+
49
51
  return json.dumps(resource_content, indent=2)
50
-
52
+
51
53
  @self.mcp.resource("memory://tables/{table_name}/schema")
52
54
  async def get_table_schema(table_name: str) -> str:
53
55
  """Provide table schema information as an MCP resource."""
54
56
  db = get_database(self.db_path)
55
57
  result = cast(Dict[str, Any], db.describe_table(table_name))
56
-
58
+
57
59
  if not result.get("success"):
58
- return json.dumps({"error": f"Failed to fetch schema for table '{table_name}'", "details": result})
59
-
60
+ return json.dumps(
61
+ {
62
+ "error": f"Failed to fetch schema for table '{table_name}'",
63
+ "details": result,
64
+ }
65
+ )
66
+
60
67
  resource_content = {
61
68
  "resource_type": "table_schema",
62
69
  "table_name": table_name,
63
70
  "description": f"Schema definition for table '{table_name}'",
64
71
  "columns": result.get("columns", []),
65
72
  "column_count": len(result.get("columns", [])),
66
- "last_updated": "dynamic"
73
+ "last_updated": "dynamic",
67
74
  }
68
-
75
+
69
76
  return json.dumps(resource_content, indent=2)
70
-
77
+
71
78
  @self.mcp.resource("memory://tables/{table_name}/data")
72
79
  async def get_table_data(table_name: str) -> str:
73
80
  """Provide table data as an MCP resource."""
74
81
  db = get_database(self.db_path)
75
82
  result = cast(Dict[str, Any], db.read_rows(table_name, {}))
76
-
83
+
77
84
  if not result.get("success"):
78
- return json.dumps({"error": f"Failed to fetch data for table '{table_name}'", "details": result})
79
-
85
+ return json.dumps(
86
+ {
87
+ "error": f"Failed to fetch data for table '{table_name}'",
88
+ "details": result,
89
+ }
90
+ )
91
+
80
92
  rows = result.get("rows", [])
81
93
  resource_content = {
82
94
  "resource_type": "table_data",
@@ -84,20 +96,24 @@ class MemoryBankResources:
84
96
  "description": f"All data from table '{table_name}'",
85
97
  "rows": rows,
86
98
  "row_count": len(rows),
87
- "last_updated": "dynamic"
99
+ "last_updated": "dynamic",
88
100
  }
89
-
101
+
90
102
  return json.dumps(resource_content, indent=2)
91
-
103
+
92
104
  @self.mcp.resource("memory://search/{query}")
93
105
  async def search_memory_content(query: str) -> str:
94
106
  """Provide search results as an MCP resource."""
95
107
  db = get_database(self.db_path)
96
- result = cast(Dict[str, Any], db.search_content(query, None, 50)) # Search all tables, limit to 50 results
97
-
108
+ result = cast(
109
+ Dict[str, Any], db.search_content(query, None, 50)
110
+ ) # Search all tables, limit to 50 results
111
+
98
112
  if not result.get("success"):
99
- return json.dumps({"error": f"Failed to search for '{query}'", "details": result})
100
-
113
+ return json.dumps(
114
+ {"error": f"Failed to search for '{query}'", "details": result}
115
+ )
116
+
101
117
  search_results = result.get("results", [])
102
118
  resource_content = {
103
119
  "resource_type": "search_results",
@@ -105,25 +121,30 @@ class MemoryBankResources:
105
121
  "description": f"Search results for query: '{query}'",
106
122
  "results": search_results,
107
123
  "result_count": len(search_results),
108
- "last_updated": "dynamic"
124
+ "last_updated": "dynamic",
109
125
  }
110
-
126
+
111
127
  return json.dumps(resource_content, indent=2)
112
-
128
+
113
129
  @self.mcp.resource("memory://analytics/overview")
114
130
  async def get_memory_overview() -> str:
115
131
  """Provide memory bank overview analytics as an MCP resource."""
116
132
  db = get_database(self.db_path)
117
-
133
+
118
134
  # Get table list
119
135
  tables_result = cast(Dict[str, Any], db.list_tables())
120
136
  if not tables_result.get("success"):
121
- return json.dumps({"error": "Failed to fetch memory overview", "details": tables_result})
122
-
137
+ return json.dumps(
138
+ {
139
+ "error": "Failed to fetch memory overview",
140
+ "details": tables_result,
141
+ }
142
+ )
143
+
123
144
  tables = tables_result.get("tables", [])
124
145
  total_rows = 0
125
146
  table_stats = {}
126
-
147
+
127
148
  # Get row counts for each table
128
149
  for table in tables:
129
150
  try:
@@ -132,27 +153,25 @@ class MemoryBankResources:
132
153
  row_count = len(rows_result.get("rows", []))
133
154
  table_stats[table] = {
134
155
  "row_count": row_count,
135
- "status": "accessible"
156
+ "status": "accessible",
136
157
  }
137
158
  total_rows += row_count
138
159
  else:
139
- table_stats[table] = {
140
- "row_count": 0,
141
- "status": "error"
142
- }
160
+ table_stats[table] = {"row_count": 0, "status": "error"}
143
161
  except Exception as e:
144
- table_stats[table] = {
145
- "row_count": 0,
146
- "status": f"error: {str(e)}"
147
- }
148
-
162
+ table_stats[table] = {"row_count": 0, "status": f"error: {str(e)}"}
163
+
149
164
  # Find largest table
150
165
  largest_table = None
151
166
  if table_stats:
152
167
  max_rows = 0
153
168
  for table_name, stats in table_stats.items():
154
169
  row_count_obj = stats.get("row_count", 0)
155
- row_count = int(row_count_obj) if isinstance(row_count_obj, (int, str)) else 0
170
+ row_count = (
171
+ int(row_count_obj)
172
+ if isinstance(row_count_obj, (int, str))
173
+ else 0
174
+ )
156
175
  if row_count > max_rows:
157
176
  max_rows = row_count
158
177
  largest_table = table_name
@@ -163,40 +182,48 @@ class MemoryBankResources:
163
182
  "summary": {
164
183
  "total_tables": len(tables),
165
184
  "total_rows": total_rows,
166
- "largest_table": largest_table
185
+ "largest_table": largest_table,
167
186
  },
168
187
  "table_statistics": table_stats,
169
- "last_updated": "dynamic"
188
+ "last_updated": "dynamic",
170
189
  }
171
-
190
+
172
191
  return json.dumps(resource_content, indent=2)
173
-
192
+
174
193
  @self.mcp.resource("memory://live/recent-activity")
175
194
  async def get_recent_activity() -> str:
176
195
  """Real-time feed of recent memory bank changes and activity."""
177
196
  db = get_database(self.db_path)
178
-
197
+
179
198
  # Get tables with timestamp columns for activity tracking
180
199
  tables_result = cast(Dict[str, Any], db.list_tables())
181
200
  if not tables_result.get("success"):
182
- return json.dumps({"error": "Failed to get tables", "details": tables_result})
183
-
201
+ return json.dumps(
202
+ {"error": "Failed to get tables", "details": tables_result}
203
+ )
204
+
184
205
  recent_activity = []
185
206
  tables = tables_result.get("tables", [])
186
-
207
+
187
208
  for table_name in tables:
188
209
  try:
189
210
  # Check if table has timestamp column
190
211
  schema_result = cast(Dict[str, Any], db.describe_table(table_name))
191
212
  if not schema_result.get("success"):
192
213
  continue
193
-
214
+
194
215
  columns = schema_result.get("columns", [])
195
- timestamp_cols = [col for col in columns if "timestamp" in col.get("name", "").lower()]
196
-
216
+ timestamp_cols = [
217
+ col
218
+ for col in columns
219
+ if "timestamp" in col.get("name", "").lower()
220
+ ]
221
+
197
222
  if timestamp_cols:
198
223
  # Get recent entries (last 10)
199
- recent_result = cast(Dict[str, Any], db.read_rows(table_name, None, 10))
224
+ recent_result = cast(
225
+ Dict[str, Any], db.read_rows(table_name, None, 10)
226
+ )
200
227
  if recent_result.get("success"):
201
228
  rows = recent_result.get("rows", [])
202
229
  for row in rows:
@@ -204,112 +231,144 @@ class MemoryBankResources:
204
231
  "table": table_name,
205
232
  "action": "content_added",
206
233
  "timestamp": row.get(timestamp_cols[0]["name"]),
207
- "content_preview": str(row).replace('"', "'")[:100] + "..." if len(str(row)) > 100 else str(row),
208
- "row_id": row.get("id")
234
+ "content_preview": (
235
+ str(row).replace('"', "'")[:100] + "..."
236
+ if len(str(row)) > 100
237
+ else str(row)
238
+ ),
239
+ "row_id": row.get("id"),
209
240
  }
210
241
  recent_activity.append(activity_entry)
211
-
242
+
212
243
  except Exception as e:
213
244
  continue
214
-
245
+
215
246
  # Sort by timestamp (most recent first)
216
247
  recent_activity.sort(key=lambda x: x.get("timestamp", ""), reverse=True)
217
248
  recent_activity = recent_activity[:20] # Limit to 20 most recent
218
-
249
+
219
250
  resource_content = {
220
251
  "resource_type": "recent_activity",
221
252
  "description": "Recent changes and additions to the memory bank",
222
253
  "activities": recent_activity,
223
254
  "activity_count": len(recent_activity),
224
255
  "last_updated": "real-time",
225
- "refresh_rate": "dynamic"
256
+ "refresh_rate": "dynamic",
226
257
  }
227
-
258
+
228
259
  return json.dumps(resource_content, indent=2)
229
-
260
+
230
261
  @self.mcp.resource("memory://live/content-suggestions")
231
262
  async def get_content_suggestions() -> str:
232
263
  """AI-powered suggestions for content improvements and organization."""
233
264
  db = get_database(self.db_path)
234
-
235
- suggestions = {
265
+
266
+ suggestions: dict[str, list[dict[str, Any]]] = {
236
267
  "organization_suggestions": [],
237
268
  "content_gaps": [],
238
269
  "semantic_opportunities": [],
239
- "quality_improvements": []
270
+ "quality_improvements": [],
240
271
  }
241
-
272
+
242
273
  try:
243
274
  # Get basic analysis
244
275
  tables_result = cast(Dict[str, Any], db.list_tables())
245
276
  if not tables_result.get("success"):
246
- return json.dumps({"error": "Failed to analyze content", "details": tables_result})
247
-
277
+ return json.dumps(
278
+ {"error": "Failed to analyze content", "details": tables_result}
279
+ )
280
+
248
281
  tables = tables_result.get("tables", [])
249
-
282
+
250
283
  for table_name in tables:
251
284
  try:
252
285
  # Analyze table content
253
286
  rows_result = cast(Dict[str, Any], db.read_rows(table_name))
254
287
  if not rows_result.get("success"):
255
288
  continue
256
-
289
+
257
290
  rows = rows_result.get("rows", [])
258
-
291
+
259
292
  # Check for organization opportunities
260
293
  if len(rows) > 50:
261
- suggestions["organization_suggestions"].append({
262
- "table": table_name,
263
- "suggestion": "Consider adding categories or tags for better organization",
264
- "reason": f"Large table with {len(rows)} rows could benefit from categorization"
265
- })
266
-
294
+ suggestions["organization_suggestions"].append(
295
+ {
296
+ "table": table_name,
297
+ "suggestion": "Consider adding categories or tags for better organization",
298
+ "reason": f"Large table with {len(rows)} rows could benefit from categorization",
299
+ }
300
+ )
301
+
267
302
  # Check for semantic search opportunities
268
303
  if is_semantic_search_available():
269
- embedding_stats = cast(Dict[str, Any], db.get_embedding_stats(table_name))
270
- if embedding_stats.get("success") and embedding_stats.get("coverage_percent", 0) == 0:
271
- schema_result = cast(Dict[str, Any], db.describe_table(table_name))
304
+ embedding_stats = cast(
305
+ Dict[str, Any], db.get_embedding_stats(table_name)
306
+ )
307
+ if (
308
+ embedding_stats.get("success")
309
+ and embedding_stats.get("coverage_percent", 0) == 0
310
+ ):
311
+ schema_result = cast(
312
+ Dict[str, Any], db.describe_table(table_name)
313
+ )
272
314
  if schema_result.get("success"):
273
- text_cols = [col for col in schema_result.get("columns", [])
274
- if "TEXT" in col.get("type", "").upper()]
315
+ text_cols = [
316
+ col
317
+ for col in schema_result.get("columns", [])
318
+ if "TEXT" in col.get("type", "").upper()
319
+ ]
275
320
  if text_cols and len(rows) > 5:
276
- suggestions["semantic_opportunities"].append({
277
- "table": table_name,
278
- "suggestion": "Set up semantic search for better content discovery",
279
- "reason": f"Table has {len(text_cols)} text columns and {len(rows)} rows",
280
- "action": f"Use add_embeddings('{table_name}', {[col['name'] for col in text_cols[:3]]})"
281
- })
282
-
321
+ suggestions["semantic_opportunities"].append(
322
+ {
323
+ "table": table_name,
324
+ "suggestion": "Set up semantic search for better content discovery",
325
+ "reason": f"Table has {len(text_cols)} text columns and {len(rows)} rows",
326
+ "action": f"Use add_embeddings('{table_name}', {[col['name'] for col in text_cols[:3]]})",
327
+ }
328
+ )
329
+
283
330
  # Check for content gaps (sparse tables)
284
331
  if 1 <= len(rows) <= 5:
285
- suggestions["content_gaps"].append({
286
- "table": table_name,
287
- "suggestion": "Consider adding more content or consolidating with other tables",
288
- "reason": f"Table has only {len(rows)} rows - might be underutilized"
289
- })
290
-
332
+ suggestions["content_gaps"].append(
333
+ {
334
+ "table": table_name,
335
+ "suggestion": "Consider adding more content or consolidating with other tables",
336
+ "reason": f"Table has only {len(rows)} rows - might be underutilized",
337
+ }
338
+ )
339
+
291
340
  # Sample content for quality analysis
292
341
  if rows:
293
342
  sample_row = rows[0]
294
- short_values = [k for k, v in sample_row.items()
295
- if isinstance(v, str) and 0 < len(v) < 10]
343
+ short_values = [
344
+ k
345
+ for k, v in sample_row.items()
346
+ if isinstance(v, str) and 0 < len(v) < 10
347
+ ]
296
348
  if len(short_values) > 2:
297
- suggestions["quality_improvements"].append({
298
- "table": table_name,
299
- "suggestion": "Consider adding more detailed content",
300
- "reason": f"Several columns have very short values: {short_values[:3]}"
301
- })
302
-
349
+ suggestions["quality_improvements"].append(
350
+ {
351
+ "table": table_name,
352
+ "suggestion": "Consider adding more detailed content",
353
+ "reason": f"Several columns have very short values: {short_values[:3]}",
354
+ }
355
+ )
356
+
303
357
  except Exception as e:
304
358
  continue
305
-
359
+
306
360
  # Prioritize suggestions
307
- priority_order = ["semantic_opportunities", "organization_suggestions", "quality_improvements", "content_gaps"]
361
+ priority_order = [
362
+ "semantic_opportunities",
363
+ "organization_suggestions",
364
+ "quality_improvements",
365
+ "content_gaps",
366
+ ]
308
367
  prioritized = {}
309
368
  for category in priority_order:
310
369
  if suggestions[category]:
311
370
  prioritized[category] = suggestions[category]
312
-
371
+
313
372
  resource_content = {
314
373
  "resource_type": "content_suggestions",
315
374
  "description": "AI-powered suggestions for improving your memory bank",
@@ -319,46 +378,50 @@ class MemoryBankResources:
319
378
  "next_actions": [
320
379
  "Review semantic opportunities for high-value tables",
321
380
  "Consider organization improvements for large tables",
322
- "Add more detailed content where suggested"
323
- ]
381
+ "Add more detailed content where suggested",
382
+ ],
324
383
  }
325
-
384
+
326
385
  return json.dumps(resource_content, indent=2)
327
-
386
+
328
387
  except Exception as e:
329
- return json.dumps({
330
- "error": f"Failed to generate content suggestions: {str(e)}",
331
- "suggestions": suggestions
332
- })
333
-
388
+ return json.dumps(
389
+ {
390
+ "error": f"Failed to generate content suggestions: {str(e)}",
391
+ "suggestions": suggestions,
392
+ }
393
+ )
394
+
334
395
  @self.mcp.resource("memory://analytics/insights")
335
396
  async def get_memory_insights() -> str:
336
397
  """Real-time analytics and insights about memory bank usage and patterns."""
337
398
  db = get_database(self.db_path)
338
-
339
- insights = {
399
+
400
+ insights: dict[str, dict[str, Any]] = {
340
401
  "usage_patterns": {},
341
402
  "content_trends": {},
342
403
  "search_recommendations": {},
343
- "health_indicators": {}
404
+ "health_indicators": {},
344
405
  }
345
-
406
+
346
407
  try:
347
408
  tables_result = cast(Dict[str, Any], db.list_tables())
348
409
  if not tables_result.get("success"):
349
- return json.dumps({"error": "Failed to get insights", "details": tables_result})
350
-
410
+ return json.dumps(
411
+ {"error": "Failed to get insights", "details": tables_result}
412
+ )
413
+
351
414
  tables = tables_result.get("tables", [])
352
415
  total_rows = 0
353
416
  content_quality_scores = []
354
-
417
+
355
418
  for table_name in tables:
356
419
  rows_result = cast(Dict[str, Any], db.read_rows(table_name))
357
420
  if rows_result.get("success"):
358
421
  rows = rows_result.get("rows", [])
359
422
  row_count = len(rows)
360
423
  total_rows += row_count
361
-
424
+
362
425
  # Calculate content quality score for this table
363
426
  if rows:
364
427
  # Sample content to estimate quality
@@ -368,63 +431,100 @@ class MemoryBankResources:
368
431
  for value in row.values():
369
432
  if isinstance(value, str):
370
433
  total_content_length += len(value)
371
-
372
- avg_content_length = total_content_length / sample_size if sample_size > 0 else 0
373
- quality_score = min(10, avg_content_length / 50) # Normalize to 0-10
434
+
435
+ avg_content_length = (
436
+ total_content_length / sample_size
437
+ if sample_size > 0
438
+ else 0
439
+ )
440
+ quality_score = min(
441
+ 10, avg_content_length / 50
442
+ ) # Normalize to 0-10
374
443
  content_quality_scores.append(quality_score)
375
-
444
+
376
445
  insights["usage_patterns"][table_name] = {
377
446
  "row_count": row_count,
378
447
  "avg_content_length": round(avg_content_length),
379
448
  "quality_score": round(quality_score, 1),
380
- "category": "high_value" if quality_score > 7 else "medium_value" if quality_score > 3 else "low_value"
449
+ "category": (
450
+ "high_value"
451
+ if quality_score > 7
452
+ else (
453
+ "medium_value"
454
+ if quality_score > 3
455
+ else "low_value"
456
+ )
457
+ ),
381
458
  }
382
-
459
+
383
460
  # Overall health indicators
384
- avg_quality = sum(content_quality_scores) / len(content_quality_scores) if content_quality_scores else 0
461
+ avg_quality = (
462
+ sum(content_quality_scores) / len(content_quality_scores)
463
+ if content_quality_scores
464
+ else 0
465
+ )
385
466
  insights["health_indicators"] = {
386
467
  "total_tables": len(tables),
387
468
  "total_content_rows": total_rows,
388
469
  "average_content_quality": round(avg_quality, 1),
389
- "content_distribution": "balanced" if len(tables) > 0 and total_rows / len(tables) > 10 else "sparse",
390
- "semantic_readiness": "available" if is_semantic_search_available() else "unavailable"
470
+ "content_distribution": (
471
+ "balanced"
472
+ if len(tables) > 0 and total_rows / len(tables) > 10
473
+ else "sparse"
474
+ ),
475
+ "semantic_readiness": (
476
+ "available" if is_semantic_search_available() else "unavailable"
477
+ ),
391
478
  }
392
-
479
+
393
480
  # Search recommendations
394
- high_value_tables = [name for name, data in insights["usage_patterns"].items()
395
- if data.get("category") == "high_value"]
396
-
481
+ high_value_tables = [
482
+ name
483
+ for name, data in insights["usage_patterns"].items()
484
+ if data.get("category") == "high_value"
485
+ ]
486
+
397
487
  if high_value_tables:
398
488
  insights["search_recommendations"]["intelligent_search"] = {
399
489
  "recommended_tables": high_value_tables,
400
- "strategy": "Use intelligent_search() for best results across high-value content"
490
+ "strategy": "Use intelligent_search() for best results across high-value content",
401
491
  }
402
-
492
+
403
493
  if is_semantic_search_available():
404
494
  insights["search_recommendations"]["semantic_opportunities"] = {
405
495
  "suggestion": "Consider semantic search for conceptual queries",
406
- "best_for": "Finding related concepts, patterns, and thematic content"
496
+ "best_for": "Finding related concepts, patterns, and thematic content",
407
497
  }
408
-
498
+
409
499
  resource_content = {
410
500
  "resource_type": "memory_insights",
411
501
  "description": "Real-time analytics and insights about your memory bank",
412
502
  "insights": insights,
413
503
  "last_updated": "real-time",
414
504
  "recommendations": [
415
- f"Focus on high-value tables: {', '.join(high_value_tables[:3])}" if high_value_tables else "Add more detailed content to improve value",
505
+ (
506
+ f"Focus on high-value tables: {', '.join(high_value_tables[:3])}"
507
+ if high_value_tables
508
+ else "Add more detailed content to improve value"
509
+ ),
416
510
  "Use intelligent_search() for optimal search results",
417
- "Consider semantic search setup for better content discovery" if is_semantic_search_available() else "Install sentence-transformers for semantic search"
418
- ]
511
+ (
512
+ "Consider semantic search setup for better content discovery"
513
+ if is_semantic_search_available()
514
+ else "Install sentence-transformers for semantic search"
515
+ ),
516
+ ],
419
517
  }
420
-
518
+
421
519
  return json.dumps(resource_content, indent=2)
422
-
520
+
423
521
  except Exception as e:
424
- return json.dumps({
425
- "error": f"Failed to generate insights: {str(e)}",
426
- "insights": insights
427
- })
522
+ return json.dumps(
523
+ {
524
+ "error": f"Failed to generate insights: {str(e)}",
525
+ "insights": insights,
526
+ }
527
+ )
428
528
 
429
529
 
430
530
  def setup_mcp_resources(mcp_app: FastMCP, db_path: str) -> MemoryBankResources: