foundry-mcp 0.7.0__py3-none-any.whl → 0.8.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. foundry_mcp/cli/__init__.py +0 -13
  2. foundry_mcp/cli/commands/session.py +1 -8
  3. foundry_mcp/cli/context.py +39 -0
  4. foundry_mcp/config.py +381 -7
  5. foundry_mcp/core/batch_operations.py +1196 -0
  6. foundry_mcp/core/discovery.py +1 -1
  7. foundry_mcp/core/llm_config.py +8 -0
  8. foundry_mcp/core/naming.py +25 -2
  9. foundry_mcp/core/prometheus.py +0 -13
  10. foundry_mcp/core/providers/__init__.py +12 -0
  11. foundry_mcp/core/providers/base.py +39 -0
  12. foundry_mcp/core/providers/claude.py +45 -1
  13. foundry_mcp/core/providers/codex.py +64 -3
  14. foundry_mcp/core/providers/cursor_agent.py +22 -3
  15. foundry_mcp/core/providers/detectors.py +34 -7
  16. foundry_mcp/core/providers/gemini.py +63 -1
  17. foundry_mcp/core/providers/opencode.py +95 -71
  18. foundry_mcp/core/providers/package-lock.json +4 -4
  19. foundry_mcp/core/providers/package.json +1 -1
  20. foundry_mcp/core/providers/validation.py +128 -0
  21. foundry_mcp/core/research/memory.py +103 -0
  22. foundry_mcp/core/research/models.py +783 -0
  23. foundry_mcp/core/research/providers/__init__.py +40 -0
  24. foundry_mcp/core/research/providers/base.py +242 -0
  25. foundry_mcp/core/research/providers/google.py +507 -0
  26. foundry_mcp/core/research/providers/perplexity.py +442 -0
  27. foundry_mcp/core/research/providers/semantic_scholar.py +544 -0
  28. foundry_mcp/core/research/providers/tavily.py +383 -0
  29. foundry_mcp/core/research/workflows/__init__.py +5 -2
  30. foundry_mcp/core/research/workflows/base.py +106 -12
  31. foundry_mcp/core/research/workflows/consensus.py +160 -17
  32. foundry_mcp/core/research/workflows/deep_research.py +4020 -0
  33. foundry_mcp/core/responses.py +240 -0
  34. foundry_mcp/core/spec.py +1 -0
  35. foundry_mcp/core/task.py +141 -12
  36. foundry_mcp/core/validation.py +6 -1
  37. foundry_mcp/server.py +0 -52
  38. foundry_mcp/tools/unified/__init__.py +37 -18
  39. foundry_mcp/tools/unified/authoring.py +0 -33
  40. foundry_mcp/tools/unified/environment.py +202 -29
  41. foundry_mcp/tools/unified/plan.py +20 -1
  42. foundry_mcp/tools/unified/provider.py +0 -40
  43. foundry_mcp/tools/unified/research.py +644 -19
  44. foundry_mcp/tools/unified/review.py +5 -2
  45. foundry_mcp/tools/unified/review_helpers.py +16 -1
  46. foundry_mcp/tools/unified/server.py +9 -24
  47. foundry_mcp/tools/unified/task.py +528 -9
  48. {foundry_mcp-0.7.0.dist-info → foundry_mcp-0.8.10.dist-info}/METADATA +2 -1
  49. {foundry_mcp-0.7.0.dist-info → foundry_mcp-0.8.10.dist-info}/RECORD +52 -46
  50. foundry_mcp/cli/flags.py +0 -266
  51. foundry_mcp/core/feature_flags.py +0 -592
  52. {foundry_mcp-0.7.0.dist-info → foundry_mcp-0.8.10.dist-info}/WHEEL +0 -0
  53. {foundry_mcp-0.7.0.dist-info → foundry_mcp-0.8.10.dist-info}/entry_points.txt +0 -0
  54. {foundry_mcp-0.7.0.dist-info → foundry_mcp-0.8.10.dist-info}/licenses/LICENSE +0 -0
