mcp-sqlite-memory-bank 1.2.3__py3-none-any.whl → 1.3.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 +6 -9
- mcp_sqlite_memory_bank/database.py +923 -0
- mcp_sqlite_memory_bank/semantic.py +386 -0
- mcp_sqlite_memory_bank/server.py +838 -1140
- mcp_sqlite_memory_bank/types.py +105 -9
- mcp_sqlite_memory_bank/utils.py +22 -54
- {mcp_sqlite_memory_bank-1.2.3.dist-info → mcp_sqlite_memory_bank-1.3.0.dist-info}/METADATA +189 -91
- mcp_sqlite_memory_bank-1.3.0.dist-info/RECORD +13 -0
- mcp_sqlite_memory_bank-1.2.3.dist-info/RECORD +0 -11
- {mcp_sqlite_memory_bank-1.2.3.dist-info → mcp_sqlite_memory_bank-1.3.0.dist-info}/WHEEL +0 -0
- {mcp_sqlite_memory_bank-1.2.3.dist-info → mcp_sqlite_memory_bank-1.3.0.dist-info}/entry_points.txt +0 -0
- {mcp_sqlite_memory_bank-1.2.3.dist-info → mcp_sqlite_memory_bank-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {mcp_sqlite_memory_bank-1.2.3.dist-info → mcp_sqlite_memory_bank-1.3.0.dist-info}/top_level.txt +0 -0
mcp_sqlite_memory_bank/types.py
CHANGED
@@ -12,17 +12,19 @@ from enum import Enum, auto
|
|
12
12
|
|
13
13
|
class SqliteType(str, Enum):
|
14
14
|
"""Valid SQLite column types."""
|
15
|
+
|
15
16
|
TEXT = "TEXT"
|
16
17
|
INTEGER = "INTEGER"
|
17
18
|
REAL = "REAL"
|
18
19
|
BLOB = "BLOB"
|
19
20
|
NULL = "NULL"
|
20
21
|
TIMESTAMP = "TIMESTAMP" # Common extension
|
21
|
-
BOOLEAN = "BOOLEAN"
|
22
|
+
BOOLEAN = "BOOLEAN" # Common extension
|
22
23
|
|
23
24
|
|
24
25
|
class ErrorCategory(Enum):
|
25
26
|
"""Categories for SQLite Memory Bank errors."""
|
27
|
+
|
26
28
|
VALIDATION = auto()
|
27
29
|
DATABASE = auto()
|
28
30
|
SCHEMA = auto()
|
@@ -33,18 +35,14 @@ class ErrorCategory(Enum):
|
|
33
35
|
@dataclass
|
34
36
|
class MemoryBankError(Exception):
|
35
37
|
"""Base class for SQLite Memory Bank errors."""
|
38
|
+
|
36
39
|
message: str
|
37
40
|
category: ErrorCategory
|
38
41
|
details: Optional[Dict[str, Any]] = None
|
39
42
|
|
40
43
|
def to_dict(self) -> Dict[str, Any]:
|
41
44
|
"""Convert error to a dict format suitable for FastMCP responses."""
|
42
|
-
return {
|
43
|
-
"success": False,
|
44
|
-
"error": self.message,
|
45
|
-
"category": self.category.name,
|
46
|
-
"details": self.details or {}
|
47
|
-
}
|
45
|
+
return {"success": False, "error": self.message, "category": self.category.name, "details": self.details or {}}
|
48
46
|
|
49
47
|
|
50
48
|
class ValidationError(MemoryBankError):
|
@@ -77,6 +75,7 @@ class DataError(MemoryBankError):
|
|
77
75
|
|
78
76
|
class TableColumn(TypedDict):
|
79
77
|
"""Structure for table column definitions."""
|
78
|
+
|
80
79
|
name: str
|
81
80
|
type: str
|
82
81
|
notnull: bool
|
@@ -86,11 +85,13 @@ class TableColumn(TypedDict):
|
|
86
85
|
|
87
86
|
class SuccessResponse(TypedDict):
|
88
87
|
"""Base structure for successful responses."""
|
88
|
+
|
89
89
|
success: Literal[True]
|
90
90
|
|
91
91
|
|
92
92
|
class ErrorResponse(TypedDict):
|
93
93
|
"""Structure for error responses."""
|
94
|
+
|
94
95
|
success: Literal[False]
|
95
96
|
error: str
|
96
97
|
category: str
|
@@ -99,59 +100,146 @@ class ErrorResponse(TypedDict):
|
|
99
100
|
|
100
101
|
class CreateTableResponse(SuccessResponse):
|
101
102
|
"""Response for create_table tool."""
|
103
|
+
|
102
104
|
pass
|
103
105
|
|
104
106
|
|
105
107
|
class DropTableResponse(SuccessResponse):
|
106
108
|
"""Response for drop_table tool."""
|
109
|
+
|
107
110
|
pass
|
108
111
|
|
109
112
|
|
110
113
|
class RenameTableResponse(SuccessResponse):
|
111
114
|
"""Response for rename_table tool."""
|
115
|
+
|
112
116
|
pass
|
113
117
|
|
114
118
|
|
115
119
|
class ListTablesResponse(SuccessResponse):
|
116
120
|
"""Response for list_tables tool."""
|
121
|
+
|
117
122
|
tables: List[str]
|
118
123
|
|
119
124
|
|
120
125
|
class DescribeTableResponse(SuccessResponse):
|
121
126
|
"""Response for describe_table tool."""
|
122
|
-
|
127
|
+
|
128
|
+
columns: List[Dict[str, Any]]
|
123
129
|
|
124
130
|
|
125
131
|
class ListAllColumnsResponse(SuccessResponse):
|
126
132
|
"""Response for list_all_columns tool."""
|
133
|
+
|
127
134
|
schemas: Dict[str, List[str]]
|
128
135
|
|
129
136
|
|
130
137
|
class CreateRowResponse(SuccessResponse):
|
131
138
|
"""Response for create_row tool."""
|
139
|
+
|
132
140
|
id: int
|
133
141
|
|
134
142
|
|
135
143
|
class ReadRowsResponse(SuccessResponse):
|
136
144
|
"""Response for read_rows tool."""
|
145
|
+
|
137
146
|
rows: List[Dict[str, Any]]
|
138
147
|
|
139
148
|
|
140
149
|
class UpdateRowsResponse(SuccessResponse):
|
141
150
|
"""Response for update_rows tool."""
|
151
|
+
|
142
152
|
rows_affected: int
|
143
153
|
|
144
154
|
|
145
155
|
class DeleteRowsResponse(SuccessResponse):
|
146
156
|
"""Response for delete_rows tool."""
|
157
|
+
|
147
158
|
rows_affected: int
|
148
159
|
|
149
160
|
|
150
161
|
class SelectQueryResponse(SuccessResponse):
|
151
162
|
"""Response for run_select_query tool."""
|
163
|
+
|
152
164
|
rows: List[Dict[str, Any]]
|
153
165
|
|
154
166
|
|
167
|
+
class SearchContentResponse(SuccessResponse):
|
168
|
+
"""Response for search_content tool."""
|
169
|
+
|
170
|
+
results: List[Dict[str, Any]]
|
171
|
+
query: str
|
172
|
+
tables_searched: List[str]
|
173
|
+
total_results: int
|
174
|
+
|
175
|
+
|
176
|
+
class ExploreTablesResponse(SuccessResponse):
|
177
|
+
"""Response for explore_tables tool."""
|
178
|
+
|
179
|
+
exploration: Dict[str, Any]
|
180
|
+
|
181
|
+
|
182
|
+
# Semantic Search Response Types
|
183
|
+
class SemanticSearchResponse(TypedDict):
|
184
|
+
"""Response type for semantic search operations."""
|
185
|
+
success: bool
|
186
|
+
results: List[Dict[str, Any]]
|
187
|
+
query: str
|
188
|
+
tables_searched: List[str]
|
189
|
+
total_results: int
|
190
|
+
model: str
|
191
|
+
similarity_threshold: float
|
192
|
+
|
193
|
+
|
194
|
+
class RelatedContentResponse(TypedDict, total=False):
|
195
|
+
"""Response type for find related content operations."""
|
196
|
+
success: bool
|
197
|
+
results: List[Dict[str, Any]]
|
198
|
+
target_row: Dict[str, Any]
|
199
|
+
total_results: int
|
200
|
+
similarity_threshold: float
|
201
|
+
model: str
|
202
|
+
message: str # Optional field
|
203
|
+
|
204
|
+
|
205
|
+
class HybridSearchResponse(TypedDict):
|
206
|
+
"""Response type for hybrid search operations."""
|
207
|
+
success: bool
|
208
|
+
results: List[Dict[str, Any]]
|
209
|
+
query: str
|
210
|
+
search_type: str
|
211
|
+
semantic_weight: float
|
212
|
+
text_weight: float
|
213
|
+
total_results: int
|
214
|
+
model: str
|
215
|
+
|
216
|
+
|
217
|
+
class EmbeddingStatsResponse(TypedDict):
|
218
|
+
"""Response type for embedding statistics."""
|
219
|
+
success: bool
|
220
|
+
table_name: str
|
221
|
+
total_rows: int
|
222
|
+
embedded_rows: int
|
223
|
+
coverage_percent: float
|
224
|
+
embedding_dimensions: Optional[int]
|
225
|
+
embedding_column: str
|
226
|
+
|
227
|
+
|
228
|
+
class GenerateEmbeddingsResponse(TypedDict):
|
229
|
+
"""Response type for embedding generation operations."""
|
230
|
+
success: bool
|
231
|
+
message: str
|
232
|
+
processed: int
|
233
|
+
model: str
|
234
|
+
embedding_dimension: int
|
235
|
+
|
236
|
+
|
237
|
+
class EmbeddingColumnResponse(TypedDict):
|
238
|
+
"""Response type for adding embedding columns."""
|
239
|
+
success: bool
|
240
|
+
message: str
|
241
|
+
|
242
|
+
|
155
243
|
# Type alias for all possible responses
|
156
244
|
ToolResponse = Union[
|
157
245
|
CreateTableResponse,
|
@@ -165,5 +253,13 @@ ToolResponse = Union[
|
|
165
253
|
UpdateRowsResponse,
|
166
254
|
DeleteRowsResponse,
|
167
255
|
SelectQueryResponse,
|
168
|
-
|
256
|
+
SearchContentResponse,
|
257
|
+
ExploreTablesResponse,
|
258
|
+
ErrorResponse,
|
259
|
+
SemanticSearchResponse,
|
260
|
+
RelatedContentResponse,
|
261
|
+
HybridSearchResponse,
|
262
|
+
EmbeddingStatsResponse,
|
263
|
+
GenerateEmbeddingsResponse,
|
264
|
+
EmbeddingColumnResponse,
|
169
265
|
]
|
mcp_sqlite_memory_bank/utils.py
CHANGED
@@ -11,15 +11,9 @@ import sqlite3
|
|
11
11
|
import logging
|
12
12
|
from functools import wraps
|
13
13
|
from typing import Any, Callable, Dict, List, TypeVar, cast, Union, Tuple
|
14
|
-
from .types import
|
15
|
-
ValidationError,
|
16
|
-
DatabaseError,
|
17
|
-
SchemaError,
|
18
|
-
MemoryBankError,
|
19
|
-
ToolResponse
|
20
|
-
)
|
14
|
+
from .types import ValidationError, DatabaseError, SchemaError, MemoryBankError, ToolResponse
|
21
15
|
|
22
|
-
T = TypeVar(
|
16
|
+
T = TypeVar("T", bound=Callable[..., ToolResponse])
|
23
17
|
|
24
18
|
|
25
19
|
def catch_errors(f: T) -> T:
|
@@ -27,6 +21,7 @@ def catch_errors(f: T) -> T:
|
|
27
21
|
Decorator to standardize error handling across tools.
|
28
22
|
Catches exceptions and converts them to appropriate error responses.
|
29
23
|
"""
|
24
|
+
|
30
25
|
@wraps(f)
|
31
26
|
def wrapper(*args: Any, **kwargs: Any) -> ToolResponse:
|
32
27
|
try:
|
@@ -36,15 +31,11 @@ def catch_errors(f: T) -> T:
|
|
36
31
|
return cast(ToolResponse, e.to_dict())
|
37
32
|
except sqlite3.Error as e:
|
38
33
|
logging.error(f"{f.__name__} database error: {e}")
|
39
|
-
return cast(ToolResponse, DatabaseError(
|
40
|
-
f"Database error in {f.__name__}: {e}",
|
41
|
-
{"sqlite_error": str(e)}
|
42
|
-
).to_dict())
|
34
|
+
return cast(ToolResponse, DatabaseError(f"Database error in {f.__name__}: {e}", {"sqlite_error": str(e)}).to_dict())
|
43
35
|
except Exception as e:
|
44
36
|
logging.error(f"Unexpected error in {f.__name__}: {e}")
|
45
|
-
return cast(ToolResponse, DatabaseError(
|
46
|
-
|
47
|
-
).to_dict())
|
37
|
+
return cast(ToolResponse, DatabaseError(f"Unexpected error in {f.__name__}: {e}").to_dict())
|
38
|
+
|
48
39
|
return cast(T, wrapper)
|
49
40
|
|
50
41
|
|
@@ -57,11 +48,10 @@ def validate_identifier(name: str, context: str = "identifier") -> None:
|
|
57
48
|
name: The identifier to validate
|
58
49
|
context: Description of what's being validated (for error messages)
|
59
50
|
"""
|
60
|
-
if not bool(re.match(r
|
51
|
+
if not bool(re.match(r"^[A-Za-z_][A-Za-z0-9_]*$", name)):
|
61
52
|
raise ValidationError(
|
62
|
-
f"Invalid {context}: {name}. Must start with letter/underscore and "
|
63
|
-
|
64
|
-
{"invalid_name": name}
|
53
|
+
f"Invalid {context}: {name}. Must start with letter/underscore and " f"contain only letters, numbers, underscores.",
|
54
|
+
{"invalid_name": name},
|
65
55
|
)
|
66
56
|
|
67
57
|
|
@@ -74,14 +64,11 @@ def validate_column_definition(column: Dict[str, Any]) -> None:
|
|
74
64
|
column: Dictionary with column definition (must have 'name' and 'type' keys)
|
75
65
|
"""
|
76
66
|
if not isinstance(column, dict):
|
77
|
-
raise ValidationError(
|
78
|
-
"Column definition must be a dictionary",
|
79
|
-
{"received": str(type(column))}
|
80
|
-
)
|
67
|
+
raise ValidationError("Column definition must be a dictionary", {"received": str(type(column))})
|
81
68
|
if "name" not in column or "type" not in column:
|
82
69
|
raise ValidationError(
|
83
70
|
"Column definition must have 'name' and 'type' keys",
|
84
|
-
{"missing_keys": [k for k in ["name", "type"] if k not in column]}
|
71
|
+
{"missing_keys": [k for k in ["name", "type"] if k not in column]},
|
85
72
|
)
|
86
73
|
validate_identifier(column["name"], "column name")
|
87
74
|
|
@@ -103,17 +90,14 @@ def get_table_columns(conn: sqlite3.Connection, table_name: str) -> List[str]:
|
|
103
90
|
cur.execute(f"PRAGMA table_info({table_name})")
|
104
91
|
columns = [row[1] for row in cur.fetchall()]
|
105
92
|
if not columns:
|
106
|
-
raise SchemaError(
|
107
|
-
f"Table does not exist: {table_name}",
|
108
|
-
{"table_name": table_name}
|
109
|
-
)
|
93
|
+
raise SchemaError(f"Table does not exist: {table_name}", {"table_name": table_name})
|
110
94
|
return columns
|
111
95
|
|
96
|
+
|
112
97
|
# Compatibility function for direct table_name usage
|
113
98
|
|
114
99
|
|
115
|
-
def get_table_columns_by_name(
|
116
|
-
table_name: str) -> Union[List[str], Dict[str, Any]]:
|
100
|
+
def get_table_columns_by_name(table_name: str) -> Union[List[str], Dict[str, Any]]:
|
117
101
|
"""
|
118
102
|
Get list of column names for a table by name.
|
119
103
|
Compatibility function for the old implementation.
|
@@ -128,14 +112,9 @@ def get_table_columns_by_name(
|
|
128
112
|
validate_identifier(table_name, "table name")
|
129
113
|
with sqlite3.connect(os.environ.get("DB_PATH", "./test.db")) as conn:
|
130
114
|
cur = conn.cursor()
|
131
|
-
cur.execute(
|
132
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
133
|
-
(table_name,)
|
134
|
-
)
|
115
|
+
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
135
116
|
if not cur.fetchone():
|
136
|
-
return {
|
137
|
-
"success": False,
|
138
|
-
"error": f"Table '{table_name}' does not exist"}
|
117
|
+
return {"success": False, "error": f"Table '{table_name}' does not exist"}
|
139
118
|
|
140
119
|
# Get column information
|
141
120
|
cur.execute(f"PRAGMA table_info({table_name})")
|
@@ -144,9 +123,7 @@ def get_table_columns_by_name(
|
|
144
123
|
except MemoryBankError as e:
|
145
124
|
return e.to_dict()
|
146
125
|
except Exception as e:
|
147
|
-
return {
|
148
|
-
"success": False,
|
149
|
-
"error": f"Exception in get_table_columns: {e}"}
|
126
|
+
return {"success": False, "error": f"Exception in get_table_columns: {e}"}
|
150
127
|
|
151
128
|
|
152
129
|
def validate_table_exists(conn: sqlite3.Connection, table_name: str) -> None:
|
@@ -159,19 +136,12 @@ def validate_table_exists(conn: sqlite3.Connection, table_name: str) -> None:
|
|
159
136
|
table_name: Name of table to check
|
160
137
|
"""
|
161
138
|
cur = conn.cursor()
|
162
|
-
cur.execute(
|
163
|
-
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
|
164
|
-
(table_name,)
|
165
|
-
)
|
139
|
+
cur.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?", (table_name,))
|
166
140
|
if not cur.fetchone():
|
167
|
-
raise SchemaError(
|
168
|
-
f"Table does not exist: {table_name}",
|
169
|
-
{"table_name": table_name}
|
170
|
-
)
|
141
|
+
raise SchemaError(f"Table does not exist: {table_name}", {"table_name": table_name})
|
171
142
|
|
172
143
|
|
173
|
-
def build_where_clause(
|
174
|
-
where: Dict[str, Any], valid_columns: List[str]) -> Union[Tuple[str, list], Dict[str, Any]]:
|
144
|
+
def build_where_clause(where: Dict[str, Any], valid_columns: List[str]) -> Union[Tuple[str, list], Dict[str, Any]]:
|
175
145
|
"""
|
176
146
|
Build a WHERE clause from a dictionary of column-value pairs.
|
177
147
|
|
@@ -192,13 +162,11 @@ def build_where_clause(
|
|
192
162
|
|
193
163
|
for col, val in where.items():
|
194
164
|
if col not in valid_columns:
|
195
|
-
return {
|
196
|
-
"success": False,
|
197
|
-
"error": f"Invalid column in where clause: {col}"}
|
165
|
+
return {"success": False, "error": f"Invalid column in where clause: {col}"}
|
198
166
|
conditions.append(f"{col}=?")
|
199
167
|
values.append(val)
|
200
168
|
|
201
|
-
clause =
|
169
|
+
clause = " AND ".join(conditions)
|
202
170
|
return clause, values
|
203
171
|
except Exception as e:
|
204
172
|
logging.error(f"Error in build_where_clause: {e}")
|