development #412

Merged
lmiranda merged 2 commits from development into main 2026-02-03 17:12:10 +00:00
6 changed files with 250 additions and 26 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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]

View File

@@ -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

View File

@@ -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.