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.

Files changed (153) hide show
  1. foundry_mcp/__init__.py +13 -0
  2. foundry_mcp/cli/__init__.py +67 -0
  3. foundry_mcp/cli/__main__.py +9 -0
  4. foundry_mcp/cli/agent.py +96 -0
  5. foundry_mcp/cli/commands/__init__.py +37 -0
  6. foundry_mcp/cli/commands/cache.py +137 -0
  7. foundry_mcp/cli/commands/dashboard.py +148 -0
  8. foundry_mcp/cli/commands/dev.py +446 -0
  9. foundry_mcp/cli/commands/journal.py +377 -0
  10. foundry_mcp/cli/commands/lifecycle.py +274 -0
  11. foundry_mcp/cli/commands/modify.py +824 -0
  12. foundry_mcp/cli/commands/plan.py +640 -0
  13. foundry_mcp/cli/commands/pr.py +393 -0
  14. foundry_mcp/cli/commands/review.py +667 -0
  15. foundry_mcp/cli/commands/session.py +472 -0
  16. foundry_mcp/cli/commands/specs.py +686 -0
  17. foundry_mcp/cli/commands/tasks.py +807 -0
  18. foundry_mcp/cli/commands/testing.py +676 -0
  19. foundry_mcp/cli/commands/validate.py +982 -0
  20. foundry_mcp/cli/config.py +98 -0
  21. foundry_mcp/cli/context.py +298 -0
  22. foundry_mcp/cli/logging.py +212 -0
  23. foundry_mcp/cli/main.py +44 -0
  24. foundry_mcp/cli/output.py +122 -0
  25. foundry_mcp/cli/registry.py +110 -0
  26. foundry_mcp/cli/resilience.py +178 -0
  27. foundry_mcp/cli/transcript.py +217 -0
  28. foundry_mcp/config.py +1454 -0
  29. foundry_mcp/core/__init__.py +144 -0
  30. foundry_mcp/core/ai_consultation.py +1773 -0
  31. foundry_mcp/core/batch_operations.py +1202 -0
  32. foundry_mcp/core/cache.py +195 -0
  33. foundry_mcp/core/capabilities.py +446 -0
  34. foundry_mcp/core/concurrency.py +898 -0
  35. foundry_mcp/core/context.py +540 -0
  36. foundry_mcp/core/discovery.py +1603 -0
  37. foundry_mcp/core/error_collection.py +728 -0
  38. foundry_mcp/core/error_store.py +592 -0
  39. foundry_mcp/core/health.py +749 -0
  40. foundry_mcp/core/intake.py +933 -0
  41. foundry_mcp/core/journal.py +700 -0
  42. foundry_mcp/core/lifecycle.py +412 -0
  43. foundry_mcp/core/llm_config.py +1376 -0
  44. foundry_mcp/core/llm_patterns.py +510 -0
  45. foundry_mcp/core/llm_provider.py +1569 -0
  46. foundry_mcp/core/logging_config.py +374 -0
  47. foundry_mcp/core/metrics_persistence.py +584 -0
  48. foundry_mcp/core/metrics_registry.py +327 -0
  49. foundry_mcp/core/metrics_store.py +641 -0
  50. foundry_mcp/core/modifications.py +224 -0
  51. foundry_mcp/core/naming.py +146 -0
  52. foundry_mcp/core/observability.py +1216 -0
  53. foundry_mcp/core/otel.py +452 -0
  54. foundry_mcp/core/otel_stubs.py +264 -0
  55. foundry_mcp/core/pagination.py +255 -0
  56. foundry_mcp/core/progress.py +387 -0
  57. foundry_mcp/core/prometheus.py +564 -0
  58. foundry_mcp/core/prompts/__init__.py +464 -0
  59. foundry_mcp/core/prompts/fidelity_review.py +691 -0
  60. foundry_mcp/core/prompts/markdown_plan_review.py +515 -0
  61. foundry_mcp/core/prompts/plan_review.py +627 -0
  62. foundry_mcp/core/providers/__init__.py +237 -0
  63. foundry_mcp/core/providers/base.py +515 -0
  64. foundry_mcp/core/providers/claude.py +472 -0
  65. foundry_mcp/core/providers/codex.py +637 -0
  66. foundry_mcp/core/providers/cursor_agent.py +630 -0
  67. foundry_mcp/core/providers/detectors.py +515 -0
  68. foundry_mcp/core/providers/gemini.py +426 -0
  69. foundry_mcp/core/providers/opencode.py +718 -0
  70. foundry_mcp/core/providers/opencode_wrapper.js +308 -0
  71. foundry_mcp/core/providers/package-lock.json +24 -0
  72. foundry_mcp/core/providers/package.json +25 -0
  73. foundry_mcp/core/providers/registry.py +607 -0
  74. foundry_mcp/core/providers/test_provider.py +171 -0
  75. foundry_mcp/core/providers/validation.py +857 -0
  76. foundry_mcp/core/rate_limit.py +427 -0
  77. foundry_mcp/core/research/__init__.py +68 -0
  78. foundry_mcp/core/research/memory.py +528 -0
  79. foundry_mcp/core/research/models.py +1234 -0
  80. foundry_mcp/core/research/providers/__init__.py +40 -0
  81. foundry_mcp/core/research/providers/base.py +242 -0
  82. foundry_mcp/core/research/providers/google.py +507 -0
  83. foundry_mcp/core/research/providers/perplexity.py +442 -0
  84. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  85. foundry_mcp/core/research/providers/tavily.py +383 -0
  86. foundry_mcp/core/research/workflows/__init__.py +25 -0
  87. foundry_mcp/core/research/workflows/base.py +298 -0
  88. foundry_mcp/core/research/workflows/chat.py +271 -0
  89. foundry_mcp/core/research/workflows/consensus.py +539 -0
  90. foundry_mcp/core/research/workflows/deep_research.py +4142 -0
  91. foundry_mcp/core/research/workflows/ideate.py +682 -0
  92. foundry_mcp/core/research/workflows/thinkdeep.py +405 -0
  93. foundry_mcp/core/resilience.py +600 -0
  94. foundry_mcp/core/responses.py +1624 -0
  95. foundry_mcp/core/review.py +366 -0
  96. foundry_mcp/core/security.py +438 -0
  97. foundry_mcp/core/spec.py +4119 -0
  98. foundry_mcp/core/task.py +2463 -0
  99. foundry_mcp/core/testing.py +839 -0
  100. foundry_mcp/core/validation.py +2357 -0
  101. foundry_mcp/dashboard/__init__.py +32 -0
  102. foundry_mcp/dashboard/app.py +119 -0
  103. foundry_mcp/dashboard/components/__init__.py +17 -0
  104. foundry_mcp/dashboard/components/cards.py +88 -0
  105. foundry_mcp/dashboard/components/charts.py +177 -0
  106. foundry_mcp/dashboard/components/filters.py +136 -0
  107. foundry_mcp/dashboard/components/tables.py +195 -0
  108. foundry_mcp/dashboard/data/__init__.py +11 -0
  109. foundry_mcp/dashboard/data/stores.py +433 -0
  110. foundry_mcp/dashboard/launcher.py +300 -0
  111. foundry_mcp/dashboard/views/__init__.py +12 -0
  112. foundry_mcp/dashboard/views/errors.py +217 -0
  113. foundry_mcp/dashboard/views/metrics.py +164 -0
  114. foundry_mcp/dashboard/views/overview.py +96 -0
  115. foundry_mcp/dashboard/views/providers.py +83 -0
  116. foundry_mcp/dashboard/views/sdd_workflow.py +255 -0
  117. foundry_mcp/dashboard/views/tool_usage.py +139 -0
  118. foundry_mcp/prompts/__init__.py +9 -0
  119. foundry_mcp/prompts/workflows.py +525 -0
  120. foundry_mcp/resources/__init__.py +9 -0
  121. foundry_mcp/resources/specs.py +591 -0
  122. foundry_mcp/schemas/__init__.py +38 -0
  123. foundry_mcp/schemas/intake-schema.json +89 -0
  124. foundry_mcp/schemas/sdd-spec-schema.json +414 -0
  125. foundry_mcp/server.py +150 -0
  126. foundry_mcp/tools/__init__.py +10 -0
  127. foundry_mcp/tools/unified/__init__.py +92 -0
  128. foundry_mcp/tools/unified/authoring.py +3620 -0
  129. foundry_mcp/tools/unified/context_helpers.py +98 -0
  130. foundry_mcp/tools/unified/documentation_helpers.py +268 -0
  131. foundry_mcp/tools/unified/environment.py +1341 -0
  132. foundry_mcp/tools/unified/error.py +479 -0
  133. foundry_mcp/tools/unified/health.py +225 -0
  134. foundry_mcp/tools/unified/journal.py +841 -0
  135. foundry_mcp/tools/unified/lifecycle.py +640 -0
  136. foundry_mcp/tools/unified/metrics.py +777 -0
  137. foundry_mcp/tools/unified/plan.py +876 -0
  138. foundry_mcp/tools/unified/pr.py +294 -0
  139. foundry_mcp/tools/unified/provider.py +589 -0
  140. foundry_mcp/tools/unified/research.py +1283 -0
  141. foundry_mcp/tools/unified/review.py +1042 -0
  142. foundry_mcp/tools/unified/review_helpers.py +314 -0
  143. foundry_mcp/tools/unified/router.py +102 -0
  144. foundry_mcp/tools/unified/server.py +565 -0
  145. foundry_mcp/tools/unified/spec.py +1283 -0
  146. foundry_mcp/tools/unified/task.py +3846 -0
  147. foundry_mcp/tools/unified/test.py +431 -0
  148. foundry_mcp/tools/unified/verification.py +520 -0
  149. foundry_mcp-0.8.22.dist-info/METADATA +344 -0
  150. foundry_mcp-0.8.22.dist-info/RECORD +153 -0
  151. foundry_mcp-0.8.22.dist-info/WHEEL +4 -0
  152. foundry_mcp-0.8.22.dist-info/entry_points.txt +3 -0
  153. 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)