foundry-mcp 0.8.22__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 foundry-mcp might be problematic. Click here for more details.
- foundry_mcp/__init__.py +13 -0
- foundry_mcp/cli/__init__.py +67 -0
- foundry_mcp/cli/__main__.py +9 -0
- foundry_mcp/cli/agent.py +96 -0
- foundry_mcp/cli/commands/__init__.py +37 -0
- foundry_mcp/cli/commands/cache.py +137 -0
- foundry_mcp/cli/commands/dashboard.py +148 -0
- foundry_mcp/cli/commands/dev.py +446 -0
- foundry_mcp/cli/commands/journal.py +377 -0
- foundry_mcp/cli/commands/lifecycle.py +274 -0
- foundry_mcp/cli/commands/modify.py +824 -0
- foundry_mcp/cli/commands/plan.py +640 -0
- foundry_mcp/cli/commands/pr.py +393 -0
- foundry_mcp/cli/commands/review.py +667 -0
- foundry_mcp/cli/commands/session.py +472 -0
- foundry_mcp/cli/commands/specs.py +686 -0
- foundry_mcp/cli/commands/tasks.py +807 -0
- foundry_mcp/cli/commands/testing.py +676 -0
- foundry_mcp/cli/commands/validate.py +982 -0
- foundry_mcp/cli/config.py +98 -0
- foundry_mcp/cli/context.py +298 -0
- foundry_mcp/cli/logging.py +212 -0
- foundry_mcp/cli/main.py +44 -0
- foundry_mcp/cli/output.py +122 -0
- foundry_mcp/cli/registry.py +110 -0
- foundry_mcp/cli/resilience.py +178 -0
- foundry_mcp/cli/transcript.py +217 -0
- foundry_mcp/config.py +1454 -0
- foundry_mcp/core/__init__.py +144 -0
- foundry_mcp/core/ai_consultation.py +1773 -0
- foundry_mcp/core/batch_operations.py +1202 -0
- foundry_mcp/core/cache.py +195 -0
- foundry_mcp/core/capabilities.py +446 -0
- foundry_mcp/core/concurrency.py +898 -0
- foundry_mcp/core/context.py +540 -0
- foundry_mcp/core/discovery.py +1603 -0
- foundry_mcp/core/error_collection.py +728 -0
- foundry_mcp/core/error_store.py +592 -0
- foundry_mcp/core/health.py +749 -0
- foundry_mcp/core/intake.py +933 -0
- foundry_mcp/core/journal.py +700 -0
- foundry_mcp/core/lifecycle.py +412 -0
- foundry_mcp/core/llm_config.py +1376 -0
- foundry_mcp/core/llm_patterns.py +510 -0
- foundry_mcp/core/llm_provider.py +1569 -0
- foundry_mcp/core/logging_config.py +374 -0
- foundry_mcp/core/metrics_persistence.py +584 -0
- foundry_mcp/core/metrics_registry.py +327 -0
- foundry_mcp/core/metrics_store.py +641 -0
- foundry_mcp/core/modifications.py +224 -0
- foundry_mcp/core/naming.py +146 -0
- foundry_mcp/core/observability.py +1216 -0
- foundry_mcp/core/otel.py +452 -0
- foundry_mcp/core/otel_stubs.py +264 -0
- foundry_mcp/core/pagination.py +255 -0
- foundry_mcp/core/progress.py +387 -0
- foundry_mcp/core/prometheus.py +564 -0
- foundry_mcp/core/prompts/__init__.py +464 -0
- foundry_mcp/core/prompts/fidelity_review.py +691 -0
- foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
- foundry_mcp/core/prompts/plan_review.py +627 -0
- foundry_mcp/core/providers/__init__.py +237 -0
- foundry_mcp/core/providers/base.py +515 -0
- foundry_mcp/core/providers/claude.py +472 -0
- foundry_mcp/core/providers/codex.py +637 -0
- foundry_mcp/core/providers/cursor_agent.py +630 -0
- foundry_mcp/core/providers/detectors.py +515 -0
- foundry_mcp/core/providers/gemini.py +426 -0
- foundry_mcp/core/providers/opencode.py +718 -0
- foundry_mcp/core/providers/opencode_wrapper.js +308 -0
- foundry_mcp/core/providers/package-lock.json +24 -0
- foundry_mcp/core/providers/package.json +25 -0
- foundry_mcp/core/providers/registry.py +607 -0
- foundry_mcp/core/providers/test_provider.py +171 -0
- foundry_mcp/core/providers/validation.py +857 -0
- foundry_mcp/core/rate_limit.py +427 -0
- foundry_mcp/core/research/__init__.py +68 -0
- foundry_mcp/core/research/memory.py +528 -0
- foundry_mcp/core/research/models.py +1234 -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 +4142 -0
- foundry_mcp/core/research/workflows/ideate.py +682 -0
- foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
- foundry_mcp/core/resilience.py +600 -0
- foundry_mcp/core/responses.py +1624 -0
- foundry_mcp/core/review.py +366 -0
- foundry_mcp/core/security.py +438 -0
- foundry_mcp/core/spec.py +4119 -0
- foundry_mcp/core/task.py +2463 -0
- foundry_mcp/core/testing.py +839 -0
- foundry_mcp/core/validation.py +2357 -0
- foundry_mcp/dashboard/__init__.py +32 -0
- foundry_mcp/dashboard/app.py +119 -0
- foundry_mcp/dashboard/components/__init__.py +17 -0
- foundry_mcp/dashboard/components/cards.py +88 -0
- foundry_mcp/dashboard/components/charts.py +177 -0
- foundry_mcp/dashboard/components/filters.py +136 -0
- foundry_mcp/dashboard/components/tables.py +195 -0
- foundry_mcp/dashboard/data/__init__.py +11 -0
- foundry_mcp/dashboard/data/stores.py +433 -0
- foundry_mcp/dashboard/launcher.py +300 -0
- foundry_mcp/dashboard/views/__init__.py +12 -0
- foundry_mcp/dashboard/views/errors.py +217 -0
- foundry_mcp/dashboard/views/metrics.py +164 -0
- foundry_mcp/dashboard/views/overview.py +96 -0
- foundry_mcp/dashboard/views/providers.py +83 -0
- foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
- foundry_mcp/dashboard/views/tool_usage.py +139 -0
- foundry_mcp/prompts/__init__.py +9 -0
- foundry_mcp/prompts/workflows.py +525 -0
- foundry_mcp/resources/__init__.py +9 -0
- foundry_mcp/resources/specs.py +591 -0
- foundry_mcp/schemas/__init__.py +38 -0
- foundry_mcp/schemas/intake-schema.json +89 -0
- foundry_mcp/schemas/sdd-spec-schema.json +414 -0
- foundry_mcp/server.py +150 -0
- foundry_mcp/tools/__init__.py +10 -0
- foundry_mcp/tools/unified/__init__.py +92 -0
- foundry_mcp/tools/unified/authoring.py +3620 -0
- foundry_mcp/tools/unified/context_helpers.py +98 -0
- foundry_mcp/tools/unified/documentation_helpers.py +268 -0
- foundry_mcp/tools/unified/environment.py +1341 -0
- foundry_mcp/tools/unified/error.py +479 -0
- foundry_mcp/tools/unified/health.py +225 -0
- foundry_mcp/tools/unified/journal.py +841 -0
- foundry_mcp/tools/unified/lifecycle.py +640 -0
- foundry_mcp/tools/unified/metrics.py +777 -0
- foundry_mcp/tools/unified/plan.py +876 -0
- foundry_mcp/tools/unified/pr.py +294 -0
- foundry_mcp/tools/unified/provider.py +589 -0
- foundry_mcp/tools/unified/research.py +1283 -0
- foundry_mcp/tools/unified/review.py +1042 -0
- foundry_mcp/tools/unified/review_helpers.py +314 -0
- foundry_mcp/tools/unified/router.py +102 -0
- foundry_mcp/tools/unified/server.py +565 -0
- foundry_mcp/tools/unified/spec.py +1283 -0
- foundry_mcp/tools/unified/task.py +3846 -0
- foundry_mcp/tools/unified/test.py +431 -0
- foundry_mcp/tools/unified/verification.py +520 -0
- foundry_mcp-0.8.22.dist-info/METADATA +344 -0
- foundry_mcp-0.8.22.dist-info/RECORD +153 -0
- foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
- foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
- foundry_mcp-0.8.22.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,1603 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tool metadata and discovery module for foundry-mcp.
|
|
3
|
+
|
|
4
|
+
Provides dataclasses and utilities for tool registration, discovery,
|
|
5
|
+
capability negotiation, and deprecation handling per MCP best practices
|
|
6
|
+
(docs/mcp_best_practices/13-tool-discovery.md).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from functools import wraps
|
|
12
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
13
|
+
|
|
14
|
+
# Schema version for discovery responses
|
|
15
|
+
SCHEMA_VERSION = "1.0.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ParameterType(str, Enum):
|
|
19
|
+
"""Types for tool parameters in JSON Schema."""
|
|
20
|
+
|
|
21
|
+
STRING = "string"
|
|
22
|
+
INTEGER = "integer"
|
|
23
|
+
NUMBER = "number"
|
|
24
|
+
BOOLEAN = "boolean"
|
|
25
|
+
ARRAY = "array"
|
|
26
|
+
OBJECT = "object"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ParameterMetadata:
|
|
31
|
+
"""
|
|
32
|
+
Metadata for a tool parameter.
|
|
33
|
+
|
|
34
|
+
Attributes:
|
|
35
|
+
name: Parameter name
|
|
36
|
+
type: Parameter type (string, integer, boolean, etc.)
|
|
37
|
+
description: Human-readable description
|
|
38
|
+
required: Whether parameter is required
|
|
39
|
+
default: Default value if not provided
|
|
40
|
+
constraints: JSON Schema constraints (pattern, min, max, enum, etc.)
|
|
41
|
+
examples: Example values for documentation
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
name: str
|
|
45
|
+
type: ParameterType
|
|
46
|
+
description: str
|
|
47
|
+
required: bool = False
|
|
48
|
+
default: Any = None
|
|
49
|
+
constraints: Dict[str, Any] = field(default_factory=dict)
|
|
50
|
+
examples: List[Any] = field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class ToolMetadata:
|
|
55
|
+
"""
|
|
56
|
+
Comprehensive metadata for an MCP tool.
|
|
57
|
+
|
|
58
|
+
Used for tool registration, discovery, and documentation generation.
|
|
59
|
+
Supports categorization, versioning, deprecation, and rate limiting.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
name: Unique tool identifier
|
|
63
|
+
description: Human-readable description (supports markdown)
|
|
64
|
+
parameters: List of parameter metadata
|
|
65
|
+
category: Tool category for grouping (e.g., "files", "users", "code")
|
|
66
|
+
version: Semantic version of the tool interface
|
|
67
|
+
deprecated: Whether tool is deprecated
|
|
68
|
+
deprecation_message: Message explaining deprecation and migration path
|
|
69
|
+
rate_limit: Rate limit description (e.g., "100/minute")
|
|
70
|
+
examples: List of usage examples with input/output
|
|
71
|
+
related_tools: Names of related tools
|
|
72
|
+
tags: Semantic tags for filtering
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
name: str
|
|
76
|
+
description: str
|
|
77
|
+
parameters: List[ParameterMetadata] = field(default_factory=list)
|
|
78
|
+
category: str = "general"
|
|
79
|
+
version: str = "1.0.0"
|
|
80
|
+
deprecated: bool = False
|
|
81
|
+
deprecation_message: Optional[str] = None
|
|
82
|
+
rate_limit: Optional[str] = None
|
|
83
|
+
examples: List[Dict[str, Any]] = field(default_factory=list)
|
|
84
|
+
related_tools: List[str] = field(default_factory=list)
|
|
85
|
+
tags: List[str] = field(default_factory=list)
|
|
86
|
+
|
|
87
|
+
def to_json_schema(self) -> Dict[str, Any]:
|
|
88
|
+
"""
|
|
89
|
+
Generate JSON Schema from tool metadata.
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
JSON Schema dict for tool input validation
|
|
93
|
+
"""
|
|
94
|
+
type_mapping = {
|
|
95
|
+
ParameterType.STRING: "string",
|
|
96
|
+
ParameterType.INTEGER: "integer",
|
|
97
|
+
ParameterType.NUMBER: "number",
|
|
98
|
+
ParameterType.BOOLEAN: "boolean",
|
|
99
|
+
ParameterType.ARRAY: "array",
|
|
100
|
+
ParameterType.OBJECT: "object",
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
properties: Dict[str, Any] = {}
|
|
104
|
+
required: List[str] = []
|
|
105
|
+
|
|
106
|
+
for param in self.parameters:
|
|
107
|
+
prop: Dict[str, Any] = {
|
|
108
|
+
"type": type_mapping[param.type],
|
|
109
|
+
"description": param.description,
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if param.default is not None:
|
|
113
|
+
prop["default"] = param.default
|
|
114
|
+
|
|
115
|
+
if param.examples:
|
|
116
|
+
prop["examples"] = param.examples
|
|
117
|
+
|
|
118
|
+
# Add constraints
|
|
119
|
+
prop.update(param.constraints)
|
|
120
|
+
|
|
121
|
+
properties[param.name] = prop
|
|
122
|
+
|
|
123
|
+
if param.required:
|
|
124
|
+
required.append(param.name)
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
128
|
+
"title": self.name,
|
|
129
|
+
"description": self.description,
|
|
130
|
+
"type": "object",
|
|
131
|
+
"properties": properties,
|
|
132
|
+
"required": required,
|
|
133
|
+
"additionalProperties": False,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
def to_summary(self) -> Dict[str, Any]:
|
|
137
|
+
"""
|
|
138
|
+
Generate summary for tool listing.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
Compact dict for list_tools responses
|
|
142
|
+
"""
|
|
143
|
+
desc = self.description
|
|
144
|
+
if len(desc) > 200:
|
|
145
|
+
desc = desc[:197] + "..."
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
"name": self.name,
|
|
149
|
+
"description": desc,
|
|
150
|
+
"category": self.category,
|
|
151
|
+
"version": self.version,
|
|
152
|
+
"deprecated": self.deprecated,
|
|
153
|
+
"tags": self.tags,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
def to_detailed(self) -> Dict[str, Any]:
|
|
157
|
+
"""
|
|
158
|
+
Generate detailed info for get_tool_schema responses.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Full dict including schema, examples, and related tools
|
|
162
|
+
"""
|
|
163
|
+
result: Dict[str, Any] = {
|
|
164
|
+
"name": self.name,
|
|
165
|
+
"version": self.version,
|
|
166
|
+
"description": self.description,
|
|
167
|
+
"category": self.category,
|
|
168
|
+
"schema": self.to_json_schema(),
|
|
169
|
+
"examples": self.examples,
|
|
170
|
+
"related_tools": self.related_tools,
|
|
171
|
+
"tags": self.tags,
|
|
172
|
+
"deprecated": self.deprecated,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if self.rate_limit:
|
|
176
|
+
result["rate_limit"] = self.rate_limit
|
|
177
|
+
|
|
178
|
+
if self.deprecation_message:
|
|
179
|
+
result["deprecation_message"] = self.deprecation_message
|
|
180
|
+
|
|
181
|
+
return result
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class ToolRegistry:
|
|
185
|
+
"""
|
|
186
|
+
Central registry for MCP tool metadata.
|
|
187
|
+
|
|
188
|
+
Provides tool registration, discovery, and filtering capabilities.
|
|
189
|
+
Used by MCP servers to expose tool information to clients.
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
>>> registry = ToolRegistry()
|
|
193
|
+
>>> registry.register(ToolMetadata(
|
|
194
|
+
... name="get_user",
|
|
195
|
+
... description="Get user by ID",
|
|
196
|
+
... category="users",
|
|
197
|
+
... ))
|
|
198
|
+
>>> tools = registry.list_tools(category="users")
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def __init__(self) -> None:
|
|
202
|
+
"""Initialize empty tool registry."""
|
|
203
|
+
self._tools: Dict[str, ToolMetadata] = {}
|
|
204
|
+
self._categories: Dict[str, List[str]] = {}
|
|
205
|
+
|
|
206
|
+
def register(self, tool: ToolMetadata) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Register a tool in the registry.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
tool: Tool metadata to register
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
ValueError: If tool with same name already registered
|
|
215
|
+
"""
|
|
216
|
+
if tool.name in self._tools:
|
|
217
|
+
raise ValueError(f"Tool '{tool.name}' already registered")
|
|
218
|
+
|
|
219
|
+
self._tools[tool.name] = tool
|
|
220
|
+
|
|
221
|
+
# Update category index
|
|
222
|
+
if tool.category not in self._categories:
|
|
223
|
+
self._categories[tool.category] = []
|
|
224
|
+
self._categories[tool.category].append(tool.name)
|
|
225
|
+
|
|
226
|
+
def unregister(self, name: str) -> bool:
|
|
227
|
+
"""
|
|
228
|
+
Remove a tool from the registry.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
name: Tool name to remove
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
True if tool was removed, False if not found
|
|
235
|
+
"""
|
|
236
|
+
if name not in self._tools:
|
|
237
|
+
return False
|
|
238
|
+
|
|
239
|
+
tool = self._tools.pop(name)
|
|
240
|
+
if tool.category in self._categories:
|
|
241
|
+
self._categories[tool.category].remove(name)
|
|
242
|
+
if not self._categories[tool.category]:
|
|
243
|
+
del self._categories[tool.category]
|
|
244
|
+
|
|
245
|
+
return True
|
|
246
|
+
|
|
247
|
+
def get(self, name: str) -> Optional[ToolMetadata]:
|
|
248
|
+
"""
|
|
249
|
+
Get tool by name.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
name: Tool name
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
ToolMetadata if found, None otherwise
|
|
256
|
+
"""
|
|
257
|
+
return self._tools.get(name)
|
|
258
|
+
|
|
259
|
+
def list_tools(
|
|
260
|
+
self,
|
|
261
|
+
*,
|
|
262
|
+
category: Optional[str] = None,
|
|
263
|
+
tag: Optional[str] = None,
|
|
264
|
+
include_deprecated: bool = False,
|
|
265
|
+
) -> List[Dict[str, Any]]:
|
|
266
|
+
"""
|
|
267
|
+
List available tools with filtering.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
category: Filter by category
|
|
271
|
+
tag: Filter by tag
|
|
272
|
+
include_deprecated: Include deprecated tools (default: False)
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
List of tool summaries
|
|
276
|
+
"""
|
|
277
|
+
tools = list(self._tools.values())
|
|
278
|
+
|
|
279
|
+
# Apply filters
|
|
280
|
+
if category:
|
|
281
|
+
tools = [t for t in tools if t.category == category]
|
|
282
|
+
|
|
283
|
+
if tag:
|
|
284
|
+
tools = [t for t in tools if tag in t.tags]
|
|
285
|
+
|
|
286
|
+
if not include_deprecated:
|
|
287
|
+
tools = [t for t in tools if not t.deprecated]
|
|
288
|
+
|
|
289
|
+
return [t.to_summary() for t in tools]
|
|
290
|
+
|
|
291
|
+
def get_tool_schema(self, name: str) -> Optional[Dict[str, Any]]:
|
|
292
|
+
"""
|
|
293
|
+
Get detailed schema for a specific tool.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
name: Tool name
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Detailed tool info including schema, or None if not found
|
|
300
|
+
"""
|
|
301
|
+
tool = self._tools.get(name)
|
|
302
|
+
if tool is None:
|
|
303
|
+
return None
|
|
304
|
+
return tool.to_detailed()
|
|
305
|
+
|
|
306
|
+
def list_categories(self) -> List[Dict[str, Any]]:
|
|
307
|
+
"""
|
|
308
|
+
List tool categories with descriptions.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
List of categories with tool counts
|
|
312
|
+
"""
|
|
313
|
+
result = []
|
|
314
|
+
for category, tool_names in sorted(self._categories.items()):
|
|
315
|
+
# Filter out deprecated tools from count
|
|
316
|
+
active_count = sum(
|
|
317
|
+
1
|
|
318
|
+
for name in tool_names
|
|
319
|
+
if name in self._tools and not self._tools[name].deprecated
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
result.append(
|
|
323
|
+
{
|
|
324
|
+
"name": category,
|
|
325
|
+
"tool_count": active_count,
|
|
326
|
+
"tools": tool_names,
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
return result
|
|
331
|
+
|
|
332
|
+
def get_stats(self) -> Dict[str, Any]:
|
|
333
|
+
"""
|
|
334
|
+
Get registry statistics.
|
|
335
|
+
|
|
336
|
+
Returns:
|
|
337
|
+
Dict with total counts and breakdown
|
|
338
|
+
"""
|
|
339
|
+
total = len(self._tools)
|
|
340
|
+
deprecated = sum(1 for t in self._tools.values() if t.deprecated)
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
"total_tools": total,
|
|
344
|
+
"active_tools": total - deprecated,
|
|
345
|
+
"deprecated_tools": deprecated,
|
|
346
|
+
"categories": len(self._categories),
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# Global registry instance
|
|
351
|
+
_registry: Optional[ToolRegistry] = None
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def get_tool_registry() -> ToolRegistry:
|
|
355
|
+
"""Get the global tool registry."""
|
|
356
|
+
global _registry
|
|
357
|
+
if _registry is None:
|
|
358
|
+
_registry = ToolRegistry()
|
|
359
|
+
return _registry
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@dataclass
|
|
363
|
+
class ServerCapabilities:
|
|
364
|
+
"""
|
|
365
|
+
Server capabilities for client negotiation.
|
|
366
|
+
|
|
367
|
+
Clients call get_capabilities() to understand what features are supported
|
|
368
|
+
before making assumptions about available functionality.
|
|
369
|
+
|
|
370
|
+
Attributes:
|
|
371
|
+
response_version: Response contract version (e.g., "response-v2")
|
|
372
|
+
supports_streaming: Whether server supports streaming responses
|
|
373
|
+
supports_batch: Whether server supports batch operations
|
|
374
|
+
supports_pagination: Whether server supports cursor-based pagination
|
|
375
|
+
max_batch_size: Maximum items in a batch request
|
|
376
|
+
rate_limit_headers: Whether responses include rate limit headers
|
|
377
|
+
supported_formats: List of supported response formats
|
|
378
|
+
feature_flags_enabled: Whether feature flags are active
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
response_version: str = "response-v2"
|
|
382
|
+
supports_streaming: bool = False
|
|
383
|
+
supports_batch: bool = True
|
|
384
|
+
supports_pagination: bool = True
|
|
385
|
+
max_batch_size: int = 100
|
|
386
|
+
rate_limit_headers: bool = True
|
|
387
|
+
supported_formats: List[str] = field(default_factory=lambda: ["json"])
|
|
388
|
+
feature_flags_enabled: bool = False
|
|
389
|
+
|
|
390
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
391
|
+
"""
|
|
392
|
+
Convert capabilities to dict for response.
|
|
393
|
+
|
|
394
|
+
Returns:
|
|
395
|
+
Dict suitable for capability negotiation responses
|
|
396
|
+
"""
|
|
397
|
+
return {
|
|
398
|
+
"response_version": self.response_version,
|
|
399
|
+
"streaming": self.supports_streaming,
|
|
400
|
+
"batch_operations": self.supports_batch,
|
|
401
|
+
"pagination": self.supports_pagination,
|
|
402
|
+
"max_batch_size": self.max_batch_size,
|
|
403
|
+
"rate_limit_headers": self.rate_limit_headers,
|
|
404
|
+
"formats": self.supported_formats,
|
|
405
|
+
"feature_flags": self.feature_flags_enabled,
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
# Global capabilities instance
|
|
410
|
+
_capabilities: Optional[ServerCapabilities] = None
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def get_capabilities() -> Dict[str, Any]:
|
|
414
|
+
"""
|
|
415
|
+
Get server capabilities for client negotiation.
|
|
416
|
+
|
|
417
|
+
Clients should call this to understand server features before making
|
|
418
|
+
assumptions about available functionality.
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Dict with capabilities, server version, and API version
|
|
422
|
+
"""
|
|
423
|
+
global _capabilities
|
|
424
|
+
if _capabilities is None:
|
|
425
|
+
_capabilities = ServerCapabilities()
|
|
426
|
+
|
|
427
|
+
return {
|
|
428
|
+
"schema_version": SCHEMA_VERSION,
|
|
429
|
+
"capabilities": _capabilities.to_dict(),
|
|
430
|
+
"server_version": "1.0.0",
|
|
431
|
+
"api_version": "2024-11-01",
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def negotiate_capabilities(
|
|
436
|
+
requested_version: Optional[str] = None,
|
|
437
|
+
requested_features: Optional[List[str]] = None,
|
|
438
|
+
) -> Dict[str, Any]:
|
|
439
|
+
"""
|
|
440
|
+
Negotiate capabilities with client.
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
requested_version: Desired response version
|
|
444
|
+
requested_features: List of requested feature names
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
Dict with negotiated capabilities and any warnings
|
|
448
|
+
"""
|
|
449
|
+
global _capabilities
|
|
450
|
+
if _capabilities is None:
|
|
451
|
+
_capabilities = ServerCapabilities()
|
|
452
|
+
|
|
453
|
+
negotiated: Dict[str, Any] = {}
|
|
454
|
+
warnings: List[str] = []
|
|
455
|
+
|
|
456
|
+
# Version negotiation
|
|
457
|
+
if requested_version:
|
|
458
|
+
if requested_version == _capabilities.response_version:
|
|
459
|
+
negotiated["response_version"] = requested_version
|
|
460
|
+
else:
|
|
461
|
+
negotiated["response_version"] = _capabilities.response_version
|
|
462
|
+
warnings.append(
|
|
463
|
+
f"Requested version '{requested_version}' not supported, "
|
|
464
|
+
f"using '{_capabilities.response_version}'"
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
# Feature negotiation
|
|
468
|
+
available_features = {
|
|
469
|
+
"streaming": _capabilities.supports_streaming,
|
|
470
|
+
"batch": _capabilities.supports_batch,
|
|
471
|
+
"pagination": _capabilities.supports_pagination,
|
|
472
|
+
"rate_limit_headers": _capabilities.rate_limit_headers,
|
|
473
|
+
"feature_flags": _capabilities.feature_flags_enabled,
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if requested_features:
|
|
477
|
+
negotiated["features"] = {}
|
|
478
|
+
for feature in requested_features:
|
|
479
|
+
if feature in available_features:
|
|
480
|
+
negotiated["features"][feature] = available_features[feature]
|
|
481
|
+
else:
|
|
482
|
+
negotiated["features"][feature] = False
|
|
483
|
+
warnings.append(f"Feature '{feature}' not recognized")
|
|
484
|
+
|
|
485
|
+
return {
|
|
486
|
+
"schema_version": SCHEMA_VERSION,
|
|
487
|
+
"negotiated": negotiated,
|
|
488
|
+
"warnings": warnings if warnings else None,
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def set_capabilities(capabilities: ServerCapabilities) -> None:
|
|
493
|
+
"""
|
|
494
|
+
Set server capabilities (for testing or custom configuration).
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
capabilities: ServerCapabilities instance to use
|
|
498
|
+
"""
|
|
499
|
+
global _capabilities
|
|
500
|
+
_capabilities = capabilities
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
# Type variable for decorator
|
|
504
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
def deprecated_tool(
|
|
508
|
+
replacement: str,
|
|
509
|
+
removal_version: str,
|
|
510
|
+
) -> Callable[[F], F]:
|
|
511
|
+
"""
|
|
512
|
+
Decorator to mark MCP tools as deprecated.
|
|
513
|
+
|
|
514
|
+
Modifies the function's docstring to include deprecation notice and
|
|
515
|
+
adds deprecation warning to response meta.warnings.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
replacement: Name of the tool that replaces this one
|
|
519
|
+
removal_version: Version in which this tool will be removed
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
Decorated function that adds deprecation warnings to responses
|
|
523
|
+
|
|
524
|
+
Example:
|
|
525
|
+
>>> @mcp.tool()
|
|
526
|
+
... @deprecated_tool(replacement="get_user", removal_version="3.0.0")
|
|
527
|
+
... def fetch_user(user_id: str) -> dict:
|
|
528
|
+
... '''Fetch user by ID (deprecated).'''
|
|
529
|
+
... return get_user(user_id)
|
|
530
|
+
"""
|
|
531
|
+
|
|
532
|
+
def decorator(func: F) -> F:
|
|
533
|
+
original_doc = func.__doc__ or ""
|
|
534
|
+
|
|
535
|
+
# Update docstring with deprecation notice
|
|
536
|
+
func.__doc__ = f"""[DEPRECATED] {original_doc}
|
|
537
|
+
|
|
538
|
+
⚠️ This tool is deprecated and will be removed in version {removal_version}.
|
|
539
|
+
Use '{replacement}' instead.
|
|
540
|
+
"""
|
|
541
|
+
|
|
542
|
+
@wraps(func)
|
|
543
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
544
|
+
result = func(*args, **kwargs)
|
|
545
|
+
|
|
546
|
+
# Add deprecation warning to response if it has meta
|
|
547
|
+
if isinstance(result, dict):
|
|
548
|
+
if "meta" not in result:
|
|
549
|
+
result["meta"] = {"version": "response-v2"}
|
|
550
|
+
|
|
551
|
+
if "warnings" not in result["meta"]:
|
|
552
|
+
result["meta"]["warnings"] = []
|
|
553
|
+
|
|
554
|
+
deprecation_warning = (
|
|
555
|
+
f"DEPRECATED: '{func.__name__}' will be removed in {removal_version}. "
|
|
556
|
+
f"Use '{replacement}' instead."
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
if deprecation_warning not in result["meta"]["warnings"]:
|
|
560
|
+
result["meta"]["warnings"].append(deprecation_warning)
|
|
561
|
+
|
|
562
|
+
return result
|
|
563
|
+
|
|
564
|
+
# Store deprecation metadata on the wrapper for introspection
|
|
565
|
+
wrapper._deprecated = True # type: ignore[attr-defined]
|
|
566
|
+
wrapper._replacement = replacement # type: ignore[attr-defined]
|
|
567
|
+
wrapper._removal_version = removal_version # type: ignore[attr-defined]
|
|
568
|
+
|
|
569
|
+
return wrapper # type: ignore[return-value]
|
|
570
|
+
|
|
571
|
+
return decorator
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def is_deprecated(func: Callable[..., Any]) -> bool:
|
|
575
|
+
"""
|
|
576
|
+
Check if a function is marked as deprecated.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
func: Function to check
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
True if function has @deprecated_tool decorator
|
|
583
|
+
"""
|
|
584
|
+
return getattr(func, "_deprecated", False)
|
|
585
|
+
|
|
586
|
+
|
|
587
|
+
def get_deprecation_info(func: Callable[..., Any]) -> Optional[Dict[str, str]]:
|
|
588
|
+
"""
|
|
589
|
+
Get deprecation info for a deprecated function.
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
func: Function to check
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
Dict with replacement and removal_version, or None if not deprecated
|
|
596
|
+
"""
|
|
597
|
+
if not is_deprecated(func):
|
|
598
|
+
return None
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
"replacement": getattr(func, "_replacement", ""),
|
|
602
|
+
"removal_version": getattr(func, "_removal_version", ""),
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
# =============================================================================
|
|
607
|
+
# Environment Tools Discovery Metadata
|
|
608
|
+
# =============================================================================
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
# Pre-defined metadata for environment tools
|
|
612
|
+
ENVIRONMENT_TOOL_METADATA: Dict[str, ToolMetadata] = {
|
|
613
|
+
"sdd-verify-toolchain": ToolMetadata(
|
|
614
|
+
name="sdd-verify-toolchain",
|
|
615
|
+
description="Verify local CLI/toolchain availability including git, python, node, and SDD CLI. "
|
|
616
|
+
"Returns readiness status for each tool with version information.",
|
|
617
|
+
parameters=[
|
|
618
|
+
ParameterMetadata(
|
|
619
|
+
name="tools",
|
|
620
|
+
type=ParameterType.ARRAY,
|
|
621
|
+
description="Specific tools to check (default: all). Valid values: git, python, node, sdd",
|
|
622
|
+
required=False,
|
|
623
|
+
examples=[["git", "python"], ["sdd"]],
|
|
624
|
+
),
|
|
625
|
+
ParameterMetadata(
|
|
626
|
+
name="verbose",
|
|
627
|
+
type=ParameterType.BOOLEAN,
|
|
628
|
+
description="Include detailed version and path information",
|
|
629
|
+
required=False,
|
|
630
|
+
default=False,
|
|
631
|
+
),
|
|
632
|
+
],
|
|
633
|
+
category="environment",
|
|
634
|
+
version="1.0.0",
|
|
635
|
+
tags=["setup", "verification", "toolchain", "cli"],
|
|
636
|
+
related_tools=["sdd-verify-environment", "sdd-init-workspace"],
|
|
637
|
+
examples=[
|
|
638
|
+
{
|
|
639
|
+
"description": "Verify all tools",
|
|
640
|
+
"input": {},
|
|
641
|
+
"output": {
|
|
642
|
+
"success": True,
|
|
643
|
+
"data": {
|
|
644
|
+
"tools": {
|
|
645
|
+
"git": {"available": True, "version": "2.39.0"},
|
|
646
|
+
"python": {"available": True, "version": "3.11.0"},
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
},
|
|
650
|
+
}
|
|
651
|
+
],
|
|
652
|
+
),
|
|
653
|
+
"sdd-init-workspace": ToolMetadata(
|
|
654
|
+
name="sdd-init-workspace",
|
|
655
|
+
description="Bootstrap working directory with specs folders, config files, and git integration. "
|
|
656
|
+
"Creates specs/active, specs/pending, specs/completed, specs/archived directories.",
|
|
657
|
+
parameters=[
|
|
658
|
+
ParameterMetadata(
|
|
659
|
+
name="path",
|
|
660
|
+
type=ParameterType.STRING,
|
|
661
|
+
description="Target directory (default: current working directory)",
|
|
662
|
+
required=False,
|
|
663
|
+
),
|
|
664
|
+
ParameterMetadata(
|
|
665
|
+
name="force",
|
|
666
|
+
type=ParameterType.BOOLEAN,
|
|
667
|
+
description="Overwrite existing configuration if present",
|
|
668
|
+
required=False,
|
|
669
|
+
default=False,
|
|
670
|
+
),
|
|
671
|
+
ParameterMetadata(
|
|
672
|
+
name="git_integration",
|
|
673
|
+
type=ParameterType.BOOLEAN,
|
|
674
|
+
description="Enable git hooks and integration",
|
|
675
|
+
required=False,
|
|
676
|
+
default=True,
|
|
677
|
+
),
|
|
678
|
+
],
|
|
679
|
+
category="environment",
|
|
680
|
+
version="1.0.0",
|
|
681
|
+
tags=["setup", "initialization", "workspace", "config"],
|
|
682
|
+
related_tools=["sdd-verify-toolchain", "sdd-detect-topology"],
|
|
683
|
+
examples=[
|
|
684
|
+
{
|
|
685
|
+
"description": "Initialize workspace in current directory",
|
|
686
|
+
"input": {},
|
|
687
|
+
"output": {
|
|
688
|
+
"success": True,
|
|
689
|
+
"data": {
|
|
690
|
+
"created_dirs": [
|
|
691
|
+
"specs/active",
|
|
692
|
+
"specs/pending",
|
|
693
|
+
"specs/completed",
|
|
694
|
+
],
|
|
695
|
+
"git_integration": True,
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
}
|
|
699
|
+
],
|
|
700
|
+
),
|
|
701
|
+
"sdd-detect-topology": ToolMetadata(
|
|
702
|
+
name="sdd-detect-topology",
|
|
703
|
+
description="Auto-detect repository layout for specs and documentation directories. "
|
|
704
|
+
"Scans directory structure to identify existing SDD configuration.",
|
|
705
|
+
parameters=[
|
|
706
|
+
ParameterMetadata(
|
|
707
|
+
name="path",
|
|
708
|
+
type=ParameterType.STRING,
|
|
709
|
+
description="Root directory to scan (default: current working directory)",
|
|
710
|
+
required=False,
|
|
711
|
+
),
|
|
712
|
+
ParameterMetadata(
|
|
713
|
+
name="depth",
|
|
714
|
+
type=ParameterType.INTEGER,
|
|
715
|
+
description="Maximum directory depth to scan",
|
|
716
|
+
required=False,
|
|
717
|
+
default=3,
|
|
718
|
+
constraints={"minimum": 1, "maximum": 10},
|
|
719
|
+
),
|
|
720
|
+
],
|
|
721
|
+
category="environment",
|
|
722
|
+
version="1.0.0",
|
|
723
|
+
tags=["detection", "topology", "repository", "layout"],
|
|
724
|
+
related_tools=["sdd-init-workspace", "sdd-verify-environment"],
|
|
725
|
+
examples=[
|
|
726
|
+
{
|
|
727
|
+
"description": "Detect repository topology",
|
|
728
|
+
"input": {"depth": 2},
|
|
729
|
+
"output": {
|
|
730
|
+
"success": True,
|
|
731
|
+
"data": {
|
|
732
|
+
"specs_dir": "specs",
|
|
733
|
+
"docs_dir": "docs",
|
|
734
|
+
"has_git": True,
|
|
735
|
+
"layout_type": "standard",
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
}
|
|
739
|
+
],
|
|
740
|
+
),
|
|
741
|
+
"sdd-verify-environment": ToolMetadata(
|
|
742
|
+
name="sdd-verify-environment",
|
|
743
|
+
description="Validate OS packages, runtime versions, and credential availability. "
|
|
744
|
+
"Performs comprehensive environment checks beyond basic toolchain verification.",
|
|
745
|
+
parameters=[
|
|
746
|
+
ParameterMetadata(
|
|
747
|
+
name="checks",
|
|
748
|
+
type=ParameterType.ARRAY,
|
|
749
|
+
description="Specific checks to run (default: all). Valid values: os, runtime, credentials",
|
|
750
|
+
required=False,
|
|
751
|
+
examples=[["os", "runtime"], ["credentials"]],
|
|
752
|
+
),
|
|
753
|
+
ParameterMetadata(
|
|
754
|
+
name="fix",
|
|
755
|
+
type=ParameterType.BOOLEAN,
|
|
756
|
+
description="Attempt to fix issues automatically (requires env_auto_fix feature flag)",
|
|
757
|
+
required=False,
|
|
758
|
+
default=False,
|
|
759
|
+
),
|
|
760
|
+
],
|
|
761
|
+
category="environment",
|
|
762
|
+
version="1.0.0",
|
|
763
|
+
tags=["verification", "environment", "runtime", "credentials"],
|
|
764
|
+
related_tools=["sdd-verify-toolchain", "sdd-detect-topology"],
|
|
765
|
+
examples=[
|
|
766
|
+
{
|
|
767
|
+
"description": "Run all environment checks",
|
|
768
|
+
"input": {},
|
|
769
|
+
"output": {
|
|
770
|
+
"success": True,
|
|
771
|
+
"data": {
|
|
772
|
+
"os": {"platform": "darwin", "version": "14.0"},
|
|
773
|
+
"runtime": {"python": "3.11.0", "node": "20.0.0"},
|
|
774
|
+
"issues": [],
|
|
775
|
+
},
|
|
776
|
+
},
|
|
777
|
+
}
|
|
778
|
+
],
|
|
779
|
+
),
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
|
|
783
|
+
@dataclass
|
|
784
|
+
class FeatureFlagDescriptor:
|
|
785
|
+
"""
|
|
786
|
+
Descriptor for a feature flag used in capability negotiation.
|
|
787
|
+
|
|
788
|
+
Attributes:
|
|
789
|
+
name: Unique flag identifier
|
|
790
|
+
description: Human-readable description
|
|
791
|
+
state: Lifecycle state (experimental, beta, stable, deprecated)
|
|
792
|
+
default_enabled: Whether flag is enabled by default
|
|
793
|
+
percentage_rollout: Rollout percentage (0-100)
|
|
794
|
+
dependencies: List of other flags this depends on
|
|
795
|
+
"""
|
|
796
|
+
|
|
797
|
+
name: str
|
|
798
|
+
description: str
|
|
799
|
+
state: str = "beta"
|
|
800
|
+
default_enabled: bool = False
|
|
801
|
+
percentage_rollout: int = 0
|
|
802
|
+
dependencies: List[str] = field(default_factory=list)
|
|
803
|
+
|
|
804
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
805
|
+
"""Convert to dict for API responses."""
|
|
806
|
+
return {
|
|
807
|
+
"name": self.name,
|
|
808
|
+
"description": self.description,
|
|
809
|
+
"state": self.state,
|
|
810
|
+
"default_enabled": self.default_enabled,
|
|
811
|
+
"percentage_rollout": self.percentage_rollout,
|
|
812
|
+
"dependencies": self.dependencies,
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
|
|
816
|
+
# Environment feature flags
|
|
817
|
+
ENVIRONMENT_FEATURE_FLAGS: Dict[str, FeatureFlagDescriptor] = {
|
|
818
|
+
"environment_tools": FeatureFlagDescriptor(
|
|
819
|
+
name="environment_tools",
|
|
820
|
+
description="Environment setup and verification tools for SDD workflows",
|
|
821
|
+
state="beta",
|
|
822
|
+
default_enabled=True,
|
|
823
|
+
percentage_rollout=100,
|
|
824
|
+
dependencies=[],
|
|
825
|
+
),
|
|
826
|
+
"env_auto_fix": FeatureFlagDescriptor(
|
|
827
|
+
name="env_auto_fix",
|
|
828
|
+
description="Automatic fix capability for environment verification issues",
|
|
829
|
+
state="experimental",
|
|
830
|
+
default_enabled=False,
|
|
831
|
+
percentage_rollout=0,
|
|
832
|
+
dependencies=["environment_tools"],
|
|
833
|
+
),
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def register_environment_tools(registry: Optional[ToolRegistry] = None) -> ToolRegistry:
|
|
838
|
+
"""
|
|
839
|
+
Register all environment tools in the registry.
|
|
840
|
+
|
|
841
|
+
Args:
|
|
842
|
+
registry: Optional registry to use. If None, uses global registry.
|
|
843
|
+
|
|
844
|
+
Returns:
|
|
845
|
+
The registry with environment tools registered.
|
|
846
|
+
"""
|
|
847
|
+
if registry is None:
|
|
848
|
+
registry = get_tool_registry()
|
|
849
|
+
|
|
850
|
+
for tool_metadata in ENVIRONMENT_TOOL_METADATA.values():
|
|
851
|
+
try:
|
|
852
|
+
registry.register(tool_metadata)
|
|
853
|
+
except ValueError:
|
|
854
|
+
# Tool already registered, skip
|
|
855
|
+
pass
|
|
856
|
+
|
|
857
|
+
return registry
|
|
858
|
+
|
|
859
|
+
|
|
860
|
+
def get_environment_capabilities() -> Dict[str, Any]:
|
|
861
|
+
"""
|
|
862
|
+
Get environment-related capabilities for capability negotiation.
|
|
863
|
+
|
|
864
|
+
Returns:
|
|
865
|
+
Dict with environment tool availability and feature flags.
|
|
866
|
+
"""
|
|
867
|
+
return {
|
|
868
|
+
"environment_readiness": {
|
|
869
|
+
"supported": True,
|
|
870
|
+
"tools": list(ENVIRONMENT_TOOL_METADATA.keys()),
|
|
871
|
+
"description": "Environment verification and workspace initialization tools",
|
|
872
|
+
},
|
|
873
|
+
"feature_flags": {
|
|
874
|
+
name: flag.to_dict() for name, flag in ENVIRONMENT_FEATURE_FLAGS.items()
|
|
875
|
+
},
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def is_environment_tool(tool_name: str) -> bool:
|
|
880
|
+
"""
|
|
881
|
+
Check if a tool name is an environment tool.
|
|
882
|
+
|
|
883
|
+
Args:
|
|
884
|
+
tool_name: Name of the tool to check
|
|
885
|
+
|
|
886
|
+
Returns:
|
|
887
|
+
True if tool is an environment tool
|
|
888
|
+
"""
|
|
889
|
+
return tool_name in ENVIRONMENT_TOOL_METADATA
|
|
890
|
+
|
|
891
|
+
|
|
892
|
+
def get_environment_tool_metadata(tool_name: str) -> Optional[ToolMetadata]:
|
|
893
|
+
"""
|
|
894
|
+
Get metadata for a specific environment tool.
|
|
895
|
+
|
|
896
|
+
Args:
|
|
897
|
+
tool_name: Name of the environment tool
|
|
898
|
+
|
|
899
|
+
Returns:
|
|
900
|
+
ToolMetadata if found, None otherwise
|
|
901
|
+
"""
|
|
902
|
+
return ENVIRONMENT_TOOL_METADATA.get(tool_name)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
# =============================================================================
|
|
906
|
+
# LLM-Powered Tools Discovery Metadata
|
|
907
|
+
# =============================================================================
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
# Pre-defined metadata for LLM-powered tools
|
|
911
|
+
LLM_TOOL_METADATA: Dict[str, ToolMetadata] = {
|
|
912
|
+
"spec-review": ToolMetadata(
|
|
913
|
+
name="spec-review",
|
|
914
|
+
description="Run an LLM-powered review session on a specification. "
|
|
915
|
+
"Performs intelligent spec analysis and generates improvement suggestions. "
|
|
916
|
+
"Supports multiple review types and external AI tool integration.",
|
|
917
|
+
parameters=[
|
|
918
|
+
ParameterMetadata(
|
|
919
|
+
name="spec_id",
|
|
920
|
+
type=ParameterType.STRING,
|
|
921
|
+
description="Specification ID to review",
|
|
922
|
+
required=True,
|
|
923
|
+
examples=["feature-auth-2025-01-15-001", "bugfix-cache-2025-02-01-001"],
|
|
924
|
+
),
|
|
925
|
+
ParameterMetadata(
|
|
926
|
+
name="review_type",
|
|
927
|
+
type=ParameterType.STRING,
|
|
928
|
+
description="Type of review to perform (defaults to config value, typically 'full')",
|
|
929
|
+
required=False,
|
|
930
|
+
default="full",
|
|
931
|
+
constraints={"enum": ["quick", "full", "security", "feasibility"]},
|
|
932
|
+
examples=["full", "quick", "security"],
|
|
933
|
+
),
|
|
934
|
+
ParameterMetadata(
|
|
935
|
+
name="tools",
|
|
936
|
+
type=ParameterType.STRING,
|
|
937
|
+
description="Comma-separated list of review tools to use",
|
|
938
|
+
required=False,
|
|
939
|
+
examples=["cursor-agent", "gemini,codex", "cursor-agent,gemini,codex"],
|
|
940
|
+
),
|
|
941
|
+
ParameterMetadata(
|
|
942
|
+
name="model",
|
|
943
|
+
type=ParameterType.STRING,
|
|
944
|
+
description="LLM model to use for review (default: from config)",
|
|
945
|
+
required=False,
|
|
946
|
+
examples=["gpt-4o", "claude-3-sonnet"],
|
|
947
|
+
),
|
|
948
|
+
ParameterMetadata(
|
|
949
|
+
name="path",
|
|
950
|
+
type=ParameterType.STRING,
|
|
951
|
+
description="Project root path (default: current directory)",
|
|
952
|
+
required=False,
|
|
953
|
+
),
|
|
954
|
+
ParameterMetadata(
|
|
955
|
+
name="dry_run",
|
|
956
|
+
type=ParameterType.BOOLEAN,
|
|
957
|
+
description="Show what would be reviewed without executing",
|
|
958
|
+
required=False,
|
|
959
|
+
default=False,
|
|
960
|
+
),
|
|
961
|
+
],
|
|
962
|
+
category="llm",
|
|
963
|
+
version="1.0.0",
|
|
964
|
+
tags=["review", "llm", "ai", "analysis", "quality"],
|
|
965
|
+
related_tools=["review-list-tools", "review-list-plan-tools", "spec-review-fidelity"],
|
|
966
|
+
examples=[
|
|
967
|
+
{
|
|
968
|
+
"description": "Full review of a specification",
|
|
969
|
+
"input": {"spec_id": "feature-auth-001", "review_type": "full"},
|
|
970
|
+
"output": {
|
|
971
|
+
"success": True,
|
|
972
|
+
"data": {
|
|
973
|
+
"spec_id": "feature-auth-001",
|
|
974
|
+
"review_type": "full",
|
|
975
|
+
"findings": [],
|
|
976
|
+
"suggestions": ["Consider adding error handling"],
|
|
977
|
+
},
|
|
978
|
+
},
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
"description": "Security review with multiple tools",
|
|
982
|
+
"input": {
|
|
983
|
+
"spec_id": "payment-flow-001",
|
|
984
|
+
"review_type": "security",
|
|
985
|
+
"tools": "cursor-agent,gemini",
|
|
986
|
+
},
|
|
987
|
+
"output": {
|
|
988
|
+
"success": True,
|
|
989
|
+
"data": {
|
|
990
|
+
"spec_id": "payment-flow-001",
|
|
991
|
+
"review_type": "security",
|
|
992
|
+
"findings": [{"severity": "medium", "issue": "Missing rate limiting"}],
|
|
993
|
+
},
|
|
994
|
+
},
|
|
995
|
+
},
|
|
996
|
+
],
|
|
997
|
+
),
|
|
998
|
+
"review-list-tools": ToolMetadata(
|
|
999
|
+
name="review-list-tools",
|
|
1000
|
+
description="List available review tools and pipelines. "
|
|
1001
|
+
"Returns the set of external AI tools that can be used for spec reviews "
|
|
1002
|
+
"along with their availability status.",
|
|
1003
|
+
parameters=[],
|
|
1004
|
+
category="llm",
|
|
1005
|
+
version="1.0.0",
|
|
1006
|
+
tags=["review", "discovery", "tools", "configuration"],
|
|
1007
|
+
related_tools=["spec-review", "review-list-plan-tools"],
|
|
1008
|
+
examples=[
|
|
1009
|
+
{
|
|
1010
|
+
"description": "List all available review tools",
|
|
1011
|
+
"input": {},
|
|
1012
|
+
"output": {
|
|
1013
|
+
"success": True,
|
|
1014
|
+
"data": {
|
|
1015
|
+
"tools": [
|
|
1016
|
+
{"name": "cursor-agent", "available": True, "version": "1.0.0"},
|
|
1017
|
+
{"name": "gemini", "available": True, "version": "2.0"},
|
|
1018
|
+
{"name": "codex", "available": False, "version": None},
|
|
1019
|
+
],
|
|
1020
|
+
"llm_status": {"configured": True, "provider": "openai"},
|
|
1021
|
+
},
|
|
1022
|
+
},
|
|
1023
|
+
}
|
|
1024
|
+
],
|
|
1025
|
+
),
|
|
1026
|
+
"review-list-plan-tools": ToolMetadata(
|
|
1027
|
+
name="review-list-plan-tools",
|
|
1028
|
+
description="Enumerate review toolchains available for plan analysis. "
|
|
1029
|
+
"Returns tools specifically designed for reviewing SDD plans "
|
|
1030
|
+
"including their capabilities and recommended usage.",
|
|
1031
|
+
parameters=[],
|
|
1032
|
+
category="llm",
|
|
1033
|
+
version="1.0.0",
|
|
1034
|
+
tags=["review", "planning", "tools", "recommendations"],
|
|
1035
|
+
related_tools=["spec-review", "review-list-tools"],
|
|
1036
|
+
examples=[
|
|
1037
|
+
{
|
|
1038
|
+
"description": "List plan review toolchains",
|
|
1039
|
+
"input": {},
|
|
1040
|
+
"output": {
|
|
1041
|
+
"success": True,
|
|
1042
|
+
"data": {
|
|
1043
|
+
"plan_tools": [
|
|
1044
|
+
{
|
|
1045
|
+
"name": "quick-review",
|
|
1046
|
+
"llm_required": False,
|
|
1047
|
+
"status": "available",
|
|
1048
|
+
},
|
|
1049
|
+
{
|
|
1050
|
+
"name": "full-review",
|
|
1051
|
+
"llm_required": True,
|
|
1052
|
+
"status": "available",
|
|
1053
|
+
},
|
|
1054
|
+
],
|
|
1055
|
+
"recommendations": ["Use 'full-review' for comprehensive analysis"],
|
|
1056
|
+
},
|
|
1057
|
+
},
|
|
1058
|
+
}
|
|
1059
|
+
],
|
|
1060
|
+
),
|
|
1061
|
+
"spec-review-fidelity": ToolMetadata(
|
|
1062
|
+
name="spec-review-fidelity",
|
|
1063
|
+
description="Compare implementation against specification and identify deviations. "
|
|
1064
|
+
"Performs a fidelity review to verify that code implementation matches "
|
|
1065
|
+
"the specification requirements. Uses AI consultation for comprehensive analysis.",
|
|
1066
|
+
parameters=[
|
|
1067
|
+
ParameterMetadata(
|
|
1068
|
+
name="spec_id",
|
|
1069
|
+
type=ParameterType.STRING,
|
|
1070
|
+
description="Specification ID to review against",
|
|
1071
|
+
required=True,
|
|
1072
|
+
examples=["feature-auth-001", "api-v2-migration-001"],
|
|
1073
|
+
),
|
|
1074
|
+
ParameterMetadata(
|
|
1075
|
+
name="task_id",
|
|
1076
|
+
type=ParameterType.STRING,
|
|
1077
|
+
description="Review specific task implementation (mutually exclusive with phase_id)",
|
|
1078
|
+
required=False,
|
|
1079
|
+
),
|
|
1080
|
+
ParameterMetadata(
|
|
1081
|
+
name="phase_id",
|
|
1082
|
+
type=ParameterType.STRING,
|
|
1083
|
+
description="Review entire phase implementation (mutually exclusive with task_id)",
|
|
1084
|
+
required=False,
|
|
1085
|
+
),
|
|
1086
|
+
ParameterMetadata(
|
|
1087
|
+
name="files",
|
|
1088
|
+
type=ParameterType.ARRAY,
|
|
1089
|
+
description="Review specific file(s) only",
|
|
1090
|
+
required=False,
|
|
1091
|
+
examples=[["src/auth.py"], ["src/api/users.py", "src/api/auth.py"]],
|
|
1092
|
+
),
|
|
1093
|
+
ParameterMetadata(
|
|
1094
|
+
name="use_ai",
|
|
1095
|
+
type=ParameterType.BOOLEAN,
|
|
1096
|
+
description="Enable AI consultation for analysis",
|
|
1097
|
+
required=False,
|
|
1098
|
+
default=True,
|
|
1099
|
+
),
|
|
1100
|
+
ParameterMetadata(
|
|
1101
|
+
name="ai_tools",
|
|
1102
|
+
type=ParameterType.ARRAY,
|
|
1103
|
+
description="Specific AI tools to consult",
|
|
1104
|
+
required=False,
|
|
1105
|
+
examples=[["cursor-agent", "gemini"]],
|
|
1106
|
+
),
|
|
1107
|
+
ParameterMetadata(
|
|
1108
|
+
name="consensus_threshold",
|
|
1109
|
+
type=ParameterType.INTEGER,
|
|
1110
|
+
description="Minimum models that must agree",
|
|
1111
|
+
required=False,
|
|
1112
|
+
default=2,
|
|
1113
|
+
constraints={"minimum": 1, "maximum": 5},
|
|
1114
|
+
),
|
|
1115
|
+
ParameterMetadata(
|
|
1116
|
+
name="incremental",
|
|
1117
|
+
type=ParameterType.BOOLEAN,
|
|
1118
|
+
description="Only review changed files since last run",
|
|
1119
|
+
required=False,
|
|
1120
|
+
default=False,
|
|
1121
|
+
),
|
|
1122
|
+
],
|
|
1123
|
+
category="llm",
|
|
1124
|
+
version="1.0.0",
|
|
1125
|
+
tags=["fidelity", "review", "verification", "compliance", "llm"],
|
|
1126
|
+
related_tools=["spec-review"],
|
|
1127
|
+
rate_limit="20/hour",
|
|
1128
|
+
examples=[
|
|
1129
|
+
{
|
|
1130
|
+
"description": "Fidelity review for a phase",
|
|
1131
|
+
"input": {"spec_id": "feature-auth-001", "phase_id": "phase-1"},
|
|
1132
|
+
"output": {
|
|
1133
|
+
"success": True,
|
|
1134
|
+
"data": {
|
|
1135
|
+
"spec_id": "feature-auth-001",
|
|
1136
|
+
"scope": "phase",
|
|
1137
|
+
"verdict": "pass",
|
|
1138
|
+
"deviations": [],
|
|
1139
|
+
"consensus": {"models_consulted": 3, "agreement": "unanimous"},
|
|
1140
|
+
},
|
|
1141
|
+
},
|
|
1142
|
+
},
|
|
1143
|
+
{
|
|
1144
|
+
"description": "Fidelity review with deviations found",
|
|
1145
|
+
"input": {"spec_id": "api-v2-001", "task_id": "task-2-1"},
|
|
1146
|
+
"output": {
|
|
1147
|
+
"success": True,
|
|
1148
|
+
"data": {
|
|
1149
|
+
"spec_id": "api-v2-001",
|
|
1150
|
+
"scope": "task",
|
|
1151
|
+
"verdict": "partial",
|
|
1152
|
+
"deviations": [
|
|
1153
|
+
{
|
|
1154
|
+
"task_id": "task-2-1",
|
|
1155
|
+
"type": "missing_implementation",
|
|
1156
|
+
"severity": "high",
|
|
1157
|
+
}
|
|
1158
|
+
],
|
|
1159
|
+
},
|
|
1160
|
+
},
|
|
1161
|
+
},
|
|
1162
|
+
],
|
|
1163
|
+
),
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
|
|
1167
|
+
# LLM feature flags for capability negotiation
|
|
1168
|
+
LLM_FEATURE_FLAGS: Dict[str, FeatureFlagDescriptor] = {
|
|
1169
|
+
"llm_tools": FeatureFlagDescriptor(
|
|
1170
|
+
name="llm_tools",
|
|
1171
|
+
description="LLM-powered review and documentation tools",
|
|
1172
|
+
state="stable",
|
|
1173
|
+
default_enabled=True,
|
|
1174
|
+
percentage_rollout=100,
|
|
1175
|
+
dependencies=[],
|
|
1176
|
+
),
|
|
1177
|
+
"llm_multi_provider": FeatureFlagDescriptor(
|
|
1178
|
+
name="llm_multi_provider",
|
|
1179
|
+
description="Multi-provider AI tool support (cursor-agent, gemini, codex)",
|
|
1180
|
+
state="stable",
|
|
1181
|
+
default_enabled=True,
|
|
1182
|
+
percentage_rollout=100,
|
|
1183
|
+
dependencies=["llm_tools"],
|
|
1184
|
+
),
|
|
1185
|
+
"llm_fidelity_review": FeatureFlagDescriptor(
|
|
1186
|
+
name="llm_fidelity_review",
|
|
1187
|
+
description="AI-powered fidelity review with consensus mechanism",
|
|
1188
|
+
state="stable",
|
|
1189
|
+
default_enabled=True,
|
|
1190
|
+
percentage_rollout=100,
|
|
1191
|
+
dependencies=["llm_tools", "llm_multi_provider"],
|
|
1192
|
+
),
|
|
1193
|
+
"llm_data_only_fallback": FeatureFlagDescriptor(
|
|
1194
|
+
name="llm_data_only_fallback",
|
|
1195
|
+
description="Graceful fallback to data-only responses when LLM unavailable",
|
|
1196
|
+
state="stable",
|
|
1197
|
+
default_enabled=True,
|
|
1198
|
+
percentage_rollout=100,
|
|
1199
|
+
dependencies=["llm_tools"],
|
|
1200
|
+
),
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
|
|
1204
|
+
def register_llm_tools(registry: Optional[ToolRegistry] = None) -> ToolRegistry:
|
|
1205
|
+
"""
|
|
1206
|
+
Register all LLM-powered tools in the registry.
|
|
1207
|
+
|
|
1208
|
+
Args:
|
|
1209
|
+
registry: Optional registry to use. If None, uses global registry.
|
|
1210
|
+
|
|
1211
|
+
Returns:
|
|
1212
|
+
The registry with LLM tools registered.
|
|
1213
|
+
"""
|
|
1214
|
+
if registry is None:
|
|
1215
|
+
registry = get_tool_registry()
|
|
1216
|
+
|
|
1217
|
+
for tool_metadata in LLM_TOOL_METADATA.values():
|
|
1218
|
+
try:
|
|
1219
|
+
registry.register(tool_metadata)
|
|
1220
|
+
except ValueError:
|
|
1221
|
+
# Tool already registered, skip
|
|
1222
|
+
pass
|
|
1223
|
+
|
|
1224
|
+
return registry
|
|
1225
|
+
|
|
1226
|
+
|
|
1227
|
+
def get_llm_capabilities() -> Dict[str, Any]:
|
|
1228
|
+
"""
|
|
1229
|
+
Get LLM-related capabilities for capability negotiation.
|
|
1230
|
+
|
|
1231
|
+
Returns:
|
|
1232
|
+
Dict with LLM tool availability, providers, and feature flags.
|
|
1233
|
+
"""
|
|
1234
|
+
return {
|
|
1235
|
+
"llm_tools": {
|
|
1236
|
+
"supported": True,
|
|
1237
|
+
"tools": list(LLM_TOOL_METADATA.keys()),
|
|
1238
|
+
"description": "LLM-powered review, documentation, and fidelity tools",
|
|
1239
|
+
},
|
|
1240
|
+
"multi_provider": {
|
|
1241
|
+
"supported": True,
|
|
1242
|
+
"providers": ["cursor-agent", "gemini", "codex"],
|
|
1243
|
+
"description": "Multi-provider AI tool integration",
|
|
1244
|
+
},
|
|
1245
|
+
"data_only_fallback": {
|
|
1246
|
+
"supported": True,
|
|
1247
|
+
"description": "Graceful degradation when LLM unavailable",
|
|
1248
|
+
},
|
|
1249
|
+
"feature_flags": {
|
|
1250
|
+
name: flag.to_dict() for name, flag in LLM_FEATURE_FLAGS.items()
|
|
1251
|
+
},
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
|
|
1255
|
+
def is_llm_tool(tool_name: str) -> bool:
|
|
1256
|
+
"""
|
|
1257
|
+
Check if a tool name is an LLM-powered tool.
|
|
1258
|
+
|
|
1259
|
+
Args:
|
|
1260
|
+
tool_name: Name of the tool to check
|
|
1261
|
+
|
|
1262
|
+
Returns:
|
|
1263
|
+
True if tool is an LLM-powered tool
|
|
1264
|
+
"""
|
|
1265
|
+
return tool_name in LLM_TOOL_METADATA
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
def get_llm_tool_metadata(tool_name: str) -> Optional[ToolMetadata]:
|
|
1269
|
+
"""
|
|
1270
|
+
Get metadata for a specific LLM tool.
|
|
1271
|
+
|
|
1272
|
+
Args:
|
|
1273
|
+
tool_name: Name of the LLM tool
|
|
1274
|
+
|
|
1275
|
+
Returns:
|
|
1276
|
+
ToolMetadata if found, None otherwise
|
|
1277
|
+
"""
|
|
1278
|
+
return LLM_TOOL_METADATA.get(tool_name)
|
|
1279
|
+
|
|
1280
|
+
|
|
1281
|
+
# =============================================================================
|
|
1282
|
+
# Provider Tools Discovery Metadata
|
|
1283
|
+
# =============================================================================
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
# Pre-defined metadata for provider tools
|
|
1287
|
+
PROVIDER_TOOL_METADATA: Dict[str, ToolMetadata] = {
|
|
1288
|
+
"provider-list": ToolMetadata(
|
|
1289
|
+
name="provider-list",
|
|
1290
|
+
description="List all registered LLM providers with availability status. "
|
|
1291
|
+
"Returns providers sorted by priority, with availability indicating "
|
|
1292
|
+
"which can currently be used for execution.",
|
|
1293
|
+
parameters=[
|
|
1294
|
+
ParameterMetadata(
|
|
1295
|
+
name="include_unavailable",
|
|
1296
|
+
type=ParameterType.BOOLEAN,
|
|
1297
|
+
description="Include providers that fail availability check",
|
|
1298
|
+
required=False,
|
|
1299
|
+
default=False,
|
|
1300
|
+
),
|
|
1301
|
+
],
|
|
1302
|
+
category="providers",
|
|
1303
|
+
version="1.0.0",
|
|
1304
|
+
tags=["providers", "discovery", "status", "availability"],
|
|
1305
|
+
related_tools=["provider-status", "provider-execute"],
|
|
1306
|
+
examples=[
|
|
1307
|
+
{
|
|
1308
|
+
"description": "List available providers",
|
|
1309
|
+
"input": {},
|
|
1310
|
+
"output": {
|
|
1311
|
+
"success": True,
|
|
1312
|
+
"data": {
|
|
1313
|
+
"providers": [
|
|
1314
|
+
{
|
|
1315
|
+
"id": "gemini",
|
|
1316
|
+
"description": "Google Gemini CLI provider",
|
|
1317
|
+
"priority": 10,
|
|
1318
|
+
"tags": ["cli", "external"],
|
|
1319
|
+
"available": True,
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
"id": "codex",
|
|
1323
|
+
"description": "OpenAI Codex CLI provider",
|
|
1324
|
+
"priority": 5,
|
|
1325
|
+
"tags": ["cli", "external"],
|
|
1326
|
+
"available": True,
|
|
1327
|
+
},
|
|
1328
|
+
],
|
|
1329
|
+
"available_count": 2,
|
|
1330
|
+
"total_count": 5,
|
|
1331
|
+
},
|
|
1332
|
+
},
|
|
1333
|
+
},
|
|
1334
|
+
{
|
|
1335
|
+
"description": "Include unavailable providers",
|
|
1336
|
+
"input": {"include_unavailable": True},
|
|
1337
|
+
"output": {
|
|
1338
|
+
"success": True,
|
|
1339
|
+
"data": {
|
|
1340
|
+
"providers": [
|
|
1341
|
+
{"id": "gemini", "available": True},
|
|
1342
|
+
{"id": "codex", "available": True},
|
|
1343
|
+
{"id": "opencode", "available": False},
|
|
1344
|
+
],
|
|
1345
|
+
"available_count": 2,
|
|
1346
|
+
"total_count": 3,
|
|
1347
|
+
},
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1350
|
+
],
|
|
1351
|
+
),
|
|
1352
|
+
"provider-status": ToolMetadata(
|
|
1353
|
+
name="provider-status",
|
|
1354
|
+
description="Get detailed status for a specific LLM provider. "
|
|
1355
|
+
"Returns availability, metadata, capabilities, and health status "
|
|
1356
|
+
"for debugging and capability introspection.",
|
|
1357
|
+
parameters=[
|
|
1358
|
+
ParameterMetadata(
|
|
1359
|
+
name="provider_id",
|
|
1360
|
+
type=ParameterType.STRING,
|
|
1361
|
+
description="Provider identifier (e.g., 'gemini', 'codex', 'cursor-agent')",
|
|
1362
|
+
required=True,
|
|
1363
|
+
examples=["gemini", "codex", "cursor-agent", "claude", "opencode"],
|
|
1364
|
+
),
|
|
1365
|
+
],
|
|
1366
|
+
category="providers",
|
|
1367
|
+
version="1.0.0",
|
|
1368
|
+
tags=["providers", "status", "health", "capabilities"],
|
|
1369
|
+
related_tools=["provider-list", "provider-execute"],
|
|
1370
|
+
examples=[
|
|
1371
|
+
{
|
|
1372
|
+
"description": "Get status for Gemini provider",
|
|
1373
|
+
"input": {"provider_id": "gemini"},
|
|
1374
|
+
"output": {
|
|
1375
|
+
"success": True,
|
|
1376
|
+
"data": {
|
|
1377
|
+
"provider_id": "gemini",
|
|
1378
|
+
"available": True,
|
|
1379
|
+
"metadata": {
|
|
1380
|
+
"name": "Gemini",
|
|
1381
|
+
"version": "1.0.0",
|
|
1382
|
+
"default_model": "gemini-pro",
|
|
1383
|
+
"supported_models": [
|
|
1384
|
+
{"id": "gemini-pro", "name": "Gemini Pro", "is_default": True}
|
|
1385
|
+
],
|
|
1386
|
+
},
|
|
1387
|
+
"capabilities": ["text_generation", "streaming"],
|
|
1388
|
+
"health": {"status": "healthy", "reason": None},
|
|
1389
|
+
},
|
|
1390
|
+
},
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
"description": "Provider not found",
|
|
1394
|
+
"input": {"provider_id": "unknown"},
|
|
1395
|
+
"output": {
|
|
1396
|
+
"success": False,
|
|
1397
|
+
"error": "Provider 'unknown' not found",
|
|
1398
|
+
"data": {"error_code": "NOT_FOUND"},
|
|
1399
|
+
},
|
|
1400
|
+
},
|
|
1401
|
+
],
|
|
1402
|
+
),
|
|
1403
|
+
"provider-execute": ToolMetadata(
|
|
1404
|
+
name="provider-execute",
|
|
1405
|
+
description="Execute a prompt through a specified LLM provider. "
|
|
1406
|
+
"Sends a prompt to the provider and returns the complete result. "
|
|
1407
|
+
"Supports model selection and generation parameters.",
|
|
1408
|
+
parameters=[
|
|
1409
|
+
ParameterMetadata(
|
|
1410
|
+
name="provider_id",
|
|
1411
|
+
type=ParameterType.STRING,
|
|
1412
|
+
description="Provider identifier (e.g., 'gemini', 'codex')",
|
|
1413
|
+
required=True,
|
|
1414
|
+
examples=["gemini", "codex", "cursor-agent"],
|
|
1415
|
+
),
|
|
1416
|
+
ParameterMetadata(
|
|
1417
|
+
name="prompt",
|
|
1418
|
+
type=ParameterType.STRING,
|
|
1419
|
+
description="The prompt text to send to the provider",
|
|
1420
|
+
required=True,
|
|
1421
|
+
examples=["Explain the concept of dependency injection"],
|
|
1422
|
+
),
|
|
1423
|
+
ParameterMetadata(
|
|
1424
|
+
name="model",
|
|
1425
|
+
type=ParameterType.STRING,
|
|
1426
|
+
description="Model override (uses provider default if not specified)",
|
|
1427
|
+
required=False,
|
|
1428
|
+
examples=["gemini-pro", "gpt-4o"],
|
|
1429
|
+
),
|
|
1430
|
+
ParameterMetadata(
|
|
1431
|
+
name="max_tokens",
|
|
1432
|
+
type=ParameterType.INTEGER,
|
|
1433
|
+
description="Maximum tokens in response",
|
|
1434
|
+
required=False,
|
|
1435
|
+
constraints={"minimum": 1, "maximum": 100000},
|
|
1436
|
+
),
|
|
1437
|
+
ParameterMetadata(
|
|
1438
|
+
name="temperature",
|
|
1439
|
+
type=ParameterType.NUMBER,
|
|
1440
|
+
description="Sampling temperature (0.0-2.0)",
|
|
1441
|
+
required=False,
|
|
1442
|
+
constraints={"minimum": 0.0, "maximum": 2.0},
|
|
1443
|
+
),
|
|
1444
|
+
ParameterMetadata(
|
|
1445
|
+
name="timeout",
|
|
1446
|
+
type=ParameterType.INTEGER,
|
|
1447
|
+
description="Request timeout in seconds",
|
|
1448
|
+
required=False,
|
|
1449
|
+
default=300,
|
|
1450
|
+
constraints={"minimum": 1, "maximum": 3600},
|
|
1451
|
+
),
|
|
1452
|
+
],
|
|
1453
|
+
category="providers",
|
|
1454
|
+
version="1.0.0",
|
|
1455
|
+
tags=["providers", "execution", "generation", "llm"],
|
|
1456
|
+
related_tools=["provider-list", "provider-status"],
|
|
1457
|
+
rate_limit="60/minute",
|
|
1458
|
+
examples=[
|
|
1459
|
+
{
|
|
1460
|
+
"description": "Execute prompt through Gemini",
|
|
1461
|
+
"input": {
|
|
1462
|
+
"provider_id": "gemini",
|
|
1463
|
+
"prompt": "What is dependency injection?",
|
|
1464
|
+
},
|
|
1465
|
+
"output": {
|
|
1466
|
+
"success": True,
|
|
1467
|
+
"data": {
|
|
1468
|
+
"provider_id": "gemini",
|
|
1469
|
+
"model": "gemini-pro",
|
|
1470
|
+
"content": "Dependency injection is a design pattern...",
|
|
1471
|
+
"token_usage": {
|
|
1472
|
+
"prompt_tokens": 10,
|
|
1473
|
+
"completion_tokens": 150,
|
|
1474
|
+
"total_tokens": 160,
|
|
1475
|
+
},
|
|
1476
|
+
"finish_reason": "stop",
|
|
1477
|
+
},
|
|
1478
|
+
},
|
|
1479
|
+
},
|
|
1480
|
+
{
|
|
1481
|
+
"description": "Provider unavailable",
|
|
1482
|
+
"input": {"provider_id": "opencode", "prompt": "Hello"},
|
|
1483
|
+
"output": {
|
|
1484
|
+
"success": False,
|
|
1485
|
+
"error": "Provider 'opencode' is not available",
|
|
1486
|
+
"data": {"error_code": "UNAVAILABLE"},
|
|
1487
|
+
},
|
|
1488
|
+
},
|
|
1489
|
+
],
|
|
1490
|
+
),
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
|
|
1494
|
+
# Provider feature flags for capability negotiation
|
|
1495
|
+
PROVIDER_FEATURE_FLAGS: Dict[str, FeatureFlagDescriptor] = {
|
|
1496
|
+
"provider_tools": FeatureFlagDescriptor(
|
|
1497
|
+
name="provider_tools",
|
|
1498
|
+
description="MCP tools for LLM provider management and execution",
|
|
1499
|
+
state="stable",
|
|
1500
|
+
default_enabled=True,
|
|
1501
|
+
percentage_rollout=100,
|
|
1502
|
+
dependencies=[],
|
|
1503
|
+
),
|
|
1504
|
+
"provider_multi_model": FeatureFlagDescriptor(
|
|
1505
|
+
name="provider_multi_model",
|
|
1506
|
+
description="Support for multiple models per provider",
|
|
1507
|
+
state="stable",
|
|
1508
|
+
default_enabled=True,
|
|
1509
|
+
percentage_rollout=100,
|
|
1510
|
+
dependencies=["provider_tools"],
|
|
1511
|
+
),
|
|
1512
|
+
"provider_streaming": FeatureFlagDescriptor(
|
|
1513
|
+
name="provider_streaming",
|
|
1514
|
+
description="Streaming response support for providers (not exposed via MCP tools)",
|
|
1515
|
+
state="beta",
|
|
1516
|
+
default_enabled=False,
|
|
1517
|
+
percentage_rollout=0,
|
|
1518
|
+
dependencies=["provider_tools"],
|
|
1519
|
+
),
|
|
1520
|
+
"provider_rate_limiting": FeatureFlagDescriptor(
|
|
1521
|
+
name="provider_rate_limiting",
|
|
1522
|
+
description="Rate limiting and circuit breaker support for providers",
|
|
1523
|
+
state="stable",
|
|
1524
|
+
default_enabled=True,
|
|
1525
|
+
percentage_rollout=100,
|
|
1526
|
+
dependencies=["provider_tools"],
|
|
1527
|
+
),
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
|
|
1531
|
+
def register_provider_tools_discovery(
|
|
1532
|
+
registry: Optional[ToolRegistry] = None,
|
|
1533
|
+
) -> ToolRegistry:
|
|
1534
|
+
"""
|
|
1535
|
+
Register all provider tools in the discovery registry.
|
|
1536
|
+
|
|
1537
|
+
Args:
|
|
1538
|
+
registry: Optional registry to use. If None, uses global registry.
|
|
1539
|
+
|
|
1540
|
+
Returns:
|
|
1541
|
+
The registry with provider tools registered.
|
|
1542
|
+
"""
|
|
1543
|
+
if registry is None:
|
|
1544
|
+
registry = get_tool_registry()
|
|
1545
|
+
|
|
1546
|
+
for tool_metadata in PROVIDER_TOOL_METADATA.values():
|
|
1547
|
+
try:
|
|
1548
|
+
registry.register(tool_metadata)
|
|
1549
|
+
except ValueError:
|
|
1550
|
+
# Tool already registered, skip
|
|
1551
|
+
pass
|
|
1552
|
+
|
|
1553
|
+
return registry
|
|
1554
|
+
|
|
1555
|
+
|
|
1556
|
+
def get_provider_capabilities() -> Dict[str, Any]:
|
|
1557
|
+
"""
|
|
1558
|
+
Get provider-related capabilities for capability negotiation.
|
|
1559
|
+
|
|
1560
|
+
Returns:
|
|
1561
|
+
Dict with provider tool availability and feature flags.
|
|
1562
|
+
"""
|
|
1563
|
+
return {
|
|
1564
|
+
"provider_tools": {
|
|
1565
|
+
"supported": True,
|
|
1566
|
+
"tools": list(PROVIDER_TOOL_METADATA.keys()),
|
|
1567
|
+
"description": "LLM provider management, status, and execution tools",
|
|
1568
|
+
},
|
|
1569
|
+
"supported_providers": {
|
|
1570
|
+
"built_in": ["gemini", "codex", "cursor-agent", "claude", "opencode"],
|
|
1571
|
+
"extensible": True,
|
|
1572
|
+
"description": "Pluggable provider architecture with registry support",
|
|
1573
|
+
},
|
|
1574
|
+
"feature_flags": {
|
|
1575
|
+
name: flag.to_dict() for name, flag in PROVIDER_FEATURE_FLAGS.items()
|
|
1576
|
+
},
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
def is_provider_tool(tool_name: str) -> bool:
|
|
1581
|
+
"""
|
|
1582
|
+
Check if a tool name is a provider tool.
|
|
1583
|
+
|
|
1584
|
+
Args:
|
|
1585
|
+
tool_name: Name of the tool to check
|
|
1586
|
+
|
|
1587
|
+
Returns:
|
|
1588
|
+
True if tool is a provider tool
|
|
1589
|
+
"""
|
|
1590
|
+
return tool_name in PROVIDER_TOOL_METADATA
|
|
1591
|
+
|
|
1592
|
+
|
|
1593
|
+
def get_provider_tool_metadata(tool_name: str) -> Optional[ToolMetadata]:
|
|
1594
|
+
"""
|
|
1595
|
+
Get metadata for a specific provider tool.
|
|
1596
|
+
|
|
1597
|
+
Args:
|
|
1598
|
+
tool_name: Name of the provider tool
|
|
1599
|
+
|
|
1600
|
+
Returns:
|
|
1601
|
+
ToolMetadata if found, None otherwise
|
|
1602
|
+
"""
|
|
1603
|
+
return PROVIDER_TOOL_METADATA.get(tool_name)
|