foundry-mcp 0.3.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.
Files changed (135) hide show
  1. foundry_mcp/__init__.py +7 -0
  2. foundry_mcp/cli/__init__.py +80 -0
  3. foundry_mcp/cli/__main__.py +9 -0
  4. foundry_mcp/cli/agent.py +96 -0
  5. foundry_mcp/cli/commands/__init__.py +37 -0
  6. foundry_mcp/cli/commands/cache.py +137 -0
  7. foundry_mcp/cli/commands/dashboard.py +148 -0
  8. foundry_mcp/cli/commands/dev.py +446 -0
  9. foundry_mcp/cli/commands/journal.py +377 -0
  10. foundry_mcp/cli/commands/lifecycle.py +274 -0
  11. foundry_mcp/cli/commands/modify.py +824 -0
  12. foundry_mcp/cli/commands/plan.py +633 -0
  13. foundry_mcp/cli/commands/pr.py +393 -0
  14. foundry_mcp/cli/commands/review.py +652 -0
  15. foundry_mcp/cli/commands/session.py +479 -0
  16. foundry_mcp/cli/commands/specs.py +856 -0
  17. foundry_mcp/cli/commands/tasks.py +807 -0
  18. foundry_mcp/cli/commands/testing.py +676 -0
  19. foundry_mcp/cli/commands/validate.py +982 -0
  20. foundry_mcp/cli/config.py +98 -0
  21. foundry_mcp/cli/context.py +259 -0
  22. foundry_mcp/cli/flags.py +266 -0
  23. foundry_mcp/cli/logging.py +212 -0
  24. foundry_mcp/cli/main.py +44 -0
  25. foundry_mcp/cli/output.py +122 -0
  26. foundry_mcp/cli/registry.py +110 -0
  27. foundry_mcp/cli/resilience.py +178 -0
  28. foundry_mcp/cli/transcript.py +217 -0
  29. foundry_mcp/config.py +850 -0
  30. foundry_mcp/core/__init__.py +144 -0
  31. foundry_mcp/core/ai_consultation.py +1636 -0
  32. foundry_mcp/core/cache.py +195 -0
  33. foundry_mcp/core/capabilities.py +446 -0
  34. foundry_mcp/core/concurrency.py +898 -0
  35. foundry_mcp/core/context.py +540 -0
  36. foundry_mcp/core/discovery.py +1603 -0
  37. foundry_mcp/core/error_collection.py +728 -0
  38. foundry_mcp/core/error_store.py +592 -0
  39. foundry_mcp/core/feature_flags.py +592 -0
  40. foundry_mcp/core/health.py +749 -0
  41. foundry_mcp/core/journal.py +694 -0
  42. foundry_mcp/core/lifecycle.py +412 -0
  43. foundry_mcp/core/llm_config.py +1350 -0
  44. foundry_mcp/core/llm_patterns.py +510 -0
  45. foundry_mcp/core/llm_provider.py +1569 -0
  46. foundry_mcp/core/logging_config.py +374 -0
  47. foundry_mcp/core/metrics_persistence.py +584 -0
  48. foundry_mcp/core/metrics_registry.py +327 -0
  49. foundry_mcp/core/metrics_store.py +641 -0
  50. foundry_mcp/core/modifications.py +224 -0
  51. foundry_mcp/core/naming.py +123 -0
  52. foundry_mcp/core/observability.py +1216 -0
  53. foundry_mcp/core/otel.py +452 -0
  54. foundry_mcp/core/otel_stubs.py +264 -0
  55. foundry_mcp/core/pagination.py +255 -0
  56. foundry_mcp/core/progress.py +317 -0
  57. foundry_mcp/core/prometheus.py +577 -0
  58. foundry_mcp/core/prompts/__init__.py +464 -0
  59. foundry_mcp/core/prompts/fidelity_review.py +546 -0
  60. foundry_mcp/core/prompts/markdown_plan_review.py +511 -0
  61. foundry_mcp/core/prompts/plan_review.py +623 -0
  62. foundry_mcp/core/providers/__init__.py +225 -0
  63. foundry_mcp/core/providers/base.py +476 -0
  64. foundry_mcp/core/providers/claude.py +460 -0
  65. foundry_mcp/core/providers/codex.py +619 -0
  66. foundry_mcp/core/providers/cursor_agent.py +642 -0
  67. foundry_mcp/core/providers/detectors.py +488 -0
  68. foundry_mcp/core/providers/gemini.py +405 -0
  69. foundry_mcp/core/providers/opencode.py +616 -0
  70. foundry_mcp/core/providers/opencode_wrapper.js +302 -0
  71. foundry_mcp/core/providers/package-lock.json +24 -0
  72. foundry_mcp/core/providers/package.json +25 -0
  73. foundry_mcp/core/providers/registry.py +607 -0
  74. foundry_mcp/core/providers/test_provider.py +171 -0
  75. foundry_mcp/core/providers/validation.py +729 -0
  76. foundry_mcp/core/rate_limit.py +427 -0
  77. foundry_mcp/core/resilience.py +600 -0
  78. foundry_mcp/core/responses.py +934 -0
  79. foundry_mcp/core/review.py +366 -0
  80. foundry_mcp/core/security.py +438 -0
  81. foundry_mcp/core/spec.py +1650 -0
  82. foundry_mcp/core/task.py +1289 -0
  83. foundry_mcp/core/testing.py +450 -0
  84. foundry_mcp/core/validation.py +2081 -0
  85. foundry_mcp/dashboard/__init__.py +32 -0
  86. foundry_mcp/dashboard/app.py +119 -0
  87. foundry_mcp/dashboard/components/__init__.py +17 -0
  88. foundry_mcp/dashboard/components/cards.py +88 -0
  89. foundry_mcp/dashboard/components/charts.py +234 -0
  90. foundry_mcp/dashboard/components/filters.py +136 -0
  91. foundry_mcp/dashboard/components/tables.py +195 -0
  92. foundry_mcp/dashboard/data/__init__.py +11 -0
  93. foundry_mcp/dashboard/data/stores.py +433 -0
  94. foundry_mcp/dashboard/launcher.py +289 -0
  95. foundry_mcp/dashboard/views/__init__.py +12 -0
  96. foundry_mcp/dashboard/views/errors.py +217 -0
  97. foundry_mcp/dashboard/views/metrics.py +174 -0
  98. foundry_mcp/dashboard/views/overview.py +160 -0
  99. foundry_mcp/dashboard/views/providers.py +83 -0
  100. foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
  101. foundry_mcp/dashboard/views/tool_usage.py +139 -0
  102. foundry_mcp/prompts/__init__.py +9 -0
  103. foundry_mcp/prompts/workflows.py +525 -0
  104. foundry_mcp/resources/__init__.py +9 -0
  105. foundry_mcp/resources/specs.py +591 -0
  106. foundry_mcp/schemas/__init__.py +38 -0
  107. foundry_mcp/schemas/sdd-spec-schema.json +386 -0
  108. foundry_mcp/server.py +164 -0
  109. foundry_mcp/tools/__init__.py +10 -0
  110. foundry_mcp/tools/unified/__init__.py +71 -0
  111. foundry_mcp/tools/unified/authoring.py +1487 -0
  112. foundry_mcp/tools/unified/context_helpers.py +98 -0
  113. foundry_mcp/tools/unified/documentation_helpers.py +198 -0
  114. foundry_mcp/tools/unified/environment.py +939 -0
  115. foundry_mcp/tools/unified/error.py +462 -0
  116. foundry_mcp/tools/unified/health.py +225 -0
  117. foundry_mcp/tools/unified/journal.py +841 -0
  118. foundry_mcp/tools/unified/lifecycle.py +632 -0
  119. foundry_mcp/tools/unified/metrics.py +777 -0
  120. foundry_mcp/tools/unified/plan.py +745 -0
  121. foundry_mcp/tools/unified/pr.py +294 -0
  122. foundry_mcp/tools/unified/provider.py +629 -0
  123. foundry_mcp/tools/unified/review.py +685 -0
  124. foundry_mcp/tools/unified/review_helpers.py +299 -0
  125. foundry_mcp/tools/unified/router.py +102 -0
  126. foundry_mcp/tools/unified/server.py +580 -0
  127. foundry_mcp/tools/unified/spec.py +808 -0
  128. foundry_mcp/tools/unified/task.py +2202 -0
  129. foundry_mcp/tools/unified/test.py +370 -0
  130. foundry_mcp/tools/unified/verification.py +520 -0
  131. foundry_mcp-0.3.3.dist-info/METADATA +337 -0
  132. foundry_mcp-0.3.3.dist-info/RECORD +135 -0
  133. foundry_mcp-0.3.3.dist-info/WHEEL +4 -0
  134. foundry_mcp-0.3.3.dist-info/entry_points.txt +3 -0
  135. foundry_mcp-0.3.3.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,374 @@
