mcp-sqlite-memory-bank 1.5.0__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.
- mcp_sqlite_memory_bank/__init__.py +2 -2
- mcp_sqlite_memory_bank/__main__.py +68 -0
- mcp_sqlite_memory_bank/database.py +234 -68
- mcp_sqlite_memory_bank/prompts.py +76 -52
- mcp_sqlite_memory_bank/resources.py +250 -150
- mcp_sqlite_memory_bank/semantic.py +50 -17
- mcp_sqlite_memory_bank/server.py +351 -23
- mcp_sqlite_memory_bank/tools/__init__.py +33 -25
- mcp_sqlite_memory_bank/tools/analytics.py +225 -139
- mcp_sqlite_memory_bank/tools/basic.py +417 -7
- mcp_sqlite_memory_bank/tools/discovery.py +1428 -0
- mcp_sqlite_memory_bank/tools/search.py +159 -72
- mcp_sqlite_memory_bank/types.py +6 -1
- mcp_sqlite_memory_bank/utils.py +165 -107
- {mcp_sqlite_memory_bank-1.5.0.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/METADATA +54 -6
- mcp_sqlite_memory_bank-1.6.0.dist-info/RECORD +21 -0
- mcp_sqlite_memory_bank-1.5.0.dist-info/RECORD +0 -19
- {mcp_sqlite_memory_bank-1.5.0.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/WHEEL +0 -0
- {mcp_sqlite_memory_bank-1.5.0.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/entry_points.txt +0 -0
- {mcp_sqlite_memory_bank-1.5.0.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_sqlite_memory_bank-1.5.0.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/top_level.txt +0 -0
@@ -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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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 =
|
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(
|
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 = [
|
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(
|
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":
|
208
|
-
|
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(
|
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
|
-
|
263
|
-
|
264
|
-
|
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(
|
270
|
-
|
271
|
-
|
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 = [
|
274
|
-
|
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
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
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
|
-
|
287
|
-
|
288
|
-
|
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 = [
|
295
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
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 = [
|
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
|
-
|
331
|
-
|
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(
|
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 =
|
373
|
-
|
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":
|
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 =
|
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":
|
390
|
-
|
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 = [
|
395
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
426
|
-
|
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:
|