spatial-memory-mcp 1.0.3__py3-none-any.whl → 1.5.3__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.
Potentially problematic release.
This version of spatial-memory-mcp might be problematic. Click here for more details.
- spatial_memory/__init__.py +97 -97
- spatial_memory/config.py +105 -0
- spatial_memory/core/__init__.py +26 -0
- spatial_memory/core/cache.py +317 -0
- spatial_memory/core/circuit_breaker.py +297 -0
- spatial_memory/core/database.py +167 -1
- spatial_memory/core/embeddings.py +92 -2
- spatial_memory/core/logging.py +194 -103
- spatial_memory/core/rate_limiter.py +309 -105
- spatial_memory/core/tracing.py +300 -0
- spatial_memory/core/validation.py +319 -319
- spatial_memory/server.py +229 -30
- spatial_memory/services/memory.py +79 -2
- spatial_memory/tools/definitions.py +695 -671
- {spatial_memory_mcp-1.0.3.dist-info → spatial_memory_mcp-1.5.3.dist-info}/METADATA +1 -1
- {spatial_memory_mcp-1.0.3.dist-info → spatial_memory_mcp-1.5.3.dist-info}/RECORD +19 -16
- {spatial_memory_mcp-1.0.3.dist-info → spatial_memory_mcp-1.5.3.dist-info}/WHEEL +0 -0
- {spatial_memory_mcp-1.0.3.dist-info → spatial_memory_mcp-1.5.3.dist-info}/entry_points.txt +0 -0
- {spatial_memory_mcp-1.0.3.dist-info → spatial_memory_mcp-1.5.3.dist-info}/licenses/LICENSE +0 -0
spatial_memory/core/logging.py
CHANGED
|
@@ -1,103 +1,194 @@
|
|
|
1
|
-
"""Secure structured logging for Spatial Memory MCP Server.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
1
|
+
"""Secure structured logging for Spatial Memory MCP Server.
|
|
2
|
+
|
|
3
|
+
This module provides secure logging with sensitive data masking and
|
|
4
|
+
optional request context tracking for observability.
|
|
5
|
+
|
|
6
|
+
Features:
|
|
7
|
+
- Sensitive data masking (API keys, passwords)
|
|
8
|
+
- JSON structured logging format
|
|
9
|
+
- Request context integration ([req=xxx][agent=yyy] prefixes)
|
|
10
|
+
- Configurable log levels and formats
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import re
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from typing import TYPE_CHECKING
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
# Patterns to mask in logs
|
|
25
|
+
SENSITIVE_PATTERNS = [
|
|
26
|
+
(re.compile(r'api[_-]?key["\']?\s*[:=]\s*["\']?[\w-]+', re.I), 'api_key=***MASKED***'),
|
|
27
|
+
(re.compile(r'sk-[a-zA-Z0-9]{20,}'), '***OPENAI_KEY***'),
|
|
28
|
+
(re.compile(r'password["\']?\s*[:=]\s*["\']?[^\s"\']+', re.I), 'password=***MASKED***'),
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _get_trace_context() -> tuple[str | None, str | None]:
|
|
33
|
+
"""Get request context without importing at module level.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
Tuple of (request_id, agent_id) or (None, None) if no context.
|
|
37
|
+
"""
|
|
38
|
+
try:
|
|
39
|
+
# Import here to avoid circular imports
|
|
40
|
+
from spatial_memory.core.tracing import get_current_context
|
|
41
|
+
|
|
42
|
+
ctx = get_current_context()
|
|
43
|
+
if ctx:
|
|
44
|
+
return ctx.request_id, ctx.agent_id
|
|
45
|
+
except ImportError:
|
|
46
|
+
pass
|
|
47
|
+
return None, None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class SecureFormatter(logging.Formatter):
|
|
51
|
+
"""Formatter that masks sensitive data and includes trace context."""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
fmt: str | None = None,
|
|
56
|
+
datefmt: str | None = None,
|
|
57
|
+
include_trace_context: bool = True,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Initialize the secure formatter.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
fmt: Format string for log messages.
|
|
63
|
+
datefmt: Date format string.
|
|
64
|
+
include_trace_context: Whether to include [req=xxx][agent=yyy] prefix.
|
|
65
|
+
"""
|
|
66
|
+
super().__init__(fmt=fmt, datefmt=datefmt)
|
|
67
|
+
self.include_trace_context = include_trace_context
|
|
68
|
+
|
|
69
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
70
|
+
"""Format log record and mask sensitive data.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
record: The log record to format.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Formatted log message with sensitive data masked and trace context.
|
|
77
|
+
"""
|
|
78
|
+
message = super().format(record)
|
|
79
|
+
|
|
80
|
+
# Add trace context prefix if available
|
|
81
|
+
if self.include_trace_context:
|
|
82
|
+
request_id, agent_id = _get_trace_context()
|
|
83
|
+
if request_id:
|
|
84
|
+
prefix_parts = [f"[req={request_id}]"]
|
|
85
|
+
if agent_id:
|
|
86
|
+
prefix_parts.append(f"[agent={agent_id}]")
|
|
87
|
+
prefix = "".join(prefix_parts) + " "
|
|
88
|
+
# Insert after timestamp and logger name
|
|
89
|
+
# Format: "2024-01-15 10:30:00 - logger - LEVEL - message"
|
|
90
|
+
# We want: "2024-01-15 10:30:00 - logger - LEVEL - [req=xxx] message"
|
|
91
|
+
parts = message.split(" - ", 3)
|
|
92
|
+
if len(parts) == 4:
|
|
93
|
+
message = f"{parts[0]} - {parts[1]} - {parts[2]} - {prefix}{parts[3]}"
|
|
94
|
+
else:
|
|
95
|
+
# Fallback: just prepend
|
|
96
|
+
message = prefix + message
|
|
97
|
+
|
|
98
|
+
for pattern, replacement in SENSITIVE_PATTERNS:
|
|
99
|
+
message = pattern.sub(replacement, message)
|
|
100
|
+
return message
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class JSONFormatter(logging.Formatter):
|
|
104
|
+
"""JSON formatter for structured logging with trace context."""
|
|
105
|
+
|
|
106
|
+
def __init__(self, include_trace_context: bool = True) -> None:
|
|
107
|
+
"""Initialize the JSON formatter.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
include_trace_context: Whether to include request_id and agent_id fields.
|
|
111
|
+
"""
|
|
112
|
+
super().__init__()
|
|
113
|
+
self.include_trace_context = include_trace_context
|
|
114
|
+
|
|
115
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
116
|
+
"""Format log record as JSON with sensitive data masked.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
record: The log record to format.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
JSON-formatted log message with sensitive data masked.
|
|
123
|
+
"""
|
|
124
|
+
log_data: dict[str, str | None] = {
|
|
125
|
+
"timestamp": datetime.now(timezone.utc).isoformat(),
|
|
126
|
+
"level": record.levelname,
|
|
127
|
+
"logger": record.name,
|
|
128
|
+
"message": record.getMessage(),
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# Add trace context if available
|
|
132
|
+
if self.include_trace_context:
|
|
133
|
+
request_id, agent_id = _get_trace_context()
|
|
134
|
+
if request_id:
|
|
135
|
+
log_data["request_id"] = request_id
|
|
136
|
+
if agent_id:
|
|
137
|
+
log_data["agent_id"] = agent_id
|
|
138
|
+
|
|
139
|
+
if record.exc_info:
|
|
140
|
+
log_data["exception"] = self.formatException(record.exc_info)
|
|
141
|
+
|
|
142
|
+
# Mask sensitive data
|
|
143
|
+
json_str = json.dumps(log_data)
|
|
144
|
+
for pattern, replacement in SENSITIVE_PATTERNS:
|
|
145
|
+
json_str = pattern.sub(replacement, json_str)
|
|
146
|
+
|
|
147
|
+
return json_str
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def configure_logging(
|
|
151
|
+
level: str = "INFO",
|
|
152
|
+
json_format: bool = False,
|
|
153
|
+
mask_sensitive: bool = True,
|
|
154
|
+
include_trace_context: bool = True,
|
|
155
|
+
) -> None:
|
|
156
|
+
"""Configure logging for the application.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
level: Logging level (DEBUG, INFO, WARNING, ERROR).
|
|
160
|
+
json_format: Use JSON format for structured logging.
|
|
161
|
+
mask_sensitive: Mask sensitive data in logs.
|
|
162
|
+
include_trace_context: Include [req=xxx][agent=yyy] in log messages.
|
|
163
|
+
"""
|
|
164
|
+
# Get root logger
|
|
165
|
+
root_logger = logging.getLogger()
|
|
166
|
+
root_logger.setLevel(level)
|
|
167
|
+
|
|
168
|
+
# Remove existing handlers
|
|
169
|
+
for handler in root_logger.handlers[:]:
|
|
170
|
+
root_logger.removeHandler(handler)
|
|
171
|
+
|
|
172
|
+
# Create console handler
|
|
173
|
+
console_handler = logging.StreamHandler()
|
|
174
|
+
console_handler.setLevel(level)
|
|
175
|
+
|
|
176
|
+
# Choose formatter
|
|
177
|
+
if json_format:
|
|
178
|
+
formatter: logging.Formatter = JSONFormatter(
|
|
179
|
+
include_trace_context=include_trace_context
|
|
180
|
+
)
|
|
181
|
+
elif mask_sensitive:
|
|
182
|
+
formatter = SecureFormatter(
|
|
183
|
+
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
184
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
185
|
+
include_trace_context=include_trace_context,
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
formatter = logging.Formatter(
|
|
189
|
+
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
|
190
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
console_handler.setFormatter(formatter)
|
|
194
|
+
root_logger.addHandler(console_handler)
|