Reduces NetBox MCP context token consumption from ~19,810 tokens (182 tools) to ~4,500 tokens (~43 tools) by enabling environment-variable-driven module filtering. Key changes: - Add NETBOX_ENABLED_MODULES env var to config.py - Filter tool registration based on enabled modules in server.py - Conditional tool class instantiation for memory efficiency - Routing guard with clear error messages for disabled modules - Startup logging shows enabled modules and tool count Also fixes documentation referencing incorrect tool names: - virtualization_* → virt_* in cmdb-assistant docs - wireless_* → wlan_* in README - circuits_list_circuit_terminations → circ_list_terminations Recommended config for cmdb-assistant users: NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
157 lines
5.2 KiB
Python
157 lines
5.2 KiB
Python
"""
|
|
Configuration loader for NetBox MCP Server.
|
|
|
|
Implements hybrid configuration system:
|
|
- System-level: ~/.config/claude/netbox.env (credentials)
|
|
- Project-level: .env (optional overrides)
|
|
"""
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
import os
|
|
import logging
|
|
from typing import Dict, List, Optional, Set
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# All available NetBox modules
|
|
ALL_MODULES = frozenset([
|
|
'dcim', 'ipam', 'circuits', 'virtualization',
|
|
'tenancy', 'vpn', 'wireless', 'extras'
|
|
])
|
|
|
|
|
|
class NetBoxConfig:
|
|
"""Configuration loader for NetBox MCP Server"""
|
|
|
|
def __init__(self):
|
|
self.api_url: Optional[str] = None
|
|
self.api_token: Optional[str] = None
|
|
self.verify_ssl: bool = True
|
|
self.timeout: int = 30
|
|
self.enabled_modules: Set[str] = set(ALL_MODULES)
|
|
|
|
def load(self) -> Dict[str, any]:
|
|
"""
|
|
Load configuration from system and project levels.
|
|
Project-level configuration overrides system-level.
|
|
|
|
Returns:
|
|
Dict containing api_url, api_token, verify_ssl, timeout
|
|
|
|
Raises:
|
|
FileNotFoundError: If system config is missing
|
|
ValueError: If required configuration is missing
|
|
"""
|
|
# Load system config
|
|
system_config = Path.home() / '.config' / 'claude' / 'netbox.env'
|
|
if system_config.exists():
|
|
load_dotenv(system_config)
|
|
logger.info(f"Loaded system configuration from {system_config}")
|
|
else:
|
|
raise FileNotFoundError(
|
|
f"System config not found: {system_config}\n"
|
|
"Create it with:\n"
|
|
" mkdir -p ~/.config/claude\n"
|
|
" cat > ~/.config/claude/netbox.env << EOF\n"
|
|
" NETBOX_API_URL=https://your-netbox-instance/api\n"
|
|
" NETBOX_API_TOKEN=your-api-token\n"
|
|
" EOF"
|
|
)
|
|
|
|
# Load project config (overrides system)
|
|
project_config = Path.cwd() / '.env'
|
|
if project_config.exists():
|
|
load_dotenv(project_config, override=True)
|
|
logger.info(f"Loaded project configuration from {project_config}")
|
|
|
|
# Extract values
|
|
self.api_url = os.getenv('NETBOX_API_URL')
|
|
self.api_token = os.getenv('NETBOX_API_TOKEN')
|
|
|
|
# Optional settings with defaults
|
|
verify_ssl_str = os.getenv('NETBOX_VERIFY_SSL', 'true').lower()
|
|
self.verify_ssl = verify_ssl_str in ('true', '1', 'yes')
|
|
|
|
timeout_str = os.getenv('NETBOX_TIMEOUT', '30')
|
|
try:
|
|
self.timeout = int(timeout_str)
|
|
except ValueError:
|
|
self.timeout = 30
|
|
logger.warning(f"Invalid NETBOX_TIMEOUT value '{timeout_str}', using default 30")
|
|
|
|
# Module filtering
|
|
self.enabled_modules = self._load_enabled_modules()
|
|
|
|
# Validate required variables
|
|
self._validate()
|
|
|
|
# Normalize API URL (remove trailing slash)
|
|
if self.api_url and self.api_url.endswith('/'):
|
|
self.api_url = self.api_url.rstrip('/')
|
|
|
|
return {
|
|
'api_url': self.api_url,
|
|
'api_token': self.api_token,
|
|
'verify_ssl': self.verify_ssl,
|
|
'timeout': self.timeout,
|
|
'enabled_modules': self.enabled_modules
|
|
}
|
|
|
|
def _validate(self) -> None:
|
|
"""
|
|
Validate that required configuration is present.
|
|
|
|
Raises:
|
|
ValueError: If required configuration is missing
|
|
"""
|
|
required = {
|
|
'NETBOX_API_URL': self.api_url,
|
|
'NETBOX_API_TOKEN': self.api_token
|
|
}
|
|
|
|
missing = [key for key, value in required.items() if not value]
|
|
|
|
if missing:
|
|
raise ValueError(
|
|
f"Missing required configuration: {', '.join(missing)}\n"
|
|
"Check your ~/.config/claude/netbox.env file"
|
|
)
|
|
|
|
def _load_enabled_modules(self) -> Set[str]:
|
|
"""
|
|
Load enabled modules from NETBOX_ENABLED_MODULES environment variable.
|
|
|
|
Format: Comma-separated list of module names.
|
|
Example: NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras
|
|
|
|
Returns:
|
|
Set of enabled module names. If env var is unset/empty, returns all modules.
|
|
"""
|
|
modules_str = os.getenv('NETBOX_ENABLED_MODULES', '').strip()
|
|
|
|
if not modules_str:
|
|
logger.info("NETBOX_ENABLED_MODULES not set, all modules enabled (default)")
|
|
return set(ALL_MODULES)
|
|
|
|
# Parse comma-separated list, strip whitespace
|
|
requested = {m.strip().lower() for m in modules_str.split(',') if m.strip()}
|
|
|
|
# Validate module names
|
|
invalid = requested - ALL_MODULES
|
|
if invalid:
|
|
logger.warning(
|
|
f"Unknown modules in NETBOX_ENABLED_MODULES: {', '.join(sorted(invalid))}. "
|
|
f"Valid modules: {', '.join(sorted(ALL_MODULES))}"
|
|
)
|
|
|
|
# Return only valid modules
|
|
enabled = requested & ALL_MODULES
|
|
|
|
if not enabled:
|
|
logger.warning("No valid modules enabled, falling back to all modules")
|
|
return set(ALL_MODULES)
|
|
|
|
logger.info(f"Enabled modules: {', '.join(sorted(enabled))}")
|
|
return enabled
|