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.
- foundry_mcp/__init__.py +7 -1
- foundry_mcp/cli/__init__.py +0 -13
- foundry_mcp/cli/commands/plan.py +10 -3
- foundry_mcp/cli/commands/review.py +19 -4
- foundry_mcp/cli/commands/session.py +1 -8
- foundry_mcp/cli/commands/specs.py +38 -208
- foundry_mcp/cli/context.py +39 -0
- foundry_mcp/cli/output.py +3 -3
- foundry_mcp/config.py +615 -11
- foundry_mcp/core/ai_consultation.py +146 -9
- foundry_mcp/core/batch_operations.py +1196 -0
- foundry_mcp/core/discovery.py +7 -7
- foundry_mcp/core/error_store.py +2 -2
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/llm_config.py +28 -2
- foundry_mcp/core/metrics_store.py +2 -2
- foundry_mcp/core/naming.py +25 -2
- foundry_mcp/core/progress.py +70 -0
- foundry_mcp/core/prometheus.py +0 -13
- foundry_mcp/core/prompts/fidelity_review.py +149 -4
- foundry_mcp/core/prompts/markdown_plan_review.py +5 -1
- foundry_mcp/core/prompts/plan_review.py +5 -1
- foundry_mcp/core/providers/__init__.py +12 -0
- foundry_mcp/core/providers/base.py +39 -0
- foundry_mcp/core/providers/claude.py +51 -48
- foundry_mcp/core/providers/codex.py +70 -60
- foundry_mcp/core/providers/cursor_agent.py +25 -47
- foundry_mcp/core/providers/detectors.py +34 -7
- foundry_mcp/core/providers/gemini.py +69 -58
- foundry_mcp/core/providers/opencode.py +101 -47
- foundry_mcp/core/providers/package-lock.json +4 -4
- foundry_mcp/core/providers/package.json +1 -1
- foundry_mcp/core/providers/validation.py +128 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1220 -0
- foundry_mcp/core/research/providers/__init__.py +40 -0
- foundry_mcp/core/research/providers/base.py +242 -0
- foundry_mcp/core/research/providers/google.py +507 -0
- foundry_mcp/core/research/providers/perplexity.py +442 -0
- foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
- foundry_mcp/core/research/providers/tavily.py +383 -0
- foundry_mcp/core/research/workflows/__init__.py +25 -0
- foundry_mcp/core/research/workflows/base.py +298 -0
- foundry_mcp/core/research/workflows/chat.py +271 -0
- foundry_mcp/core/research/workflows/consensus.py +539 -0
- foundry_mcp/core/research/workflows/deep_research.py +4020 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/responses.py +690 -0
- foundry_mcp/core/spec.py +2439 -236
- foundry_mcp/core/task.py +1205 -31
- foundry_mcp/core/testing.py +512 -123
- foundry_mcp/core/validation.py +319 -43
- foundry_mcp/dashboard/components/charts.py +0 -57
- foundry_mcp/dashboard/launcher.py +11 -0
- foundry_mcp/dashboard/views/metrics.py +25 -35
- foundry_mcp/dashboard/views/overview.py +1 -65
- foundry_mcp/resources/specs.py +25 -25
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +33 -5
- foundry_mcp/server.py +0 -14
- foundry_mcp/tools/unified/__init__.py +39 -18
- foundry_mcp/tools/unified/authoring.py +2371 -248
- foundry_mcp/tools/unified/documentation_helpers.py +69 -6
- foundry_mcp/tools/unified/environment.py +434 -32
- foundry_mcp/tools/unified/error.py +18 -1
- foundry_mcp/tools/unified/lifecycle.py +8 -0
- foundry_mcp/tools/unified/plan.py +133 -2
- foundry_mcp/tools/unified/provider.py +0 -40
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +374 -17
- foundry_mcp/tools/unified/review_helpers.py +16 -1
- foundry_mcp/tools/unified/server.py +9 -24
- foundry_mcp/tools/unified/spec.py +367 -0
- foundry_mcp/tools/unified/task.py +1664 -30
- foundry_mcp/tools/unified/test.py +69 -8
- {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/METADATA +8 -1
- foundry_mcp-0.8.10.dist-info/RECORD +153 -0
- foundry_mcp/cli/flags.py +0 -266
- foundry_mcp/core/feature_flags.py +0 -592
- foundry_mcp-0.3.3.dist-info/RECORD +0 -135
- {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/WHEEL +0 -0
- {foundry_mcp-0.3.3.dist-info → foundry_mcp-0.8.10.dist-info}/entry_points.txt +0 -0
- {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
|
+
)
|