foundry-mcp 0.3.3__py3-none-any.whl → 0.8.10__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 (85) hide show
  1. foundry_mcp/__init__.py +7 -1
  2. foundry_mcp/cli/__init__.py +0 -13
  3. foundry_mcp/cli/commands/plan.py +10 -3
  4. foundry_mcp/cli/commands/review.py +19 -4
  5. foundry_mcp/cli/commands/session.py +1 -8
  6. foundry_mcp/cli/commands/specs.py +38 -208
  7. foundry_mcp/cli/context.py +39 -0
  8. foundry_mcp/cli/output.py +3 -3
  9. foundry_mcp/config.py +615 -11
  10. foundry_mcp/core/ai_consultation.py +146 -9
  11. foundry_mcp/core/batch_operations.py +1196 -0
  12. foundry_mcp/core/discovery.py +7 -7
  13. foundry_mcp/core/error_store.py +2 -2
  14. foundry_mcp/core/intake.py +933 -0
  15. foundry_mcp/core/llm_config.py +28 -2
  16. foundry_mcp/core/metrics_store.py +2 -2
  17. foundry_mcp/core/naming.py +25 -2
  18. foundry_mcp/core/progress.py +70 -0
  19. foundry_mcp/core/prometheus.py +0 -13
  20. foundry_mcp/core/prompts/fidelity_review.py +149 -4
  21. foundry_mcp/core/prompts/markdown_plan_review.py +5 -1
  22. foundry_mcp/core/prompts/plan_review.py +5 -1
  23. foundry_mcp/core/providers/__init__.py +12 -0
  24. foundry_mcp/core/providers/base.py +39 -0
  25. foundry_mcp/core/providers/claude.py +51 -48
  26. foundry_mcp/core/providers/codex.py +70 -60
  27. foundry_mcp/core/providers/cursor_agent.py +25 -47
  28. foundry_mcp/core/providers/detectors.py +34 -7
  29. foundry_mcp/core/providers/gemini.py +69 -58
  30. foundry_mcp/core/providers/opencode.py +101 -47
  31. foundry_mcp/core/providers/package-lock.json +4 -4
  32. foundry_mcp/core/providers/package.json +1 -1
  33. foundry_mcp/core/providers/validation.py +128 -0
  34. foundry_mcp/core/research/__init__.py +68 -0
  35. foundry_mcp/core/research/memory.py +528 -0
  36. foundry_mcp/core/research/models.py +1220 -0
  37. foundry_mcp/core/research/providers/__init__.py +40 -0
  38. foundry_mcp/core/research/providers/base.py +242 -0
  39. foundry_mcp/core/research/providers/google.py +507 -0
  40. foundry_mcp/core/research/providers/perplexity.py +442 -0
  41. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  42. foundry_mcp/core/research/providers/tavily.py +383 -0
  43. foundry_mcp/core/research/workflows/__init__.py +25 -0
  44. foundry_mcp/core/research/workflows/base.py +298 -0
  45. foundry_mcp/core/research/workflows/chat.py +271 -0
  46. foundry_mcp/core/research/workflows/consensus.py +539 -0
  47. foundry_mcp/core/research/workflows/deep_research.py +4020 -0
  48. foundry_mcp/core/research/workflows/ideate.py +682 -0
  49. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  50. foundry_mcp/core/responses.py +690 -0
  51. foundry_mcp/core/spec.py +2439 -236
  52. foundry_mcp/core/task.py +1205 -31
  53. foundry_mcp/core/testing.py +512 -123
  54. foundry_mcp/core/validation.py +319 -43
  55. foundry_mcp/dashboard/components/charts.py +0 -57
  56. foundry_mcp/dashboard/launcher.py +11 -0
  57. foundry_mcp/dashboard/views/metrics.py +25 -35
  58. foundry_mcp/dashboard/views/overview.py +1 -65
  59. foundry_mcp/resources/specs.py +25 -25
  60. foundry_mcp/schemas/intake-schema.json +89 -0
  61. foundry_mcp/schemas/sdd-spec-schema.json +33 -5
  62. foundry_mcp/server.py +0 -14
  63. foundry_mcp/tools/unified/__init__.py +39 -18
  64. foundry_mcp/tools/unified/authoring.py +2371 -248
  65. foundry_mcp/tools/unified/documentation_helpers.py +69 -6
  66. foundry_mcp/tools/unified/environment.py +434 -32
  67. foundry_mcp/tools/unified/error.py +18 -1
  68. foundry_mcp/tools/unified/lifecycle.py +8 -0
  69. foundry_mcp/tools/unified/plan.py +133 -2
  70. foundry_mcp/tools/unified/provider.py +0 -40
  71. foundry_mcp/tools/unified/research.py +1283 -0
  72. foundry_mcp/tools/unified/review.py +374 -17
  73. foundry_mcp/tools/unified/review_helpers.py +16 -1
  74. foundry_mcp/tools/unified/server.py +9 -24
  75. foundry_mcp/tools/unified/spec.py +367 -0
  76. foundry_mcp/tools/unified/task.py +1664 -30
  77. foundry_mcp/tools/unified/test.py +69 -8
  78. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/METADATA +8 -1
  79. foundry_mcp-0.8.10.dist-info/RECORD +153 -0
  80. foundry_mcp/cli/flags.py +0 -266
  81. foundry_mcp/core/feature_flags.py +0 -592
  82. foundry_mcp-0.3.3.dist-info/RECORD +0 -135
  83. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/WHEEL +0 -0
  84. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/entry_points.txt +0 -0
  85. {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,40 @@
1
+ """Search providers for deep research workflow.
2
+
3
+ This package provides abstract base classes and concrete implementations
4
+ for search providers used during the GATHERING phase of deep research.
5
+
6
+ Supported providers:
7
+ - TavilySearchProvider: Web search via Tavily API
8
+ - PerplexitySearchProvider: Web search via Perplexity Search API
9
+ - GoogleSearchProvider: Web search via Google Custom Search API
10
+ - SemanticScholarProvider: Academic paper search via Semantic Scholar API
11
+ """
12
+
13
+ from foundry_mcp.core.research.providers.base import (
14
+ AuthenticationError,
15
+ RateLimitError,
16
+ SearchProvider,
17
+ SearchProviderError,
18
+ SearchResult,
19
+ )
20
+ from foundry_mcp.core.research.providers.google import GoogleSearchProvider
21
+ from foundry_mcp.core.research.providers.perplexity import PerplexitySearchProvider
22
+ from foundry_mcp.core.research.providers.semantic_scholar import (
23
+ SemanticScholarProvider,
24
+ )
25
+ from foundry_mcp.core.research.providers.tavily import TavilySearchProvider
26
+
27
+ __all__ = [
28
+ # Abstract base
29
+ "SearchProvider",
30
+ "SearchResult",
31
+ # Concrete providers
32
+ "TavilySearchProvider",
33
+ "PerplexitySearchProvider",
34
+ "GoogleSearchProvider",
35
+ "SemanticScholarProvider",
36
+ # Errors
37
+ "SearchProviderError",
38
+ "RateLimitError",
39
+ "AuthenticationError",
40
+ ]
@@ -0,0 +1,242 @@
1
+ """Abstract base class for search providers.
2
+
3
+ This module defines the SearchProvider interface that all concrete
4
+ search providers must implement. The interface enables dependency
5
+ injection and easy mocking for testing.
6
+
7
+ Example usage:
8
+ class TavilySearchProvider(SearchProvider):
9
+ def get_provider_name(self) -> str:
10
+ return "tavily"
11
+
12
+ async def search(
13
+ self,
14
+ query: str,
15
+ max_results: int = 10,
16
+ **kwargs: Any,
17
+ ) -> list[ResearchSource]:
18
+ # Implementation...
19
+ pass
20
+ """
21
+
22
+ from abc import ABC, abstractmethod
23
+ from dataclasses import dataclass, field
24
+ from datetime import datetime
25
+ from typing import Any, Optional
26
+
27
+ from foundry_mcp.core.research.models import (
28
+ ResearchSource,
29
+ SourceQuality,
30
+ SourceType,
31
+ )
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class SearchResult:
36
+ """Normalized search result from any provider.
37
+
38
+ This dataclass provides a common structure for raw search results
39
+ before they are converted to ResearchSource objects. It captures
40
+ the essential fields returned by search APIs.
41
+
42
+ Attributes:
43
+ url: URL of the search result
44
+ title: Title or headline of the result
45
+ snippet: Brief excerpt or description
46
+ content: Full content if available (e.g., from Tavily's extract)
47
+ score: Relevance score from the search provider (0.0-1.0)
48
+ published_date: Publication date if available
49
+ source: Source domain or publication name
50
+ metadata: Additional provider-specific metadata
51
+ """
52
+
53
+ url: str
54
+ title: str
55
+ snippet: Optional[str] = None
56
+ content: Optional[str] = None
57
+ score: Optional[float] = None
58
+ published_date: Optional[datetime] = None
59
+ source: Optional[str] = None
60
+ metadata: dict[str, Any] = field(default_factory=dict)
61
+
62
+ def to_research_source(
63
+ self,
64
+ source_type: SourceType = SourceType.WEB,
65
+ sub_query_id: Optional[str] = None,
66
+ ) -> ResearchSource:
67
+ """Convert this search result to a ResearchSource.
68
+
69
+ Args:
70
+ source_type: Type of source (WEB, ACADEMIC, etc.)
71
+ sub_query_id: ID of the SubQuery that initiated this search
72
+
73
+ Returns:
74
+ ResearchSource object with quality set to UNKNOWN (to be assessed later)
75
+ """
76
+ return ResearchSource(
77
+ url=self.url,
78
+ title=self.title,
79
+ source_type=source_type,
80
+ quality=SourceQuality.UNKNOWN,
81
+ snippet=self.snippet,
82
+ content=self.content,
83
+ sub_query_id=sub_query_id,
84
+ metadata={
85
+ **self.metadata,
86
+ "score": self.score,
87
+ "published_date": (
88
+ self.published_date.isoformat() if self.published_date else None
89
+ ),
90
+ "source": self.source,
91
+ },
92
+ )
93
+
94
+
95
+ class SearchProvider(ABC):
96
+ """Abstract base class for search providers.
97
+
98
+ All concrete search providers (Tavily, Google, SemanticScholar) must
99
+ implement this interface. This enables:
100
+ - Dependency injection for flexible provider selection
101
+ - Easy mocking for unit testing
102
+ - Consistent API across different search backends
103
+
104
+ Subclasses should:
105
+ - Implement get_provider_name() to return a unique identifier
106
+ - Implement search() to execute queries against the provider
107
+ - Optionally override rate_limit property for rate limiting config
108
+
109
+ Example:
110
+ provider = TavilySearchProvider(api_key="...")
111
+ sources = await provider.search("machine learning trends", max_results=5)
112
+ """
113
+
114
+ @abstractmethod
115
+ def get_provider_name(self) -> str:
116
+ """Return the unique identifier for this provider.
117
+
118
+ Returns:
119
+ Provider name (e.g., "tavily", "google", "semantic_scholar")
120
+ """
121
+ ...
122
+
123
+ @abstractmethod
124
+ async def search(
125
+ self,
126
+ query: str,
127
+ max_results: int = 10,
128
+ **kwargs: Any,
129
+ ) -> list[ResearchSource]:
130
+ """Execute a search query and return research sources.
131
+
132
+ This method should:
133
+ 1. Make the API call to the search provider
134
+ 2. Parse the response into SearchResult objects
135
+ 3. Convert SearchResults to ResearchSource objects
136
+ 4. Handle rate limiting and retries internally
137
+
138
+ Args:
139
+ query: The search query string
140
+ max_results: Maximum number of results to return (default: 10)
141
+ **kwargs: Provider-specific options (e.g., search_depth for Tavily)
142
+
143
+ Returns:
144
+ List of ResearchSource objects with quality set to UNKNOWN
145
+
146
+ Raises:
147
+ SearchProviderError: If the search fails after retries
148
+ """
149
+ ...
150
+
151
+ @property
152
+ def rate_limit(self) -> Optional[float]:
153
+ """Return the rate limit in requests per second.
154
+
155
+ Override this property to specify rate limiting behavior.
156
+ Return None to disable rate limiting (default).
157
+
158
+ Returns:
159
+ Requests per second limit, or None if unlimited
160
+ """
161
+ return None
162
+
163
+ async def health_check(self) -> bool:
164
+ """Check if the provider is available and properly configured.
165
+
166
+ Default implementation returns True. Override to add actual
167
+ health checks (e.g., API key validation, connectivity test).
168
+
169
+ Returns:
170
+ True if provider is healthy, False otherwise
171
+ """
172
+ return True
173
+
174
+
175
+ class SearchProviderError(Exception):
176
+ """Base exception for search provider errors.
177
+
178
+ Attributes:
179
+ provider: Name of the provider that raised the error
180
+ message: Human-readable error description
181
+ retryable: Whether the error is potentially transient
182
+ original_error: The underlying exception if available
183
+ """
184
+
185
+ def __init__(
186
+ self,
187
+ provider: str,
188
+ message: str,
189
+ retryable: bool = False,
190
+ original_error: Optional[Exception] = None,
191
+ ):
192
+ self.provider = provider
193
+ self.message = message
194
+ self.retryable = retryable
195
+ self.original_error = original_error
196
+ super().__init__(f"[{provider}] {message}")
197
+
198
+
199
+ class RateLimitError(SearchProviderError):
200
+ """Raised when a provider's rate limit is exceeded.
201
+
202
+ This error is always retryable. The retry_after field indicates
203
+ how long to wait before retrying (if provided by the API).
204
+ """
205
+
206
+ def __init__(
207
+ self,
208
+ provider: str,
209
+ retry_after: Optional[float] = None,
210
+ original_error: Optional[Exception] = None,
211
+ ):
212
+ self.retry_after = retry_after
213
+ message = "Rate limit exceeded"
214
+ if retry_after:
215
+ message += f" (retry after {retry_after}s)"
216
+ super().__init__(
217
+ provider=provider,
218
+ message=message,
219
+ retryable=True,
220
+ original_error=original_error,
221
+ )
222
+
223
+
224
+ class AuthenticationError(SearchProviderError):
225
+ """Raised when API authentication fails.
226
+
227
+ This error is NOT retryable - the API key or credentials
228
+ need to be fixed before retrying.
229
+ """
230
+
231
+ def __init__(
232
+ self,
233
+ provider: str,
234
+ message: str = "Authentication failed",
235
+ original_error: Optional[Exception] = None,
236
+ ):
237
+ super().__init__(
238
+ provider=provider,
239
+ message=message,
240
+ retryable=False,
241
+ original_error=original_error,
242
+ )