1
+ """Structured logging configuration with automatic context injection.
2
+
3
+ This module provides logging utilities that automatically inject request context
4
+ (correlation ID, client ID, etc.) into log records, enabling unified log
5
+ correlation across the foundry-mcp codebase.
6
+
7
+ Usage:
8
+ from foundry_mcp.core.logging_config import (
9
+ get_logger,
10
+ configure_logging,
11
+ ContextFilter,
12
+ )
13
+
14
+ # Get a logger with automatic context injection
15
+ logger = get_logger(__name__)
16
+
17
+ # Within a request context, logs automatically include correlation_id
18
+ with sync_request_context() as ctx:
19
+ logger.info("Processing request") # Includes correlation_id in record
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import logging
26
+ import sys
27
+ from datetime import datetime, timezone
28
+ from typing import Any, Dict, Optional, TextIO, Union
29
+
30
+ from foundry_mcp.core.context import (
31
+ get_client_id,
32
+ get_correlation_id,
33
+ get_start_time,
34
+ get_trace_context,
35
+ )
36
+
37
+ __all__ = [
38
+ "ContextFilter",
39
+ "StructuredFormatter",
40
+ "configure_logging",
41
+ "get_logger",
42
+ "add_context_filter",
43
+ ]
44
+
45
+
46
+ class ContextFilter(logging.Filter):
47
+ """Logging filter that injects request context into log records.
48
+
49
+ This filter adds the following attributes to every log record:
50
+ - correlation_id: Current request correlation ID
51
+ - client_id: Current client identifier
52
+ - elapsed_ms: Milliseconds since request start
53
+ - trace_id: W3C trace ID if available
54
+
55
+ Example:
56
+ handler = logging.StreamHandler()
57
+ handler.addFilter(ContextFilter())
58
+ logger.addHandler(handler)
59
+ """
60
+
61
+ def filter(self, record: logging.LogRecord) -> bool:
62
+ """Add context attributes to the log record.
63
+
64
+ Args:
65
+ record: Log record to enrich
66
+
67
+ Returns:
68
+ Always True (allows all records through)
69
+ """
70
+ # Always inject context, even if empty
71
+ record.correlation_id = get_correlation_id() or "-"
72
+ record.client_id = get_client_id() or "anonymous"
73
+
74
+ # Calculate elapsed time
75
+ start_time = get_start_time()
76
+ if start_time > 0:
77
+ import time
78
+
79
+ record.elapsed_ms = round((time.time() - start_time) * 1000, 2)
80
+ else:
81
+ record.elapsed_ms = 0.0
82
+
83
+ # Add trace context if available
84
+ trace_ctx = get_trace_context()
85
+ if trace_ctx:
86
+ record.trace_id = trace_ctx.trace_id
87
+ record.parent_id = trace_ctx.parent_id
88
+ record.trace_sampled = trace_ctx.is_sampled
89
+ else:
90
+ record.trace_id = "-"
91
+ record.parent_id = "-"
92
+ record.trace_sampled = False
93
+
94
+ return True
95
+
96
+
97
+ class StructuredFormatter(logging.Formatter):
98
+ """JSON-structured log formatter for machine-readable output.
99
+
100
+ Produces newline-delimited JSON logs suitable for log aggregation
101
+ systems like Elasticsearch, Splunk, or CloudWatch.
102
+
103
+ Example output:
104
+ {"timestamp":"2024-01-15T10:30:45.123Z","level":"INFO",
105
+ "logger":"foundry_mcp.tools.specs","message":"Spec loaded",
106
+ "correlation_id":"req_a1b2c3d4e5f6","client_id":"user123",
107
+ "elapsed_ms":42.5}
108
+ """
109
+
110
+ def __init__(
111
+ self,
112
+ *,
113
+ include_extra: bool = True,
114
+ include_exception: bool = True,
115
+ timestamp_format: str = "iso",
116
+ ):
117
+ """Initialize the structured formatter.
118
+
119
+ Args:
120
+ include_extra: Include extra record attributes
121
+ include_exception: Include exception info in output
122
+ timestamp_format: "iso" for ISO 8601, "unix" for Unix timestamp
123
+ """
124
+ super().__init__()
125
+ self.include_extra = include_extra
126
+ self.include_exception = include_exception
127
+ self.timestamp_format = timestamp_format
128
+
129
+ # Standard attributes to exclude from "extra"
130
+ self._standard_attrs = {
131
+ "name",
132
+ "msg",
133
+ "args",
134
+ "levelname",
135
+ "levelno",
136
+ "pathname",
137
+ "filename",
138
+ "module",
139
+ "lineno",
140
+ "funcName",
141
+ "created",
142
+ "msecs",
143
+ "relativeCreated",
144
+ "thread",
145
+ "threadName",
146
+ "processName",
147
+ "process",
148
+ "message",
149
+ "exc_info",
150
+ "exc_text",
151
+ "stack_info",
152
+ # Context attributes (handled separately)
153
+ "correlation_id",
154
+ "client_id",
155
+ "elapsed_ms",
156
+ "trace_id",
157
+ "parent_id",
158
+ "trace_sampled",
159
+ }
160
+
161
+ def format(self, record: logging.LogRecord) -> str:
162
+ """Format the log record as JSON.
163
+
164
+ Args:
165
+ record: Log record to format
166
+
167
+ Returns:
168
+ JSON-formatted log line
169
+ """
170
+ # Build base log entry
171
+ log_entry: Dict[str, Any] = {}
172
+
173
+ # Timestamp
174
+ if self.timestamp_format == "iso":
175
+ log_entry["timestamp"] = datetime.fromtimestamp(
176
+ record.created, tz=timezone.utc
177
+ ).isoformat()
178
+ else:
179
+ log_entry["timestamp"] = record.created
180
+
181
+ # Core fields
182
+ log_entry["level"] = record.levelname
183
+ log_entry["logger"] = record.name
184
+ log_entry["message"] = record.getMessage()
185
+
186
+ # Location (optional, useful for debugging)
187
+ log_entry["location"] = {
188
+ "file": record.filename,
189
+ "line": record.lineno,
190
+ "function": record.funcName,
191
+ }
192
+
193
+ # Context fields (injected by ContextFilter)
194
+ log_entry["correlation_id"] = getattr(record, "correlation_id", "-")
195
+ log_entry["client_id"] = getattr(record, "client_id", "anonymous")
196
+ log_entry["elapsed_ms"] = getattr(record, "elapsed_ms", 0.0)
197
+
198
+ # Trace context
199
+ trace_id = getattr(record, "trace_id", "-")
200
+ if trace_id and trace_id != "-":
201
+ log_entry["trace"] = {
202
+ "trace_id": trace_id,
203
+ "parent_id": getattr(record, "parent_id", "-"),
204
+ "sampled": getattr(record, "trace_sampled", False),
205
+ }
206
+
207
+ # Exception info
208
+ if self.include_exception and record.exc_info:
209
+ log_entry["exception"] = self.formatException(record.exc_info)
210
+
211
+ # Extra attributes
212
+ if self.include_extra:
213
+ extra = {}
214
+ for key, value in record.__dict__.items():
215
+ if key not in self._standard_attrs:
216
+ try:
217
+ # Ensure value is JSON-serializable
218
+ json.dumps(value)
219
+ extra[key] = value
220
+ except (TypeError, ValueError):
221
+ extra[key] = str(value)
222
+ if extra:
223
+ log_entry["extra"] = extra
224
+
225
+ return json.dumps(log_entry, default=str)
226
+
227
+
228
+ class HumanReadableFormatter(logging.Formatter):
229
+ """Human-readable formatter with context prefix.
230
+
231
+ Produces logs in format:
232
+ [LEVEL] [correlation_id] logger: message
233
+
234
+ Example:
235
+ [INFO] [req_a1b2c3] foundry_mcp.tools.specs: Spec loaded successfully
236
+ """
237
+
238
+ def __init__(
239
+ self,
240
+ *,
241
+ include_timestamp: bool = True,
242
+ include_location: bool = False,
243
+ color: bool = False,
244
+ ):
245
+ """Initialize the human-readable formatter.
246
+
247
+ Args:
248
+ include_timestamp: Include timestamp in output
249
+ include_location: Include file:line in output
250
+ color: Use ANSI colors (disabled by default per project standards)
251
+ """
252
+ super().__init__()
253
+ self.include_timestamp = include_timestamp
254
+ self.include_location = include_location
255
+ self.color = color
256
+
257
+ def format(self, record: logging.LogRecord) -> str:
258
+ """Format the log record for human reading.
259
+
260
+ Args:
261
+ record: Log record to format
262
+
263
+ Returns:
264
+ Formatted log line
265
+ """
266
+ parts = []
267
+
268
+ # Timestamp
269
+ if self.include_timestamp:
270
+ ts = datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S")
271
+ parts.append(ts)
272
+
273
+ # Level
274
+ parts.append(f"[{record.levelname}]")
275
+
276
+ # Correlation ID
277
+ corr_id = getattr(record, "correlation_id", "-")
278
+ if corr_id and corr_id != "-":
279
+ parts.append(f"[{corr_id}]")
280
+
281
+ # Logger name (shortened)
282
+ logger_name = record.name
283
+ if logger_name.startswith("foundry_mcp."):
284
+ logger_name = logger_name[12:] # Remove "foundry_mcp."
285
+ parts.append(f"{logger_name}:")
286
+
287
+ # Message
288
+ parts.append(record.getMessage())
289
+
290
+ # Location
291
+ if self.include_location:
292
+ parts.append(f"({record.filename}:{record.lineno})")
293
+
294
+ result = " ".join(parts)
295
+
296
+ # Exception
297
+ if record.exc_info:
298
+ result += "\n" + self.formatException(record.exc_info)
299
+
300
+ return result
301
+
302
+
303
+ def configure_logging(
304
+ *,
305
+ level: Union[int, str] = logging.INFO,
306
+ format: str = "structured", # "structured" or "human"
307
+ stream: Optional[TextIO] = None,
308
+ add_context: bool = True,
309
+ ) -> logging.Logger:
310
+ """Configure the root foundry_mcp logger.
311
+
312
+ Args:
313
+ level: Log level (default: INFO)
314
+ format: Output format ("structured" for JSON, "human" for readable)
315
+ stream: Output stream (default: stderr)
316
+ add_context: Add ContextFilter for automatic context injection
317
+
318
+ Returns:
319
+ Configured root logger for foundry_mcp
320
+ """
321
+ logger = logging.getLogger("foundry_mcp")
322
+ logger.setLevel(level)
323
+
324
+ # Remove existing handlers
325
+ logger.handlers.clear()
326
+
327
+ # Create handler
328
+ handler = logging.StreamHandler(stream or sys.stderr)
329
+ handler.setLevel(level)
330
+
331
+ # Add formatter
332
+ if format == "structured":
333
+ handler.setFormatter(StructuredFormatter())
334
+ else:
335
+ handler.setFormatter(HumanReadableFormatter())
336
+
337
+ # Add context filter
338
+ if add_context:
339
+ handler.addFilter(ContextFilter())
340
+
341
+ logger.addHandler(handler)
342
+
343
+ return logger
344
+
345
+
346
+ def get_logger(name: str) -> logging.Logger:
347
+ """Get a logger with automatic context injection.
348
+
349
+ The logger inherits from the foundry_mcp root logger, which should
350
+ be configured via configure_logging(). Context injection happens
351
+ automatically via the ContextFilter on the root handler.
352
+
353
+ Args:
354
+ name: Logger name (typically __name__)
355
+
356
+ Returns:
357
+ Logger instance
358
+ """
359
+ # Ensure name is under foundry_mcp namespace
360
+ if not name.startswith("foundry_mcp"):
361
+ name = f"foundry_mcp.{name}"
362
+
363
+ return logging.getLogger(name)
364
+
365
+
366
+ def add_context_filter(handler: logging.Handler) -> None:
367
+ """Add ContextFilter to an existing handler.
368
+
369
+ Useful for adding context injection to handlers created elsewhere.
370
+
371
+ Args:
372
+ handler: Handler to add filter to
373
+ """
374
+ handler.addFilter(ContextFilter())