spatial-memory-mcp 1.0.2__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.

@@ -1,103 +1,194 @@
1
- """Secure structured logging for Spatial Memory MCP Server."""
2
-
3
- import json
4
- import logging
5
- import re
6
- from datetime import datetime, timezone
7
-
8
- # Patterns to mask in logs
9
- SENSITIVE_PATTERNS = [
10
- (re.compile(r'api[_-]?key["\']?\s*[:=]\s*["\']?[\w-]+', re.I), 'api_key=***MASKED***'),
11
- (re.compile(r'sk-[a-zA-Z0-9]{20,}'), '***OPENAI_KEY***'),
12
- (re.compile(r'password["\']?\s*[:=]\s*["\']?[^\s"\']+', re.I), 'password=***MASKED***'),
13
- ]
14
-
15
-
16
- class SecureFormatter(logging.Formatter):
17
- """Formatter that masks sensitive data."""
18
-
19
- def format(self, record: logging.LogRecord) -> str:
20
- """Format log record and mask sensitive data.
21
-
22
- Args:
23
- record: The log record to format.
24
-
25
- Returns:
26
- Formatted log message with sensitive data masked.
27
- """
28
- message = super().format(record)
29
- for pattern, replacement in SENSITIVE_PATTERNS:
30
- message = pattern.sub(replacement, message)
31
- return message
32
-
33
-
34
- class JSONFormatter(logging.Formatter):
35
- """JSON formatter for structured logging."""
36
-
37
- def format(self, record: logging.LogRecord) -> str:
38
- """Format log record as JSON with sensitive data masked.
39
-
40
- Args:
41
- record: The log record to format.
42
-
43
- Returns:
44
- JSON-formatted log message with sensitive data masked.
45
- """
46
- log_data = {
47
- "timestamp": datetime.now(timezone.utc).isoformat(),
48
- "level": record.levelname,
49
- "logger": record.name,
50
- "message": record.getMessage(),
51
- }
52
-
53
- if record.exc_info:
54
- log_data["exception"] = self.formatException(record.exc_info)
55
-
56
- # Mask sensitive data
57
- json_str = json.dumps(log_data)
58
- for pattern, replacement in SENSITIVE_PATTERNS:
59
- json_str = pattern.sub(replacement, json_str)
60
-
61
- return json_str
62
-
63
-
64
- def configure_logging(
65
- level: str = "INFO",
66
- json_format: bool = False,
67
- mask_sensitive: bool = True,
68
- ) -> None:
69
- """Configure logging for the application.
70
-
71
- Args:
72
- level: Logging level (DEBUG, INFO, WARNING, ERROR).
73
- json_format: Use JSON format for structured logging.
74
- mask_sensitive: Mask sensitive data in logs.
75
- """
76
- # Get root logger
77
- root_logger = logging.getLogger()
78
- root_logger.setLevel(level)
79
-
80
- # Remove existing handlers
81
- for handler in root_logger.handlers[:]:
82
- root_logger.removeHandler(handler)
83
-
84
- # Create console handler
85
- console_handler = logging.StreamHandler()
86
- console_handler.setLevel(level)
87
-
88
- # Choose formatter
89
- if json_format:
90
- formatter: logging.Formatter = JSONFormatter()
91
- elif mask_sensitive:
92
- formatter = SecureFormatter(
93
- fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
94
- datefmt="%Y-%m-%d %H:%M:%S",
95
- )
96
- else:
97
- formatter = logging.Formatter(
98
- fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
99
- datefmt="%Y-%m-%d %H:%M:%S",
100
- )
101
-
102
- console_handler.setFormatter(formatter)
103
- root_logger.addHandler(console_handler)
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)