@@ -29,27 +29,46 @@ if TYPE_CHECKING: # pragma: no cover - import-time typing only
29
29
 
30
30
  def register_unified_tools(mcp: "FastMCP", config: "ServerConfig") -> None:
31
31
  """Register all unified tool routers."""
32
- register_unified_health_tool(mcp, config)
33
- register_unified_plan_tool(mcp, config)
34
- register_unified_pr_tool(mcp, config)
35
- register_unified_error_tool(mcp, config)
36
- register_unified_metrics_tool(mcp, config)
37
- register_unified_journal_tool(mcp, config)
38
- register_unified_authoring_tool(mcp, config)
39
- register_unified_review_tool(mcp, config)
40
- register_unified_spec_tool(mcp, config)
32
+ disabled = set(config.disabled_tools)
33
+
34
+ if "health" not in disabled:
35
+ register_unified_health_tool(mcp, config)
36
+ if "plan" not in disabled:
37
+ register_unified_plan_tool(mcp, config)
38
+ if "pr" not in disabled:
39
+ register_unified_pr_tool(mcp, config)
40
+ if "error" not in disabled:
41
+ register_unified_error_tool(mcp, config)
42
+ if "metrics" not in disabled:
43
+ register_unified_metrics_tool(mcp, config)
44
+ if "journal" not in disabled:
45
+ register_unified_journal_tool(mcp, config)
46
+ if "authoring" not in disabled:
47
+ register_unified_authoring_tool(mcp, config)
48
+ if "review" not in disabled:
49
+ register_unified_review_tool(mcp, config)
50
+ if "spec" not in disabled:
51
+ register_unified_spec_tool(mcp, config)
41
52
 
42
53
  from importlib import import_module
43
54
 
44
- _task_router = import_module("foundry_mcp.tools.unified.task")
45
- _task_router.register_unified_task_tool(mcp, config)
46
- register_unified_provider_tool(mcp, config)
47
- register_unified_environment_tool(mcp, config)
48
- register_unified_lifecycle_tool(mcp, config)
49
- register_unified_verification_tool(mcp, config)
50
- register_unified_server_tool(mcp, config)
51
- register_unified_test_tool(mcp, config)
52
- register_unified_research_tool(mcp, config)
55
+ if "task" not in disabled:
56
+ _task_router = import_module("foundry_mcp.tools.unified.task")
57
+ _task_router.register_unified_task_tool(mcp, config)
58
+ if "provider" not in disabled:
59
+ register_unified_provider_tool(mcp, config)
60
+ if "environment" not in disabled:
61
+ register_unified_environment_tool(mcp, config)
62
+ if "lifecycle" not in disabled:
63
+ register_unified_lifecycle_tool(mcp, config)
64
+ if "verification" not in disabled:
65
+ register_unified_verification_tool(mcp, config)
66
+ if "server" not in disabled:
67
+ register_unified_server_tool(mcp, config)
68
+ if "test" not in disabled:
69
+ register_unified_test_tool(mcp, config)
70
+ if "research" not in disabled:
71
+ register_unified_research_tool(mcp, config)
53
72
 
54
73
 
