feat: Add DISABLED_TOOLS environment variable for selective tool disabling (#127)
## Description This PR adds support for selectively disabling tools via the DISABLED_TOOLS environment variable, allowing users to customize which MCP tools are available in their Zen server instance. This feature enables better control over tool availability for security, performance, or organizational requirements. ## Changes Made - [x] Added `DISABLED_TOOLS` environment variable support to selectively disable tools - [x] Implemented tool filtering logic with protection for essential tools (version, listmodels) - [x] Added comprehensive validation with warnings for unknown tools and attempts to disable essential tools - [x] Updated `.env.example` with DISABLED_TOOLS documentation and examples - [x] Added comprehensive test suite (16 tests) covering all edge cases - [x] No breaking changes - feature is opt-in with default behavior unchanged ## Configuration Add to `.env` file: ```bash # Optional: Tool Selection # Comma-separated list of tools to disable. If not set, all tools are enabled. # Essential tools (version, listmodels) cannot be disabled. # Available tools: chat, thinkdeep, planner, consensus, codereview, precommit, # debug, docgen, analyze, refactor, tracer, testgen # Examples: # DISABLED_TOOLS= # All tools enabled (default) # DISABLED_TOOLS=debug,tracer # Disable debug and tracer tools # DISABLED_TOOLS=planner,consensus # Disable planning tools
This commit is contained in:
committed by
GitHub
parent
3b250c95df
commit
a355b80afc
92
server.py
92
server.py
@@ -158,6 +158,97 @@ logger = logging.getLogger(__name__)
|
||||
# This name is used by MCP clients to identify and connect to this specific server
|
||||
server: Server = Server("zen-server")
|
||||
|
||||
|
||||
# Constants for tool filtering
|
||||
ESSENTIAL_TOOLS = {"version", "listmodels"}
|
||||
|
||||
|
||||
def parse_disabled_tools_env() -> set[str]:
|
||||
"""
|
||||
Parse the DISABLED_TOOLS environment variable into a set of tool names.
|
||||
|
||||
Returns:
|
||||
Set of lowercase tool names to disable, empty set if none specified
|
||||
"""
|
||||
disabled_tools_env = os.getenv("DISABLED_TOOLS", "").strip()
|
||||
if not disabled_tools_env:
|
||||
return set()
|
||||
return {t.strip().lower() for t in disabled_tools_env.split(",") if t.strip()}
|
||||
|
||||
|
||||
def validate_disabled_tools(disabled_tools: set[str], all_tools: dict[str, Any]) -> None:
|
||||
"""
|
||||
Validate the disabled tools list and log appropriate warnings.
|
||||
|
||||
Args:
|
||||
disabled_tools: Set of tool names requested to be disabled
|
||||
all_tools: Dictionary of all available tool instances
|
||||
"""
|
||||
essential_disabled = disabled_tools & ESSENTIAL_TOOLS
|
||||
if essential_disabled:
|
||||
logger.warning(f"Cannot disable essential tools: {sorted(essential_disabled)}")
|
||||
unknown_tools = disabled_tools - set(all_tools.keys())
|
||||
if unknown_tools:
|
||||
logger.warning(f"Unknown tools in DISABLED_TOOLS: {sorted(unknown_tools)}")
|
||||
|
||||
|
||||
def apply_tool_filter(all_tools: dict[str, Any], disabled_tools: set[str]) -> dict[str, Any]:
|
||||
"""
|
||||
Apply the disabled tools filter to create the final tools dictionary.
|
||||
|
||||
Args:
|
||||
all_tools: Dictionary of all available tool instances
|
||||
disabled_tools: Set of tool names to disable
|
||||
|
||||
Returns:
|
||||
Dictionary containing only enabled tools
|
||||
"""
|
||||
enabled_tools = {}
|
||||
for tool_name, tool_instance in all_tools.items():
|
||||
if tool_name in ESSENTIAL_TOOLS or tool_name not in disabled_tools:
|
||||
enabled_tools[tool_name] = tool_instance
|
||||
else:
|
||||
logger.debug(f"Tool '{tool_name}' disabled via DISABLED_TOOLS")
|
||||
return enabled_tools
|
||||
|
||||
|
||||
def log_tool_configuration(disabled_tools: set[str], enabled_tools: dict[str, Any]) -> None:
|
||||
"""
|
||||
Log the final tool configuration for visibility.
|
||||
|
||||
Args:
|
||||
disabled_tools: Set of tool names that were requested to be disabled
|
||||
enabled_tools: Dictionary of tools that remain enabled
|
||||
"""
|
||||
if not disabled_tools:
|
||||
logger.info("All tools enabled (DISABLED_TOOLS not set)")
|
||||
return
|
||||
actual_disabled = disabled_tools - ESSENTIAL_TOOLS
|
||||
if actual_disabled:
|
||||
logger.debug(f"Disabled tools: {sorted(actual_disabled)}")
|
||||
logger.info(f"Active tools: {sorted(enabled_tools.keys())}")
|
||||
|
||||
|
||||
def filter_disabled_tools(all_tools: dict[str, Any]) -> dict[str, Any]:
|
||||
"""
|
||||
Filter tools based on DISABLED_TOOLS environment variable.
|
||||
|
||||
Args:
|
||||
all_tools: Dictionary of all available tool instances
|
||||
|
||||
Returns:
|
||||
dict: Filtered dictionary containing only enabled tools
|
||||
"""
|
||||
disabled_tools = parse_disabled_tools_env()
|
||||
if not disabled_tools:
|
||||
log_tool_configuration(disabled_tools, all_tools)
|
||||
return all_tools
|
||||
validate_disabled_tools(disabled_tools, all_tools)
|
||||
enabled_tools = apply_tool_filter(all_tools, disabled_tools)
|
||||
log_tool_configuration(disabled_tools, enabled_tools)
|
||||
return enabled_tools
|
||||
|
||||
|
||||
# Initialize the tool registry with all available AI-powered tools
|
||||
# Each tool provides specialized functionality for different development tasks
|
||||
# Tools are instantiated once and reused across requests (stateless design)
|
||||
@@ -178,6 +269,7 @@ TOOLS = {
|
||||
"listmodels": ListModelsTool(), # List all available AI models by provider
|
||||
"version": VersionTool(), # Display server version and system information
|
||||
}
|
||||
TOOLS = filter_disabled_tools(TOOLS)
|
||||
|
||||
# Rich prompt templates for all tools
|
||||
PROMPT_TEMPLATES = {
|
||||
|
||||
Reference in New Issue
Block a user