From d9d80d77cbddb964f62bf6ed515d7fcf55c7b720 Mon Sep 17 00:00:00 2001 From: lmiranda Date: Tue, 3 Feb 2026 12:08:46 -0500 Subject: [PATCH] feat(netbox): add module-based tool filtering for token optimization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 33 ++++++ mcp-servers/netbox/README.md | 81 ++++++++++++-- mcp-servers/netbox/mcp_server/config.py | 52 ++++++++- mcp-servers/netbox/mcp_server/server.py | 102 ++++++++++++++++-- .../cmdb-assistant/claude-md-integration.md | 6 +- .../cmdb-assistant/commands/cmdb-search.md | 2 +- 6 files changed, 250 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 53fc0a6..43e3dce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,39 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added + +#### NetBox MCP Server: Module-Based Tool Filtering + +Environment-variable-driven module filtering to reduce token consumption: + +- **New config option**: `NETBOX_ENABLED_MODULES` in `~/.config/claude/netbox.env` +- **Token savings**: ~15,000 tokens (from ~19,810 to ~4,500) with recommended config +- **Default behavior**: All modules enabled if env var unset (backward compatible) +- **Startup logging**: Shows enabled modules and tool count on initialization +- **Routing guard**: Clear error message when calling disabled module's tools + +**Recommended configuration for cmdb-assistant users:** +```bash +NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras +``` + +This enables ~43 tools covering all cmdb-assistant commands while staying well below the 25K token warning threshold. + +### Fixed + +#### cmdb-assistant Documentation: Incorrect Tool Names + +Fixed documentation referencing non-existent `virtualization_*` tool names: + +| File | Wrong | Correct | +|------|-------|---------| +| `claude-md-integration.md` | `virtualization_list_virtual_machines` | `virt_list_vms` | +| `claude-md-integration.md` | `virtualization_create_virtual_machine` | `virt_create_vm` | +| `cmdb-search.md` | `virtualization_list_virtual_machines` | `virt_list_vms` | + +Also fixed NetBox README.md tool name references for virtualization, wireless, and circuits modules. + --- ## [5.9.0] - 2026-02-03 diff --git a/mcp-servers/netbox/README.md b/mcp-servers/netbox/README.md index d061df8..b88faf9 100644 --- a/mcp-servers/netbox/README.md +++ b/mcp-servers/netbox/README.md @@ -79,6 +79,69 @@ Add to your Claude Code MCP configuration (`~/.config/claude/mcp.json` or projec 1. **System-level** (`~/.config/claude/netbox.env`): Credentials and defaults 2. **Project-level** (`.env` in current directory): Optional overrides +## Module Filtering (Token Optimization) + +By default, the NetBox MCP server registers all 182 tools across 8 modules, consuming ~19,810 tokens of context. For most workflows, you only need a subset of modules. + +### Configuration + +Add `NETBOX_ENABLED_MODULES` to your `~/.config/claude/netbox.env`: + +```bash +# Enable only specific modules (comma-separated) +NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras +``` + +If unset, all modules are enabled (backward compatible). + +### Available Modules + +| Module | Tool Count | Description | cmdb-assistant Commands | +|--------|------------|-------------|------------------------| +| `dcim` | ~60 | Sites, devices, racks, interfaces, cables | `/cmdb-device`, `/cmdb-site`, `/cmdb-search`, `/cmdb-topology` | +| `ipam` | ~40 | IP addresses, prefixes, VLANs, VRFs | `/cmdb-ip`, `/ip-conflicts`, `/cmdb-search` | +| `virtualization` | ~20 | Clusters, VMs, VM interfaces | `/cmdb-search`, `/cmdb-audit`, `/cmdb-register` | +| `extras` | ~12 | Tags, journal entries, audit log | `/change-audit`, `/cmdb-register` | +| `circuits` | ~15 | Providers, circuits, terminations | — | +| `tenancy` | ~12 | Tenants, contacts | — | +| `vpn` | ~15 | Tunnels, IKE/IPSec policies, L2VPN | — | +| `wireless` | ~8 | Wireless LANs, links, groups | — | + +### Recommended Configurations + +**For cmdb-assistant users** (~43 tools, ~4,500 tokens): +```bash +NETBOX_ENABLED_MODULES=dcim,ipam,virtualization,extras +``` + +**Basic infrastructure** (~100 tools): +```bash +NETBOX_ENABLED_MODULES=dcim,ipam +``` + +**Full CMDB** (all modules, ~182 tools): +```bash +# Omit NETBOX_ENABLED_MODULES or set to all modules +NETBOX_ENABLED_MODULES=dcim,ipam,circuits,virtualization,tenancy,vpn,wireless,extras +``` + +### Startup Logging + +On startup, the server logs enabled modules and tool count: + +``` +NetBox MCP Server initialized: 43 tools registered (modules: dcim, extras, ipam, virtualization) +``` + +### Disabled Tool Behavior + +Calling a tool from a disabled module returns a clear error: + +``` +Tool 'circuits_list_circuits' is not available (module 'circuits' not enabled). +Enabled modules: dcim, extras, ipam, virtualization +``` + ## Available Tools ### DCIM (Data Center Infrastructure Management) @@ -128,18 +191,18 @@ Add to your Claude Code MCP configuration (`~/.config/claude/mcp.json` or projec | `circuits_create_provider` | Create a provider | | `circuits_list_circuits` | List circuits | | `circuits_create_circuit` | Create a circuit | -| `circuits_list_circuit_terminations` | List terminations | +| `circ_list_terminations` | List terminations | | ... and more | ### Virtualization | Tool | Description | |------|-------------| -| `virtualization_list_clusters` | List clusters | -| `virtualization_create_cluster` | Create a cluster | -| `virtualization_list_virtual_machines` | List VMs | -| `virtualization_create_virtual_machine` | Create a VM | -| `virtualization_list_vm_interfaces` | List VM interfaces | +| `virt_list_clusters` | List clusters | +| `virt_create_cluster` | Create a cluster | +| `virt_list_vms` | List VMs | +| `virt_create_vm` | Create a VM | +| `virt_list_vm_ifaces` | List VM interfaces | | ... and more | ### Tenancy @@ -167,9 +230,9 @@ Add to your Claude Code MCP configuration (`~/.config/claude/mcp.json` or projec | Tool | Description | |------|-------------| -| `wireless_list_wireless_lans` | List wireless LANs | -| `wireless_create_wireless_lan` | Create a WLAN | -| `wireless_list_wireless_links` | List wireless links | +| `wlan_list_lans` | List wireless LANs | +| `wlan_create_lan` | Create a WLAN | +| `wlan_list_links` | List wireless links | | ... and more | ### Extras diff --git a/mcp-servers/netbox/mcp_server/config.py b/mcp-servers/netbox/mcp_server/config.py index 584025e..9aafd7b 100644 --- a/mcp-servers/netbox/mcp_server/config.py +++ b/mcp-servers/netbox/mcp_server/config.py @@ -9,11 +9,17 @@ from pathlib import Path from dotenv import load_dotenv import os import logging -from typing import Dict, Optional +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""" @@ -23,6 +29,7 @@ class NetBoxConfig: 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]: """ @@ -73,6 +80,9 @@ class NetBoxConfig: 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() @@ -84,7 +94,8 @@ class NetBoxConfig: 'api_url': self.api_url, 'api_token': self.api_token, 'verify_ssl': self.verify_ssl, - 'timeout': self.timeout + 'timeout': self.timeout, + 'enabled_modules': self.enabled_modules } def _validate(self) -> None: @@ -106,3 +117,40 @@ class NetBoxConfig: 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 diff --git a/mcp-servers/netbox/mcp_server/server.py b/mcp-servers/netbox/mcp_server/server.py index 95e0238..9d6cd0b 100644 --- a/mcp-servers/netbox/mcp_server/server.py +++ b/mcp-servers/netbox/mcp_server/server.py @@ -8,11 +8,12 @@ Tenancy, VPN, Wireless, and Extras. import asyncio import logging import json +from typing import Optional, Set from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent -from .config import NetBoxConfig +from .config import NetBoxConfig, ALL_MODULES from .netbox_client import NetBoxClient from .tools.dcim import DCIMTools from .tools.ipam import IPAMTools @@ -1453,6 +1454,49 @@ TOOL_NAME_MAP = { } +# Map tool name prefixes to module names. +# This handles both full prefixes and shortened prefixes used in TOOL_NAME_MAP. +PREFIX_TO_MODULE = { + 'dcim': 'dcim', + 'ipam': 'ipam', + 'circuits': 'circuits', + 'circ': 'circuits', # Shortened prefix + 'virtualization': 'virtualization', + 'virt': 'virtualization', # Shortened prefix + 'tenancy': 'tenancy', + 'vpn': 'vpn', + 'wireless': 'wireless', + 'wlan': 'wireless', # Shortened prefix + 'extras': 'extras', +} + + +def _get_tool_module(tool_name: str) -> Optional[str]: + """ + Determine which module a tool belongs to. + + Checks TOOL_NAME_MAP first for shortened names, then falls back to prefix extraction. + + Args: + tool_name: The tool name (e.g., 'dcim_list_devices', 'virt_list_vms') + + Returns: + Module name (e.g., 'dcim', 'virtualization') or None if unknown + """ + # Check mapped short names first + if tool_name in TOOL_NAME_MAP: + category, _ = TOOL_NAME_MAP[tool_name] + return category + + # Fall back to prefix extraction + parts = tool_name.split('_', 1) + if len(parts) < 2: + return None + + prefix = parts[0] + return PREFIX_TO_MODULE.get(prefix) + + class NetBoxMCPServer: """MCP Server for NetBox integration""" @@ -1460,6 +1504,8 @@ class NetBoxMCPServer: self.server = Server("netbox-mcp") self.config = None self.client = None + self.enabled_modules: Set[str] = set(ALL_MODULES) + # Tool instances - only instantiated for enabled modules self.dcim_tools = None self.ipam_tools = None self.circuits_tools = None @@ -1474,18 +1520,39 @@ class NetBoxMCPServer: try: config_loader = NetBoxConfig() self.config = config_loader.load() + self.enabled_modules = self.config['enabled_modules'] self.client = NetBoxClient() - self.dcim_tools = DCIMTools(self.client) - self.ipam_tools = IPAMTools(self.client) - self.circuits_tools = CircuitsTools(self.client) - self.virtualization_tools = VirtualizationTools(self.client) - self.tenancy_tools = TenancyTools(self.client) - self.vpn_tools = VPNTools(self.client) - self.wireless_tools = WirelessTools(self.client) - self.extras_tools = ExtrasTools(self.client) - logger.info(f"NetBox MCP Server initialized for {self.config['api_url']}") + # Conditionally instantiate tool classes for enabled modules only + if 'dcim' in self.enabled_modules: + self.dcim_tools = DCIMTools(self.client) + if 'ipam' in self.enabled_modules: + self.ipam_tools = IPAMTools(self.client) + if 'circuits' in self.enabled_modules: + self.circuits_tools = CircuitsTools(self.client) + if 'virtualization' in self.enabled_modules: + self.virtualization_tools = VirtualizationTools(self.client) + if 'tenancy' in self.enabled_modules: + self.tenancy_tools = TenancyTools(self.client) + if 'vpn' in self.enabled_modules: + self.vpn_tools = VPNTools(self.client) + if 'wireless' in self.enabled_modules: + self.wireless_tools = WirelessTools(self.client) + if 'extras' in self.enabled_modules: + self.extras_tools = ExtrasTools(self.client) + + # Count tools that will be registered + tool_count = sum( + 1 for name in TOOL_DEFINITIONS + if _get_tool_module(name) in self.enabled_modules + ) + + modules_str = ', '.join(sorted(self.enabled_modules)) + logger.info( + f"NetBox MCP Server initialized: {tool_count} tools registered " + f"(modules: {modules_str})" + ) except Exception as e: logger.error(f"Failed to initialize: {e}") raise @@ -1495,9 +1562,14 @@ class NetBoxMCPServer: @self.server.list_tools() async def list_tools() -> list[Tool]: - """Return list of available tools""" + """Return list of available tools, filtered by enabled modules""" tools = [] for name, definition in TOOL_DEFINITIONS.items(): + # Filter tools by enabled modules + module = _get_tool_module(name) + if module not in self.enabled_modules: + continue + tools.append(Tool( name=name, description=definition['description'], @@ -1532,6 +1604,14 @@ class NetBoxMCPServer: 'virtualization_list_virtual_machines') to meet the 28-character limit. TOOL_NAME_MAP handles the translation to actual method names. """ + # Check module is enabled (routing guard) + module = _get_tool_module(name) + if module and module not in self.enabled_modules: + raise ValueError( + f"Tool '{name}' is not available (module '{module}' not enabled). " + f"Enabled modules: {', '.join(sorted(self.enabled_modules))}" + ) + # Check if this is a mapped short name if name in TOOL_NAME_MAP: category, method_name = TOOL_NAME_MAP[name] diff --git a/plugins/cmdb-assistant/claude-md-integration.md b/plugins/cmdb-assistant/claude-md-integration.md index 3342c65..d0e9b39 100644 --- a/plugins/cmdb-assistant/claude-md-integration.md +++ b/plugins/cmdb-assistant/claude-md-integration.md @@ -32,9 +32,9 @@ The following NetBox MCP tools are available for infrastructure management: - `ipam_list_available_ips`, `ipam_create_available_ip` - IP allocation **Virtualization:** -- `virtualization_list_virtual_machines`, `virtualization_create_virtual_machine` - VM management -- `virtualization_list_clusters`, `virtualization_create_cluster` - Cluster management -- `virtualization_list_vm_interfaces` - VM interface management +- `virt_list_vms`, `virt_create_vm`, `virt_update_vm`, `virt_delete_vm` - VM management +- `virt_list_clusters`, `virt_create_cluster`, `virt_update_cluster`, `virt_delete_cluster` - Cluster management +- `virt_list_vm_ifaces`, `virt_create_vm_iface` - VM interface management **Circuits:** - `circuits_list_circuits`, `circuits_create_circuit` - Circuit management diff --git a/plugins/cmdb-assistant/commands/cmdb-search.md b/plugins/cmdb-assistant/commands/cmdb-search.md index b1ac8b2..56d3787 100644 --- a/plugins/cmdb-assistant/commands/cmdb-search.md +++ b/plugins/cmdb-assistant/commands/cmdb-search.md @@ -31,7 +31,7 @@ When the user provides a search query, determine the best approach: 3. **Site search**: Use `dcim_list_sites` with name filter 4. **Prefix search**: Use `ipam_list_prefixes` with prefix or within filter 5. **VLAN search**: Use `ipam_list_vlans` with vid or name filter -6. **VM search**: Use `virtualization_list_virtual_machines` with name filter +6. **VM search**: Use `virt_list_vms` with name filter For broad searches, query multiple endpoints and consolidate results. -- 2.49.1