55
74
  __all__ = [
@@ -13,7 +13,6 @@ from mcp.server.fastmcp import FastMCP
13
13
 
14
14
  from foundry_mcp.config import ServerConfig
15
15
  from foundry_mcp.core.context import generate_correlation_id, get_correlation_id
16
- from foundry_mcp.core.feature_flags import FeatureFlag, FlagState, get_flag_service
17
16
  from foundry_mcp.core.intake import IntakeStore, LockAcquisitionError, INTAKE_ID_PATTERN
18
17
  from foundry_mcp.core.naming import canonical_tool
19
18
  from foundry_mcp.core.observability import audit_log, get_metrics, mcp_tool
@@ -57,38 +56,6 @@ from foundry_mcp.tools.unified.router import (
57
56
  logger = logging.getLogger(__name__)
58
57
  _metrics = get_metrics()
59
58
 
60
- # Register intake_tools feature flag
61
- _flag_service = get_flag_service()
62
- try:
63
- _flag_service.register(
64
- FeatureFlag(
65
- name="intake_tools",
66
- description="Bikelane intake queue tools (add, list, dismiss)",
67
- state=FlagState.EXPERIMENTAL,
68
- default_enabled=False,
69
- )
70
- )
71
- except ValueError:
72
- pass # Flag already registered
73
-
74
-
75
- def _intake_feature_flag_blocked(request_id: str) -> Optional[dict]:
76
- """Check if intake tools are blocked by feature flag."""
77
- if _flag_service.is_enabled("intake_tools"):
78
- return None
79
-
80
- return asdict(
81
- error_response(
82
- "Intake tools are disabled by feature flag",
83
- error_code=ErrorCode.FEATURE_DISABLED,
84
- error_type=ErrorType.FEATURE_FLAG,
85
- data={"feature": "intake_tools"},
86
- remediation="Enable the 'intake_tools' feature flag to use intake actions.",
87
- request_id=request_id,
88
- )
89
- )
90
-
91
-
92
59
  _ACTION_SUMMARY = {
93
60
  "spec-create": "Scaffold a new SDD specification",
94
61
  "spec-template": "List/show/apply spec templates",
@@ -15,7 +15,6 @@ from mcp.server.fastmcp import FastMCP
15
15
 
16
16
  from foundry_mcp.config import ServerConfig, _PACKAGE_VERSION
17
17
  from foundry_mcp.core.context import generate_correlation_id, get_correlation_id
18
- from foundry_mcp.core.feature_flags import FeatureFlag, FlagState, get_flag_service
19
18
  from foundry_mcp.core.naming import canonical_tool
20
19
  from foundry_mcp.core.observability import audit_log, get_metrics, mcp_tool
21
20
  from foundry_mcp.core.responses import (
@@ -32,18 +31,6 @@ from foundry_mcp.tools.unified.router import (
32
31
 
33
32
  logger = logging.getLogger(__name__)
34
33
  _metrics = get_metrics()
35
- _flag_service = get_flag_service()
36
- try:
37
- _flag_service.register(
38
- FeatureFlag(
39
- name="environment_tools",
40
- description="Environment readiness and workspace tooling",
41
- state=FlagState.BETA,
42
- default_enabled=True,
43
- )
44
- )
45
- except ValueError:
46
- pass
47
34
 
48
35
  _DEFAULT_TOML_TEMPLATE = """[workspace]
49
36
  specs_dir = "./specs"
@@ -52,23 +39,46 @@ specs_dir = "./specs"
52
39
  level = "INFO"
53
40
  structured = true
54
41
 
55
- [server]
56
- name = "foundry-mcp"
57
- version = "{version}"
42
+ [tools]
43
+ # Disable tools to reduce context window usage
44
+ # Available: health, plan, pr, error, metrics, journal, authoring, review,
45
+ # spec, task, provider, environment, lifecycle, verification,
46
+ # server, test, research
47
+ disabled_tools = ["error", "metrics", "health"]
58
48
 
59
49
  [workflow]
60
50
  mode = "single"
61
51
  auto_validate = true
62
52
  journal_enabled = true
63
53
 
54
+ [implement]
55
+ # Default flags for /implement command (can be overridden via CLI flags)
56
+ auto = false # --auto: skip prompts between tasks
57
+ delegate = false # --delegate: use subagent(s) for implementation
58
+ parallel = false # --parallel: run subagents concurrently (implies delegate)
59
+
64
60
  [consultation]
65
61
  # priority = [] # Appended by setup based on detected providers
66
62
  default_timeout = 300
63
+
64
+ [research]
65
+ # Research tool configuration (chat, consensus, thinkdeep, ideate, deep)
66
+ # default_provider = "[cli]provider:model" # Appended by setup
67
+ # consensus_providers = [] # Appended by setup (same as consultation.priority)
67
68
  max_retries = 2
68
69
  retry_delay = 5.0
69
70
  fallback_enabled = true
70
71
  cache_ttl = 3600
71
72
 
73
+ [research.deep]
74
+ # Deep research workflow settings
75
+ max_iterations = 3
76
+ max_sub_queries = 5
77
+ max_sources_per_query = 5
78
+ follow_links = true
79
+ max_concurrent = 3
80
+ timeout_per_operation = 120
81
+
72
82
  [consultation.workflows.fidelity_review]
73
83
  min_models = 2
74
84
  timeout_override = 600.0
@@ -220,6 +230,7 @@ _ACTION_SUMMARY = {
220
230
  "detect": "Detect repository topology (project type, specs/docs)",
221
231
  "detect-test-runner": "Detect appropriate test runner for the project",
222
232
  "setup": "Complete SDD setup with permissions + config",
233
+ "get-config": "Read configuration sections from foundry-mcp.toml",
223
234
  }
224
235
 
225
236
 
@@ -232,19 +243,8 @@ def _request_id() -> str:
232
243
 
233
244
 
234
245
  def _feature_flag_blocked(request_id: str) -> Optional[dict]:
235
- if _flag_service.is_enabled("environment_tools"):
236
- return None
237
-
238
- return asdict(
239
- error_response(
240
- "Environment tools are disabled by feature flag",
241
- error_code=ErrorCode.FEATURE_DISABLED,
242
- error_type=ErrorType.FEATURE_FLAG,
243
- data={"feature": "environment_tools"},
244
- remediation="Enable the 'environment_tools' feature flag to call environment actions.",
245
- request_id=request_id,
246
- )
247
- )
246
+ # Feature flags disabled - always allow
247
+ return None
248
248
 
249
249
 
250
250
  def _validation_error(
@@ -1056,6 +1056,169 @@ def _handle_setup(
1056
1056
  )
1057
1057
 
1058
1058
 
1059
+ def _handle_get_config(
1060
+ *,
1061
+ config: ServerConfig, # noqa: ARG001 - config object available but we read TOML directly
1062
+ sections: Optional[List[str]] = None,
1063
+ key: Optional[str] = None,
1064
+ **_: Any,
1065
+ ) -> dict:
1066
+ """Read configuration sections from foundry-mcp.toml.
1067
+
1068
+ Returns the requested sections from the TOML config file.
1069
+ Supported sections: implement, git.
1070
+
1071
+ Args:
1072
+ sections: List of section names to return (default: all supported sections)
1073
+ key: Specific key within section (only valid when requesting single section)
1074
+ """
1075
+ import tomllib
1076
+
1077
+ request_id = _request_id()
1078
+ blocked = _feature_flag_blocked(request_id)
1079
+ if blocked:
1080
+ return blocked
1081
+
1082
+ # Validate sections parameter
1083
+ supported_sections = {"implement", "git"}
1084
+ if sections is not None:
1085
+ if not isinstance(sections, list):
1086
+ return _validation_error(
1087
+ action="get-config",
1088
+ field="sections",
1089
+ message="Expected a list of section names",
1090
+ request_id=request_id,
1091
+ code=ErrorCode.INVALID_FORMAT,
1092
+ )
1093
+ invalid = set(sections) - supported_sections
1094
+ if invalid:
1095
+ return _validation_error(
1096
+ action="get-config",
1097
+ field="sections",
1098
+ message=f"Unsupported sections: {', '.join(sorted(invalid))}. Supported: {', '.join(sorted(supported_sections))}",
1099
+ request_id=request_id,
1100
+ )
1101
+
1102
+ # Validate key parameter
1103
+ if key is not None:
1104
+ if not isinstance(key, str):
1105
+ return _validation_error(
1106
+ action="get-config",
1107
+ field="key",
1108
+ message="Expected a string",
1109
+ request_id=request_id,
1110
+ code=ErrorCode.INVALID_FORMAT,
1111
+ )
1112
+ if sections is None or len(sections) != 1:
1113
+ return _validation_error(
1114
+ action="get-config",
1115
+ field="key",
1116
+ message="The 'key' parameter is only valid when requesting exactly one section",
1117
+ request_id=request_id,
1118
+ )
1119
+
1120
+ metric_key = _metric_name("get-config")
1121
+ try:
1122
+ # Find the TOML config file
1123
+ toml_path = None
1124
+ for candidate in ["foundry-mcp.toml", ".foundry-mcp.toml"]:
1125
+ if Path(candidate).exists():
1126
+ toml_path = Path(candidate)
1127
+ break
1128
+
1129
+ if not toml_path:
1130
+ _metrics.counter(metric_key, labels={"status": "not_found"})
1131
+ return asdict(
1132
+ error_response(
1133
+ "No foundry-mcp.toml config file found",
1134
+ error_code=ErrorCode.NOT_FOUND,
1135
+ error_type=ErrorType.NOT_FOUND,
1136
+ remediation="Run environment(action=setup) to create the config file",
1137
+ request_id=request_id,
1138
+ )
1139
+ )
1140
+
1141
+ # Read and parse TOML
1142
+ with open(toml_path, "rb") as f:
1143
+ data = tomllib.load(f)
1144
+
1145
+ # Determine which sections to return
1146
+ requested = set(sections) if sections else supported_sections
1147
+
1148
+ # Build result with only supported sections
1149
+ result: Dict[str, Any] = {}
1150
+
1151
+ if "implement" in requested and "implement" in data:
1152
+ impl_data = data["implement"]
1153
+ result["implement"] = {
1154
+ "auto": impl_data.get("auto", False),
1155
+ "delegate": impl_data.get("delegate", False),
1156
+ "parallel": impl_data.get("parallel", False),
1157
+ }
1158
+
1159
+ if "git" in requested and "git" in data:
1160
+ git_data = data["git"]
1161
+ result["git"] = {
1162
+ "enabled": git_data.get("enabled", True),
1163
+ "auto_commit": git_data.get("auto_commit", False),
1164
+ "auto_push": git_data.get("auto_push", False),
1165
+ "auto_pr": git_data.get("auto_pr", False),
1166
+ "commit_cadence": git_data.get("commit_cadence", "task"),
1167
+ }
1168
+
1169
+ # If sections were requested but not found, include them as empty/defaults
1170
+ for section in requested:
1171
+ if section not in result:
1172
+ if section == "implement":
1173
+ result["implement"] = {
1174
+ "auto": False,
1175
+ "delegate": False,
1176
+ "parallel": False,
1177
+ }
1178
+ elif section == "git":
1179
+ result["git"] = {
1180
+ "enabled": True,
1181
+ "auto_commit": False,
1182
+ "auto_push": False,
1183
+ "auto_pr": False,
1184
+ "commit_cadence": "task",
1185
+ }
1186
+
1187
+ # If a specific key was requested, extract just that value
1188
+ if key is not None:
1189
+ section_name = sections[0] # Already validated to be exactly one section
1190
+ section_data = result.get(section_name, {})
1191
+ if key not in section_data:
1192
+ return _validation_error(
1193
+ action="get-config",
1194
+ field="key",
1195
+ message=f"Key '{key}' not found in section '{section_name}'",
1196
+ request_id=request_id,
1197
+ code=ErrorCode.NOT_FOUND,
1198
+ )
1199
+ result = {section_name: {key: section_data[key]}}
1200
+
1201
+ _metrics.counter(metric_key, labels={"status": "success"})
1202
+ return asdict(
1203
+ success_response(
1204
+ data={"sections": result, "config_file": str(toml_path)},
1205
+ request_id=request_id,
1206
+ )
1207
+ )
1208
+ except Exception as exc:
1209
+ logger.exception("Error reading config")
1210
+ _metrics.counter(metric_key, labels={"status": "error"})
1211
+ return asdict(
1212
+ error_response(
1213
+ f"Failed to read config: {exc}",
1214
+ error_code=ErrorCode.INTERNAL_ERROR,
1215
+ error_type=ErrorType.INTERNAL,
1216
+ remediation="Check foundry-mcp.toml syntax and retry",
1217
+ request_id=request_id,
1218
+ )
1219
+ )
1220
+
1221
+
1059
1222
  _ENVIRONMENT_ROUTER = ActionRouter(
1060
1223
  tool_name="environment",
1061
1224
  actions=[
@@ -1103,6 +1266,12 @@ _ENVIRONMENT_ROUTER = ActionRouter(
1103
1266
  summary=_ACTION_SUMMARY["setup"],
1104
1267
  aliases=("sdd-setup", "sdd_setup"),
1105
1268
  ),
1269
+ ActionDefinition(
1270
+ name="get-config",
1271
+ handler=_handle_get_config,
1272
+ summary=_ACTION_SUMMARY["get-config"],
1273
+ aliases=("config", "read-config", "get_config"),
1274
+ ),
1106
1275
  ],
1107
1276
  )
1108
1277
 
@@ -1143,6 +1312,8 @@ def register_unified_environment_tool(mcp: FastMCP, config: ServerConfig) -> Non
1143
1312
  permissions_preset: str = "full",
1144
1313
  create_toml: bool = True,
1145
1314
  dry_run: bool = False,
1315
+ sections: Optional[List[str]] = None,
1316
+ key: Optional[str] = None,
1146
1317
  ) -> dict:
1147
1318
  payload = {
1148
1319
  "path": path,
@@ -1155,6 +1326,8 @@ def register_unified_environment_tool(mcp: FastMCP, config: ServerConfig) -> Non
1155
1326
  "permissions_preset": permissions_preset,
1156
1327
  "create_toml": create_toml,
1157
1328
  "dry_run": dry_run,
1329
+ "sections": sections,
1330
+ "key": key,
1158
1331
  }
1159
1332
  return _dispatch_environment_action(
1160
1333
  action=action, payload=payload, config=config
@@ -19,6 +19,7 @@ from foundry_mcp.core.ai_consultation import (
19
19
  ConsultationWorkflow,
20
20
  ConsensusResult,
21
21
  )
22
+ from foundry_mcp.core.llm_config import load_consultation_config
22
23
  from foundry_mcp.core.naming import canonical_tool
23
24
  from foundry_mcp.core.observability import get_metrics, mcp_tool
24
25
  from foundry_mcp.core.providers import available_providers
@@ -29,6 +30,7 @@ from foundry_mcp.core.responses import (
29
30
  error_response,
30
31
  success_response,
31
32
  )
33
+ from foundry_mcp.core.llm_config import load_consultation_config
32
34
  from foundry_mcp.core.security import is_prompt_injection
33
35
  from foundry_mcp.core.spec import find_specs_directory
34
36
  from foundry_mcp.tools.unified.router import (
@@ -55,6 +57,20 @@ def _extract_plan_name(plan_path: str) -> str:
55
57
  return Path(plan_path).stem
56
58
 
57
59
 
60
+ def _find_config_file(start_path: Path) -> Optional[Path]:
61
+ """Find foundry-mcp.toml by walking up from start_path."""
62
+ current = start_path if start_path.is_dir() else start_path.parent
63
+ for _ in range(10): # Limit depth to prevent infinite loops
64
+ config_file = current / "foundry-mcp.toml"
65
+ if config_file.exists():
66
+ return config_file
67
+ parent = current.parent
68
+ if parent == current: # Reached root
69
+ break
70
+ current = parent
71
+ return None
72
+
73
+
58
74
  def _parse_review_summary(content: str) -> dict:
59
75
  """Parse review markdown to extract section counts."""
60
76
 
@@ -323,7 +339,10 @@ def perform_plan_review(
323
339
  template_id = REVIEW_TYPE_TO_TEMPLATE[review_type]
324
340
 
325
341
  try:
326
- orchestrator = ConsultationOrchestrator()
342
+ # Load consultation config from workspace to get provider priority list
343
+ config_file = _find_config_file(plan_file)
344
+ consultation_config = load_consultation_config(config_file=config_file)
345
+ orchestrator = ConsultationOrchestrator(config=consultation_config)
327
346
  request = ConsultationRequest(
328
347
  workflow=ConsultationWorkflow.MARKDOWN_PLAN_REVIEW,
329
348
  prompt_id=template_id,
@@ -11,7 +11,6 @@ from mcp.server.fastmcp import FastMCP
11
11
 
12
12
  from foundry_mcp.config import ServerConfig
13
13
  from foundry_mcp.core.context import generate_correlation_id, get_correlation_id
14
- from foundry_mcp.core.feature_flags import FeatureFlag, FlagState, get_flag_service
15
14
  from foundry_mcp.core.llm_provider import RateLimitError
16
15
  from foundry_mcp.core.naming import canonical_tool
17
16
  from foundry_mcp.core.observability import get_metrics, mcp_tool
@@ -42,19 +41,6 @@ from foundry_mcp.tools.unified.router import (
42
41
 
43
42
  logger = logging.getLogger(__name__)
44
43
  _metrics = get_metrics()
45
- _flag_service = get_flag_service()
46
- try:
47
- _flag_service.register(
48
- FeatureFlag(
49
- name="provider_tools",
50
- description="LLM provider management and execution tools",
51
- state=FlagState.BETA,
52
- default_enabled=True,
53
- )
54
- )
55
- except ValueError:
56
- # Flag already registered
57
- pass
58
44
 
59
45
  _ACTION_SUMMARY = {
60
46
  "list": "List registered providers with optional unavailable entries",
@@ -92,22 +78,6 @@ def _validation_error(
92
78
  )
93
79
 
94
80
 
95
- def _feature_flag_blocked(request_id: str) -> Optional[dict]:
96
- if _flag_service.is_enabled("provider_tools"):
97
- return None
98
-
99
- return asdict(
100
- error_response(
101
- "Provider tools are disabled by feature flag",
102
- error_code=ErrorCode.FEATURE_DISABLED,
103
- error_type=ErrorType.FEATURE_FLAG,
104
- data={"feature": "provider_tools"},
105
- remediation="Enable the 'provider_tools' feature flag to call provider actions.",
106
- request_id=request_id,
107
- )
108
- )
109
-
110
-
111
81
  def _handle_list(
112
82
  *,
113
83
  config: ServerConfig, # noqa: ARG001 - reserved for future hooks
@@ -115,9 +85,6 @@ def _handle_list(
115
85
  **_: Any,
116
86
  ) -> dict:
117
87
  request_id = _request_id()
118
- blocked = _feature_flag_blocked(request_id)
119
- if blocked:
120
- return blocked
121
88
 
122
89
  include = include_unavailable if isinstance(include_unavailable, bool) else False
123
90
  if include_unavailable is not None and not isinstance(include_unavailable, bool):
@@ -182,9 +149,6 @@ def _handle_status(
182
149
  **_: Any,
183
150
  ) -> dict:
184
151
  request_id = _request_id()
185
- blocked = _feature_flag_blocked(request_id)
186
- if blocked:
187
- return blocked
188
152
 
189
153
  if not isinstance(provider_id, str) or not provider_id.strip():
190
154
  return _validation_error(
@@ -288,10 +252,6 @@ def _handle_execute(
288
252
  **_: Any,
289
253
  ) -> dict:
290
254
  request_id = _request_id()
291
- blocked = _feature_flag_blocked(request_id)
292
- if blocked:
293
- return blocked
294
-
295
255
  action = "execute"
296
256
 
297
257
  if not isinstance(provider_id, str) or not provider_id.strip():