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.
- mcp_sqlite_memory_bank/__init__.py +2 -2
- mcp_sqlite_memory_bank/__main__.py +20 -11
- 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 +203 -31
- mcp_sqlite_memory_bank/tools/__init__.py +26 -29
- 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 +636 -384
- 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.1.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.1.dist-info/RECORD +0 -21
- {mcp_sqlite_memory_bank-1.5.1.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/WHEEL +0 -0
- {mcp_sqlite_memory_bank-1.5.1.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/entry_points.txt +0 -0
- {mcp_sqlite_memory_bank-1.5.1.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_sqlite_memory_bank-1.5.1.dist-info → mcp_sqlite_memory_bank-1.6.0.dist-info}/top_level.txt +0 -0
@@ -23,7 +23,10 @@ def search_content(
|
|
23
23
|
) -> ToolResponse:
|
24
24
|
"""Perform full-text search across table content using natural language queries."""
|
25
25
|
from .. import server
|
26
|
-
|
26
|
+
|
27
|
+
return cast(
|
28
|
+
ToolResponse, get_database(server.DB_PATH).search_content(query, tables, limit)
|
29
|
+
)
|
27
30
|
|
28
31
|
|
29
32
|
@catch_errors
|
@@ -33,7 +36,11 @@ def explore_tables(
|
|
33
36
|
) -> ToolResponse:
|
34
37
|
"""Explore and discover table structures and content for better searchability."""
|
35
38
|
from .. import server
|
36
|
-
|
39
|
+
|
40
|
+
return cast(
|
41
|
+
ToolResponse,
|
42
|
+
get_database(server.DB_PATH).explore_tables(pattern, include_row_counts),
|
43
|
+
)
|
37
44
|
|
38
45
|
|
39
46
|
@catch_errors
|
@@ -45,9 +52,13 @@ def add_embeddings(
|
|
45
52
|
) -> ToolResponse:
|
46
53
|
"""Generate and store vector embeddings for semantic search on table content."""
|
47
54
|
from .. import server
|
48
|
-
|
49
|
-
|
50
|
-
|
55
|
+
|
56
|
+
return cast(
|
57
|
+
ToolResponse,
|
58
|
+
get_database(server.DB_PATH).generate_embeddings(
|
59
|
+
table_name, text_columns, embedding_column, model_name
|
60
|
+
),
|
61
|
+
)
|
51
62
|
|
52
63
|
|
53
64
|
@catch_errors
|
@@ -60,9 +71,13 @@ def semantic_search(
|
|
60
71
|
) -> ToolResponse:
|
61
72
|
"""Find content using natural language semantic similarity rather than exact keyword matching."""
|
62
73
|
from .. import server
|
63
|
-
|
64
|
-
|
65
|
-
|
74
|
+
|
75
|
+
return cast(
|
76
|
+
ToolResponse,
|
77
|
+
get_database(server.DB_PATH).semantic_search(
|
78
|
+
query, tables, "embedding", None, similarity_threshold, limit, model_name
|
79
|
+
),
|
80
|
+
)
|
66
81
|
|
67
82
|
|
68
83
|
@catch_errors
|
@@ -75,9 +90,13 @@ def find_related(
|
|
75
90
|
) -> ToolResponse:
|
76
91
|
"""Find content related to a specific row by semantic similarity."""
|
77
92
|
from .. import server
|
78
|
-
|
79
|
-
|
80
|
-
|
93
|
+
|
94
|
+
return cast(
|
95
|
+
ToolResponse,
|
96
|
+
get_database(server.DB_PATH).find_related_content(
|
97
|
+
table_name, row_id, "embedding", similarity_threshold, limit, model_name
|
98
|
+
),
|
99
|
+
)
|
81
100
|
|
82
101
|
|
83
102
|
@catch_errors
|
@@ -91,9 +110,20 @@ def smart_search(
|
|
91
110
|
) -> ToolResponse:
|
92
111
|
"""Intelligent hybrid search combining semantic understanding with keyword matching."""
|
93
112
|
from .. import server
|
94
|
-
|
95
|
-
|
96
|
-
|
113
|
+
|
114
|
+
return cast(
|
115
|
+
ToolResponse,
|
116
|
+
get_database(server.DB_PATH).hybrid_search(
|
117
|
+
query,
|
118
|
+
tables,
|
119
|
+
None,
|
120
|
+
"embedding",
|
121
|
+
semantic_weight,
|
122
|
+
text_weight,
|
123
|
+
limit,
|
124
|
+
model_name,
|
125
|
+
),
|
126
|
+
)
|
97
127
|
|
98
128
|
|
99
129
|
@catch_errors
|
@@ -103,7 +133,11 @@ def embedding_stats(
|
|
103
133
|
) -> ToolResponse:
|
104
134
|
"""Get statistics about semantic search readiness for a table."""
|
105
135
|
from .. import server
|
106
|
-
|
136
|
+
|
137
|
+
return cast(
|
138
|
+
ToolResponse,
|
139
|
+
get_database(server.DB_PATH).get_embedding_stats(table_name, embedding_column),
|
140
|
+
)
|
107
141
|
|
108
142
|
|
109
143
|
@catch_errors
|
@@ -153,9 +187,10 @@ def auto_semantic_search(
|
|
153
187
|
"""
|
154
188
|
try:
|
155
189
|
from .. import server
|
190
|
+
|
156
191
|
db = get_database(server.DB_PATH)
|
157
192
|
auto_embedded_tables: List[str] = []
|
158
|
-
|
193
|
+
|
159
194
|
# Get tables to search
|
160
195
|
search_tables: List[str]
|
161
196
|
if tables:
|
@@ -169,59 +204,79 @@ def auto_semantic_search(
|
|
169
204
|
search_tables = all_tables
|
170
205
|
else:
|
171
206
|
search_tables = []
|
172
|
-
|
207
|
+
|
173
208
|
# Auto-embed text columns in tables that don't have embeddings
|
174
209
|
for table_name in search_tables:
|
175
210
|
try:
|
176
211
|
# Check if table has embeddings
|
177
212
|
stats_result = db.get_embedding_stats(table_name, "embedding")
|
178
213
|
coverage_percent = stats_result.get("coverage_percent", 0)
|
179
|
-
if
|
214
|
+
if (
|
215
|
+
stats_result.get("success")
|
216
|
+
and isinstance(coverage_percent, (int, float))
|
217
|
+
and coverage_percent > 0
|
218
|
+
):
|
180
219
|
continue # Table already has embeddings
|
181
|
-
|
220
|
+
|
182
221
|
# Get table schema to find text columns
|
183
222
|
schema_result = db.describe_table(table_name)
|
184
223
|
if not schema_result.get("success"):
|
185
224
|
continue
|
186
|
-
|
225
|
+
|
187
226
|
# Find text columns
|
188
227
|
text_columns = []
|
189
228
|
columns = schema_result.get("columns", [])
|
190
229
|
if isinstance(columns, list):
|
191
230
|
for col in columns:
|
192
|
-
if
|
231
|
+
if (
|
232
|
+
isinstance(col, dict)
|
233
|
+
and "TEXT" in col.get("type", "").upper()
|
234
|
+
):
|
193
235
|
text_columns.append(col["name"])
|
194
|
-
|
236
|
+
|
195
237
|
# Auto-embed text columns
|
196
238
|
if text_columns:
|
197
|
-
embed_result = db.generate_embeddings(
|
239
|
+
embed_result = db.generate_embeddings(
|
240
|
+
table_name, text_columns, "embedding", model_name
|
241
|
+
)
|
198
242
|
if embed_result.get("success"):
|
199
243
|
auto_embedded_tables.append(table_name)
|
200
|
-
|
244
|
+
|
201
245
|
except Exception:
|
202
246
|
# If auto-embedding fails, continue without it
|
203
247
|
continue
|
204
|
-
|
248
|
+
|
205
249
|
# Perform semantic search
|
206
250
|
search_result = db.semantic_search(
|
207
|
-
query,
|
251
|
+
query,
|
252
|
+
search_tables,
|
253
|
+
"embedding",
|
254
|
+
None,
|
255
|
+
similarity_threshold,
|
256
|
+
limit,
|
257
|
+
model_name,
|
208
258
|
)
|
209
|
-
|
259
|
+
|
210
260
|
# Add auto-embedding info to result
|
211
261
|
if isinstance(search_result, dict):
|
212
262
|
search_result["auto_embedded_tables"] = auto_embedded_tables
|
213
263
|
if auto_embedded_tables:
|
214
|
-
search_result["auto_embedding_note"] =
|
215
|
-
|
264
|
+
search_result["auto_embedding_note"] = (
|
265
|
+
f"Automatically generated embeddings for {len(auto_embedded_tables)} table(s)"
|
266
|
+
)
|
267
|
+
|
216
268
|
return cast(ToolResponse, search_result)
|
217
|
-
|
269
|
+
|
218
270
|
except Exception as e:
|
219
|
-
return cast(
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
271
|
+
return cast(
|
272
|
+
ToolResponse,
|
273
|
+
{
|
274
|
+
"success": False,
|
275
|
+
"error": f"Auto semantic search failed: {str(e)}",
|
276
|
+
"category": "SEMANTIC_SEARCH_ERROR",
|
277
|
+
"details": {"query": query, "tables": tables},
|
278
|
+
},
|
279
|
+
)
|
225
280
|
|
226
281
|
|
227
282
|
@catch_errors
|
@@ -269,9 +324,10 @@ def auto_smart_search(
|
|
269
324
|
"""
|
270
325
|
try:
|
271
326
|
from .. import server
|
327
|
+
|
272
328
|
db = get_database(server.DB_PATH)
|
273
329
|
auto_embedded_tables: List[str] = []
|
274
|
-
|
330
|
+
|
275
331
|
# Get tables to search
|
276
332
|
search_tables: List[str]
|
277
333
|
if tables:
|
@@ -285,49 +341,69 @@ def auto_smart_search(
|
|
285
341
|
search_tables = all_tables
|
286
342
|
else:
|
287
343
|
search_tables = []
|
288
|
-
|
344
|
+
|
289
345
|
# Auto-embed text columns in tables that don't have embeddings
|
290
346
|
for table_name in search_tables:
|
291
347
|
try:
|
292
348
|
# Check if table has embeddings
|
293
349
|
stats_result = db.get_embedding_stats(table_name, "embedding")
|
294
350
|
coverage_percent = stats_result.get("coverage_percent", 0)
|
295
|
-
if
|
351
|
+
if (
|
352
|
+
stats_result.get("success")
|
353
|
+
and isinstance(coverage_percent, (int, float))
|
354
|
+
and coverage_percent > 0
|
355
|
+
):
|
296
356
|
continue # Table already has embeddings
|
297
|
-
|
357
|
+
|
298
358
|
# Get table schema to find text columns
|
299
359
|
schema_result = db.describe_table(table_name)
|
300
360
|
if not schema_result.get("success"):
|
301
361
|
continue
|
302
|
-
|
362
|
+
|
303
363
|
# Find text columns
|
304
364
|
text_columns = []
|
305
365
|
columns = schema_result.get("columns", [])
|
306
366
|
if isinstance(columns, list):
|
307
367
|
for col in columns:
|
308
|
-
if
|
368
|
+
if (
|
369
|
+
isinstance(col, dict)
|
370
|
+
and "TEXT" in col.get("type", "").upper()
|
371
|
+
):
|
309
372
|
text_columns.append(col["name"])
|
310
|
-
|
373
|
+
|
311
374
|
# Auto-embed text columns
|
312
375
|
if text_columns:
|
313
|
-
embed_result = db.generate_embeddings(
|
376
|
+
embed_result = db.generate_embeddings(
|
377
|
+
table_name, text_columns, "embedding", model_name
|
378
|
+
)
|
314
379
|
if embed_result.get("success"):
|
315
380
|
auto_embedded_tables.append(table_name)
|
316
|
-
|
381
|
+
|
317
382
|
except Exception:
|
318
383
|
# If auto-embedding fails, continue without it
|
319
384
|
continue
|
320
|
-
|
385
|
+
|
321
386
|
# Now perform hybrid search using the same pattern as smart_search
|
322
387
|
try:
|
323
388
|
hybrid_result = get_database(server.DB_PATH).hybrid_search(
|
324
|
-
query,
|
389
|
+
query,
|
390
|
+
search_tables,
|
391
|
+
None,
|
392
|
+
"embedding",
|
393
|
+
semantic_weight,
|
394
|
+
text_weight,
|
395
|
+
limit,
|
396
|
+
model_name,
|
325
397
|
)
|
326
398
|
except Exception as search_error:
|
327
399
|
# If hybrid search fails, fall back to regular content search
|
328
|
-
logging.warning(
|
400
|
+
logging.warning(
|
401
|
+
f"Hybrid search failed, falling back to content search: {search_error}"
|
402
|
+
)
|
329
403
|
try:
|
330
|
-
fallback_result = get_database(server.DB_PATH).search_content(
|
404
|
+
fallback_result = get_database(server.DB_PATH).search_content(
|
405
|
+
query, search_tables, limit
|
406
|
+
)
|
331
407
|
if fallback_result.get("success"):
|
332
408
|
# Create a new dictionary to avoid type issues
|
333
409
|
enhanced_fallback = dict(fallback_result)
|
@@ -336,21 +412,27 @@ def auto_smart_search(
|
|
336
412
|
enhanced_fallback["fallback_reason"] = str(search_error)
|
337
413
|
return cast(ToolResponse, enhanced_fallback)
|
338
414
|
except Exception as fallback_error:
|
339
|
-
return cast(
|
415
|
+
return cast(
|
416
|
+
ToolResponse,
|
417
|
+
{
|
418
|
+
"success": False,
|
419
|
+
"error": f"Both hybrid and fallback search failed. Hybrid: {search_error}, Fallback: {fallback_error}",
|
420
|
+
"category": "HYBRID_SEARCH_ERROR",
|
421
|
+
"details": {"query": query, "tables": tables},
|
422
|
+
},
|
423
|
+
)
|
424
|
+
|
425
|
+
# If we get here, both searches failed
|
426
|
+
return cast(
|
427
|
+
ToolResponse,
|
428
|
+
{
|
340
429
|
"success": False,
|
341
|
-
"error": f"
|
430
|
+
"error": f"Hybrid search failed: {search_error}",
|
342
431
|
"category": "HYBRID_SEARCH_ERROR",
|
343
|
-
"details": {"query": query, "tables": tables}
|
344
|
-
}
|
345
|
-
|
346
|
-
|
347
|
-
return cast(ToolResponse, {
|
348
|
-
"success": False,
|
349
|
-
"error": f"Hybrid search failed: {search_error}",
|
350
|
-
"category": "HYBRID_SEARCH_ERROR",
|
351
|
-
"details": {"query": query, "tables": tables}
|
352
|
-
})
|
353
|
-
|
432
|
+
"details": {"query": query, "tables": tables},
|
433
|
+
},
|
434
|
+
)
|
435
|
+
|
354
436
|
# Add auto-embedding info to result
|
355
437
|
if isinstance(hybrid_result, dict) and hybrid_result.get("success"):
|
356
438
|
# Convert to mutable dict to add extra fields
|
@@ -358,23 +440,28 @@ def auto_smart_search(
|
|
358
440
|
final_result["search_type"] = "auto_hybrid"
|
359
441
|
final_result["auto_embedded_tables"] = auto_embedded_tables
|
360
442
|
if auto_embedded_tables:
|
361
|
-
final_result["auto_embedding_note"] =
|
443
|
+
final_result["auto_embedding_note"] = (
|
444
|
+
f"Automatically generated embeddings for {len(auto_embedded_tables)} table(s)"
|
445
|
+
)
|
362
446
|
return cast(ToolResponse, final_result)
|
363
447
|
else:
|
364
448
|
return cast(ToolResponse, hybrid_result)
|
365
|
-
|
449
|
+
|
366
450
|
except Exception as e:
|
367
451
|
# Add detailed error information for debugging
|
368
452
|
error_details = {
|
369
|
-
"query": query,
|
453
|
+
"query": query,
|
370
454
|
"tables": tables,
|
371
455
|
"error_type": type(e).__name__,
|
372
456
|
"error_str": str(e),
|
373
|
-
"traceback": traceback.format_exc()
|
457
|
+
"traceback": traceback.format_exc(),
|
374
458
|
}
|
375
|
-
return cast(
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
459
|
+
return cast(
|
460
|
+
ToolResponse,
|
461
|
+
{
|
462
|
+
"success": False,
|
463
|
+
"error": f"Auto smart search failed: {str(e)}",
|
464
|
+
"category": "HYBRID_SEARCH_ERROR",
|
465
|
+
"details": error_details,
|
466
|
+
},
|
467
|
+
)
|
mcp_sqlite_memory_bank/types.py
CHANGED
@@ -42,7 +42,12 @@ class MemoryBankError(Exception):
|
|
42
42
|
|
43
43
|
def to_dict(self) -> Dict[str, Any]:
|
44
44
|
"""Convert error to a dict format suitable for FastMCP responses."""
|
45
|
-
return {
|
45
|
+
return {
|
46
|
+
"success": False,
|
47
|
+
"error": self.message,
|
48
|
+
"category": self.category.name,
|
49
|
+
"details": self.details or {},
|
50
|
+
}
|
46
51
|
|
47
52
|
|
48
53
|
class ValidationError(MemoryBankError):
|