spatial-memory-mcp 1.9.1__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.
- spatial_memory/__init__.py +97 -0
- spatial_memory/__main__.py +271 -0
- spatial_memory/adapters/__init__.py +7 -0
- spatial_memory/adapters/lancedb_repository.py +880 -0
- spatial_memory/config.py +769 -0
- spatial_memory/core/__init__.py +118 -0
- spatial_memory/core/cache.py +317 -0
- spatial_memory/core/circuit_breaker.py +297 -0
- spatial_memory/core/connection_pool.py +220 -0
- spatial_memory/core/consolidation_strategies.py +401 -0
- spatial_memory/core/database.py +3072 -0
- spatial_memory/core/db_idempotency.py +242 -0
- spatial_memory/core/db_indexes.py +576 -0
- spatial_memory/core/db_migrations.py +588 -0
- spatial_memory/core/db_search.py +512 -0
- spatial_memory/core/db_versioning.py +178 -0
- spatial_memory/core/embeddings.py +558 -0
- spatial_memory/core/errors.py +317 -0
- spatial_memory/core/file_security.py +701 -0
- spatial_memory/core/filesystem.py +178 -0
- spatial_memory/core/health.py +289 -0
- spatial_memory/core/helpers.py +79 -0
- spatial_memory/core/import_security.py +433 -0
- spatial_memory/core/lifecycle_ops.py +1067 -0
- spatial_memory/core/logging.py +194 -0
- spatial_memory/core/metrics.py +192 -0
- spatial_memory/core/models.py +660 -0
- spatial_memory/core/rate_limiter.py +326 -0
- spatial_memory/core/response_types.py +500 -0
- spatial_memory/core/security.py +588 -0
- spatial_memory/core/spatial_ops.py +430 -0
- spatial_memory/core/tracing.py +300 -0
- spatial_memory/core/utils.py +110 -0
- spatial_memory/core/validation.py +406 -0
- spatial_memory/factory.py +444 -0
- spatial_memory/migrations/__init__.py +40 -0
- spatial_memory/ports/__init__.py +11 -0
- spatial_memory/ports/repositories.py +630 -0
- spatial_memory/py.typed +0 -0
- spatial_memory/server.py +1214 -0
- spatial_memory/services/__init__.py +70 -0
- spatial_memory/services/decay_manager.py +411 -0
- spatial_memory/services/export_import.py +1031 -0
- spatial_memory/services/lifecycle.py +1139 -0
- spatial_memory/services/memory.py +412 -0
- spatial_memory/services/spatial.py +1152 -0
- spatial_memory/services/utility.py +429 -0
- spatial_memory/tools/__init__.py +5 -0
- spatial_memory/tools/definitions.py +695 -0
- spatial_memory/verify.py +140 -0
- spatial_memory_mcp-1.9.1.dist-info/METADATA +509 -0
- spatial_memory_mcp-1.9.1.dist-info/RECORD +55 -0
- spatial_memory_mcp-1.9.1.dist-info/WHEEL +4 -0
- spatial_memory_mcp-1.9.1.dist-info/entry_points.txt +2 -0
- spatial_memory_mcp-1.9.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
"""Custom exceptions for Spatial Memory MCP Server."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def sanitize_path_for_error(path: str | Path) -> str:
|
|
7
|
+
"""Extract only the filename from a path for safe error messages.
|
|
8
|
+
|
|
9
|
+
Prevents leaking full system paths in error messages which could
|
|
10
|
+
expose sensitive directory structure information.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
path: Full path or filename.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
Just the filename portion.
|
|
17
|
+
"""
|
|
18
|
+
if isinstance(path, Path):
|
|
19
|
+
return path.name
|
|
20
|
+
return Path(path).name
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class SpatialMemoryError(Exception):
|
|
24
|
+
"""Base exception for all spatial memory errors."""
|
|
25
|
+
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MemoryNotFoundError(SpatialMemoryError):
|
|
30
|
+
"""Raised when a memory ID doesn't exist."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, memory_id: str) -> None:
|
|
33
|
+
self.memory_id = memory_id
|
|
34
|
+
super().__init__(f"Memory not found: {memory_id}")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NamespaceNotFoundError(SpatialMemoryError):
|
|
38
|
+
"""Raised when a namespace doesn't exist."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, namespace: str) -> None:
|
|
41
|
+
self.namespace = namespace
|
|
42
|
+
super().__init__(f"Namespace not found: {namespace}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class EmbeddingError(SpatialMemoryError):
|
|
46
|
+
"""Raised when embedding generation fails."""
|
|
47
|
+
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class StorageError(SpatialMemoryError):
|
|
52
|
+
"""Raised when database operations fail."""
|
|
53
|
+
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PartialBatchInsertError(StorageError):
|
|
58
|
+
"""Raised when batch insert partially fails.
|
|
59
|
+
|
|
60
|
+
Provides information about which records were successfully inserted
|
|
61
|
+
before the failure, enabling recovery or rollback.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
message: str,
|
|
67
|
+
succeeded_ids: list[str],
|
|
68
|
+
total_requested: int,
|
|
69
|
+
failed_batch_index: int | None = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Initialize with details about partial failure.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
message: Error description.
|
|
75
|
+
succeeded_ids: IDs of successfully inserted records.
|
|
76
|
+
total_requested: Total number of records requested to insert.
|
|
77
|
+
failed_batch_index: Index of the batch that failed (if batched).
|
|
78
|
+
"""
|
|
79
|
+
self.succeeded_ids = succeeded_ids
|
|
80
|
+
self.total_requested = total_requested
|
|
81
|
+
self.failed_batch_index = failed_batch_index
|
|
82
|
+
super().__init__(
|
|
83
|
+
f"{message}. "
|
|
84
|
+
f"Inserted {len(succeeded_ids)}/{total_requested} records before failure."
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ValidationError(SpatialMemoryError):
|
|
89
|
+
"""Raised when input validation fails."""
|
|
90
|
+
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ConfigurationError(SpatialMemoryError):
|
|
95
|
+
"""Raised when configuration is invalid."""
|
|
96
|
+
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ClusteringError(SpatialMemoryError):
|
|
101
|
+
"""Raised when clustering fails (e.g., too few memories)."""
|
|
102
|
+
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class VisualizationError(SpatialMemoryError):
|
|
107
|
+
"""Raised when visualization generation fails."""
|
|
108
|
+
|
|
109
|
+
pass
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class InsufficientMemoriesError(SpatialMemoryError):
|
|
113
|
+
"""Raised when operation requires more memories than available."""
|
|
114
|
+
|
|
115
|
+
def __init__(self, required: int, available: int, operation: str) -> None:
|
|
116
|
+
self.required = required
|
|
117
|
+
self.available = available
|
|
118
|
+
self.operation = operation
|
|
119
|
+
super().__init__(
|
|
120
|
+
f"{operation} requires at least {required} memories, but only {available} available"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class JourneyError(SpatialMemoryError):
|
|
125
|
+
"""Raised when journey path cannot be computed."""
|
|
126
|
+
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class WanderError(SpatialMemoryError):
|
|
131
|
+
"""Raised when wander cannot continue."""
|
|
132
|
+
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class DecayError(SpatialMemoryError):
|
|
137
|
+
"""Raised when decay calculation or application fails."""
|
|
138
|
+
|
|
139
|
+
pass
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class ReinforcementError(SpatialMemoryError):
|
|
143
|
+
"""Raised when reinforcement fails."""
|
|
144
|
+
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class ExtractionError(SpatialMemoryError):
|
|
149
|
+
"""Raised when memory extraction fails."""
|
|
150
|
+
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ConsolidationError(SpatialMemoryError):
|
|
155
|
+
"""Raised when consolidation fails."""
|
|
156
|
+
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# =============================================================================
|
|
161
|
+
# Phase 5 Error Types - Utility Operations
|
|
162
|
+
# =============================================================================
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class ExportError(SpatialMemoryError):
|
|
166
|
+
"""Raised when memory export fails."""
|
|
167
|
+
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class MemoryImportError(SpatialMemoryError):
|
|
172
|
+
"""Raised when memory import fails.
|
|
173
|
+
|
|
174
|
+
Note: Named MemoryImportError to avoid shadowing Python's built-in ImportError.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
pass
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class NamespaceOperationError(SpatialMemoryError):
|
|
181
|
+
"""Raised when namespace operation fails."""
|
|
182
|
+
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class PathSecurityError(SpatialMemoryError):
|
|
187
|
+
"""Raised when a file path violates security constraints.
|
|
188
|
+
|
|
189
|
+
Examples:
|
|
190
|
+
- Path traversal attempt (../)
|
|
191
|
+
- Path outside allowed directories
|
|
192
|
+
- Symlink to disallowed location
|
|
193
|
+
- Invalid file extension
|
|
194
|
+
|
|
195
|
+
Note:
|
|
196
|
+
Error messages only include the filename, not the full path,
|
|
197
|
+
to avoid leaking system directory structure.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
def __init__(
|
|
201
|
+
self,
|
|
202
|
+
path: str,
|
|
203
|
+
violation_type: str,
|
|
204
|
+
message: str | None = None,
|
|
205
|
+
) -> None:
|
|
206
|
+
self.path = path
|
|
207
|
+
self.violation_type = violation_type
|
|
208
|
+
safe_name = sanitize_path_for_error(path)
|
|
209
|
+
self.message = message or f"Path security violation ({violation_type}): {safe_name}"
|
|
210
|
+
super().__init__(self.message)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class FileSizeLimitError(SpatialMemoryError):
|
|
214
|
+
"""Raised when a file exceeds size limits.
|
|
215
|
+
|
|
216
|
+
Note:
|
|
217
|
+
Error messages only include the filename, not the full path,
|
|
218
|
+
to avoid leaking system directory structure.
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
def __init__(
|
|
222
|
+
self,
|
|
223
|
+
path: str,
|
|
224
|
+
actual_size_bytes: int,
|
|
225
|
+
max_size_bytes: int,
|
|
226
|
+
) -> None:
|
|
227
|
+
self.path = path
|
|
228
|
+
self.actual_size_bytes = actual_size_bytes
|
|
229
|
+
self.max_size_bytes = max_size_bytes
|
|
230
|
+
actual_mb = actual_size_bytes / (1024 * 1024)
|
|
231
|
+
max_mb = max_size_bytes / (1024 * 1024)
|
|
232
|
+
safe_name = sanitize_path_for_error(path)
|
|
233
|
+
super().__init__(
|
|
234
|
+
f"File exceeds size limit: {safe_name} is {actual_mb:.2f}MB "
|
|
235
|
+
f"(max: {max_mb:.2f}MB)"
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class DimensionMismatchError(ValidationError):
|
|
240
|
+
"""Raised when imported vectors have wrong dimensions."""
|
|
241
|
+
|
|
242
|
+
def __init__(
|
|
243
|
+
self,
|
|
244
|
+
expected_dim: int,
|
|
245
|
+
actual_dim: int,
|
|
246
|
+
record_index: int | None = None,
|
|
247
|
+
) -> None:
|
|
248
|
+
self.expected_dim = expected_dim
|
|
249
|
+
self.actual_dim = actual_dim
|
|
250
|
+
self.record_index = record_index
|
|
251
|
+
location = f" at record {record_index}" if record_index is not None else ""
|
|
252
|
+
super().__init__(
|
|
253
|
+
f"Vector dimension mismatch{location}: expected {expected_dim}, "
|
|
254
|
+
f"got {actual_dim}"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class SchemaValidationError(ValidationError):
|
|
259
|
+
"""Raised when import data fails schema validation."""
|
|
260
|
+
|
|
261
|
+
def __init__(
|
|
262
|
+
self,
|
|
263
|
+
field: str,
|
|
264
|
+
error: str,
|
|
265
|
+
record_index: int | None = None,
|
|
266
|
+
) -> None:
|
|
267
|
+
self.field = field
|
|
268
|
+
self.error = error
|
|
269
|
+
self.record_index = record_index
|
|
270
|
+
location = f" at record {record_index}" if record_index is not None else ""
|
|
271
|
+
super().__init__(f"Schema validation failed for '{field}'{location}: {error}")
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class ImportRecordLimitError(SpatialMemoryError):
|
|
275
|
+
"""Raised when import file contains too many records."""
|
|
276
|
+
|
|
277
|
+
def __init__(
|
|
278
|
+
self,
|
|
279
|
+
actual_count: int,
|
|
280
|
+
max_count: int,
|
|
281
|
+
) -> None:
|
|
282
|
+
self.actual_count = actual_count
|
|
283
|
+
self.max_count = max_count
|
|
284
|
+
super().__init__(
|
|
285
|
+
f"Import file contains {actual_count} records " f"(max: {max_count})"
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# =============================================================================
|
|
290
|
+
# Cross-Process Locking Error
|
|
291
|
+
# =============================================================================
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class FileLockError(SpatialMemoryError):
|
|
295
|
+
"""Raised when cross-process file lock cannot be acquired."""
|
|
296
|
+
|
|
297
|
+
def __init__(
|
|
298
|
+
self,
|
|
299
|
+
lock_path: str,
|
|
300
|
+
timeout: float,
|
|
301
|
+
message: str | None = None,
|
|
302
|
+
) -> None:
|
|
303
|
+
self.lock_path = lock_path
|
|
304
|
+
self.timeout = timeout
|
|
305
|
+
self.message = message or f"Failed to acquire file lock at {lock_path} after {timeout}s"
|
|
306
|
+
super().__init__(self.message)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
# =============================================================================
|
|
310
|
+
# Migration Error
|
|
311
|
+
# =============================================================================
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class MigrationError(SpatialMemoryError):
|
|
315
|
+
"""Raised when a database migration fails."""
|
|
316
|
+
|
|
317
|
+
pass
|