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