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