From 8129be5ef33127c8cc85da32ceb14b130413747d Mon Sep 17 00:00:00 2001 From: lmiranda Date: Sun, 8 Feb 2026 21:25:43 -0500 Subject: [PATCH] feat(netbox)!: gut server from 182 to 37 tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: Removed circuits, tenancy, VPN, wireless modules entirely. Stripped DCIM, IPAM, virtualization, extras to only essential tools. Deleted NETBOX_ENABLED_MODULES filtering — no longer needed. - Delete circuits.py, tenancy.py, vpn.py, wireless.py - Strip dcim.py to sites, devices, interfaces only (11 tools) - Strip ipam.py to IPs, prefixes, services only (10 tools) - Strip virtualization.py to clusters, VMs, VM interfaces only (10 tools) - Strip extras.py to tags, journal entries only (6 tools) - Remove all module filtering code from config.py and server.py - Rewrite README.md with accurate 37-tool documentation - Update CHANGELOG.md with breaking change entry - Token reduction: ~19,810 → ~3,700 (~81%) Remaining work: Update cmdb-assistant plugin skills (10 files) in follow-up PR Co-Authored-By: Claude Sonnet 4.5 --- CHANGELOG.md | 31 + mcp-servers/netbox/README.md | 471 +++--- mcp-servers/netbox/mcp_server/config.py | 52 +- mcp-servers/netbox/mcp_server/server.py | 1380 ++--------------- .../netbox/mcp_server/tools/__init__.py | 8 - .../netbox/mcp_server/tools/circuits.py | 373 ----- mcp-servers/netbox/mcp_server/tools/dcim.py | 748 +-------- mcp-servers/netbox/mcp_server/tools/extras.py | 475 +----- mcp-servers/netbox/mcp_server/tools/ipam.py | 543 +------ .../netbox/mcp_server/tools/tenancy.py | 281 ---- .../netbox/mcp_server/tools/virtualization.py | 136 +- mcp-servers/netbox/mcp_server/tools/vpn.py | 428 ----- .../netbox/mcp_server/tools/wireless.py | 166 -- 13 files changed, 317 insertions(+), 4775 deletions(-) delete mode 100644 mcp-servers/netbox/mcp_server/tools/circuits.py delete mode 100644 mcp-servers/netbox/mcp_server/tools/tenancy.py delete mode 100644 mcp-servers/netbox/mcp_server/tools/vpn.py delete mode 100644 mcp-servers/netbox/mcp_server/tools/wireless.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ec9ecb..b23d73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Changed — BREAKING + +#### NetBox MCP Server: Gutted to 37 Tools (from 182) + +Removed all tools not needed for tracking applications, services, databases, and VPS/servers. + +**Deleted modules (entire files removed):** +- `circuits.py` — providers, circuits, terminations +- `tenancy.py` — tenants, contacts +- `vpn.py` — tunnels, IKE/IPSec, L2VPN +- `wireless.py` — WLANs, links, groups + +**Deleted tool categories within remaining modules:** +- DCIM: regions, locations, racks, rack roles, manufacturers, device types, platforms, cables, console ports, front/rear ports, module bays, power panels, power feeds, virtual chassis, inventory items +- IPAM: VLANs, VLAN groups, VRFs, ASNs, RIRs, aggregates, available IPs, available prefixes +- Virtualization: cluster types, cluster groups, all delete operations +- Extras: custom fields, webhooks, config contexts, update/delete for tags + +**Deleted infrastructure:** +- `NETBOX_ENABLED_MODULES` env var and all module filtering code +- `ALL_MODULES` constant, `PREFIX_TO_MODULE` dict, `_get_tool_module()` function +- Conditional module instantiation in server.py + +**Token impact:** ~19,810 → ~3,700 tokens (~81% reduction) + +**Remaining tools (37):** +- DCIM: sites (4), devices (4), interfaces (3) = 11 +- IPAM: IPs (4), prefixes (3), services (3) = 10 +- Virtualization: clusters (3), VMs (4), VM interfaces (3) = 10 +- Extras: tags (3), journal entries (3) = 6 + ### Added - **All plugins:** Dispatch files now active command handlers — bare `/noun` shows available sub-commands and prompts for selection instead of doing nothing diff --git a/mcp-servers/netbox/README.md b/mcp-servers/netbox/README.md index a09dd22..1cb0756 100644 --- a/mcp-servers/netbox/README.md +++ b/mcp-servers/netbox/README.md @@ -1,19 +1,17 @@ # NetBox MCP Server -MCP (Model Context Protocol) server for comprehensive NetBox API integration with Claude Code. +MCP (Model Context Protocol) server for essential NetBox API integration with Claude Code. ## Overview -This MCP server provides Claude Code with full access to the NetBox REST API, enabling infrastructure management, documentation, and automation workflows. It covers all major NetBox application areas: +This MCP server provides Claude Code with focused access to the NetBox REST API for tracking **servers, services, IP addresses, and databases**. It has been optimized to include only essential tools: -- **DCIM** - Sites, Locations, Racks, Devices, Interfaces, Cables, Power -- **IPAM** - IP Addresses, Prefixes, VLANs, VRFs, ASNs, Services -- **Circuits** - Providers, Circuits, Terminations +- **DCIM** - Sites, Devices (servers/VPS), Interfaces +- **IPAM** - IP Addresses, Prefixes, Services (applications/databases) - **Virtualization** - Clusters, Virtual Machines, VM Interfaces -- **Tenancy** - Tenants, Contacts, Contact Assignments -- **VPN** - Tunnels, IKE/IPSec Policies, L2VPN -- **Wireless** - Wireless LANs, Links, Groups -- **Extras** - Tags, Custom Fields, Webhooks, Config Contexts, Audit Log +- **Extras** - Tags, Journal Entries (audit/notes) + +**Total:** 37 tools (~3,700 tokens) — down from 182 tools (~19,810 tokens). ## Installation @@ -49,312 +47,227 @@ EOF ### 3. Register with Claude Code -Add to your Claude Code MCP configuration (`~/.config/claude/mcp.json` or project `.mcp.json`): +Add to your Claude Code MCP configuration (`.claude/mcp.json` or project-level `.mcp.json`): ```json { "mcpServers": { "netbox": { "command": "/path/to/mcp-servers/netbox/.venv/bin/python", - "args": ["-m", "mcp_server.server"], + "args": ["-m", "mcp_server"], "cwd": "/path/to/mcp-servers/netbox" } } } ``` +**Windows:** +```json +{ + "mcpServers": { + "netbox": { + "command": "C:\\path\\to\\mcp-servers\\netbox\\.venv\\Scripts\\python.exe", + "args": ["-m", "mcp_server"], + "cwd": "C:\\path\\to\\mcp-servers\\netbox" + } + } +} +``` + +## Available Tools (37 Total) + +### DCIM: Sites, Devices, Interfaces (11 tools) + +**Sites (4):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `dcim_list_sites` | List sites | `name`, `status` | +| `dcim_get_site` | Get site by ID | `id` (required) | +| `dcim_create_site` | Create site | `name`, `slug` (required), `status` | +| `dcim_update_site` | Update site | `id` (required), fields to update | + +**Devices (4):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `dcim_list_devices` | List devices (servers/VPS) | `name`, `site_id`, `status`, `role_id` | +| `dcim_get_device` | Get device by ID | `id` (required) | +| `dcim_create_device` | Create device | `name`, `device_type`, `role`, `site` (required) | +| `dcim_update_device` | Update device | `id` (required), fields to update | + +**Interfaces (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `dcim_list_interfaces` | List device interfaces | `device_id`, `name`, `type` | +| `dcim_get_interface` | Get interface by ID | `id` (required) | +| `dcim_create_interface` | Create interface | `device`, `name`, `type` (required) | + +### IPAM: IPs, Prefixes, Services (10 tools) + +**IP Addresses (4):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `ipam_list_ip_addresses` | List IP addresses | `address`, `device_id`, `status` | +| `ipam_get_ip_address` | Get IP by ID | `id` (required) | +| `ipam_create_ip_address` | Create IP address | `address` (required), `status`, `assigned_object_type` | +| `ipam_update_ip_address` | Update IP address | `id` (required), fields to update | + +**Prefixes (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `ipam_list_prefixes` | List prefixes | `prefix`, `site_id`, `status` | +| `ipam_get_prefix` | Get prefix by ID | `id` (required) | +| `ipam_create_prefix` | Create prefix | `prefix` (required), `status`, `site` | + +**Services (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `ipam_list_services` | List services (apps/databases) | `device_id`, `virtual_machine_id`, `name` | +| `ipam_get_service` | Get service by ID | `id` (required) | +| `ipam_create_service` | Create service | `name`, `ports`, `protocol` (required), `device`, `virtual_machine` | + +### Virtualization: Clusters, VMs, VM Interfaces (10 tools) + +**Clusters (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `virt_list_clusters` | List virtualization clusters | `name`, `site_id` | +| `virt_get_cluster` | Get cluster by ID | `id` (required) | +| `virt_create_cluster` | Create cluster | `name`, `type` (required), `site` | + +**Virtual Machines (4):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `virt_list_vms` | List VMs | `name`, `cluster_id`, `site_id`, `status` | +| `virt_get_vm` | Get VM by ID | `id` (required) | +| `virt_create_vm` | Create VM | `name`, `cluster` (required), `vcpus`, `memory`, `disk` | +| `virt_update_vm` | Update VM | `id` (required), fields to update | + +**VM Interfaces (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `virt_list_vm_ifaces` | List VM interfaces | `virtual_machine_id` | +| `virt_get_vm_iface` | Get VM interface by ID | `id` (required) | +| `virt_create_vm_iface` | Create VM interface | `virtual_machine`, `name` (required) | + +### Extras: Tags, Journal Entries (6 tools) + +**Tags (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `extras_list_tags` | List tags | `name` | +| `extras_get_tag` | Get tag by ID | `id` (required) | +| `extras_create_tag` | Create tag | `name`, `slug` (required), `color` | + +**Journal Entries (3):** +| Tool | Description | Parameters | +|------|-------------|-----------| +| `extras_list_journal_entries` | List journal entries | `assigned_object_type`, `assigned_object_id` | +| `extras_get_journal_entry` | Get journal entry by ID | `id` (required) | +| `extras_create_journal_entry` | Create journal entry | `assigned_object_type`, `assigned_object_id`, `comments` (required), `kind` | + ## Configuration -### Environment Variables +All configuration is done via environment variables in `~/.config/claude/netbox.env`: | Variable | Required | Default | Description | |----------|----------|---------|-------------| -| `NETBOX_API_URL` | Yes | - | Full URL to NetBox API (e.g., `https://netbox.example.com/api`) | -| `NETBOX_API_TOKEN` | Yes | - | API authentication token | -| `NETBOX_VERIFY_SSL` | No | `true` | Verify SSL certificates | -| `NETBOX_TIMEOUT` | No | `30` | Request timeout in seconds | - -### Configuration Hierarchy - -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`, `/cmdb ip-conflicts`, `/cmdb search` | -| `virtualization` | ~20 | Clusters, VMs, VM interfaces | `/cmdb search`, `/cmdb audit`, `/cmdb register` | -| `extras` | ~12 | Tags, journal entries, audit log | `/cmdb 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) - -| Tool | Description | -|------|-------------| -| `dcim_list_sites` | List all sites | -| `dcim_get_site` | Get site details | -| `dcim_create_site` | Create a new site | -| `dcim_update_site` | Update a site | -| `dcim_delete_site` | Delete a site | -| `dcim_list_devices` | List all devices | -| `dcim_get_device` | Get device details | -| `dcim_create_device` | Create a new device | -| `dcim_update_device` | Update a device | -| `dcim_delete_device` | Delete a device | -| `dcim_list_interfaces` | List device interfaces | -| `dcim_create_interface` | Create an interface | -| `dcim_list_racks` | List all racks | -| `dcim_create_rack` | Create a new rack | -| `dcim_list_cables` | List all cables | -| `dcim_create_cable` | Create a cable connection | -| ... and many more | - -### IPAM (IP Address Management) - -| Tool | Description | -|------|-------------| -| `ipam_list_prefixes` | List IP prefixes | -| `ipam_create_prefix` | Create a prefix | -| `ipam_list_available_prefixes` | List available child prefixes | -| `ipam_create_available_prefix` | Auto-allocate a prefix | -| `ipam_list_ip_addresses` | List IP addresses | -| `ipam_create_ip_address` | Create an IP address | -| `ipam_list_available_ips` | List available IPs in prefix | -| `ipam_create_available_ip` | Auto-allocate an IP | -| `ipam_list_vlans` | List VLANs | -| `ipam_create_vlan` | Create a VLAN | -| `ipam_list_vrfs` | List VRFs | -| ... and many more | - -### Circuits - -| Tool | Description | -|------|-------------| -| `circuits_list_providers` | List circuit providers | -| `circuits_create_provider` | Create a provider | -| `circuits_list_circuits` | List circuits | -| `circuits_create_circuit` | Create a circuit | -| `circ_list_terminations` | List terminations | -| ... and more | - -### Virtualization - -| Tool | Description | -|------|-------------| -| `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 - -| Tool | Description | -|------|-------------| -| `tenancy_list_tenants` | List tenants | -| `tenancy_create_tenant` | Create a tenant | -| `tenancy_list_contacts` | List contacts | -| `tenancy_create_contact` | Create a contact | -| ... and more | - -### VPN - -| Tool | Description | -|------|-------------| -| `vpn_list_tunnels` | List VPN tunnels | -| `vpn_create_tunnel` | Create a tunnel | -| `vpn_list_l2vpns` | List L2VPNs | -| `vpn_list_ike_policies` | List IKE policies | -| `vpn_list_ipsec_policies` | List IPSec policies | -| ... and more | - -### Wireless - -| Tool | Description | -|------|-------------| -| `wlan_list_lans` | List wireless LANs | -| `wlan_create_lan` | Create a WLAN | -| `wlan_list_links` | List wireless links | -| ... and more | - -### Extras - -| Tool | Description | -|------|-------------| -| `extras_list_tags` | List all tags | -| `extras_create_tag` | Create a tag | -| `extras_list_custom_fields` | List custom fields | -| `extras_list_webhooks` | List webhooks | -| `extras_list_journal_entries` | List journal entries | -| `extras_create_journal_entry` | Create journal entry | -| `extras_list_object_changes` | View audit log | -| `extras_list_config_contexts` | List config contexts | -| ... and more | - -## Usage Examples - -### List all devices at a site - -``` -Use the dcim_list_devices tool with site_id filter to see all devices at site 5 -``` - -### Create a new prefix and allocate IPs - -``` -1. Use ipam_create_prefix to create 10.0.1.0/24 -2. Use ipam_list_available_ips with the prefix ID to see available addresses -3. Use ipam_create_available_ip to auto-allocate the next IP -``` - -### Document a new server - -``` -1. Use dcim_create_device to create the device -2. Use dcim_create_interface to add network interfaces -3. Use ipam_create_ip_address to assign IPs to interfaces -4. Use extras_create_journal_entry to add notes -``` - -### Audit recent changes - -``` -Use extras_list_object_changes to see recent modifications in NetBox -``` +| `NETBOX_API_URL` | Yes | — | NetBox API URL (e.g., https://netbox.example.com/api) | +| `NETBOX_API_TOKEN` | Yes | — | NetBox API token | +| `NETBOX_VERIFY_SSL` | No | true | Verify SSL certificates | +| `NETBOX_TIMEOUT` | No | 30 | Request timeout in seconds | ## Architecture +### Hybrid Configuration + +- **System-level:** `~/.config/claude/netbox.env` (credentials) +- **Project-level:** `.env` (optional overrides) + +### Tool Routing + +Tool names follow the pattern `{module}_{action}_{resource}`: +- `dcim_list_sites` → DCIMTools.list_sites() +- `ipam_create_service` → IPAMTools.create_service() +- `virt_list_vms` → VirtualizationTools.list_virtual_machines() + +Shortened names (virt_*) are mapped via TOOL_NAME_MAP to meet the 28-character MCP limit. + +### Error Handling + +All tools return JSON responses. Errors are caught and returned as: +```json +{ + "error": "Error message", + "status_code": 404 +} ``` -mcp-servers/netbox/ + +## Development + +### Testing + +```bash +# Test import +python -c "from mcp_server.server import NetBoxMCPServer; print('OK')" + +# Test tool count +python -c "from mcp_server.server import TOOL_DEFINITIONS; print(f'{len(TOOL_DEFINITIONS)} tools')" +``` + +### File Structure + +``` +netbox/ ├── mcp_server/ │ ├── __init__.py +│ ├── server.py # Main MCP server (37 TOOL_DEFINITIONS) │ ├── config.py # Configuration loader -│ ├── netbox_client.py # Generic HTTP client -│ ├── server.py # MCP server entry point +│ ├── netbox_client.py # HTTP client wrapper │ └── tools/ │ ├── __init__.py -│ ├── dcim.py # DCIM operations -│ ├── ipam.py # IPAM operations -│ ├── circuits.py # Circuits operations -│ ├── virtualization.py -│ ├── tenancy.py -│ ├── vpn.py -│ ├── wireless.py -│ └── extras.py -├── tests/ -│ └── __init__.py +│ ├── dcim.py # Sites, Devices, Interfaces +│ ├── ipam.py # IPs, Prefixes, Services +│ ├── virtualization.py # Clusters, VMs, VM Interfaces +│ └── extras.py # Tags, Journal Entries +├── .venv/ # Python virtual environment ├── requirements.txt └── README.md ``` -## API Coverage - -This MCP server provides comprehensive coverage of the NetBox REST API v4.x: - -- Full CRUD operations for all major models -- Filtering and search capabilities -- Special endpoints (available prefixes, available IPs) -- Pagination handling (automatic) -- Error handling with detailed messages - -## Error Handling - -The server returns detailed error messages from the NetBox API, including: -- Validation errors -- Authentication failures -- Not found errors -- Permission errors - -## Security Notes - -- API tokens should be kept secure and not committed to version control -- Use environment variables or the system config file for credentials -- SSL verification is enabled by default -- Consider using read-only tokens for query-only workflows - ## Troubleshooting -### Common Issues +### MCP Server Won't Start -1. **Connection refused**: Check `NETBOX_API_URL` is correct and accessible -2. **401 Unauthorized**: Verify your API token is valid -3. **SSL errors**: Set `NETBOX_VERIFY_SSL=false` for self-signed certs (not recommended for production) -4. **Timeout errors**: Increase `NETBOX_TIMEOUT` for slow connections - -### Debug Mode - -Enable debug logging: - -```python -import logging -logging.basicConfig(level=logging.DEBUG) +**Check configuration:** +```bash +cat ~/.config/claude/netbox.env ``` -## Contributing +**Test credentials:** +```bash +curl -H "Authorization: Token YOUR_TOKEN" https://netbox.example.com/api/ +``` -1. Follow the existing code patterns -2. Add tests for new functionality -3. Update documentation for new tools -4. Ensure compatibility with NetBox 4.x API +### Tools Not Appearing in Claude + +**Verify MCP registration:** +```bash +cat ~/.claude/mcp.json # or project-level .mcp.json +``` + +**Check MCP server logs:** +Claude Code will show MCP server stderr in the UI. + +### Connection Errors + +- Verify `NETBOX_API_URL` ends with `/api` +- Check firewall/network connectivity to NetBox instance +- Ensure API token has required permissions ## License -MIT License - Part of the Leo Claude Marketplace. +MIT License - See LICENSE file for details. + +## Contributing + +This MCP server is part of the leo-claude-mktplace project. For issues or contributions, refer to the main repository. diff --git a/mcp-servers/netbox/mcp_server/config.py b/mcp-servers/netbox/mcp_server/config.py index 9aafd7b..584025e 100644 --- a/mcp-servers/netbox/mcp_server/config.py +++ b/mcp-servers/netbox/mcp_server/config.py @@ -9,17 +9,11 @@ from pathlib import Path from dotenv import load_dotenv import os import logging -from typing import Dict, List, Optional, Set +from typing import Dict, Optional 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""" @@ -29,7 +23,6 @@ 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]: """ @@ -80,9 +73,6 @@ 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() @@ -94,8 +84,7 @@ class NetBoxConfig: 'api_url': self.api_url, 'api_token': self.api_token, 'verify_ssl': self.verify_ssl, - 'timeout': self.timeout, - 'enabled_modules': self.enabled_modules + 'timeout': self.timeout } def _validate(self) -> None: @@ -117,40 +106,3 @@ 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 9d6cd0b..0ba0307 100644 --- a/mcp-servers/netbox/mcp_server/server.py +++ b/mcp-servers/netbox/mcp_server/server.py @@ -1,27 +1,22 @@ """ MCP Server entry point for NetBox integration. -Provides comprehensive NetBox tools to Claude Code via JSON-RPC 2.0 over stdio. -Covers the entire NetBox REST API: DCIM, IPAM, Circuits, Virtualization, -Tenancy, VPN, Wireless, and Extras. +Provides essential NetBox tools to Claude Code via JSON-RPC 2.0 over stdio. +Covers DCIM, IPAM, Virtualization, and Extras modules. """ import asyncio import logging import json -from typing import Optional, Set +from typing import Optional from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent -from .config import NetBoxConfig, ALL_MODULES +from .config import NetBoxConfig from .netbox_client import NetBoxClient from .tools.dcim import DCIMTools from .tools.ipam import IPAMTools -from .tools.circuits import CircuitsTools from .tools.virtualization import VirtualizationTools -from .tools.tenancy import TenancyTools -from .tools.vpn import VPNTools -from .tools.wireless import WirelessTools from .tools.extras import ExtrasTools # Suppress noisy MCP validation warnings on stderr @@ -31,51 +26,14 @@ logging.getLogger("mcp").setLevel(logging.ERROR) logger = logging.getLogger(__name__) -# Tool definitions organized by category +# Tool definitions - 37 essential tools for tracking servers, services, IPs, and databases TOOL_DEFINITIONS = { - # ==================== DCIM Tools ==================== - 'dcim_list_regions': { - 'description': 'List all regions in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'slug': {'type': 'string', 'description': 'Filter by slug'} - } - }, - 'dcim_get_region': { - 'description': 'Get a specific region by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Region ID'}}, - 'required': ['id'] - }, - 'dcim_create_region': { - 'description': 'Create a new region', - 'properties': { - 'name': {'type': 'string', 'description': 'Region name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'parent': {'type': 'integer', 'description': 'Parent region ID'} - }, - 'required': ['name', 'slug'] - }, - 'dcim_update_region': { - 'description': 'Update an existing region', - 'properties': { - 'id': {'type': 'integer', 'description': 'Region ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'slug': {'type': 'string', 'description': 'New slug'} - }, - 'required': ['id'] - }, - 'dcim_delete_region': { - 'description': 'Delete a region', - 'properties': {'id': {'type': 'integer', 'description': 'Region ID'}}, - 'required': ['id'] - }, + # ==================== DCIM: Servers, Sites, Interfaces ==================== 'dcim_list_sites': { 'description': 'List all sites in NetBox', 'properties': { 'name': {'type': 'string', 'description': 'Filter by name'}, - 'status': {'type': 'string', 'description': 'Filter by status (active, planned, staging, decommissioning, retired)'}, - 'region_id': {'type': 'integer', 'description': 'Filter by region ID'}, - 'tenant_id': {'type': 'integer', 'description': 'Filter by tenant ID'} + 'status': {'type': 'string', 'description': 'Filter by status'}, } }, 'dcim_get_site': { @@ -88,14 +46,7 @@ TOOL_DEFINITIONS = { 'properties': { 'name': {'type': 'string', 'description': 'Site name'}, 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'status': {'type': 'string', 'description': 'Site status'}, - 'region': {'type': 'integer', 'description': 'Region ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'facility': {'type': 'string', 'description': 'Facility name'}, - 'time_zone': {'type': 'string', 'description': 'Time zone'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'physical_address': {'type': 'string', 'description': 'Physical address'}, - 'shipping_address': {'type': 'string', 'description': 'Shipping address'} + 'status': {'type': 'string', 'description': 'Status'}, }, 'required': ['name', 'slug'] }, @@ -104,269 +55,17 @@ TOOL_DEFINITIONS = { 'properties': { 'id': {'type': 'integer', 'description': 'Site ID'}, 'name': {'type': 'string', 'description': 'New name'}, - 'slug': {'type': 'string', 'description': 'New slug'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'region': {'type': 'integer', 'description': 'Region ID'}, - 'group': {'type': 'integer', 'description': 'Site group ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'facility': {'type': 'string', 'description': 'Facility name'}, - 'time_zone': {'type': 'string', 'description': 'Time zone'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'physical_address': {'type': 'string', 'description': 'Physical address'}, - 'shipping_address': {'type': 'string', 'description': 'Shipping address'}, - 'latitude': {'type': 'number', 'description': 'Latitude'}, - 'longitude': {'type': 'number', 'description': 'Longitude'}, - 'comments': {'type': 'string', 'description': 'Comments'} + 'status': {'type': 'string', 'description': 'New status'}, }, 'required': ['id'] }, - 'dcim_delete_site': { - 'description': 'Delete a site', - 'properties': {'id': {'type': 'integer', 'description': 'Site ID'}}, - 'required': ['id'] - }, - 'dcim_list_locations': { - 'description': 'List all locations in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'} - } - }, - 'dcim_get_location': { - 'description': 'Get a specific location by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Location ID'}}, - 'required': ['id'] - }, - 'dcim_create_location': { - 'description': 'Create a new location', - 'properties': { - 'name': {'type': 'string', 'description': 'Location name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'parent': {'type': 'integer', 'description': 'Parent location ID'} - }, - 'required': ['name', 'slug', 'site'] - }, - 'dcim_update_location': { - 'description': 'Update an existing location', - 'properties': { - 'id': {'type': 'integer', 'description': 'Location ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'slug': {'type': 'string', 'description': 'New slug'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'parent': {'type': 'integer', 'description': 'Parent location ID'}, - 'description': {'type': 'string', 'description': 'Description'} - }, - 'required': ['id'] - }, - 'dcim_delete_location': { - 'description': 'Delete a location', - 'properties': {'id': {'type': 'integer', 'description': 'Location ID'}}, - 'required': ['id'] - }, - 'dcim_list_racks': { - 'description': 'List all racks in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'}, - 'location_id': {'type': 'integer', 'description': 'Filter by location ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'dcim_get_rack': { - 'description': 'Get a specific rack by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Rack ID'}}, - 'required': ['id'] - }, - 'dcim_create_rack': { - 'description': 'Create a new rack', - 'properties': { - 'name': {'type': 'string', 'description': 'Rack name'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'location': {'type': 'integer', 'description': 'Location ID'}, - 'status': {'type': 'string', 'description': 'Rack status'}, - 'u_height': {'type': 'integer', 'description': 'Rack height in U'} - }, - 'required': ['name', 'site'] - }, - 'dcim_update_rack': { - 'description': 'Update an existing rack', - 'properties': { - 'id': {'type': 'integer', 'description': 'Rack ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'location': {'type': 'integer', 'description': 'Location ID'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'role': {'type': 'integer', 'description': 'Role ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'u_height': {'type': 'integer', 'description': 'Rack height in U'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} - }, - 'required': ['id'] - }, - 'dcim_delete_rack': { - 'description': 'Delete a rack', - 'properties': {'id': {'type': 'integer', 'description': 'Rack ID'}}, - 'required': ['id'] - }, - 'dcim_list_manufacturers': { - 'description': 'List all manufacturers in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'dcim_get_manufacturer': { - 'description': 'Get a specific manufacturer by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Manufacturer ID'}}, - 'required': ['id'] - }, - 'dcim_create_manufacturer': { - 'description': 'Create a new manufacturer', - 'properties': { - 'name': {'type': 'string', 'description': 'Manufacturer name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, - 'dcim_update_manufacturer': { - 'description': 'Update an existing manufacturer', - 'properties': { - 'id': {'type': 'integer', 'description': 'Manufacturer ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'slug': {'type': 'string', 'description': 'New slug'}, - 'description': {'type': 'string', 'description': 'Description'} - }, - 'required': ['id'] - }, - 'dcim_delete_manufacturer': { - 'description': 'Delete a manufacturer', - 'properties': {'id': {'type': 'integer', 'description': 'Manufacturer ID'}}, - 'required': ['id'] - }, - 'dcim_list_device_types': { - 'description': 'List all device types in NetBox', - 'properties': { - 'manufacturer_id': {'type': 'integer', 'description': 'Filter by manufacturer ID'}, - 'model': {'type': 'string', 'description': 'Filter by model name'} - } - }, - 'dcim_get_device_type': { - 'description': 'Get a specific device type by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Device type ID'}}, - 'required': ['id'] - }, - 'dcim_create_device_type': { - 'description': 'Create a new device type', - 'properties': { - 'manufacturer': {'type': 'integer', 'description': 'Manufacturer ID'}, - 'model': {'type': 'string', 'description': 'Model name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'u_height': {'type': 'number', 'description': 'Height in rack units'} - }, - 'required': ['manufacturer', 'model', 'slug'] - }, - 'dcim_update_device_type': { - 'description': 'Update an existing device type', - 'properties': { - 'id': {'type': 'integer', 'description': 'Device type ID'}, - 'manufacturer': {'type': 'integer', 'description': 'Manufacturer ID'}, - 'model': {'type': 'string', 'description': 'Model name'}, - 'slug': {'type': 'string', 'description': 'New slug'}, - 'u_height': {'type': 'number', 'description': 'Height in rack units'}, - 'is_full_depth': {'type': 'boolean', 'description': 'Is full depth'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} - }, - 'required': ['id'] - }, - 'dcim_delete_device_type': { - 'description': 'Delete a device type', - 'properties': {'id': {'type': 'integer', 'description': 'Device type ID'}}, - 'required': ['id'] - }, - 'dcim_list_device_roles': { - 'description': 'List all device roles in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'dcim_get_device_role': { - 'description': 'Get a specific device role by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Device role ID'}}, - 'required': ['id'] - }, - 'dcim_create_device_role': { - 'description': 'Create a new device role', - 'properties': { - 'name': {'type': 'string', 'description': 'Role name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'color': {'type': 'string', 'description': 'Hex color code'}, - 'vm_role': {'type': 'boolean', 'description': 'Can be assigned to VMs'} - }, - 'required': ['name', 'slug'] - }, - 'dcim_update_device_role': { - 'description': 'Update an existing device role', - 'properties': { - 'id': {'type': 'integer', 'description': 'Device role ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'slug': {'type': 'string', 'description': 'New slug'}, - 'color': {'type': 'string', 'description': 'Hex color code'}, - 'vm_role': {'type': 'boolean', 'description': 'Can be assigned to VMs'}, - 'description': {'type': 'string', 'description': 'Description'} - }, - 'required': ['id'] - }, - 'dcim_delete_device_role': { - 'description': 'Delete a device role', - 'properties': {'id': {'type': 'integer', 'description': 'Device role ID'}}, - 'required': ['id'] - }, - 'dcim_list_platforms': { - 'description': 'List all platforms in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'manufacturer_id': {'type': 'integer', 'description': 'Filter by manufacturer ID'} - } - }, - 'dcim_get_platform': { - 'description': 'Get a specific platform by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Platform ID'}}, - 'required': ['id'] - }, - 'dcim_create_platform': { - 'description': 'Create a new platform', - 'properties': { - 'name': {'type': 'string', 'description': 'Platform name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'manufacturer': {'type': 'integer', 'description': 'Manufacturer ID'} - }, - 'required': ['name', 'slug'] - }, - 'dcim_update_platform': { - 'description': 'Update an existing platform', - 'properties': { - 'id': {'type': 'integer', 'description': 'Platform ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'slug': {'type': 'string', 'description': 'New slug'}, - 'manufacturer': {'type': 'integer', 'description': 'Manufacturer ID'}, - 'description': {'type': 'string', 'description': 'Description'} - }, - 'required': ['id'] - }, - 'dcim_delete_platform': { - 'description': 'Delete a platform', - 'properties': {'id': {'type': 'integer', 'description': 'Platform ID'}}, - 'required': ['id'] - }, 'dcim_list_devices': { - 'description': 'List all devices in NetBox', + 'description': 'List all devices (servers/VPS) in NetBox', 'properties': { 'name': {'type': 'string', 'description': 'Filter by name'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'}, - 'rack_id': {'type': 'integer', 'description': 'Filter by rack ID'}, + 'site_id': {'type': 'integer', 'description': 'Filter by site'}, 'status': {'type': 'string', 'description': 'Filter by status'}, - 'role_id': {'type': 'integer', 'description': 'Filter by role ID'}, - 'device_type_id': {'type': 'integer', 'description': 'Filter by device type ID'}, - 'manufacturer_id': {'type': 'integer', 'description': 'Filter by manufacturer ID'}, - 'serial': {'type': 'string', 'description': 'Filter by serial number'} + 'role_id': {'type': 'integer', 'description': 'Filter by role'}, } }, 'dcim_get_device': { @@ -381,16 +80,7 @@ TOOL_DEFINITIONS = { 'device_type': {'type': 'integer', 'description': 'Device type ID'}, 'role': {'type': 'integer', 'description': 'Device role ID'}, 'site': {'type': 'integer', 'description': 'Site ID'}, - 'status': {'type': 'string', 'description': 'Device status'}, - 'rack': {'type': 'integer', 'description': 'Rack ID'}, - 'position': {'type': 'number', 'description': 'Position in rack'}, - 'serial': {'type': 'string', 'description': 'Serial number'}, - 'platform': {'type': 'integer', 'description': 'Platform ID'}, - 'primary_ip4': {'type': 'integer', 'description': 'Primary IPv4 address ID'}, - 'primary_ip6': {'type': 'integer', 'description': 'Primary IPv6 address ID'}, - 'asset_tag': {'type': 'string', 'description': 'Asset tag'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} + 'status': {'type': 'string', 'description': 'Status'}, }, 'required': ['name', 'device_type', 'role', 'site'] }, @@ -400,30 +90,15 @@ TOOL_DEFINITIONS = { 'id': {'type': 'integer', 'description': 'Device ID'}, 'name': {'type': 'string', 'description': 'New name'}, 'status': {'type': 'string', 'description': 'New status'}, - 'platform': {'type': 'integer', 'description': 'Platform ID'}, - 'primary_ip4': {'type': 'integer', 'description': 'Primary IPv4 address ID'}, - 'primary_ip6': {'type': 'integer', 'description': 'Primary IPv6 address ID'}, - 'serial': {'type': 'string', 'description': 'Serial number'}, - 'asset_tag': {'type': 'string', 'description': 'Asset tag'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'rack': {'type': 'integer', 'description': 'Rack ID'}, - 'position': {'type': 'number', 'description': 'Position in rack'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} }, 'required': ['id'] }, - 'dcim_delete_device': { - 'description': 'Delete a device', - 'properties': {'id': {'type': 'integer', 'description': 'Device ID'}}, - 'required': ['id'] - }, 'dcim_list_interfaces': { - 'description': 'List all device interfaces in NetBox', + 'description': 'List device interfaces', 'properties': { - 'device_id': {'type': 'integer', 'description': 'Filter by device ID'}, + 'device_id': {'type': 'integer', 'description': 'Filter by device'}, 'name': {'type': 'string', 'description': 'Filter by name'}, - 'type': {'type': 'string', 'description': 'Filter by interface type'} + 'type': {'type': 'string', 'description': 'Filter by type'}, } }, 'dcim_get_interface': { @@ -432,248 +107,22 @@ TOOL_DEFINITIONS = { 'required': ['id'] }, 'dcim_create_interface': { - 'description': 'Create a new device interface', + 'description': 'Create a new interface on a device', 'properties': { 'device': {'type': 'integer', 'description': 'Device ID'}, 'name': {'type': 'string', 'description': 'Interface name'}, - 'type': {'type': 'string', 'description': 'Interface type (e.g., 1000base-t, 10gbase-x-sfpp)'}, - 'enabled': {'type': 'boolean', 'description': 'Interface enabled'}, - 'mac_address': {'type': 'string', 'description': 'MAC address'} + 'type': {'type': 'string', 'description': 'Interface type'}, }, 'required': ['device', 'name', 'type'] }, - 'dcim_update_interface': { - 'description': 'Update an existing interface', - 'properties': { - 'id': {'type': 'integer', 'description': 'Interface ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'type': {'type': 'string', 'description': 'Interface type'}, - 'enabled': {'type': 'boolean', 'description': 'Interface enabled'}, - 'mtu': {'type': 'integer', 'description': 'MTU'}, - 'mac_address': {'type': 'string', 'description': 'MAC address'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'mode': {'type': 'string', 'description': 'VLAN mode'}, - 'untagged_vlan': {'type': 'integer', 'description': 'Untagged VLAN ID'}, - 'tagged_vlans': {'type': 'array', 'description': 'Tagged VLAN IDs'} - }, - 'required': ['id'] - }, - 'dcim_delete_interface': { - 'description': 'Delete an interface', - 'properties': {'id': {'type': 'integer', 'description': 'Interface ID'}}, - 'required': ['id'] - }, - 'dcim_list_cables': { - 'description': 'List all cables in NetBox', - 'properties': { - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'}, - 'device_id': {'type': 'integer', 'description': 'Filter by device ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'dcim_get_cable': { - 'description': 'Get a specific cable by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Cable ID'}}, - 'required': ['id'] - }, - 'dcim_create_cable': { - 'description': 'Create a new cable connection', - 'properties': { - 'a_terminations': {'type': 'array', 'description': 'A-side terminations [{object_type, object_id}]'}, - 'b_terminations': {'type': 'array', 'description': 'B-side terminations [{object_type, object_id}]'}, - 'type': {'type': 'string', 'description': 'Cable type'}, - 'status': {'type': 'string', 'description': 'Cable status'}, - 'label': {'type': 'string', 'description': 'Cable label'} - }, - 'required': ['a_terminations', 'b_terminations'] - }, - 'dcim_update_cable': { - 'description': 'Update an existing cable', - 'properties': { - 'id': {'type': 'integer', 'description': 'Cable ID'}, - 'type': {'type': 'string', 'description': 'Cable type'}, - 'status': {'type': 'string', 'description': 'Cable status'}, - 'label': {'type': 'string', 'description': 'Cable label'}, - 'color': {'type': 'string', 'description': 'Cable color'}, - 'length': {'type': 'number', 'description': 'Cable length'}, - 'length_unit': {'type': 'string', 'description': 'Length unit'} - }, - 'required': ['id'] - }, - 'dcim_delete_cable': { - 'description': 'Delete a cable', - 'properties': {'id': {'type': 'integer', 'description': 'Cable ID'}}, - 'required': ['id'] - }, - 'dcim_list_power_panels': { - 'description': 'List all power panels in NetBox', - 'properties': {'site_id': {'type': 'integer', 'description': 'Filter by site ID'}} - }, - 'dcim_get_power_panel': { - 'description': 'Get a specific power panel by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Power panel ID'}}, - 'required': ['id'] - }, - 'dcim_create_power_panel': { - 'description': 'Create a new power panel', - 'properties': { - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'name': {'type': 'string', 'description': 'Panel name'}, - 'location': {'type': 'integer', 'description': 'Location ID'} - }, - 'required': ['site', 'name'] - }, - 'dcim_list_power_feeds': { - 'description': 'List all power feeds in NetBox', - 'properties': {'power_panel_id': {'type': 'integer', 'description': 'Filter by power panel ID'}} - }, - 'dcim_get_power_feed': { - 'description': 'Get a specific power feed by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Power feed ID'}}, - 'required': ['id'] - }, - 'dcim_create_power_feed': { - 'description': 'Create a new power feed', - 'properties': { - 'power_panel': {'type': 'integer', 'description': 'Power panel ID'}, - 'name': {'type': 'string', 'description': 'Feed name'}, - 'voltage': {'type': 'integer', 'description': 'Voltage'}, - 'amperage': {'type': 'integer', 'description': 'Amperage'} - }, - 'required': ['power_panel', 'name'] - }, - 'dcim_list_virtual_chassis': { - 'description': 'List all virtual chassis in NetBox', - 'properties': {} - }, - 'dcim_get_virtual_chassis': { - 'description': 'Get a specific virtual chassis by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Virtual chassis ID'}}, - 'required': ['id'] - }, - 'dcim_list_inventory_items': { - 'description': 'List all inventory items in NetBox', - 'properties': {'device_id': {'type': 'integer', 'description': 'Filter by device ID'}} - }, - 'dcim_get_inventory_item': { - 'description': 'Get a specific inventory item by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Inventory item ID'}}, - 'required': ['id'] - }, - # ==================== IPAM Tools ==================== - 'ipam_list_vrfs': { - 'description': 'List all VRFs in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'rd': {'type': 'string', 'description': 'Filter by route distinguisher'} - } - }, - 'ipam_get_vrf': { - 'description': 'Get a specific VRF by ID', - 'properties': {'id': {'type': 'integer', 'description': 'VRF ID'}}, - 'required': ['id'] - }, - 'ipam_create_vrf': { - 'description': 'Create a new VRF', - 'properties': { - 'name': {'type': 'string', 'description': 'VRF name'}, - 'rd': {'type': 'string', 'description': 'Route distinguisher'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'} - }, - 'required': ['name'] - }, - 'ipam_update_vrf': { - 'description': 'Update an existing VRF', - 'properties': { - 'id': {'type': 'integer', 'description': 'VRF ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'rd': {'type': 'string', 'description': 'Route distinguisher'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'enforce_unique': {'type': 'boolean', 'description': 'Enforce unique IPs'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} - }, - 'required': ['id'] - }, - 'ipam_delete_vrf': { - 'description': 'Delete a VRF', - 'properties': {'id': {'type': 'integer', 'description': 'VRF ID'}}, - 'required': ['id'] - }, - 'ipam_list_prefixes': { - 'description': 'List all IP prefixes in NetBox', - 'properties': { - 'prefix': {'type': 'string', 'description': 'Filter by prefix (CIDR)'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'}, - 'vrf_id': {'type': 'integer', 'description': 'Filter by VRF ID'}, - 'vlan_id': {'type': 'integer', 'description': 'Filter by VLAN ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'}, - 'within': {'type': 'string', 'description': 'Find prefixes within this prefix'} - } - }, - 'ipam_get_prefix': { - 'description': 'Get a specific prefix by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Prefix ID'}}, - 'required': ['id'] - }, - 'ipam_create_prefix': { - 'description': 'Create a new IP prefix', - 'properties': { - 'prefix': {'type': 'string', 'description': 'Prefix in CIDR notation'}, - 'status': {'type': 'string', 'description': 'Status (active, container, reserved, deprecated)'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'vrf': {'type': 'integer', 'description': 'VRF ID'}, - 'vlan': {'type': 'integer', 'description': 'VLAN ID'}, - 'role': {'type': 'integer', 'description': 'Role ID'}, - 'is_pool': {'type': 'boolean', 'description': 'Is a pool'} - }, - 'required': ['prefix'] - }, - 'ipam_update_prefix': { - 'description': 'Update an existing prefix', - 'properties': { - 'id': {'type': 'integer', 'description': 'Prefix ID'}, - 'prefix': {'type': 'string', 'description': 'Prefix in CIDR notation'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'vrf': {'type': 'integer', 'description': 'VRF ID'}, - 'vlan': {'type': 'integer', 'description': 'VLAN ID'}, - 'role': {'type': 'integer', 'description': 'Role ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'is_pool': {'type': 'boolean', 'description': 'Is a pool'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} - }, - 'required': ['id'] - }, - 'ipam_delete_prefix': { - 'description': 'Delete a prefix', - 'properties': {'id': {'type': 'integer', 'description': 'Prefix ID'}}, - 'required': ['id'] - }, - 'ipam_list_available_prefixes': { - 'description': 'List available child prefixes within a prefix', - 'properties': {'id': {'type': 'integer', 'description': 'Parent prefix ID'}}, - 'required': ['id'] - }, - 'ipam_create_available_prefix': { - 'description': 'Create a new prefix from available space', - 'properties': { - 'id': {'type': 'integer', 'description': 'Parent prefix ID'}, - 'prefix_length': {'type': 'integer', 'description': 'Desired prefix length'} - }, - 'required': ['id', 'prefix_length'] - }, + # ==================== IPAM: IPs, Prefixes, Services ==================== 'ipam_list_ip_addresses': { - 'description': 'List all IP addresses in NetBox', + 'description': 'List IP addresses', 'properties': { 'address': {'type': 'string', 'description': 'Filter by address'}, - 'vrf_id': {'type': 'integer', 'description': 'Filter by VRF ID'}, - 'device_id': {'type': 'integer', 'description': 'Filter by device ID'}, - 'virtual_machine_id': {'type': 'integer', 'description': 'Filter by VM ID'}, + 'device_id': {'type': 'integer', 'description': 'Filter by device'}, 'status': {'type': 'string', 'description': 'Filter by status'}, - 'dns_name': {'type': 'string', 'description': 'Filter by DNS name'} } }, 'ipam_get_ip_address': { @@ -682,169 +131,51 @@ TOOL_DEFINITIONS = { 'required': ['id'] }, 'ipam_create_ip_address': { - 'description': 'Create a new IP address', + 'description': 'Create an IP address', 'properties': { - 'address': {'type': 'string', 'description': 'IP address with prefix length'}, + 'address': {'type': 'string', 'description': 'IP address with prefix (e.g., 192.168.1.1/24)'}, 'status': {'type': 'string', 'description': 'Status'}, - 'vrf': {'type': 'integer', 'description': 'VRF ID'}, - 'dns_name': {'type': 'string', 'description': 'DNS name'}, - 'assigned_object_type': {'type': 'string', 'description': 'Object type to assign to'}, - 'assigned_object_id': {'type': 'integer', 'description': 'Object ID to assign to'} + 'assigned_object_type': {'type': 'string', 'description': 'Object type (dcim.interface or virtualization.vminterface)'}, + 'assigned_object_id': {'type': 'integer', 'description': 'Object ID'}, }, 'required': ['address'] }, 'ipam_update_ip_address': { - 'description': 'Update an existing IP address', + 'description': 'Update an IP address', 'properties': { 'id': {'type': 'integer', 'description': 'IP address ID'}, - 'address': {'type': 'string', 'description': 'IP address with prefix length'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'vrf': {'type': 'integer', 'description': 'VRF ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'dns_name': {'type': 'string', 'description': 'DNS name'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'}, - 'assigned_object_type': {'type': 'string', 'description': 'Object type to assign to'}, - 'assigned_object_id': {'type': 'integer', 'description': 'Object ID to assign to'} + 'status': {'type': 'string', 'description': 'New status'}, }, 'required': ['id'] }, - 'ipam_delete_ip_address': { - 'description': 'Delete an IP address', - 'properties': {'id': {'type': 'integer', 'description': 'IP address ID'}}, - 'required': ['id'] - }, - 'ipam_list_available_ips': { - 'description': 'List available IP addresses within a prefix', - 'properties': {'id': {'type': 'integer', 'description': 'Prefix ID'}}, - 'required': ['id'] - }, - 'ipam_create_available_ip': { - 'description': 'Create a new IP address from available space in prefix', - 'properties': {'id': {'type': 'integer', 'description': 'Prefix ID'}}, - 'required': ['id'] - }, - 'ipam_list_vlan_groups': { - 'description': 'List all VLAN groups in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'ipam_get_vlan_group': { - 'description': 'Get a specific VLAN group by ID', - 'properties': {'id': {'type': 'integer', 'description': 'VLAN group ID'}}, - 'required': ['id'] - }, - 'ipam_create_vlan_group': { - 'description': 'Create a new VLAN group', - 'properties': { - 'name': {'type': 'string', 'description': 'Group name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'min_vid': {'type': 'integer', 'description': 'Minimum VLAN ID'}, - 'max_vid': {'type': 'integer', 'description': 'Maximum VLAN ID'} - }, - 'required': ['name', 'slug'] - }, - 'ipam_list_vlans': { - 'description': 'List all VLANs in NetBox', - 'properties': { - 'vid': {'type': 'integer', 'description': 'Filter by VLAN ID'}, - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'}, - 'group_id': {'type': 'integer', 'description': 'Filter by VLAN group ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'ipam_get_vlan': { - 'description': 'Get a specific VLAN by ID', - 'properties': {'id': {'type': 'integer', 'description': 'VLAN ID'}}, - 'required': ['id'] - }, - 'ipam_create_vlan': { - 'description': 'Create a new VLAN', - 'properties': { - 'vid': {'type': 'integer', 'description': 'VLAN ID number'}, - 'name': {'type': 'string', 'description': 'VLAN name'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'group': {'type': 'integer', 'description': 'VLAN group ID'} - }, - 'required': ['vid', 'name'] - }, - 'ipam_update_vlan': { - 'description': 'Update an existing VLAN', - 'properties': { - 'id': {'type': 'integer', 'description': 'VLAN ID'}, - 'vid': {'type': 'integer', 'description': 'VLAN ID number'}, - 'name': {'type': 'string', 'description': 'VLAN name'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'group': {'type': 'integer', 'description': 'VLAN group ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'role': {'type': 'integer', 'description': 'Role ID'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} - }, - 'required': ['id'] - }, - 'ipam_delete_vlan': { - 'description': 'Delete a VLAN', - 'properties': {'id': {'type': 'integer', 'description': 'VLAN ID'}}, - 'required': ['id'] - }, - 'ipam_list_asns': { - 'description': 'List all ASNs in NetBox', - 'properties': { - 'asn': {'type': 'integer', 'description': 'Filter by ASN number'}, - 'rir_id': {'type': 'integer', 'description': 'Filter by RIR ID'} - } - }, - 'ipam_get_asn': { - 'description': 'Get a specific ASN by ID', - 'properties': {'id': {'type': 'integer', 'description': 'ASN ID'}}, - 'required': ['id'] - }, - 'ipam_create_asn': { - 'description': 'Create a new ASN', - 'properties': { - 'asn': {'type': 'integer', 'description': 'ASN number'}, - 'rir': {'type': 'integer', 'description': 'RIR ID'} - }, - 'required': ['asn', 'rir'] - }, - 'ipam_list_rirs': { - 'description': 'List all RIRs in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'ipam_get_rir': { - 'description': 'Get a specific RIR by ID', - 'properties': {'id': {'type': 'integer', 'description': 'RIR ID'}}, - 'required': ['id'] - }, - 'ipam_list_aggregates': { - 'description': 'List all aggregates in NetBox', + 'ipam_list_prefixes': { + 'description': 'List IP prefixes', 'properties': { 'prefix': {'type': 'string', 'description': 'Filter by prefix'}, - 'rir_id': {'type': 'integer', 'description': 'Filter by RIR ID'} + 'site_id': {'type': 'integer', 'description': 'Filter by site'}, + 'status': {'type': 'string', 'description': 'Filter by status'}, } }, - 'ipam_get_aggregate': { - 'description': 'Get a specific aggregate by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Aggregate ID'}}, + 'ipam_get_prefix': { + 'description': 'Get a specific prefix by ID', + 'properties': {'id': {'type': 'integer', 'description': 'Prefix ID'}}, 'required': ['id'] }, - 'ipam_create_aggregate': { - 'description': 'Create a new aggregate', + 'ipam_create_prefix': { + 'description': 'Create an IP prefix', 'properties': { - 'prefix': {'type': 'string', 'description': 'Prefix in CIDR notation'}, - 'rir': {'type': 'integer', 'description': 'RIR ID'} + 'prefix': {'type': 'string', 'description': 'IP prefix (e.g., 192.168.1.0/24)'}, + 'status': {'type': 'string', 'description': 'Status'}, + 'site': {'type': 'integer', 'description': 'Site ID'}, }, - 'required': ['prefix', 'rir'] + 'required': ['prefix'] }, 'ipam_list_services': { - 'description': 'List all services in NetBox', + 'description': 'List services (applications, databases, etc.)', 'properties': { - 'device_id': {'type': 'integer', 'description': 'Filter by device ID'}, - 'virtual_machine_id': {'type': 'integer', 'description': 'Filter by VM ID'}, - 'name': {'type': 'string', 'description': 'Filter by name'} + 'device_id': {'type': 'integer', 'description': 'Filter by device'}, + 'virtual_machine_id': {'type': 'integer', 'description': 'Filter by VM'}, + 'name': {'type': 'string', 'description': 'Filter by name'}, } }, 'ipam_get_service': { @@ -853,165 +184,23 @@ TOOL_DEFINITIONS = { 'required': ['id'] }, 'ipam_create_service': { - 'description': 'Create a new service', + 'description': 'Create a service (app, database, etc.)', 'properties': { 'name': {'type': 'string', 'description': 'Service name'}, - 'ports': {'type': 'array', 'description': 'List of ports'}, + 'ports': {'type': 'array', 'description': 'Port numbers', 'items': {'type': 'integer'}}, 'protocol': {'type': 'string', 'description': 'Protocol (tcp/udp)'}, - 'device': {'type': 'integer', 'description': 'Device ID'}, - 'virtual_machine': {'type': 'integer', 'description': 'VM ID'} + 'device': {'type': 'integer', 'description': 'Device ID (if on physical server)'}, + 'virtual_machine': {'type': 'integer', 'description': 'VM ID (if on VM)'}, }, 'required': ['name', 'ports', 'protocol'] }, - # ==================== Circuits Tools ==================== - 'circuits_list_providers': { - 'description': 'List all circuit providers in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'circuits_get_provider': { - 'description': 'Get a specific provider by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Provider ID'}}, - 'required': ['id'] - }, - 'circuits_create_provider': { - 'description': 'Create a new circuit provider', - 'properties': { - 'name': {'type': 'string', 'description': 'Provider name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, - 'circuits_update_provider': { - 'description': 'Update an existing provider', - 'properties': {'id': {'type': 'integer', 'description': 'Provider ID'}}, - 'required': ['id'] - }, - 'circuits_delete_provider': { - 'description': 'Delete a provider', - 'properties': {'id': {'type': 'integer', 'description': 'Provider ID'}}, - 'required': ['id'] - }, - # NOTE: circuit_types tools shortened to meet 28-char limit - 'circ_list_types': { - 'description': 'List all circuit types in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'circ_get_type': { - 'description': 'Get a specific circuit type by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Circuit type ID'}}, - 'required': ['id'] - }, - 'circ_create_type': { - 'description': 'Create a new circuit type', - 'properties': { - 'name': {'type': 'string', 'description': 'Type name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, - 'circuits_list_circuits': { - 'description': 'List all circuits in NetBox', - 'properties': { - 'cid': {'type': 'string', 'description': 'Filter by circuit ID'}, - 'provider_id': {'type': 'integer', 'description': 'Filter by provider ID'}, - 'type_id': {'type': 'integer', 'description': 'Filter by type ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'circuits_get_circuit': { - 'description': 'Get a specific circuit by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Circuit ID'}}, - 'required': ['id'] - }, - 'circuits_create_circuit': { - 'description': 'Create a new circuit', - 'properties': { - 'cid': {'type': 'string', 'description': 'Circuit ID'}, - 'provider': {'type': 'integer', 'description': 'Provider ID'}, - 'type': {'type': 'integer', 'description': 'Circuit type ID'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'} - }, - 'required': ['cid', 'provider', 'type'] - }, - 'circuits_update_circuit': { - 'description': 'Update an existing circuit', - 'properties': {'id': {'type': 'integer', 'description': 'Circuit ID'}}, - 'required': ['id'] - }, - 'circuits_delete_circuit': { - 'description': 'Delete a circuit', - 'properties': {'id': {'type': 'integer', 'description': 'Circuit ID'}}, - 'required': ['id'] - }, - # NOTE: circuit_terminations tools shortened to meet 28-char limit - 'circ_list_terminations': { - 'description': 'List all circuit terminations in NetBox', - 'properties': { - 'circuit_id': {'type': 'integer', 'description': 'Filter by circuit ID'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'} - } - }, - 'circ_get_termination': { - 'description': 'Get a specific circuit termination by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Termination ID'}}, - 'required': ['id'] - }, - 'circ_create_termination': { - 'description': 'Create a new circuit termination', - 'properties': { - 'circuit': {'type': 'integer', 'description': 'Circuit ID'}, - 'term_side': {'type': 'string', 'description': 'Termination side (A/Z)'}, - 'site': {'type': 'integer', 'description': 'Site ID'} - }, - 'required': ['circuit', 'term_side'] - }, - - # ==================== Virtualization Tools ==================== - # NOTE: Tool names shortened from 'virtualization_' to 'virt_' to meet - # 28-char limit (Claude API 64-char limit minus 36-char prefix) - 'virt_list_cluster_types': { - 'description': 'List all cluster types in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'virt_get_cluster_type': { - 'description': 'Get a specific cluster type by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Cluster type ID'}}, - 'required': ['id'] - }, - 'virt_create_cluster_type': { - 'description': 'Create a new cluster type', - 'properties': { - 'name': {'type': 'string', 'description': 'Type name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, - 'virt_list_cluster_groups': { - 'description': 'List all cluster groups in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'virt_get_cluster_group': { - 'description': 'Get a specific cluster group by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Cluster group ID'}}, - 'required': ['id'] - }, - 'virt_create_cluster_group': { - 'description': 'Create a new cluster group', - 'properties': { - 'name': {'type': 'string', 'description': 'Group name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, + # ==================== Virtualization: VMs, Clusters ==================== 'virt_list_clusters': { - 'description': 'List all clusters in NetBox', + 'description': 'List virtualization clusters', 'properties': { 'name': {'type': 'string', 'description': 'Filter by name'}, - 'type_id': {'type': 'integer', 'description': 'Filter by type ID'}, - 'group_id': {'type': 'integer', 'description': 'Filter by group ID'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'} + 'site_id': {'type': 'integer', 'description': 'Filter by site'}, } }, 'virt_get_cluster': { @@ -1020,300 +209,74 @@ TOOL_DEFINITIONS = { 'required': ['id'] }, 'virt_create_cluster': { - 'description': 'Create a new cluster', + 'description': 'Create a virtualization cluster', 'properties': { 'name': {'type': 'string', 'description': 'Cluster name'}, 'type': {'type': 'integer', 'description': 'Cluster type ID'}, - 'group': {'type': 'integer', 'description': 'Cluster group ID'}, 'site': {'type': 'integer', 'description': 'Site ID'}, - 'status': {'type': 'string', 'description': 'Status'} }, 'required': ['name', 'type'] }, - 'virt_update_cluster': { - 'description': 'Update an existing cluster', - 'properties': { - 'id': {'type': 'integer', 'description': 'Cluster ID'}, - 'name': {'type': 'string', 'description': 'New name'}, - 'type': {'type': 'integer', 'description': 'Cluster type ID'}, - 'group': {'type': 'integer', 'description': 'Cluster group ID'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} - }, - 'required': ['id'] - }, - 'virt_delete_cluster': { - 'description': 'Delete a cluster', - 'properties': {'id': {'type': 'integer', 'description': 'Cluster ID'}}, - 'required': ['id'] - }, 'virt_list_vms': { - 'description': 'List all virtual machines in NetBox', + 'description': 'List virtual machines', 'properties': { 'name': {'type': 'string', 'description': 'Filter by name'}, - 'cluster_id': {'type': 'integer', 'description': 'Filter by cluster ID'}, - 'site_id': {'type': 'integer', 'description': 'Filter by site ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} + 'cluster_id': {'type': 'integer', 'description': 'Filter by cluster'}, + 'site_id': {'type': 'integer', 'description': 'Filter by site'}, + 'status': {'type': 'string', 'description': 'Filter by status'}, } }, 'virt_get_vm': { - 'description': 'Get a specific virtual machine by ID', + 'description': 'Get a specific VM by ID', 'properties': {'id': {'type': 'integer', 'description': 'VM ID'}}, 'required': ['id'] }, 'virt_create_vm': { - 'description': 'Create a new virtual machine', + 'description': 'Create a virtual machine', 'properties': { 'name': {'type': 'string', 'description': 'VM name'}, 'cluster': {'type': 'integer', 'description': 'Cluster ID'}, 'status': {'type': 'string', 'description': 'Status'}, - 'role': {'type': 'integer', 'description': 'Role ID'}, - 'vcpus': {'type': 'number', 'description': 'Number of vCPUs'}, + 'vcpus': {'type': 'number', 'description': 'vCPU count'}, 'memory': {'type': 'integer', 'description': 'Memory in MB'}, - 'disk': {'type': 'integer', 'description': 'Disk in GB'} + 'disk': {'type': 'integer', 'description': 'Disk in GB'}, }, - 'required': ['name'] + 'required': ['name', 'cluster'] }, 'virt_update_vm': { - 'description': 'Update an existing virtual machine', + 'description': 'Update a virtual machine', 'properties': { 'id': {'type': 'integer', 'description': 'VM ID'}, 'name': {'type': 'string', 'description': 'New name'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'cluster': {'type': 'integer', 'description': 'Cluster ID'}, - 'site': {'type': 'integer', 'description': 'Site ID'}, - 'role': {'type': 'integer', 'description': 'Role ID'}, - 'tenant': {'type': 'integer', 'description': 'Tenant ID'}, - 'platform': {'type': 'integer', 'description': 'Platform ID'}, - 'vcpus': {'type': 'number', 'description': 'Number of vCPUs'}, - 'memory': {'type': 'integer', 'description': 'Memory in MB'}, - 'disk': {'type': 'integer', 'description': 'Disk in GB'}, - 'primary_ip4': {'type': 'integer', 'description': 'Primary IPv4 address ID'}, - 'primary_ip6': {'type': 'integer', 'description': 'Primary IPv6 address ID'}, - 'description': {'type': 'string', 'description': 'Description'}, - 'comments': {'type': 'string', 'description': 'Comments'} + 'status': {'type': 'string', 'description': 'New status'}, }, 'required': ['id'] }, - 'virt_delete_vm': { - 'description': 'Delete a virtual machine', - 'properties': {'id': {'type': 'integer', 'description': 'VM ID'}}, - 'required': ['id'] - }, 'virt_list_vm_ifaces': { - 'description': 'List all VM interfaces in NetBox', + 'description': 'List VM interfaces', 'properties': { - 'virtual_machine_id': {'type': 'integer', 'description': 'Filter by VM ID'}, - 'name': {'type': 'string', 'description': 'Filter by name'} + 'virtual_machine_id': {'type': 'integer', 'description': 'Filter by VM'}, } }, 'virt_get_vm_iface': { 'description': 'Get a specific VM interface by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Interface ID'}}, + 'properties': {'id': {'type': 'integer', 'description': 'VM interface ID'}}, 'required': ['id'] }, 'virt_create_vm_iface': { - 'description': 'Create a new VM interface', + 'description': 'Create a VM interface', 'properties': { 'virtual_machine': {'type': 'integer', 'description': 'VM ID'}, 'name': {'type': 'string', 'description': 'Interface name'}, - 'enabled': {'type': 'boolean', 'description': 'Enabled'} }, 'required': ['virtual_machine', 'name'] }, - # ==================== Tenancy Tools ==================== - 'tenancy_list_tenant_groups': { - 'description': 'List all tenant groups in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'tenancy_get_tenant_group': { - 'description': 'Get a specific tenant group by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Tenant group ID'}}, - 'required': ['id'] - }, - 'tenancy_create_tenant_group': { - 'description': 'Create a new tenant group', - 'properties': { - 'name': {'type': 'string', 'description': 'Group name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, - 'tenancy_list_tenants': { - 'description': 'List all tenants in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'group_id': {'type': 'integer', 'description': 'Filter by group ID'} - } - }, - 'tenancy_get_tenant': { - 'description': 'Get a specific tenant by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Tenant ID'}}, - 'required': ['id'] - }, - 'tenancy_create_tenant': { - 'description': 'Create a new tenant', - 'properties': { - 'name': {'type': 'string', 'description': 'Tenant name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'group': {'type': 'integer', 'description': 'Tenant group ID'} - }, - 'required': ['name', 'slug'] - }, - 'tenancy_update_tenant': { - 'description': 'Update an existing tenant', - 'properties': {'id': {'type': 'integer', 'description': 'Tenant ID'}}, - 'required': ['id'] - }, - 'tenancy_delete_tenant': { - 'description': 'Delete a tenant', - 'properties': {'id': {'type': 'integer', 'description': 'Tenant ID'}}, - 'required': ['id'] - }, - 'tenancy_list_contacts': { - 'description': 'List all contacts in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'email': {'type': 'string', 'description': 'Filter by email'} - } - }, - 'tenancy_get_contact': { - 'description': 'Get a specific contact by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Contact ID'}}, - 'required': ['id'] - }, - 'tenancy_create_contact': { - 'description': 'Create a new contact', - 'properties': { - 'name': {'type': 'string', 'description': 'Contact name'}, - 'email': {'type': 'string', 'description': 'Email address'}, - 'phone': {'type': 'string', 'description': 'Phone number'} - }, - 'required': ['name'] - }, - - # ==================== VPN Tools ==================== - 'vpn_list_tunnels': { - 'description': 'List all VPN tunnels in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'vpn_get_tunnel': { - 'description': 'Get a specific tunnel by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Tunnel ID'}}, - 'required': ['id'] - }, - 'vpn_create_tunnel': { - 'description': 'Create a new VPN tunnel', - 'properties': { - 'name': {'type': 'string', 'description': 'Tunnel name'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'encapsulation': {'type': 'string', 'description': 'Encapsulation type'} - }, - 'required': ['name'] - }, - 'vpn_list_l2vpns': { - 'description': 'List all L2VPNs in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'type': {'type': 'string', 'description': 'Filter by type'} - } - }, - 'vpn_get_l2vpn': { - 'description': 'Get a specific L2VPN by ID', - 'properties': {'id': {'type': 'integer', 'description': 'L2VPN ID'}}, - 'required': ['id'] - }, - 'vpn_create_l2vpn': { - 'description': 'Create a new L2VPN', - 'properties': { - 'name': {'type': 'string', 'description': 'L2VPN name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'type': {'type': 'string', 'description': 'Type'} - }, - 'required': ['name', 'slug', 'type'] - }, - 'vpn_list_ike_policies': { - 'description': 'List all IKE policies in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'vpn_list_ipsec_policies': { - 'description': 'List all IPSec policies in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'vpn_list_ipsec_profiles': { - 'description': 'List all IPSec profiles in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - - # ==================== Wireless Tools ==================== - # NOTE: Tool names shortened from 'wireless_' to 'wlan_' to meet - # 28-char limit (Claude API 64-char limit minus 36-char prefix) - 'wlan_list_groups': { - 'description': 'List all wireless LAN groups in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'wlan_get_group': { - 'description': 'Get a specific wireless LAN group by ID', - 'properties': {'id': {'type': 'integer', 'description': 'WLAN group ID'}}, - 'required': ['id'] - }, - 'wlan_create_group': { - 'description': 'Create a new wireless LAN group', - 'properties': { - 'name': {'type': 'string', 'description': 'Group name'}, - 'slug': {'type': 'string', 'description': 'URL-friendly slug'} - }, - 'required': ['name', 'slug'] - }, - 'wlan_list_lans': { - 'description': 'List all wireless LANs in NetBox', - 'properties': { - 'ssid': {'type': 'string', 'description': 'Filter by SSID'}, - 'group_id': {'type': 'integer', 'description': 'Filter by group ID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'wlan_get_lan': { - 'description': 'Get a specific wireless LAN by ID', - 'properties': {'id': {'type': 'integer', 'description': 'WLAN ID'}}, - 'required': ['id'] - }, - 'wlan_create_lan': { - 'description': 'Create a new wireless LAN', - 'properties': { - 'ssid': {'type': 'string', 'description': 'SSID'}, - 'status': {'type': 'string', 'description': 'Status'}, - 'group': {'type': 'integer', 'description': 'Group ID'}, - 'vlan': {'type': 'integer', 'description': 'VLAN ID'} - }, - 'required': ['ssid'] - }, - 'wlan_list_links': { - 'description': 'List all wireless links in NetBox', - 'properties': { - 'ssid': {'type': 'string', 'description': 'Filter by SSID'}, - 'status': {'type': 'string', 'description': 'Filter by status'} - } - }, - 'wlan_get_link': { - 'description': 'Get a specific wireless link by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Link ID'}}, - 'required': ['id'] - }, - - # ==================== Extras Tools ==================== + # ==================== Extras: Tags, Journal Entries ==================== 'extras_list_tags': { 'description': 'List all tags in NetBox', 'properties': { 'name': {'type': 'string', 'description': 'Filter by name'}, - 'slug': {'type': 'string', 'description': 'Filter by slug'} } }, 'extras_get_tag': { @@ -1326,177 +289,51 @@ TOOL_DEFINITIONS = { 'properties': { 'name': {'type': 'string', 'description': 'Tag name'}, 'slug': {'type': 'string', 'description': 'URL-friendly slug'}, - 'color': {'type': 'string', 'description': 'Hex color code'} + 'color': {'type': 'string', 'description': 'Hex color code'}, }, 'required': ['name', 'slug'] }, - 'extras_update_tag': { - 'description': 'Update an existing tag', - 'properties': {'id': {'type': 'integer', 'description': 'Tag ID'}}, - 'required': ['id'] - }, - 'extras_delete_tag': { - 'description': 'Delete a tag', - 'properties': {'id': {'type': 'integer', 'description': 'Tag ID'}}, - 'required': ['id'] - }, - 'extras_list_custom_fields': { - 'description': 'List all custom fields in NetBox', - 'properties': { - 'name': {'type': 'string', 'description': 'Filter by name'}, - 'type': {'type': 'string', 'description': 'Filter by type'} - } - }, - 'extras_get_custom_field': { - 'description': 'Get a specific custom field by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Custom field ID'}}, - 'required': ['id'] - }, - 'extras_list_webhooks': { - 'description': 'List all webhooks in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'extras_get_webhook': { - 'description': 'Get a specific webhook by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Webhook ID'}}, - 'required': ['id'] - }, 'extras_list_journal_entries': { - 'description': 'List all journal entries in NetBox', + 'description': 'List journal entries (audit/notes)', 'properties': { - 'assigned_object_type': {'type': 'string', 'description': 'Filter by object type'}, - 'assigned_object_id': {'type': 'integer', 'description': 'Filter by object ID'} + 'assigned_object_type': {'type': 'string', 'description': 'Object type'}, + 'assigned_object_id': {'type': 'integer', 'description': 'Object ID'}, } }, 'extras_get_journal_entry': { 'description': 'Get a specific journal entry by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Journal entry ID'}}, + 'properties': {'id': {'type': 'integer', 'description': 'Entry ID'}}, 'required': ['id'] }, 'extras_create_journal_entry': { - 'description': 'Create a new journal entry', + 'description': 'Create a journal entry', 'properties': { - 'assigned_object_type': {'type': 'string', 'description': 'Object type'}, + 'assigned_object_type': {'type': 'string', 'description': 'Object type (e.g., dcim.device)'}, 'assigned_object_id': {'type': 'integer', 'description': 'Object ID'}, - 'comments': {'type': 'string', 'description': 'Comments'}, - 'kind': {'type': 'string', 'description': 'Kind (info, success, warning, danger)'} + 'comments': {'type': 'string', 'description': 'Journal entry text'}, + 'kind': {'type': 'string', 'description': 'Kind (info, success, warning, danger)'}, }, 'required': ['assigned_object_type', 'assigned_object_id', 'comments'] }, - 'extras_list_config_contexts': { - 'description': 'List all config contexts in NetBox', - 'properties': {'name': {'type': 'string', 'description': 'Filter by name'}} - }, - 'extras_get_config_context': { - 'description': 'Get a specific config context by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Config context ID'}}, - 'required': ['id'] - }, - 'extras_list_object_changes': { - 'description': 'List all object changes (audit log) in NetBox', - 'properties': { - 'user_id': {'type': 'integer', 'description': 'Filter by user ID'}, - 'changed_object_type': {'type': 'string', 'description': 'Filter by object type'}, - 'action': {'type': 'string', 'description': 'Filter by action (create, update, delete)'} - } - }, - 'extras_get_object_change': { - 'description': 'Get a specific object change by ID', - 'properties': {'id': {'type': 'integer', 'description': 'Object change ID'}}, - 'required': ['id'] - }, } -# Map shortened tool names to (category, method_name) for routing. -# This is necessary because tool names were shortened to meet the 28-character -# limit imposed by Claude API's 64-character tool name limit minus the -# 36-character prefix used by Claude Code for MCP tools. +# Tool name mappings for shortened virtualization tools (virt_ prefix) TOOL_NAME_MAP = { - # Virtualization tools (virt_ -> virtualization category) - 'virt_list_cluster_types': ('virtualization', 'list_cluster_types'), - 'virt_get_cluster_type': ('virtualization', 'get_cluster_type'), - 'virt_create_cluster_type': ('virtualization', 'create_cluster_type'), - 'virt_list_cluster_groups': ('virtualization', 'list_cluster_groups'), - 'virt_get_cluster_group': ('virtualization', 'get_cluster_group'), - 'virt_create_cluster_group': ('virtualization', 'create_cluster_group'), + # Virtualization tools (virt_ prefix -> virtualization category) 'virt_list_clusters': ('virtualization', 'list_clusters'), 'virt_get_cluster': ('virtualization', 'get_cluster'), 'virt_create_cluster': ('virtualization', 'create_cluster'), - 'virt_update_cluster': ('virtualization', 'update_cluster'), - 'virt_delete_cluster': ('virtualization', 'delete_cluster'), 'virt_list_vms': ('virtualization', 'list_virtual_machines'), 'virt_get_vm': ('virtualization', 'get_virtual_machine'), 'virt_create_vm': ('virtualization', 'create_virtual_machine'), 'virt_update_vm': ('virtualization', 'update_virtual_machine'), - 'virt_delete_vm': ('virtualization', 'delete_virtual_machine'), 'virt_list_vm_ifaces': ('virtualization', 'list_vm_interfaces'), 'virt_get_vm_iface': ('virtualization', 'get_vm_interface'), 'virt_create_vm_iface': ('virtualization', 'create_vm_interface'), - - # Circuits tools (circ_ -> circuits category, for shortened names only) - 'circ_list_types': ('circuits', 'list_circuit_types'), - 'circ_get_type': ('circuits', 'get_circuit_type'), - 'circ_create_type': ('circuits', 'create_circuit_type'), - 'circ_list_terminations': ('circuits', 'list_circuit_terminations'), - 'circ_get_termination': ('circuits', 'get_circuit_termination'), - 'circ_create_termination': ('circuits', 'create_circuit_termination'), - - # Wireless tools (wlan_ -> wireless category) - 'wlan_list_groups': ('wireless', 'list_wireless_lan_groups'), - 'wlan_get_group': ('wireless', 'get_wireless_lan_group'), - 'wlan_create_group': ('wireless', 'create_wireless_lan_group'), - 'wlan_list_lans': ('wireless', 'list_wireless_lans'), - 'wlan_get_lan': ('wireless', 'get_wireless_lan'), - 'wlan_create_lan': ('wireless', 'create_wireless_lan'), - 'wlan_list_links': ('wireless', 'list_wireless_links'), - 'wlan_get_link': ('wireless', 'get_wireless_link'), } -# 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""" @@ -1504,15 +341,10 @@ 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 + # Tool instances - always instantiate all 4 modules self.dcim_tools = None self.ipam_tools = None - self.circuits_tools = None self.virtualization_tools = None - self.tenancy_tools = None - self.vpn_tools = None - self.wireless_tools = None self.extras_tools = None async def initialize(self): @@ -1520,38 +352,19 @@ class NetBoxMCPServer: try: config_loader = NetBoxConfig() self.config = config_loader.load() - self.enabled_modules = self.config['enabled_modules'] self.client = NetBoxClient() - # 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) + # Always instantiate all 4 modules + self.dcim_tools = DCIMTools(self.client) + self.ipam_tools = IPAMTools(self.client) + self.virtualization_tools = VirtualizationTools(self.client) + 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)) + tool_count = len(TOOL_DEFINITIONS) logger.info( f"NetBox MCP Server initialized: {tool_count} tools registered " - f"(modules: {modules_str})" + f"(modules: dcim, ipam, virtualization, extras)" ) except Exception as e: logger.error(f"Failed to initialize: {e}") @@ -1562,14 +375,9 @@ class NetBoxMCPServer: @self.server.list_tools() async def list_tools() -> list[Tool]: - """Return list of available tools, filtered by enabled modules""" + """Return list of available tools""" 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'], @@ -1604,14 +412,6 @@ 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] @@ -1626,11 +426,7 @@ class NetBoxMCPServer: tool_map = { 'dcim': self.dcim_tools, 'ipam': self.ipam_tools, - 'circuits': self.circuits_tools, 'virtualization': self.virtualization_tools, - 'tenancy': self.tenancy_tools, - 'vpn': self.vpn_tools, - 'wireless': self.wireless_tools, 'extras': self.extras_tools } diff --git a/mcp-servers/netbox/mcp_server/tools/__init__.py b/mcp-servers/netbox/mcp_server/tools/__init__.py index bdf9cc3..5d8a602 100644 --- a/mcp-servers/netbox/mcp_server/tools/__init__.py +++ b/mcp-servers/netbox/mcp_server/tools/__init__.py @@ -1,20 +1,12 @@ """NetBox MCP tools package.""" from .dcim import DCIMTools from .ipam import IPAMTools -from .circuits import CircuitsTools from .virtualization import VirtualizationTools -from .tenancy import TenancyTools -from .vpn import VPNTools -from .wireless import WirelessTools from .extras import ExtrasTools __all__ = [ 'DCIMTools', 'IPAMTools', - 'CircuitsTools', 'VirtualizationTools', - 'TenancyTools', - 'VPNTools', - 'WirelessTools', 'ExtrasTools', ] diff --git a/mcp-servers/netbox/mcp_server/tools/circuits.py b/mcp-servers/netbox/mcp_server/tools/circuits.py deleted file mode 100644 index 3e8c428..0000000 --- a/mcp-servers/netbox/mcp_server/tools/circuits.py +++ /dev/null @@ -1,373 +0,0 @@ -""" -Circuits tools for NetBox MCP Server. - -Covers: Providers, Circuits, Circuit Types, Circuit Terminations, and related models. -""" -import logging -from typing import List, Dict, Optional, Any -from ..netbox_client import NetBoxClient - -logger = logging.getLogger(__name__) - - -class CircuitsTools: - """Tools for Circuits operations in NetBox""" - - def __init__(self, client: NetBoxClient): - self.client = client - self.base_endpoint = 'circuits' - - # ==================== Providers ==================== - - async def list_providers( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all circuit providers.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/providers', params=params) - - async def get_provider(self, id: int) -> Dict: - """Get a specific provider by ID.""" - return self.client.get(f'{self.base_endpoint}/providers', id) - - async def create_provider( - self, - name: str, - slug: str, - asns: Optional[List[int]] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new provider.""" - data = {'name': name, 'slug': slug, **kwargs} - if asns: - data['asns'] = asns - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/providers', data) - - async def update_provider(self, id: int, **kwargs) -> Dict: - """Update a provider.""" - return self.client.patch(f'{self.base_endpoint}/providers', id, kwargs) - - async def delete_provider(self, id: int) -> None: - """Delete a provider.""" - self.client.delete(f'{self.base_endpoint}/providers', id) - - # ==================== Provider Accounts ==================== - - async def list_provider_accounts( - self, - provider_id: Optional[int] = None, - name: Optional[str] = None, - account: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all provider accounts.""" - params = {k: v for k, v in { - 'provider_id': provider_id, 'name': name, 'account': account, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/provider-accounts', params=params) - - async def get_provider_account(self, id: int) -> Dict: - """Get a specific provider account by ID.""" - return self.client.get(f'{self.base_endpoint}/provider-accounts', id) - - async def create_provider_account( - self, - provider: int, - account: str, - name: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new provider account.""" - data = {'provider': provider, 'account': account, **kwargs} - if name: - data['name'] = name - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/provider-accounts', data) - - async def update_provider_account(self, id: int, **kwargs) -> Dict: - """Update a provider account.""" - return self.client.patch(f'{self.base_endpoint}/provider-accounts', id, kwargs) - - async def delete_provider_account(self, id: int) -> None: - """Delete a provider account.""" - self.client.delete(f'{self.base_endpoint}/provider-accounts', id) - - # ==================== Provider Networks ==================== - - async def list_provider_networks( - self, - provider_id: Optional[int] = None, - name: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all provider networks.""" - params = {k: v for k, v in { - 'provider_id': provider_id, 'name': name, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/provider-networks', params=params) - - async def get_provider_network(self, id: int) -> Dict: - """Get a specific provider network by ID.""" - return self.client.get(f'{self.base_endpoint}/provider-networks', id) - - async def create_provider_network( - self, - provider: int, - name: str, - service_id: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new provider network.""" - data = {'provider': provider, 'name': name, **kwargs} - if service_id: - data['service_id'] = service_id - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/provider-networks', data) - - async def update_provider_network(self, id: int, **kwargs) -> Dict: - """Update a provider network.""" - return self.client.patch(f'{self.base_endpoint}/provider-networks', id, kwargs) - - async def delete_provider_network(self, id: int) -> None: - """Delete a provider network.""" - self.client.delete(f'{self.base_endpoint}/provider-networks', id) - - # ==================== Circuit Types ==================== - - async def list_circuit_types( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all circuit types.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/circuit-types', params=params) - - async def get_circuit_type(self, id: int) -> Dict: - """Get a specific circuit type by ID.""" - return self.client.get(f'{self.base_endpoint}/circuit-types', id) - - async def create_circuit_type( - self, - name: str, - slug: str, - color: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new circuit type.""" - data = {'name': name, 'slug': slug, **kwargs} - if color: - data['color'] = color - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/circuit-types', data) - - async def update_circuit_type(self, id: int, **kwargs) -> Dict: - """Update a circuit type.""" - return self.client.patch(f'{self.base_endpoint}/circuit-types', id, kwargs) - - async def delete_circuit_type(self, id: int) -> None: - """Delete a circuit type.""" - self.client.delete(f'{self.base_endpoint}/circuit-types', id) - - # ==================== Circuit Groups ==================== - - async def list_circuit_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all circuit groups.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/circuit-groups', params=params) - - async def get_circuit_group(self, id: int) -> Dict: - """Get a specific circuit group by ID.""" - return self.client.get(f'{self.base_endpoint}/circuit-groups', id) - - async def create_circuit_group( - self, - name: str, - slug: str, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new circuit group.""" - data = {'name': name, 'slug': slug, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/circuit-groups', data) - - async def update_circuit_group(self, id: int, **kwargs) -> Dict: - """Update a circuit group.""" - return self.client.patch(f'{self.base_endpoint}/circuit-groups', id, kwargs) - - async def delete_circuit_group(self, id: int) -> None: - """Delete a circuit group.""" - self.client.delete(f'{self.base_endpoint}/circuit-groups', id) - - # ==================== Circuit Group Assignments ==================== - - async def list_circuit_group_assignments( - self, - group_id: Optional[int] = None, - circuit_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all circuit group assignments.""" - params = {k: v for k, v in { - 'group_id': group_id, 'circuit_id': circuit_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/circuit-group-assignments', params=params) - - async def get_circuit_group_assignment(self, id: int) -> Dict: - """Get a specific circuit group assignment by ID.""" - return self.client.get(f'{self.base_endpoint}/circuit-group-assignments', id) - - async def create_circuit_group_assignment( - self, - group: int, - circuit: int, - priority: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new circuit group assignment.""" - data = {'group': group, 'circuit': circuit, **kwargs} - if priority: - data['priority'] = priority - return self.client.create(f'{self.base_endpoint}/circuit-group-assignments', data) - - async def update_circuit_group_assignment(self, id: int, **kwargs) -> Dict: - """Update a circuit group assignment.""" - return self.client.patch(f'{self.base_endpoint}/circuit-group-assignments', id, kwargs) - - async def delete_circuit_group_assignment(self, id: int) -> None: - """Delete a circuit group assignment.""" - self.client.delete(f'{self.base_endpoint}/circuit-group-assignments', id) - - # ==================== Circuits ==================== - - async def list_circuits( - self, - cid: Optional[str] = None, - provider_id: Optional[int] = None, - provider_account_id: Optional[int] = None, - type_id: Optional[int] = None, - status: Optional[str] = None, - tenant_id: Optional[int] = None, - site_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all circuits with optional filtering.""" - params = {k: v for k, v in { - 'cid': cid, 'provider_id': provider_id, 'provider_account_id': provider_account_id, - 'type_id': type_id, 'status': status, 'tenant_id': tenant_id, 'site_id': site_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/circuits', params=params) - - async def get_circuit(self, id: int) -> Dict: - """Get a specific circuit by ID.""" - return self.client.get(f'{self.base_endpoint}/circuits', id) - - async def create_circuit( - self, - cid: str, - provider: int, - type: int, - status: str = 'active', - provider_account: Optional[int] = None, - tenant: Optional[int] = None, - install_date: Optional[str] = None, - termination_date: Optional[str] = None, - commit_rate: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new circuit.""" - data = {'cid': cid, 'provider': provider, 'type': type, 'status': status, **kwargs} - for key, val in [ - ('provider_account', provider_account), ('tenant', tenant), - ('install_date', install_date), ('termination_date', termination_date), - ('commit_rate', commit_rate), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/circuits', data) - - async def update_circuit(self, id: int, **kwargs) -> Dict: - """Update a circuit.""" - return self.client.patch(f'{self.base_endpoint}/circuits', id, kwargs) - - async def delete_circuit(self, id: int) -> None: - """Delete a circuit.""" - self.client.delete(f'{self.base_endpoint}/circuits', id) - - # ==================== Circuit Terminations ==================== - - async def list_circuit_terminations( - self, - circuit_id: Optional[int] = None, - site_id: Optional[int] = None, - provider_network_id: Optional[int] = None, - term_side: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all circuit terminations.""" - params = {k: v for k, v in { - 'circuit_id': circuit_id, 'site_id': site_id, - 'provider_network_id': provider_network_id, 'term_side': term_side, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/circuit-terminations', params=params) - - async def get_circuit_termination(self, id: int) -> Dict: - """Get a specific circuit termination by ID.""" - return self.client.get(f'{self.base_endpoint}/circuit-terminations', id) - - async def create_circuit_termination( - self, - circuit: int, - term_side: str, - site: Optional[int] = None, - provider_network: Optional[int] = None, - port_speed: Optional[int] = None, - upstream_speed: Optional[int] = None, - xconnect_id: Optional[str] = None, - pp_info: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new circuit termination.""" - data = {'circuit': circuit, 'term_side': term_side, **kwargs} - for key, val in [ - ('site', site), ('provider_network', provider_network), - ('port_speed', port_speed), ('upstream_speed', upstream_speed), - ('xconnect_id', xconnect_id), ('pp_info', pp_info), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/circuit-terminations', data) - - async def update_circuit_termination(self, id: int, **kwargs) -> Dict: - """Update a circuit termination.""" - return self.client.patch(f'{self.base_endpoint}/circuit-terminations', id, kwargs) - - async def delete_circuit_termination(self, id: int) -> None: - """Delete a circuit termination.""" - self.client.delete(f'{self.base_endpoint}/circuit-terminations', id) - - async def get_circuit_termination_paths(self, id: int) -> Dict: - """Get cable paths for a circuit termination.""" - return self.client.get(f'{self.base_endpoint}/circuit-terminations', f'{id}/paths') diff --git a/mcp-servers/netbox/mcp_server/tools/dcim.py b/mcp-servers/netbox/mcp_server/tools/dcim.py index 400baba..54d1234 100644 --- a/mcp-servers/netbox/mcp_server/tools/dcim.py +++ b/mcp-servers/netbox/mcp_server/tools/dcim.py @@ -1,7 +1,7 @@ """ DCIM (Data Center Infrastructure Management) tools for NetBox MCP Server. -Covers: Sites, Locations, Racks, Devices, Cables, Interfaces, and related models. +Covers: Sites, Devices, and Interfaces only. """ import logging from typing import List, Dict, Optional, Any @@ -17,74 +17,6 @@ class DCIMTools: self.client = client self.base_endpoint = 'dcim' - # ==================== Regions ==================== - - async def list_regions( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - parent_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all regions with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/regions', params=params) - - async def get_region(self, id: int) -> Dict: - """Get a specific region by ID.""" - return self.client.get(f'{self.base_endpoint}/regions', id) - - async def create_region(self, name: str, slug: str, parent: Optional[int] = None, **kwargs) -> Dict: - """Create a new region.""" - data = {'name': name, 'slug': slug, **kwargs} - if parent: - data['parent'] = parent - return self.client.create(f'{self.base_endpoint}/regions', data) - - async def update_region(self, id: int, **kwargs) -> Dict: - """Update a region.""" - return self.client.patch(f'{self.base_endpoint}/regions', id, kwargs) - - async def delete_region(self, id: int) -> None: - """Delete a region.""" - self.client.delete(f'{self.base_endpoint}/regions', id) - - # ==================== Site Groups ==================== - - async def list_site_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - parent_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all site groups with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/site-groups', params=params) - - async def get_site_group(self, id: int) -> Dict: - """Get a specific site group by ID.""" - return self.client.get(f'{self.base_endpoint}/site-groups', id) - - async def create_site_group(self, name: str, slug: str, parent: Optional[int] = None, **kwargs) -> Dict: - """Create a new site group.""" - data = {'name': name, 'slug': slug, **kwargs} - if parent: - data['parent'] = parent - return self.client.create(f'{self.base_endpoint}/site-groups', data) - - async def update_site_group(self, id: int, **kwargs) -> Dict: - """Update a site group.""" - return self.client.patch(f'{self.base_endpoint}/site-groups', id, kwargs) - - async def delete_site_group(self, id: int) -> None: - """Delete a site group.""" - self.client.delete(f'{self.base_endpoint}/site-groups', id) - # ==================== Sites ==================== async def list_sites( @@ -142,359 +74,6 @@ class DCIMTools: """Update a site.""" return self.client.patch(f'{self.base_endpoint}/sites', id, kwargs) - async def delete_site(self, id: int) -> None: - """Delete a site.""" - self.client.delete(f'{self.base_endpoint}/sites', id) - - # ==================== Locations ==================== - - async def list_locations( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - site_id: Optional[int] = None, - parent_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all locations with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'site_id': site_id, 'parent_id': parent_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/locations', params=params) - - async def get_location(self, id: int) -> Dict: - """Get a specific location by ID.""" - return self.client.get(f'{self.base_endpoint}/locations', id) - - async def create_location( - self, - name: str, - slug: str, - site: int, - parent: Optional[int] = None, - **kwargs - ) -> Dict: - """Create a new location.""" - data = {'name': name, 'slug': slug, 'site': site, **kwargs} - if parent: - data['parent'] = parent - return self.client.create(f'{self.base_endpoint}/locations', data) - - async def update_location(self, id: int, **kwargs) -> Dict: - """Update a location.""" - return self.client.patch(f'{self.base_endpoint}/locations', id, kwargs) - - async def delete_location(self, id: int) -> None: - """Delete a location.""" - self.client.delete(f'{self.base_endpoint}/locations', id) - - # ==================== Rack Roles ==================== - - async def list_rack_roles(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all rack roles.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/rack-roles', params=params) - - async def get_rack_role(self, id: int) -> Dict: - """Get a specific rack role by ID.""" - return self.client.get(f'{self.base_endpoint}/rack-roles', id) - - async def create_rack_role(self, name: str, slug: str, color: str = '9e9e9e', **kwargs) -> Dict: - """Create a new rack role.""" - data = {'name': name, 'slug': slug, 'color': color, **kwargs} - return self.client.create(f'{self.base_endpoint}/rack-roles', data) - - async def update_rack_role(self, id: int, **kwargs) -> Dict: - """Update a rack role.""" - return self.client.patch(f'{self.base_endpoint}/rack-roles', id, kwargs) - - async def delete_rack_role(self, id: int) -> None: - """Delete a rack role.""" - self.client.delete(f'{self.base_endpoint}/rack-roles', id) - - # ==================== Rack Types ==================== - - async def list_rack_types(self, manufacturer_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all rack types.""" - params = {k: v for k, v in {'manufacturer_id': manufacturer_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/rack-types', params=params) - - async def get_rack_type(self, id: int) -> Dict: - """Get a specific rack type by ID.""" - return self.client.get(f'{self.base_endpoint}/rack-types', id) - - async def create_rack_type( - self, - manufacturer: int, - model: str, - slug: str, - form_factor: str = '4-post-frame', - width: int = 19, - u_height: int = 42, - **kwargs - ) -> Dict: - """Create a new rack type.""" - data = { - 'manufacturer': manufacturer, 'model': model, 'slug': slug, - 'form_factor': form_factor, 'width': width, 'u_height': u_height, **kwargs - } - return self.client.create(f'{self.base_endpoint}/rack-types', data) - - async def update_rack_type(self, id: int, **kwargs) -> Dict: - """Update a rack type.""" - return self.client.patch(f'{self.base_endpoint}/rack-types', id, kwargs) - - async def delete_rack_type(self, id: int) -> None: - """Delete a rack type.""" - self.client.delete(f'{self.base_endpoint}/rack-types', id) - - # ==================== Racks ==================== - - async def list_racks( - self, - name: Optional[str] = None, - site_id: Optional[int] = None, - location_id: Optional[int] = None, - status: Optional[str] = None, - role_id: Optional[int] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all racks with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'site_id': site_id, 'location_id': location_id, - 'status': status, 'role_id': role_id, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/racks', params=params) - - async def get_rack(self, id: int) -> Dict: - """Get a specific rack by ID.""" - return self.client.get(f'{self.base_endpoint}/racks', id) - - async def create_rack( - self, - name: str, - site: int, - status: str = 'active', - location: Optional[int] = None, - role: Optional[int] = None, - tenant: Optional[int] = None, - rack_type: Optional[int] = None, - width: int = 19, - u_height: int = 42, - **kwargs - ) -> Dict: - """Create a new rack.""" - data = {'name': name, 'site': site, 'status': status, 'width': width, 'u_height': u_height, **kwargs} - for key, val in [('location', location), ('role', role), ('tenant', tenant), ('rack_type', rack_type)]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/racks', data) - - async def update_rack(self, id: int, **kwargs) -> Dict: - """Update a rack.""" - return self.client.patch(f'{self.base_endpoint}/racks', id, kwargs) - - async def delete_rack(self, id: int) -> None: - """Delete a rack.""" - self.client.delete(f'{self.base_endpoint}/racks', id) - - # ==================== Rack Reservations ==================== - - async def list_rack_reservations( - self, - rack_id: Optional[int] = None, - site_id: Optional[int] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all rack reservations.""" - params = {k: v for k, v in { - 'rack_id': rack_id, 'site_id': site_id, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/rack-reservations', params=params) - - async def get_rack_reservation(self, id: int) -> Dict: - """Get a specific rack reservation by ID.""" - return self.client.get(f'{self.base_endpoint}/rack-reservations', id) - - async def create_rack_reservation( - self, - rack: int, - units: List[int], - user: int, - description: str, - tenant: Optional[int] = None, - **kwargs - ) -> Dict: - """Create a new rack reservation.""" - data = {'rack': rack, 'units': units, 'user': user, 'description': description, **kwargs} - if tenant: - data['tenant'] = tenant - return self.client.create(f'{self.base_endpoint}/rack-reservations', data) - - async def update_rack_reservation(self, id: int, **kwargs) -> Dict: - """Update a rack reservation.""" - return self.client.patch(f'{self.base_endpoint}/rack-reservations', id, kwargs) - - async def delete_rack_reservation(self, id: int) -> None: - """Delete a rack reservation.""" - self.client.delete(f'{self.base_endpoint}/rack-reservations', id) - - # ==================== Manufacturers ==================== - - async def list_manufacturers(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all manufacturers.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/manufacturers', params=params) - - async def get_manufacturer(self, id: int) -> Dict: - """Get a specific manufacturer by ID.""" - return self.client.get(f'{self.base_endpoint}/manufacturers', id) - - async def create_manufacturer(self, name: str, slug: str, **kwargs) -> Dict: - """Create a new manufacturer.""" - data = {'name': name, 'slug': slug, **kwargs} - return self.client.create(f'{self.base_endpoint}/manufacturers', data) - - async def update_manufacturer(self, id: int, **kwargs) -> Dict: - """Update a manufacturer.""" - return self.client.patch(f'{self.base_endpoint}/manufacturers', id, kwargs) - - async def delete_manufacturer(self, id: int) -> None: - """Delete a manufacturer.""" - self.client.delete(f'{self.base_endpoint}/manufacturers', id) - - # ==================== Device Types ==================== - - async def list_device_types( - self, - manufacturer_id: Optional[int] = None, - model: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all device types.""" - params = {k: v for k, v in { - 'manufacturer_id': manufacturer_id, 'model': model, 'slug': slug, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/device-types', params=params) - - async def get_device_type(self, id: int) -> Dict: - """Get a specific device type by ID.""" - return self.client.get(f'{self.base_endpoint}/device-types', id) - - async def create_device_type( - self, - manufacturer: int, - model: str, - slug: str, - u_height: float = 1.0, - is_full_depth: bool = True, - **kwargs - ) -> Dict: - """Create a new device type.""" - data = { - 'manufacturer': manufacturer, 'model': model, 'slug': slug, - 'u_height': u_height, 'is_full_depth': is_full_depth, **kwargs - } - return self.client.create(f'{self.base_endpoint}/device-types', data) - - async def update_device_type(self, id: int, **kwargs) -> Dict: - """Update a device type.""" - return self.client.patch(f'{self.base_endpoint}/device-types', id, kwargs) - - async def delete_device_type(self, id: int) -> None: - """Delete a device type.""" - self.client.delete(f'{self.base_endpoint}/device-types', id) - - # ==================== Module Types ==================== - - async def list_module_types(self, manufacturer_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all module types.""" - params = {k: v for k, v in {'manufacturer_id': manufacturer_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/module-types', params=params) - - async def get_module_type(self, id: int) -> Dict: - """Get a specific module type by ID.""" - return self.client.get(f'{self.base_endpoint}/module-types', id) - - async def create_module_type(self, manufacturer: int, model: str, **kwargs) -> Dict: - """Create a new module type.""" - data = {'manufacturer': manufacturer, 'model': model, **kwargs} - return self.client.create(f'{self.base_endpoint}/module-types', data) - - async def update_module_type(self, id: int, **kwargs) -> Dict: - """Update a module type.""" - return self.client.patch(f'{self.base_endpoint}/module-types', id, kwargs) - - async def delete_module_type(self, id: int) -> None: - """Delete a module type.""" - self.client.delete(f'{self.base_endpoint}/module-types', id) - - # ==================== Device Roles ==================== - - async def list_device_roles(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all device roles.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/device-roles', params=params) - - async def get_device_role(self, id: int) -> Dict: - """Get a specific device role by ID.""" - return self.client.get(f'{self.base_endpoint}/device-roles', id) - - async def create_device_role( - self, - name: str, - slug: str, - color: str = '9e9e9e', - vm_role: bool = False, - **kwargs - ) -> Dict: - """Create a new device role.""" - data = {'name': name, 'slug': slug, 'color': color, 'vm_role': vm_role, **kwargs} - return self.client.create(f'{self.base_endpoint}/device-roles', data) - - async def update_device_role(self, id: int, **kwargs) -> Dict: - """Update a device role.""" - return self.client.patch(f'{self.base_endpoint}/device-roles', id, kwargs) - - async def delete_device_role(self, id: int) -> None: - """Delete a device role.""" - self.client.delete(f'{self.base_endpoint}/device-roles', id) - - # ==================== Platforms ==================== - - async def list_platforms(self, name: Optional[str] = None, manufacturer_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all platforms.""" - params = {k: v for k, v in {'name': name, 'manufacturer_id': manufacturer_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/platforms', params=params) - - async def get_platform(self, id: int) -> Dict: - """Get a specific platform by ID.""" - return self.client.get(f'{self.base_endpoint}/platforms', id) - - async def create_platform( - self, - name: str, - slug: str, - manufacturer: Optional[int] = None, - **kwargs - ) -> Dict: - """Create a new platform.""" - data = {'name': name, 'slug': slug, **kwargs} - if manufacturer: - data['manufacturer'] = manufacturer - return self.client.create(f'{self.base_endpoint}/platforms', data) - - async def update_platform(self, id: int, **kwargs) -> Dict: - """Update a platform.""" - return self.client.patch(f'{self.base_endpoint}/platforms', id, kwargs) - - async def delete_platform(self, id: int) -> None: - """Delete a platform.""" - self.client.delete(f'{self.base_endpoint}/platforms', id) - # ==================== Devices ==================== async def list_devices( @@ -565,34 +144,6 @@ class DCIMTools: """Update a device.""" return self.client.patch(f'{self.base_endpoint}/devices', id, kwargs) - async def delete_device(self, id: int) -> None: - """Delete a device.""" - self.client.delete(f'{self.base_endpoint}/devices', id) - - # ==================== Modules ==================== - - async def list_modules(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all modules.""" - params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/modules', params=params) - - async def get_module(self, id: int) -> Dict: - """Get a specific module by ID.""" - return self.client.get(f'{self.base_endpoint}/modules', id) - - async def create_module(self, device: int, module_bay: int, module_type: int, **kwargs) -> Dict: - """Create a new module.""" - data = {'device': device, 'module_bay': module_bay, 'module_type': module_type, **kwargs} - return self.client.create(f'{self.base_endpoint}/modules', data) - - async def update_module(self, id: int, **kwargs) -> Dict: - """Update a module.""" - return self.client.patch(f'{self.base_endpoint}/modules', id, kwargs) - - async def delete_module(self, id: int) -> None: - """Delete a module.""" - self.client.delete(f'{self.base_endpoint}/modules', id) - # ==================== Interfaces ==================== async def list_interfaces( @@ -636,300 +187,3 @@ class DCIMTools: if val is not None: data[key] = val return self.client.create(f'{self.base_endpoint}/interfaces', data) - - async def update_interface(self, id: int, **kwargs) -> Dict: - """Update an interface.""" - return self.client.patch(f'{self.base_endpoint}/interfaces', id, kwargs) - - async def delete_interface(self, id: int) -> None: - """Delete an interface.""" - self.client.delete(f'{self.base_endpoint}/interfaces', id) - - # ==================== Console Ports ==================== - - async def list_console_ports(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all console ports.""" - params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/console-ports', params=params) - - async def get_console_port(self, id: int) -> Dict: - """Get a specific console port by ID.""" - return self.client.get(f'{self.base_endpoint}/console-ports', id) - - async def create_console_port(self, device: int, name: str, **kwargs) -> Dict: - """Create a new console port.""" - data = {'device': device, 'name': name, **kwargs} - return self.client.create(f'{self.base_endpoint}/console-ports', data) - - async def update_console_port(self, id: int, **kwargs) -> Dict: - """Update a console port.""" - return self.client.patch(f'{self.base_endpoint}/console-ports', id, kwargs) - - async def delete_console_port(self, id: int) -> None: - """Delete a console port.""" - self.client.delete(f'{self.base_endpoint}/console-ports', id) - - # ==================== Console Server Ports ==================== - - async def list_console_server_ports(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all console server ports.""" - params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/console-server-ports', params=params) - - async def get_console_server_port(self, id: int) -> Dict: - """Get a specific console server port by ID.""" - return self.client.get(f'{self.base_endpoint}/console-server-ports', id) - - async def create_console_server_port(self, device: int, name: str, **kwargs) -> Dict: - """Create a new console server port.""" - data = {'device': device, 'name': name, **kwargs} - return self.client.create(f'{self.base_endpoint}/console-server-ports', data) - - async def update_console_server_port(self, id: int, **kwargs) -> Dict: - """Update a console server port.""" - return self.client.patch(f'{self.base_endpoint}/console-server-ports', id, kwargs) - - async def delete_console_server_port(self, id: int) -> None: - """Delete a console server port.""" - self.client.delete(f'{self.base_endpoint}/console-server-ports', id) - - # ==================== Power Ports ==================== - - async def list_power_ports(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all power ports.""" - params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/power-ports', params=params) - - async def get_power_port(self, id: int) -> Dict: - """Get a specific power port by ID.""" - return self.client.get(f'{self.base_endpoint}/power-ports', id) - - async def create_power_port(self, device: int, name: str, **kwargs) -> Dict: - """Create a new power port.""" - data = {'device': device, 'name': name, **kwargs} - return self.client.create(f'{self.base_endpoint}/power-ports', data) - - async def update_power_port(self, id: int, **kwargs) -> Dict: - """Update a power port.""" - return self.client.patch(f'{self.base_endpoint}/power-ports', id, kwargs) - - async def delete_power_port(self, id: int) -> None: - """Delete a power port.""" - self.client.delete(f'{self.base_endpoint}/power-ports', id) - - # ==================== Power Outlets ==================== - - async def list_power_outlets(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all power outlets.""" - params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/power-outlets', params=params) - - async def get_power_outlet(self, id: int) -> Dict: - """Get a specific power outlet by ID.""" - return self.client.get(f'{self.base_endpoint}/power-outlets', id) - - async def create_power_outlet(self, device: int, name: str, **kwargs) -> Dict: - """Create a new power outlet.""" - data = {'device': device, 'name': name, **kwargs} - return self.client.create(f'{self.base_endpoint}/power-outlets', data) - - async def update_power_outlet(self, id: int, **kwargs) -> Dict: - """Update a power outlet.""" - return self.client.patch(f'{self.base_endpoint}/power-outlets', id, kwargs) - - async def delete_power_outlet(self, id: int) -> None: - """Delete a power outlet.""" - self.client.delete(f'{self.base_endpoint}/power-outlets', id) - - # ==================== Power Panels ==================== - - async def list_power_panels(self, site_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all power panels.""" - params = {k: v for k, v in {'site_id': site_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/power-panels', params=params) - - async def get_power_panel(self, id: int) -> Dict: - """Get a specific power panel by ID.""" - return self.client.get(f'{self.base_endpoint}/power-panels', id) - - async def create_power_panel(self, site: int, name: str, location: Optional[int] = None, **kwargs) -> Dict: - """Create a new power panel.""" - data = {'site': site, 'name': name, **kwargs} - if location: - data['location'] = location - return self.client.create(f'{self.base_endpoint}/power-panels', data) - - async def update_power_panel(self, id: int, **kwargs) -> Dict: - """Update a power panel.""" - return self.client.patch(f'{self.base_endpoint}/power-panels', id, kwargs) - - async def delete_power_panel(self, id: int) -> None: - """Delete a power panel.""" - self.client.delete(f'{self.base_endpoint}/power-panels', id) - - # ==================== Power Feeds ==================== - - async def list_power_feeds(self, power_panel_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all power feeds.""" - params = {k: v for k, v in {'power_panel_id': power_panel_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/power-feeds', params=params) - - async def get_power_feed(self, id: int) -> Dict: - """Get a specific power feed by ID.""" - return self.client.get(f'{self.base_endpoint}/power-feeds', id) - - async def create_power_feed( - self, - power_panel: int, - name: str, - status: str = 'active', - type: str = 'primary', - supply: str = 'ac', - phase: str = 'single-phase', - voltage: int = 120, - amperage: int = 20, - **kwargs - ) -> Dict: - """Create a new power feed.""" - data = { - 'power_panel': power_panel, 'name': name, 'status': status, - 'type': type, 'supply': supply, 'phase': phase, - 'voltage': voltage, 'amperage': amperage, **kwargs - } - return self.client.create(f'{self.base_endpoint}/power-feeds', data) - - async def update_power_feed(self, id: int, **kwargs) -> Dict: - """Update a power feed.""" - return self.client.patch(f'{self.base_endpoint}/power-feeds', id, kwargs) - - async def delete_power_feed(self, id: int) -> None: - """Delete a power feed.""" - self.client.delete(f'{self.base_endpoint}/power-feeds', id) - - # ==================== Cables ==================== - - async def list_cables( - self, - site_id: Optional[int] = None, - device_id: Optional[int] = None, - rack_id: Optional[int] = None, - type: Optional[str] = None, - status: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all cables.""" - params = {k: v for k, v in { - 'site_id': site_id, 'device_id': device_id, 'rack_id': rack_id, - 'type': type, 'status': status, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/cables', params=params) - - async def get_cable(self, id: int) -> Dict: - """Get a specific cable by ID.""" - return self.client.get(f'{self.base_endpoint}/cables', id) - - async def create_cable( - self, - a_terminations: List[Dict], - b_terminations: List[Dict], - type: Optional[str] = None, - status: str = 'connected', - label: Optional[str] = None, - color: Optional[str] = None, - length: Optional[float] = None, - length_unit: Optional[str] = None, - **kwargs - ) -> Dict: - """ - Create a new cable. - - a_terminations and b_terminations are lists of dicts with: - - object_type: e.g., 'dcim.interface' - - object_id: ID of the object - """ - data = { - 'a_terminations': a_terminations, - 'b_terminations': b_terminations, - 'status': status, - **kwargs - } - for key, val in [ - ('type', type), ('label', label), ('color', color), - ('length', length), ('length_unit', length_unit) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/cables', data) - - async def update_cable(self, id: int, **kwargs) -> Dict: - """Update a cable.""" - return self.client.patch(f'{self.base_endpoint}/cables', id, kwargs) - - async def delete_cable(self, id: int) -> None: - """Delete a cable.""" - self.client.delete(f'{self.base_endpoint}/cables', id) - - # ==================== Virtual Chassis ==================== - - async def list_virtual_chassis(self, **kwargs) -> List[Dict]: - """List all virtual chassis.""" - return self.client.list(f'{self.base_endpoint}/virtual-chassis', params=kwargs) - - async def get_virtual_chassis(self, id: int) -> Dict: - """Get a specific virtual chassis by ID.""" - return self.client.get(f'{self.base_endpoint}/virtual-chassis', id) - - async def create_virtual_chassis(self, name: str, domain: Optional[str] = None, **kwargs) -> Dict: - """Create a new virtual chassis.""" - data = {'name': name, **kwargs} - if domain: - data['domain'] = domain - return self.client.create(f'{self.base_endpoint}/virtual-chassis', data) - - async def update_virtual_chassis(self, id: int, **kwargs) -> Dict: - """Update a virtual chassis.""" - return self.client.patch(f'{self.base_endpoint}/virtual-chassis', id, kwargs) - - async def delete_virtual_chassis(self, id: int) -> None: - """Delete a virtual chassis.""" - self.client.delete(f'{self.base_endpoint}/virtual-chassis', id) - - # ==================== Inventory Items ==================== - - async def list_inventory_items(self, device_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all inventory items.""" - params = {k: v for k, v in {'device_id': device_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/inventory-items', params=params) - - async def get_inventory_item(self, id: int) -> Dict: - """Get a specific inventory item by ID.""" - return self.client.get(f'{self.base_endpoint}/inventory-items', id) - - async def create_inventory_item( - self, - device: int, - name: str, - parent: Optional[int] = None, - manufacturer: Optional[int] = None, - part_id: Optional[str] = None, - serial: Optional[str] = None, - asset_tag: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new inventory item.""" - data = {'device': device, 'name': name, **kwargs} - for key, val in [ - ('parent', parent), ('manufacturer', manufacturer), - ('part_id', part_id), ('serial', serial), ('asset_tag', asset_tag) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/inventory-items', data) - - async def update_inventory_item(self, id: int, **kwargs) -> Dict: - """Update an inventory item.""" - return self.client.patch(f'{self.base_endpoint}/inventory-items', id, kwargs) - - async def delete_inventory_item(self, id: int) -> None: - """Delete an inventory item.""" - self.client.delete(f'{self.base_endpoint}/inventory-items', id) diff --git a/mcp-servers/netbox/mcp_server/tools/extras.py b/mcp-servers/netbox/mcp_server/tools/extras.py index 6c17766..cb69e47 100644 --- a/mcp-servers/netbox/mcp_server/tools/extras.py +++ b/mcp-servers/netbox/mcp_server/tools/extras.py @@ -1,7 +1,7 @@ """ Extras tools for NetBox MCP Server. -Covers: Tags, Custom Fields, Custom Links, Webhooks, Journal Entries, and more. +Covers: Tags and Journal Entries only. """ import logging from typing import List, Dict, Optional, Any @@ -50,209 +50,6 @@ class ExtrasTools: data['description'] = description return self.client.create(f'{self.base_endpoint}/tags', data) - async def update_tag(self, id: int, **kwargs) -> Dict: - """Update a tag.""" - return self.client.patch(f'{self.base_endpoint}/tags', id, kwargs) - - async def delete_tag(self, id: int) -> None: - """Delete a tag.""" - self.client.delete(f'{self.base_endpoint}/tags', id) - - # ==================== Custom Fields ==================== - - async def list_custom_fields( - self, - name: Optional[str] = None, - type: Optional[str] = None, - content_types: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all custom fields.""" - params = {k: v for k, v in { - 'name': name, 'type': type, 'content_types': content_types, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/custom-fields', params=params) - - async def get_custom_field(self, id: int) -> Dict: - """Get a specific custom field by ID.""" - return self.client.get(f'{self.base_endpoint}/custom-fields', id) - - async def create_custom_field( - self, - name: str, - content_types: List[str], - type: str = 'text', - label: Optional[str] = None, - description: Optional[str] = None, - required: bool = False, - filter_logic: str = 'loose', - default: Optional[Any] = None, - weight: int = 100, - validation_minimum: Optional[int] = None, - validation_maximum: Optional[int] = None, - validation_regex: Optional[str] = None, - choice_set: Optional[int] = None, - **kwargs - ) -> Dict: - """Create a new custom field.""" - data = { - 'name': name, 'content_types': content_types, 'type': type, - 'required': required, 'filter_logic': filter_logic, 'weight': weight, **kwargs - } - for key, val in [ - ('label', label), ('description', description), ('default', default), - ('validation_minimum', validation_minimum), ('validation_maximum', validation_maximum), - ('validation_regex', validation_regex), ('choice_set', choice_set) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/custom-fields', data) - - async def update_custom_field(self, id: int, **kwargs) -> Dict: - """Update a custom field.""" - return self.client.patch(f'{self.base_endpoint}/custom-fields', id, kwargs) - - async def delete_custom_field(self, id: int) -> None: - """Delete a custom field.""" - self.client.delete(f'{self.base_endpoint}/custom-fields', id) - - # ==================== Custom Field Choice Sets ==================== - - async def list_custom_field_choice_sets(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all custom field choice sets.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/custom-field-choice-sets', params=params) - - async def get_custom_field_choice_set(self, id: int) -> Dict: - """Get a specific custom field choice set by ID.""" - return self.client.get(f'{self.base_endpoint}/custom-field-choice-sets', id) - - async def create_custom_field_choice_set( - self, - name: str, - extra_choices: List[List[str]], - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new custom field choice set.""" - data = {'name': name, 'extra_choices': extra_choices, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/custom-field-choice-sets', data) - - async def update_custom_field_choice_set(self, id: int, **kwargs) -> Dict: - """Update a custom field choice set.""" - return self.client.patch(f'{self.base_endpoint}/custom-field-choice-sets', id, kwargs) - - async def delete_custom_field_choice_set(self, id: int) -> None: - """Delete a custom field choice set.""" - self.client.delete(f'{self.base_endpoint}/custom-field-choice-sets', id) - - # ==================== Custom Links ==================== - - async def list_custom_links( - self, - name: Optional[str] = None, - content_types: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all custom links.""" - params = {k: v for k, v in { - 'name': name, 'content_types': content_types, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/custom-links', params=params) - - async def get_custom_link(self, id: int) -> Dict: - """Get a specific custom link by ID.""" - return self.client.get(f'{self.base_endpoint}/custom-links', id) - - async def create_custom_link( - self, - name: str, - content_types: List[str], - link_text: str, - link_url: str, - enabled: bool = True, - new_window: bool = False, - weight: int = 100, - group_name: Optional[str] = None, - button_class: str = 'outline-dark', - **kwargs - ) -> Dict: - """Create a new custom link.""" - data = { - 'name': name, 'content_types': content_types, - 'link_text': link_text, 'link_url': link_url, - 'enabled': enabled, 'new_window': new_window, - 'weight': weight, 'button_class': button_class, **kwargs - } - if group_name: - data['group_name'] = group_name - return self.client.create(f'{self.base_endpoint}/custom-links', data) - - async def update_custom_link(self, id: int, **kwargs) -> Dict: - """Update a custom link.""" - return self.client.patch(f'{self.base_endpoint}/custom-links', id, kwargs) - - async def delete_custom_link(self, id: int) -> None: - """Delete a custom link.""" - self.client.delete(f'{self.base_endpoint}/custom-links', id) - - # ==================== Webhooks ==================== - - async def list_webhooks(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all webhooks.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/webhooks', params=params) - - async def get_webhook(self, id: int) -> Dict: - """Get a specific webhook by ID.""" - return self.client.get(f'{self.base_endpoint}/webhooks', id) - - async def create_webhook( - self, - name: str, - payload_url: str, - content_types: List[str], - type_create: bool = True, - type_update: bool = True, - type_delete: bool = True, - type_job_start: bool = False, - type_job_end: bool = False, - enabled: bool = True, - http_method: str = 'POST', - http_content_type: str = 'application/json', - additional_headers: Optional[str] = None, - body_template: Optional[str] = None, - secret: Optional[str] = None, - ssl_verification: bool = True, - ca_file_path: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new webhook.""" - data = { - 'name': name, 'payload_url': payload_url, 'content_types': content_types, - 'type_create': type_create, 'type_update': type_update, 'type_delete': type_delete, - 'type_job_start': type_job_start, 'type_job_end': type_job_end, - 'enabled': enabled, 'http_method': http_method, - 'http_content_type': http_content_type, 'ssl_verification': ssl_verification, **kwargs - } - for key, val in [ - ('additional_headers', additional_headers), ('body_template', body_template), - ('secret', secret), ('ca_file_path', ca_file_path) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/webhooks', data) - - async def update_webhook(self, id: int, **kwargs) -> Dict: - """Update a webhook.""" - return self.client.patch(f'{self.base_endpoint}/webhooks', id, kwargs) - - async def delete_webhook(self, id: int) -> None: - """Delete a webhook.""" - self.client.delete(f'{self.base_endpoint}/webhooks', id) - # ==================== Journal Entries ==================== async def list_journal_entries( @@ -288,273 +85,3 @@ class ExtrasTools: 'comments': comments, 'kind': kind, **kwargs } return self.client.create(f'{self.base_endpoint}/journal-entries', data) - - async def update_journal_entry(self, id: int, **kwargs) -> Dict: - """Update a journal entry.""" - return self.client.patch(f'{self.base_endpoint}/journal-entries', id, kwargs) - - async def delete_journal_entry(self, id: int) -> None: - """Delete a journal entry.""" - self.client.delete(f'{self.base_endpoint}/journal-entries', id) - - # ==================== Config Contexts ==================== - - async def list_config_contexts(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all config contexts.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/config-contexts', params=params) - - async def get_config_context(self, id: int) -> Dict: - """Get a specific config context by ID.""" - return self.client.get(f'{self.base_endpoint}/config-contexts', id) - - async def create_config_context( - self, - name: str, - data: Dict[str, Any], - weight: int = 1000, - description: Optional[str] = None, - is_active: bool = True, - regions: Optional[List[int]] = None, - site_groups: Optional[List[int]] = None, - sites: Optional[List[int]] = None, - locations: Optional[List[int]] = None, - device_types: Optional[List[int]] = None, - roles: Optional[List[int]] = None, - platforms: Optional[List[int]] = None, - cluster_types: Optional[List[int]] = None, - cluster_groups: Optional[List[int]] = None, - clusters: Optional[List[int]] = None, - tenant_groups: Optional[List[int]] = None, - tenants: Optional[List[int]] = None, - tags: Optional[List[str]] = None, - **kwargs - ) -> Dict: - """Create a new config context.""" - context_data = { - 'name': name, 'data': data, 'weight': weight, 'is_active': is_active, **kwargs - } - for key, val in [ - ('description', description), ('regions', regions), - ('site_groups', site_groups), ('sites', sites), - ('locations', locations), ('device_types', device_types), - ('roles', roles), ('platforms', platforms), - ('cluster_types', cluster_types), ('cluster_groups', cluster_groups), - ('clusters', clusters), ('tenant_groups', tenant_groups), - ('tenants', tenants), ('tags', tags) - ]: - if val is not None: - context_data[key] = val - return self.client.create(f'{self.base_endpoint}/config-contexts', context_data) - - async def update_config_context(self, id: int, **kwargs) -> Dict: - """Update a config context.""" - return self.client.patch(f'{self.base_endpoint}/config-contexts', id, kwargs) - - async def delete_config_context(self, id: int) -> None: - """Delete a config context.""" - self.client.delete(f'{self.base_endpoint}/config-contexts', id) - - # ==================== Config Templates ==================== - - async def list_config_templates(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all config templates.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/config-templates', params=params) - - async def get_config_template(self, id: int) -> Dict: - """Get a specific config template by ID.""" - return self.client.get(f'{self.base_endpoint}/config-templates', id) - - async def create_config_template( - self, - name: str, - template_code: str, - description: Optional[str] = None, - environment_params: Optional[Dict[str, Any]] = None, - **kwargs - ) -> Dict: - """Create a new config template.""" - data = {'name': name, 'template_code': template_code, **kwargs} - if description: - data['description'] = description - if environment_params: - data['environment_params'] = environment_params - return self.client.create(f'{self.base_endpoint}/config-templates', data) - - async def update_config_template(self, id: int, **kwargs) -> Dict: - """Update a config template.""" - return self.client.patch(f'{self.base_endpoint}/config-templates', id, kwargs) - - async def delete_config_template(self, id: int) -> None: - """Delete a config template.""" - self.client.delete(f'{self.base_endpoint}/config-templates', id) - - # ==================== Export Templates ==================== - - async def list_export_templates( - self, - name: Optional[str] = None, - content_types: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all export templates.""" - params = {k: v for k, v in { - 'name': name, 'content_types': content_types, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/export-templates', params=params) - - async def get_export_template(self, id: int) -> Dict: - """Get a specific export template by ID.""" - return self.client.get(f'{self.base_endpoint}/export-templates', id) - - async def create_export_template( - self, - name: str, - content_types: List[str], - template_code: str, - description: Optional[str] = None, - mime_type: str = 'text/plain', - file_extension: Optional[str] = None, - as_attachment: bool = True, - **kwargs - ) -> Dict: - """Create a new export template.""" - data = { - 'name': name, 'content_types': content_types, - 'template_code': template_code, 'mime_type': mime_type, - 'as_attachment': as_attachment, **kwargs - } - if description: - data['description'] = description - if file_extension: - data['file_extension'] = file_extension - return self.client.create(f'{self.base_endpoint}/export-templates', data) - - async def update_export_template(self, id: int, **kwargs) -> Dict: - """Update an export template.""" - return self.client.patch(f'{self.base_endpoint}/export-templates', id, kwargs) - - async def delete_export_template(self, id: int) -> None: - """Delete an export template.""" - self.client.delete(f'{self.base_endpoint}/export-templates', id) - - # ==================== Saved Filters ==================== - - async def list_saved_filters( - self, - name: Optional[str] = None, - content_types: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all saved filters.""" - params = {k: v for k, v in { - 'name': name, 'content_types': content_types, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/saved-filters', params=params) - - async def get_saved_filter(self, id: int) -> Dict: - """Get a specific saved filter by ID.""" - return self.client.get(f'{self.base_endpoint}/saved-filters', id) - - async def create_saved_filter( - self, - name: str, - slug: str, - content_types: List[str], - parameters: Dict[str, Any], - description: Optional[str] = None, - weight: int = 100, - enabled: bool = True, - shared: bool = True, - **kwargs - ) -> Dict: - """Create a new saved filter.""" - data = { - 'name': name, 'slug': slug, 'content_types': content_types, - 'parameters': parameters, 'weight': weight, - 'enabled': enabled, 'shared': shared, **kwargs - } - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/saved-filters', data) - - async def update_saved_filter(self, id: int, **kwargs) -> Dict: - """Update a saved filter.""" - return self.client.patch(f'{self.base_endpoint}/saved-filters', id, kwargs) - - async def delete_saved_filter(self, id: int) -> None: - """Delete a saved filter.""" - self.client.delete(f'{self.base_endpoint}/saved-filters', id) - - # ==================== Image Attachments ==================== - - async def list_image_attachments( - self, - object_type: Optional[str] = None, - object_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all image attachments.""" - params = {k: v for k, v in { - 'object_type': object_type, 'object_id': object_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/image-attachments', params=params) - - async def get_image_attachment(self, id: int) -> Dict: - """Get a specific image attachment by ID.""" - return self.client.get(f'{self.base_endpoint}/image-attachments', id) - - async def delete_image_attachment(self, id: int) -> None: - """Delete an image attachment.""" - self.client.delete(f'{self.base_endpoint}/image-attachments', id) - - # ==================== Object Changes (Audit Log) ==================== - - async def list_object_changes( - self, - user_id: Optional[int] = None, - changed_object_type: Optional[str] = None, - changed_object_id: Optional[int] = None, - action: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all object changes (audit log).""" - params = {k: v for k, v in { - 'user_id': user_id, 'changed_object_type': changed_object_type, - 'changed_object_id': changed_object_id, 'action': action, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/object-changes', params=params) - - async def get_object_change(self, id: int) -> Dict: - """Get a specific object change by ID.""" - return self.client.get(f'{self.base_endpoint}/object-changes', id) - - # ==================== Scripts ==================== - - async def list_scripts(self, **kwargs) -> List[Dict]: - """List all available scripts.""" - return self.client.list(f'{self.base_endpoint}/scripts', params=kwargs) - - async def get_script(self, id: str) -> Dict: - """Get a specific script by ID.""" - return self.client.get(f'{self.base_endpoint}/scripts', id) - - async def run_script(self, id: str, data: Dict[str, Any], commit: bool = True) -> Dict: - """Run a script with the provided data.""" - payload = {'data': data, 'commit': commit} - return self.client.create(f'{self.base_endpoint}/scripts/{id}', payload) - - # ==================== Reports ==================== - - async def list_reports(self, **kwargs) -> List[Dict]: - """List all available reports.""" - return self.client.list(f'{self.base_endpoint}/reports', params=kwargs) - - async def get_report(self, id: str) -> Dict: - """Get a specific report by ID.""" - return self.client.get(f'{self.base_endpoint}/reports', id) - - async def run_report(self, id: str) -> Dict: - """Run a report.""" - return self.client.create(f'{self.base_endpoint}/reports/{id}', {}) diff --git a/mcp-servers/netbox/mcp_server/tools/ipam.py b/mcp-servers/netbox/mcp_server/tools/ipam.py index 336b3d2..7ef51d2 100644 --- a/mcp-servers/netbox/mcp_server/tools/ipam.py +++ b/mcp-servers/netbox/mcp_server/tools/ipam.py @@ -1,7 +1,7 @@ """ IPAM (IP Address Management) tools for NetBox MCP Server. -Covers: IP Addresses, Prefixes, VLANs, VRFs, ASNs, and related models. +Covers: IP Addresses, Prefixes, and Services only. """ import logging from typing import List, Dict, Optional, Any @@ -17,164 +17,6 @@ class IPAMTools: self.client = client self.base_endpoint = 'ipam' - # ==================== ASN Ranges ==================== - - async def list_asn_ranges(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all ASN ranges.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/asn-ranges', params=params) - - async def get_asn_range(self, id: int) -> Dict: - """Get a specific ASN range by ID.""" - return self.client.get(f'{self.base_endpoint}/asn-ranges', id) - - async def create_asn_range(self, name: str, slug: str, rir: int, start: int, end: int, **kwargs) -> Dict: - """Create a new ASN range.""" - data = {'name': name, 'slug': slug, 'rir': rir, 'start': start, 'end': end, **kwargs} - return self.client.create(f'{self.base_endpoint}/asn-ranges', data) - - async def update_asn_range(self, id: int, **kwargs) -> Dict: - """Update an ASN range.""" - return self.client.patch(f'{self.base_endpoint}/asn-ranges', id, kwargs) - - async def delete_asn_range(self, id: int) -> None: - """Delete an ASN range.""" - self.client.delete(f'{self.base_endpoint}/asn-ranges', id) - - # ==================== ASNs ==================== - - async def list_asns( - self, - asn: Optional[int] = None, - rir_id: Optional[int] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all ASNs.""" - params = {k: v for k, v in { - 'asn': asn, 'rir_id': rir_id, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/asns', params=params) - - async def get_asn(self, id: int) -> Dict: - """Get a specific ASN by ID.""" - return self.client.get(f'{self.base_endpoint}/asns', id) - - async def create_asn( - self, - asn: int, - rir: int, - tenant: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new ASN.""" - data = {'asn': asn, 'rir': rir, **kwargs} - if tenant: - data['tenant'] = tenant - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/asns', data) - - async def update_asn(self, id: int, **kwargs) -> Dict: - """Update an ASN.""" - return self.client.patch(f'{self.base_endpoint}/asns', id, kwargs) - - async def delete_asn(self, id: int) -> None: - """Delete an ASN.""" - self.client.delete(f'{self.base_endpoint}/asns', id) - - # ==================== RIRs ==================== - - async def list_rirs(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all RIRs (Regional Internet Registries).""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/rirs', params=params) - - async def get_rir(self, id: int) -> Dict: - """Get a specific RIR by ID.""" - return self.client.get(f'{self.base_endpoint}/rirs', id) - - async def create_rir(self, name: str, slug: str, is_private: bool = False, **kwargs) -> Dict: - """Create a new RIR.""" - data = {'name': name, 'slug': slug, 'is_private': is_private, **kwargs} - return self.client.create(f'{self.base_endpoint}/rirs', data) - - async def update_rir(self, id: int, **kwargs) -> Dict: - """Update a RIR.""" - return self.client.patch(f'{self.base_endpoint}/rirs', id, kwargs) - - async def delete_rir(self, id: int) -> None: - """Delete a RIR.""" - self.client.delete(f'{self.base_endpoint}/rirs', id) - - # ==================== Aggregates ==================== - - async def list_aggregates( - self, - prefix: Optional[str] = None, - rir_id: Optional[int] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all aggregates.""" - params = {k: v for k, v in { - 'prefix': prefix, 'rir_id': rir_id, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/aggregates', params=params) - - async def get_aggregate(self, id: int) -> Dict: - """Get a specific aggregate by ID.""" - return self.client.get(f'{self.base_endpoint}/aggregates', id) - - async def create_aggregate( - self, - prefix: str, - rir: int, - tenant: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new aggregate.""" - data = {'prefix': prefix, 'rir': rir, **kwargs} - if tenant: - data['tenant'] = tenant - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/aggregates', data) - - async def update_aggregate(self, id: int, **kwargs) -> Dict: - """Update an aggregate.""" - return self.client.patch(f'{self.base_endpoint}/aggregates', id, kwargs) - - async def delete_aggregate(self, id: int) -> None: - """Delete an aggregate.""" - self.client.delete(f'{self.base_endpoint}/aggregates', id) - - # ==================== Roles ==================== - - async def list_roles(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all IPAM roles.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/roles', params=params) - - async def get_role(self, id: int) -> Dict: - """Get a specific role by ID.""" - return self.client.get(f'{self.base_endpoint}/roles', id) - - async def create_role(self, name: str, slug: str, weight: int = 1000, **kwargs) -> Dict: - """Create a new IPAM role.""" - data = {'name': name, 'slug': slug, 'weight': weight, **kwargs} - return self.client.create(f'{self.base_endpoint}/roles', data) - - async def update_role(self, id: int, **kwargs) -> Dict: - """Update a role.""" - return self.client.patch(f'{self.base_endpoint}/roles', id, kwargs) - - async def delete_role(self, id: int) -> None: - """Delete a role.""" - self.client.delete(f'{self.base_endpoint}/roles', id) - # ==================== Prefixes ==================== async def list_prefixes( @@ -230,83 +72,6 @@ class IPAMTools: data[key] = val return self.client.create(f'{self.base_endpoint}/prefixes', data) - async def update_prefix(self, id: int, **kwargs) -> Dict: - """Update a prefix.""" - return self.client.patch(f'{self.base_endpoint}/prefixes', id, kwargs) - - async def delete_prefix(self, id: int) -> None: - """Delete a prefix.""" - self.client.delete(f'{self.base_endpoint}/prefixes', id) - - async def list_available_prefixes(self, id: int) -> List[Dict]: - """List available child prefixes within a prefix.""" - return self.client.list(f'{self.base_endpoint}/prefixes/{id}/available-prefixes', paginate=False) - - async def create_available_prefix(self, id: int, prefix_length: int, **kwargs) -> Dict: - """Create a new prefix from available space.""" - data = {'prefix_length': prefix_length, **kwargs} - return self.client.create(f'{self.base_endpoint}/prefixes/{id}/available-prefixes', data) - - async def list_available_ips(self, id: int) -> List[Dict]: - """List available IP addresses within a prefix.""" - return self.client.list(f'{self.base_endpoint}/prefixes/{id}/available-ips', paginate=False) - - async def create_available_ip(self, id: int, **kwargs) -> Dict: - """Create a new IP address from available space in prefix.""" - return self.client.create(f'{self.base_endpoint}/prefixes/{id}/available-ips', kwargs) - - # ==================== IP Ranges ==================== - - async def list_ip_ranges( - self, - start_address: Optional[str] = None, - end_address: Optional[str] = None, - vrf_id: Optional[int] = None, - tenant_id: Optional[int] = None, - status: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all IP ranges.""" - params = {k: v for k, v in { - 'start_address': start_address, 'end_address': end_address, - 'vrf_id': vrf_id, 'tenant_id': tenant_id, 'status': status, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/ip-ranges', params=params) - - async def get_ip_range(self, id: int) -> Dict: - """Get a specific IP range by ID.""" - return self.client.get(f'{self.base_endpoint}/ip-ranges', id) - - async def create_ip_range( - self, - start_address: str, - end_address: str, - status: str = 'active', - vrf: Optional[int] = None, - tenant: Optional[int] = None, - role: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new IP range.""" - data = {'start_address': start_address, 'end_address': end_address, 'status': status, **kwargs} - for key, val in [('vrf', vrf), ('tenant', tenant), ('role', role), ('description', description)]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/ip-ranges', data) - - async def update_ip_range(self, id: int, **kwargs) -> Dict: - """Update an IP range.""" - return self.client.patch(f'{self.base_endpoint}/ip-ranges', id, kwargs) - - async def delete_ip_range(self, id: int) -> None: - """Delete an IP range.""" - self.client.delete(f'{self.base_endpoint}/ip-ranges', id) - - async def list_available_ips_in_range(self, id: int) -> List[Dict]: - """List available IP addresses within an IP range.""" - return self.client.list(f'{self.base_endpoint}/ip-ranges/{id}/available-ips', paginate=False) - # ==================== IP Addresses ==================== async def list_ip_addresses( @@ -368,271 +133,6 @@ class IPAMTools: """Update an IP address.""" return self.client.patch(f'{self.base_endpoint}/ip-addresses', id, kwargs) - async def delete_ip_address(self, id: int) -> None: - """Delete an IP address.""" - self.client.delete(f'{self.base_endpoint}/ip-addresses', id) - - # ==================== FHRP Groups ==================== - - async def list_fhrp_groups( - self, - protocol: Optional[str] = None, - group_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all FHRP groups.""" - params = {k: v for k, v in {'protocol': protocol, 'group_id': group_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/fhrp-groups', params=params) - - async def get_fhrp_group(self, id: int) -> Dict: - """Get a specific FHRP group by ID.""" - return self.client.get(f'{self.base_endpoint}/fhrp-groups', id) - - async def create_fhrp_group( - self, - protocol: str, - group_id: int, - auth_type: Optional[str] = None, - auth_key: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new FHRP group.""" - data = {'protocol': protocol, 'group_id': group_id, **kwargs} - if auth_type: - data['auth_type'] = auth_type - if auth_key: - data['auth_key'] = auth_key - return self.client.create(f'{self.base_endpoint}/fhrp-groups', data) - - async def update_fhrp_group(self, id: int, **kwargs) -> Dict: - """Update an FHRP group.""" - return self.client.patch(f'{self.base_endpoint}/fhrp-groups', id, kwargs) - - async def delete_fhrp_group(self, id: int) -> None: - """Delete an FHRP group.""" - self.client.delete(f'{self.base_endpoint}/fhrp-groups', id) - - # ==================== FHRP Group Assignments ==================== - - async def list_fhrp_group_assignments(self, group_id: Optional[int] = None, **kwargs) -> List[Dict]: - """List all FHRP group assignments.""" - params = {k: v for k, v in {'group_id': group_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/fhrp-group-assignments', params=params) - - async def get_fhrp_group_assignment(self, id: int) -> Dict: - """Get a specific FHRP group assignment by ID.""" - return self.client.get(f'{self.base_endpoint}/fhrp-group-assignments', id) - - async def create_fhrp_group_assignment( - self, - group: int, - interface_type: str, - interface_id: int, - priority: int = 100, - **kwargs - ) -> Dict: - """Create a new FHRP group assignment.""" - data = { - 'group': group, 'interface_type': interface_type, - 'interface_id': interface_id, 'priority': priority, **kwargs - } - return self.client.create(f'{self.base_endpoint}/fhrp-group-assignments', data) - - async def update_fhrp_group_assignment(self, id: int, **kwargs) -> Dict: - """Update an FHRP group assignment.""" - return self.client.patch(f'{self.base_endpoint}/fhrp-group-assignments', id, kwargs) - - async def delete_fhrp_group_assignment(self, id: int) -> None: - """Delete an FHRP group assignment.""" - self.client.delete(f'{self.base_endpoint}/fhrp-group-assignments', id) - - # ==================== VLAN Groups ==================== - - async def list_vlan_groups( - self, - name: Optional[str] = None, - site_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all VLAN groups.""" - params = {k: v for k, v in {'name': name, 'site_id': site_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/vlan-groups', params=params) - - async def get_vlan_group(self, id: int) -> Dict: - """Get a specific VLAN group by ID.""" - return self.client.get(f'{self.base_endpoint}/vlan-groups', id) - - async def create_vlan_group( - self, - name: str, - slug: str, - scope_type: Optional[str] = None, - scope_id: Optional[int] = None, - min_vid: int = 1, - max_vid: int = 4094, - **kwargs - ) -> Dict: - """Create a new VLAN group.""" - data = {'name': name, 'slug': slug, 'min_vid': min_vid, 'max_vid': max_vid, **kwargs} - if scope_type: - data['scope_type'] = scope_type - if scope_id: - data['scope_id'] = scope_id - return self.client.create(f'{self.base_endpoint}/vlan-groups', data) - - async def update_vlan_group(self, id: int, **kwargs) -> Dict: - """Update a VLAN group.""" - return self.client.patch(f'{self.base_endpoint}/vlan-groups', id, kwargs) - - async def delete_vlan_group(self, id: int) -> None: - """Delete a VLAN group.""" - self.client.delete(f'{self.base_endpoint}/vlan-groups', id) - - async def list_available_vlans(self, id: int) -> List[Dict]: - """List available VLANs in a VLAN group.""" - return self.client.list(f'{self.base_endpoint}/vlan-groups/{id}/available-vlans', paginate=False) - - # ==================== VLANs ==================== - - async def list_vlans( - self, - vid: Optional[int] = None, - name: Optional[str] = None, - site_id: Optional[int] = None, - group_id: Optional[int] = None, - role_id: Optional[int] = None, - tenant_id: Optional[int] = None, - status: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all VLANs with optional filtering.""" - params = {k: v for k, v in { - 'vid': vid, 'name': name, 'site_id': site_id, 'group_id': group_id, - 'role_id': role_id, 'tenant_id': tenant_id, 'status': status, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/vlans', params=params) - - async def get_vlan(self, id: int) -> Dict: - """Get a specific VLAN by ID.""" - return self.client.get(f'{self.base_endpoint}/vlans', id) - - async def create_vlan( - self, - vid: int, - name: str, - status: str = 'active', - site: Optional[int] = None, - group: Optional[int] = None, - role: Optional[int] = None, - tenant: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new VLAN.""" - data = {'vid': vid, 'name': name, 'status': status, **kwargs} - for key, val in [ - ('site', site), ('group', group), ('role', role), - ('tenant', tenant), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/vlans', data) - - async def update_vlan(self, id: int, **kwargs) -> Dict: - """Update a VLAN.""" - return self.client.patch(f'{self.base_endpoint}/vlans', id, kwargs) - - async def delete_vlan(self, id: int) -> None: - """Delete a VLAN.""" - self.client.delete(f'{self.base_endpoint}/vlans', id) - - # ==================== VRFs ==================== - - async def list_vrfs( - self, - name: Optional[str] = None, - rd: Optional[str] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all VRFs with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'rd': rd, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/vrfs', params=params) - - async def get_vrf(self, id: int) -> Dict: - """Get a specific VRF by ID.""" - return self.client.get(f'{self.base_endpoint}/vrfs', id) - - async def create_vrf( - self, - name: str, - rd: Optional[str] = None, - tenant: Optional[int] = None, - enforce_unique: bool = True, - description: Optional[str] = None, - import_targets: Optional[List[int]] = None, - export_targets: Optional[List[int]] = None, - **kwargs - ) -> Dict: - """Create a new VRF.""" - data = {'name': name, 'enforce_unique': enforce_unique, **kwargs} - for key, val in [ - ('rd', rd), ('tenant', tenant), ('description', description), - ('import_targets', import_targets), ('export_targets', export_targets) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/vrfs', data) - - async def update_vrf(self, id: int, **kwargs) -> Dict: - """Update a VRF.""" - return self.client.patch(f'{self.base_endpoint}/vrfs', id, kwargs) - - async def delete_vrf(self, id: int) -> None: - """Delete a VRF.""" - self.client.delete(f'{self.base_endpoint}/vrfs', id) - - # ==================== Route Targets ==================== - - async def list_route_targets( - self, - name: Optional[str] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all route targets.""" - params = {k: v for k, v in {'name': name, 'tenant_id': tenant_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/route-targets', params=params) - - async def get_route_target(self, id: int) -> Dict: - """Get a specific route target by ID.""" - return self.client.get(f'{self.base_endpoint}/route-targets', id) - - async def create_route_target( - self, - name: str, - tenant: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new route target.""" - data = {'name': name, **kwargs} - if tenant: - data['tenant'] = tenant - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/route-targets', data) - - async def update_route_target(self, id: int, **kwargs) -> Dict: - """Update a route target.""" - return self.client.patch(f'{self.base_endpoint}/route-targets', id, kwargs) - - async def delete_route_target(self, id: int) -> None: - """Delete a route target.""" - self.client.delete(f'{self.base_endpoint}/route-targets', id) - # ==================== Services ==================== async def list_services( @@ -675,44 +175,3 @@ class IPAMTools: if val is not None: data[key] = val return self.client.create(f'{self.base_endpoint}/services', data) - - async def update_service(self, id: int, **kwargs) -> Dict: - """Update a service.""" - return self.client.patch(f'{self.base_endpoint}/services', id, kwargs) - - async def delete_service(self, id: int) -> None: - """Delete a service.""" - self.client.delete(f'{self.base_endpoint}/services', id) - - # ==================== Service Templates ==================== - - async def list_service_templates(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all service templates.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/service-templates', params=params) - - async def get_service_template(self, id: int) -> Dict: - """Get a specific service template by ID.""" - return self.client.get(f'{self.base_endpoint}/service-templates', id) - - async def create_service_template( - self, - name: str, - ports: List[int], - protocol: str, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new service template.""" - data = {'name': name, 'ports': ports, 'protocol': protocol, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/service-templates', data) - - async def update_service_template(self, id: int, **kwargs) -> Dict: - """Update a service template.""" - return self.client.patch(f'{self.base_endpoint}/service-templates', id, kwargs) - - async def delete_service_template(self, id: int) -> None: - """Delete a service template.""" - self.client.delete(f'{self.base_endpoint}/service-templates', id) diff --git a/mcp-servers/netbox/mcp_server/tools/tenancy.py b/mcp-servers/netbox/mcp_server/tools/tenancy.py deleted file mode 100644 index bb1e3b7..0000000 --- a/mcp-servers/netbox/mcp_server/tools/tenancy.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -Tenancy tools for NetBox MCP Server. - -Covers: Tenants, Tenant Groups, Contacts, Contact Groups, and Contact Roles. -""" -import logging -from typing import List, Dict, Optional, Any -from ..netbox_client import NetBoxClient - -logger = logging.getLogger(__name__) - - -class TenancyTools: - """Tools for Tenancy operations in NetBox""" - - def __init__(self, client: NetBoxClient): - self.client = client - self.base_endpoint = 'tenancy' - - # ==================== Tenant Groups ==================== - - async def list_tenant_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - parent_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all tenant groups.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/tenant-groups', params=params) - - async def get_tenant_group(self, id: int) -> Dict: - """Get a specific tenant group by ID.""" - return self.client.get(f'{self.base_endpoint}/tenant-groups', id) - - async def create_tenant_group( - self, - name: str, - slug: str, - parent: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new tenant group.""" - data = {'name': name, 'slug': slug, **kwargs} - if parent: - data['parent'] = parent - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/tenant-groups', data) - - async def update_tenant_group(self, id: int, **kwargs) -> Dict: - """Update a tenant group.""" - return self.client.patch(f'{self.base_endpoint}/tenant-groups', id, kwargs) - - async def delete_tenant_group(self, id: int) -> None: - """Delete a tenant group.""" - self.client.delete(f'{self.base_endpoint}/tenant-groups', id) - - # ==================== Tenants ==================== - - async def list_tenants( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - group_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all tenants with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'group_id': group_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/tenants', params=params) - - async def get_tenant(self, id: int) -> Dict: - """Get a specific tenant by ID.""" - return self.client.get(f'{self.base_endpoint}/tenants', id) - - async def create_tenant( - self, - name: str, - slug: str, - group: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new tenant.""" - data = {'name': name, 'slug': slug, **kwargs} - if group: - data['group'] = group - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/tenants', data) - - async def update_tenant(self, id: int, **kwargs) -> Dict: - """Update a tenant.""" - return self.client.patch(f'{self.base_endpoint}/tenants', id, kwargs) - - async def delete_tenant(self, id: int) -> None: - """Delete a tenant.""" - self.client.delete(f'{self.base_endpoint}/tenants', id) - - # ==================== Contact Groups ==================== - - async def list_contact_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - parent_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all contact groups.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/contact-groups', params=params) - - async def get_contact_group(self, id: int) -> Dict: - """Get a specific contact group by ID.""" - return self.client.get(f'{self.base_endpoint}/contact-groups', id) - - async def create_contact_group( - self, - name: str, - slug: str, - parent: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new contact group.""" - data = {'name': name, 'slug': slug, **kwargs} - if parent: - data['parent'] = parent - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/contact-groups', data) - - async def update_contact_group(self, id: int, **kwargs) -> Dict: - """Update a contact group.""" - return self.client.patch(f'{self.base_endpoint}/contact-groups', id, kwargs) - - async def delete_contact_group(self, id: int) -> None: - """Delete a contact group.""" - self.client.delete(f'{self.base_endpoint}/contact-groups', id) - - # ==================== Contact Roles ==================== - - async def list_contact_roles( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all contact roles.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/contact-roles', params=params) - - async def get_contact_role(self, id: int) -> Dict: - """Get a specific contact role by ID.""" - return self.client.get(f'{self.base_endpoint}/contact-roles', id) - - async def create_contact_role( - self, - name: str, - slug: str, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new contact role.""" - data = {'name': name, 'slug': slug, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/contact-roles', data) - - async def update_contact_role(self, id: int, **kwargs) -> Dict: - """Update a contact role.""" - return self.client.patch(f'{self.base_endpoint}/contact-roles', id, kwargs) - - async def delete_contact_role(self, id: int) -> None: - """Delete a contact role.""" - self.client.delete(f'{self.base_endpoint}/contact-roles', id) - - # ==================== Contacts ==================== - - async def list_contacts( - self, - name: Optional[str] = None, - group_id: Optional[int] = None, - email: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all contacts with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'group_id': group_id, 'email': email, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/contacts', params=params) - - async def get_contact(self, id: int) -> Dict: - """Get a specific contact by ID.""" - return self.client.get(f'{self.base_endpoint}/contacts', id) - - async def create_contact( - self, - name: str, - group: Optional[int] = None, - title: Optional[str] = None, - phone: Optional[str] = None, - email: Optional[str] = None, - address: Optional[str] = None, - link: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new contact.""" - data = {'name': name, **kwargs} - for key, val in [ - ('group', group), ('title', title), ('phone', phone), - ('email', email), ('address', address), ('link', link), - ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/contacts', data) - - async def update_contact(self, id: int, **kwargs) -> Dict: - """Update a contact.""" - return self.client.patch(f'{self.base_endpoint}/contacts', id, kwargs) - - async def delete_contact(self, id: int) -> None: - """Delete a contact.""" - self.client.delete(f'{self.base_endpoint}/contacts', id) - - # ==================== Contact Assignments ==================== - - async def list_contact_assignments( - self, - contact_id: Optional[int] = None, - role_id: Optional[int] = None, - object_type: Optional[str] = None, - object_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all contact assignments.""" - params = {k: v for k, v in { - 'contact_id': contact_id, 'role_id': role_id, - 'object_type': object_type, 'object_id': object_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/contact-assignments', params=params) - - async def get_contact_assignment(self, id: int) -> Dict: - """Get a specific contact assignment by ID.""" - return self.client.get(f'{self.base_endpoint}/contact-assignments', id) - - async def create_contact_assignment( - self, - contact: int, - role: int, - object_type: str, - object_id: int, - priority: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new contact assignment.""" - data = { - 'contact': contact, 'role': role, - 'object_type': object_type, 'object_id': object_id, **kwargs - } - if priority: - data['priority'] = priority - return self.client.create(f'{self.base_endpoint}/contact-assignments', data) - - async def update_contact_assignment(self, id: int, **kwargs) -> Dict: - """Update a contact assignment.""" - return self.client.patch(f'{self.base_endpoint}/contact-assignments', id, kwargs) - - async def delete_contact_assignment(self, id: int) -> None: - """Delete a contact assignment.""" - self.client.delete(f'{self.base_endpoint}/contact-assignments', id) diff --git a/mcp-servers/netbox/mcp_server/tools/virtualization.py b/mcp-servers/netbox/mcp_server/tools/virtualization.py index 9bf3e84..21a7241 100644 --- a/mcp-servers/netbox/mcp_server/tools/virtualization.py +++ b/mcp-servers/netbox/mcp_server/tools/virtualization.py @@ -1,7 +1,7 @@ """ Virtualization tools for NetBox MCP Server. -Covers: Clusters, Virtual Machines, VM Interfaces, and related models. +Covers: Clusters, Virtual Machines, and VM Interfaces only. """ import logging from typing import List, Dict, Optional, Any @@ -17,80 +17,6 @@ class VirtualizationTools: self.client = client self.base_endpoint = 'virtualization' - # ==================== Cluster Types ==================== - - async def list_cluster_types( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all cluster types.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/cluster-types', params=params) - - async def get_cluster_type(self, id: int) -> Dict: - """Get a specific cluster type by ID.""" - return self.client.get(f'{self.base_endpoint}/cluster-types', id) - - async def create_cluster_type( - self, - name: str, - slug: str, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new cluster type.""" - data = {'name': name, 'slug': slug, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/cluster-types', data) - - async def update_cluster_type(self, id: int, **kwargs) -> Dict: - """Update a cluster type.""" - return self.client.patch(f'{self.base_endpoint}/cluster-types', id, kwargs) - - async def delete_cluster_type(self, id: int) -> None: - """Delete a cluster type.""" - self.client.delete(f'{self.base_endpoint}/cluster-types', id) - - # ==================== Cluster Groups ==================== - - async def list_cluster_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all cluster groups.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/cluster-groups', params=params) - - async def get_cluster_group(self, id: int) -> Dict: - """Get a specific cluster group by ID.""" - return self.client.get(f'{self.base_endpoint}/cluster-groups', id) - - async def create_cluster_group( - self, - name: str, - slug: str, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new cluster group.""" - data = {'name': name, 'slug': slug, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/cluster-groups', data) - - async def update_cluster_group(self, id: int, **kwargs) -> Dict: - """Update a cluster group.""" - return self.client.patch(f'{self.base_endpoint}/cluster-groups', id, kwargs) - - async def delete_cluster_group(self, id: int) -> None: - """Delete a cluster group.""" - self.client.delete(f'{self.base_endpoint}/cluster-groups', id) - # ==================== Clusters ==================== async def list_clusters( @@ -134,14 +60,6 @@ class VirtualizationTools: data[key] = val return self.client.create(f'{self.base_endpoint}/clusters', data) - async def update_cluster(self, id: int, **kwargs) -> Dict: - """Update a cluster.""" - return self.client.patch(f'{self.base_endpoint}/clusters', id, kwargs) - - async def delete_cluster(self, id: int) -> None: - """Delete a cluster.""" - self.client.delete(f'{self.base_endpoint}/clusters', id) - # ==================== Virtual Machines ==================== async def list_virtual_machines( @@ -201,10 +119,6 @@ class VirtualizationTools: """Update a virtual machine.""" return self.client.patch(f'{self.base_endpoint}/virtual-machines', id, kwargs) - async def delete_virtual_machine(self, id: int) -> None: - """Delete a virtual machine.""" - self.client.delete(f'{self.base_endpoint}/virtual-machines', id) - # ==================== VM Interfaces ==================== async def list_vm_interfaces( @@ -246,51 +160,3 @@ class VirtualizationTools: if val is not None: data[key] = val return self.client.create(f'{self.base_endpoint}/interfaces', data) - - async def update_vm_interface(self, id: int, **kwargs) -> Dict: - """Update a VM interface.""" - return self.client.patch(f'{self.base_endpoint}/interfaces', id, kwargs) - - async def delete_vm_interface(self, id: int) -> None: - """Delete a VM interface.""" - self.client.delete(f'{self.base_endpoint}/interfaces', id) - - # ==================== Virtual Disks ==================== - - async def list_virtual_disks( - self, - virtual_machine_id: Optional[int] = None, - name: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all virtual disks.""" - params = {k: v for k, v in { - 'virtual_machine_id': virtual_machine_id, 'name': name, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/virtual-disks', params=params) - - async def get_virtual_disk(self, id: int) -> Dict: - """Get a specific virtual disk by ID.""" - return self.client.get(f'{self.base_endpoint}/virtual-disks', id) - - async def create_virtual_disk( - self, - virtual_machine: int, - name: str, - size: int, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new virtual disk.""" - data = {'virtual_machine': virtual_machine, 'name': name, 'size': size, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/virtual-disks', data) - - async def update_virtual_disk(self, id: int, **kwargs) -> Dict: - """Update a virtual disk.""" - return self.client.patch(f'{self.base_endpoint}/virtual-disks', id, kwargs) - - async def delete_virtual_disk(self, id: int) -> None: - """Delete a virtual disk.""" - self.client.delete(f'{self.base_endpoint}/virtual-disks', id) diff --git a/mcp-servers/netbox/mcp_server/tools/vpn.py b/mcp-servers/netbox/mcp_server/tools/vpn.py deleted file mode 100644 index e936e74..0000000 --- a/mcp-servers/netbox/mcp_server/tools/vpn.py +++ /dev/null @@ -1,428 +0,0 @@ -""" -VPN tools for NetBox MCP Server. - -Covers: Tunnels, Tunnel Groups, Tunnel Terminations, IKE/IPSec Policies, and L2VPN. -""" -import logging -from typing import List, Dict, Optional, Any -from ..netbox_client import NetBoxClient - -logger = logging.getLogger(__name__) - - -class VPNTools: - """Tools for VPN operations in NetBox""" - - def __init__(self, client: NetBoxClient): - self.client = client - self.base_endpoint = 'vpn' - - # ==================== Tunnel Groups ==================== - - async def list_tunnel_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all tunnel groups.""" - params = {k: v for k, v in {'name': name, 'slug': slug, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/tunnel-groups', params=params) - - async def get_tunnel_group(self, id: int) -> Dict: - """Get a specific tunnel group by ID.""" - return self.client.get(f'{self.base_endpoint}/tunnel-groups', id) - - async def create_tunnel_group( - self, - name: str, - slug: str, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new tunnel group.""" - data = {'name': name, 'slug': slug, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/tunnel-groups', data) - - async def update_tunnel_group(self, id: int, **kwargs) -> Dict: - """Update a tunnel group.""" - return self.client.patch(f'{self.base_endpoint}/tunnel-groups', id, kwargs) - - async def delete_tunnel_group(self, id: int) -> None: - """Delete a tunnel group.""" - self.client.delete(f'{self.base_endpoint}/tunnel-groups', id) - - # ==================== Tunnels ==================== - - async def list_tunnels( - self, - name: Optional[str] = None, - status: Optional[str] = None, - group_id: Optional[int] = None, - encapsulation: Optional[str] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all tunnels with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'status': status, 'group_id': group_id, - 'encapsulation': encapsulation, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/tunnels', params=params) - - async def get_tunnel(self, id: int) -> Dict: - """Get a specific tunnel by ID.""" - return self.client.get(f'{self.base_endpoint}/tunnels', id) - - async def create_tunnel( - self, - name: str, - status: str = 'active', - encapsulation: str = 'ipsec-tunnel', - group: Optional[int] = None, - ipsec_profile: Optional[int] = None, - tenant: Optional[int] = None, - tunnel_id: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new tunnel.""" - data = {'name': name, 'status': status, 'encapsulation': encapsulation, **kwargs} - for key, val in [ - ('group', group), ('ipsec_profile', ipsec_profile), - ('tenant', tenant), ('tunnel_id', tunnel_id), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/tunnels', data) - - async def update_tunnel(self, id: int, **kwargs) -> Dict: - """Update a tunnel.""" - return self.client.patch(f'{self.base_endpoint}/tunnels', id, kwargs) - - async def delete_tunnel(self, id: int) -> None: - """Delete a tunnel.""" - self.client.delete(f'{self.base_endpoint}/tunnels', id) - - # ==================== Tunnel Terminations ==================== - - async def list_tunnel_terminations( - self, - tunnel_id: Optional[int] = None, - role: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all tunnel terminations.""" - params = {k: v for k, v in { - 'tunnel_id': tunnel_id, 'role': role, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/tunnel-terminations', params=params) - - async def get_tunnel_termination(self, id: int) -> Dict: - """Get a specific tunnel termination by ID.""" - return self.client.get(f'{self.base_endpoint}/tunnel-terminations', id) - - async def create_tunnel_termination( - self, - tunnel: int, - role: str, - termination_type: str, - termination_id: int, - outside_ip: Optional[int] = None, - **kwargs - ) -> Dict: - """Create a new tunnel termination.""" - data = { - 'tunnel': tunnel, 'role': role, - 'termination_type': termination_type, 'termination_id': termination_id, **kwargs - } - if outside_ip: - data['outside_ip'] = outside_ip - return self.client.create(f'{self.base_endpoint}/tunnel-terminations', data) - - async def update_tunnel_termination(self, id: int, **kwargs) -> Dict: - """Update a tunnel termination.""" - return self.client.patch(f'{self.base_endpoint}/tunnel-terminations', id, kwargs) - - async def delete_tunnel_termination(self, id: int) -> None: - """Delete a tunnel termination.""" - self.client.delete(f'{self.base_endpoint}/tunnel-terminations', id) - - # ==================== IKE Proposals ==================== - - async def list_ike_proposals(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all IKE proposals.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/ike-proposals', params=params) - - async def get_ike_proposal(self, id: int) -> Dict: - """Get a specific IKE proposal by ID.""" - return self.client.get(f'{self.base_endpoint}/ike-proposals', id) - - async def create_ike_proposal( - self, - name: str, - authentication_method: str, - encryption_algorithm: str, - authentication_algorithm: str, - group: int, - sa_lifetime: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new IKE proposal.""" - data = { - 'name': name, 'authentication_method': authentication_method, - 'encryption_algorithm': encryption_algorithm, - 'authentication_algorithm': authentication_algorithm, 'group': group, **kwargs - } - if sa_lifetime: - data['sa_lifetime'] = sa_lifetime - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/ike-proposals', data) - - async def update_ike_proposal(self, id: int, **kwargs) -> Dict: - """Update an IKE proposal.""" - return self.client.patch(f'{self.base_endpoint}/ike-proposals', id, kwargs) - - async def delete_ike_proposal(self, id: int) -> None: - """Delete an IKE proposal.""" - self.client.delete(f'{self.base_endpoint}/ike-proposals', id) - - # ==================== IKE Policies ==================== - - async def list_ike_policies(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all IKE policies.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/ike-policies', params=params) - - async def get_ike_policy(self, id: int) -> Dict: - """Get a specific IKE policy by ID.""" - return self.client.get(f'{self.base_endpoint}/ike-policies', id) - - async def create_ike_policy( - self, - name: str, - version: int, - mode: str, - proposals: List[int], - preshared_key: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new IKE policy.""" - data = {'name': name, 'version': version, 'mode': mode, 'proposals': proposals, **kwargs} - if preshared_key: - data['preshared_key'] = preshared_key - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/ike-policies', data) - - async def update_ike_policy(self, id: int, **kwargs) -> Dict: - """Update an IKE policy.""" - return self.client.patch(f'{self.base_endpoint}/ike-policies', id, kwargs) - - async def delete_ike_policy(self, id: int) -> None: - """Delete an IKE policy.""" - self.client.delete(f'{self.base_endpoint}/ike-policies', id) - - # ==================== IPSec Proposals ==================== - - async def list_ipsec_proposals(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all IPSec proposals.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/ipsec-proposals', params=params) - - async def get_ipsec_proposal(self, id: int) -> Dict: - """Get a specific IPSec proposal by ID.""" - return self.client.get(f'{self.base_endpoint}/ipsec-proposals', id) - - async def create_ipsec_proposal( - self, - name: str, - encryption_algorithm: str, - authentication_algorithm: str, - sa_lifetime_seconds: Optional[int] = None, - sa_lifetime_data: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new IPSec proposal.""" - data = { - 'name': name, 'encryption_algorithm': encryption_algorithm, - 'authentication_algorithm': authentication_algorithm, **kwargs - } - for key, val in [ - ('sa_lifetime_seconds', sa_lifetime_seconds), - ('sa_lifetime_data', sa_lifetime_data), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/ipsec-proposals', data) - - async def update_ipsec_proposal(self, id: int, **kwargs) -> Dict: - """Update an IPSec proposal.""" - return self.client.patch(f'{self.base_endpoint}/ipsec-proposals', id, kwargs) - - async def delete_ipsec_proposal(self, id: int) -> None: - """Delete an IPSec proposal.""" - self.client.delete(f'{self.base_endpoint}/ipsec-proposals', id) - - # ==================== IPSec Policies ==================== - - async def list_ipsec_policies(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all IPSec policies.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/ipsec-policies', params=params) - - async def get_ipsec_policy(self, id: int) -> Dict: - """Get a specific IPSec policy by ID.""" - return self.client.get(f'{self.base_endpoint}/ipsec-policies', id) - - async def create_ipsec_policy( - self, - name: str, - proposals: List[int], - pfs_group: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new IPSec policy.""" - data = {'name': name, 'proposals': proposals, **kwargs} - if pfs_group: - data['pfs_group'] = pfs_group - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/ipsec-policies', data) - - async def update_ipsec_policy(self, id: int, **kwargs) -> Dict: - """Update an IPSec policy.""" - return self.client.patch(f'{self.base_endpoint}/ipsec-policies', id, kwargs) - - async def delete_ipsec_policy(self, id: int) -> None: - """Delete an IPSec policy.""" - self.client.delete(f'{self.base_endpoint}/ipsec-policies', id) - - # ==================== IPSec Profiles ==================== - - async def list_ipsec_profiles(self, name: Optional[str] = None, **kwargs) -> List[Dict]: - """List all IPSec profiles.""" - params = {k: v for k, v in {'name': name, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/ipsec-profiles', params=params) - - async def get_ipsec_profile(self, id: int) -> Dict: - """Get a specific IPSec profile by ID.""" - return self.client.get(f'{self.base_endpoint}/ipsec-profiles', id) - - async def create_ipsec_profile( - self, - name: str, - mode: str, - ike_policy: int, - ipsec_policy: int, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new IPSec profile.""" - data = {'name': name, 'mode': mode, 'ike_policy': ike_policy, 'ipsec_policy': ipsec_policy, **kwargs} - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/ipsec-profiles', data) - - async def update_ipsec_profile(self, id: int, **kwargs) -> Dict: - """Update an IPSec profile.""" - return self.client.patch(f'{self.base_endpoint}/ipsec-profiles', id, kwargs) - - async def delete_ipsec_profile(self, id: int) -> None: - """Delete an IPSec profile.""" - self.client.delete(f'{self.base_endpoint}/ipsec-profiles', id) - - # ==================== L2VPN ==================== - - async def list_l2vpns( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - type: Optional[str] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all L2VPNs with optional filtering.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'type': type, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/l2vpns', params=params) - - async def get_l2vpn(self, id: int) -> Dict: - """Get a specific L2VPN by ID.""" - return self.client.get(f'{self.base_endpoint}/l2vpns', id) - - async def create_l2vpn( - self, - name: str, - slug: str, - type: str, - identifier: Optional[int] = None, - tenant: Optional[int] = None, - description: Optional[str] = None, - import_targets: Optional[List[int]] = None, - export_targets: Optional[List[int]] = None, - **kwargs - ) -> Dict: - """Create a new L2VPN.""" - data = {'name': name, 'slug': slug, 'type': type, **kwargs} - for key, val in [ - ('identifier', identifier), ('tenant', tenant), ('description', description), - ('import_targets', import_targets), ('export_targets', export_targets) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/l2vpns', data) - - async def update_l2vpn(self, id: int, **kwargs) -> Dict: - """Update an L2VPN.""" - return self.client.patch(f'{self.base_endpoint}/l2vpns', id, kwargs) - - async def delete_l2vpn(self, id: int) -> None: - """Delete an L2VPN.""" - self.client.delete(f'{self.base_endpoint}/l2vpns', id) - - # ==================== L2VPN Terminations ==================== - - async def list_l2vpn_terminations( - self, - l2vpn_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all L2VPN terminations.""" - params = {k: v for k, v in {'l2vpn_id': l2vpn_id, **kwargs}.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/l2vpn-terminations', params=params) - - async def get_l2vpn_termination(self, id: int) -> Dict: - """Get a specific L2VPN termination by ID.""" - return self.client.get(f'{self.base_endpoint}/l2vpn-terminations', id) - - async def create_l2vpn_termination( - self, - l2vpn: int, - assigned_object_type: str, - assigned_object_id: int, - **kwargs - ) -> Dict: - """Create a new L2VPN termination.""" - data = { - 'l2vpn': l2vpn, 'assigned_object_type': assigned_object_type, - 'assigned_object_id': assigned_object_id, **kwargs - } - return self.client.create(f'{self.base_endpoint}/l2vpn-terminations', data) - - async def update_l2vpn_termination(self, id: int, **kwargs) -> Dict: - """Update an L2VPN termination.""" - return self.client.patch(f'{self.base_endpoint}/l2vpn-terminations', id, kwargs) - - async def delete_l2vpn_termination(self, id: int) -> None: - """Delete an L2VPN termination.""" - self.client.delete(f'{self.base_endpoint}/l2vpn-terminations', id) diff --git a/mcp-servers/netbox/mcp_server/tools/wireless.py b/mcp-servers/netbox/mcp_server/tools/wireless.py deleted file mode 100644 index f88626b..0000000 --- a/mcp-servers/netbox/mcp_server/tools/wireless.py +++ /dev/null @@ -1,166 +0,0 @@ -""" -Wireless tools for NetBox MCP Server. - -Covers: Wireless LANs, Wireless LAN Groups, and Wireless Links. -""" -import logging -from typing import List, Dict, Optional, Any -from ..netbox_client import NetBoxClient - -logger = logging.getLogger(__name__) - - -class WirelessTools: - """Tools for Wireless operations in NetBox""" - - def __init__(self, client: NetBoxClient): - self.client = client - self.base_endpoint = 'wireless' - - # ==================== Wireless LAN Groups ==================== - - async def list_wireless_lan_groups( - self, - name: Optional[str] = None, - slug: Optional[str] = None, - parent_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all wireless LAN groups.""" - params = {k: v for k, v in { - 'name': name, 'slug': slug, 'parent_id': parent_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/wireless-lan-groups', params=params) - - async def get_wireless_lan_group(self, id: int) -> Dict: - """Get a specific wireless LAN group by ID.""" - return self.client.get(f'{self.base_endpoint}/wireless-lan-groups', id) - - async def create_wireless_lan_group( - self, - name: str, - slug: str, - parent: Optional[int] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new wireless LAN group.""" - data = {'name': name, 'slug': slug, **kwargs} - if parent: - data['parent'] = parent - if description: - data['description'] = description - return self.client.create(f'{self.base_endpoint}/wireless-lan-groups', data) - - async def update_wireless_lan_group(self, id: int, **kwargs) -> Dict: - """Update a wireless LAN group.""" - return self.client.patch(f'{self.base_endpoint}/wireless-lan-groups', id, kwargs) - - async def delete_wireless_lan_group(self, id: int) -> None: - """Delete a wireless LAN group.""" - self.client.delete(f'{self.base_endpoint}/wireless-lan-groups', id) - - # ==================== Wireless LANs ==================== - - async def list_wireless_lans( - self, - ssid: Optional[str] = None, - group_id: Optional[int] = None, - vlan_id: Optional[int] = None, - tenant_id: Optional[int] = None, - status: Optional[str] = None, - auth_type: Optional[str] = None, - **kwargs - ) -> List[Dict]: - """List all wireless LANs with optional filtering.""" - params = {k: v for k, v in { - 'ssid': ssid, 'group_id': group_id, 'vlan_id': vlan_id, - 'tenant_id': tenant_id, 'status': status, 'auth_type': auth_type, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/wireless-lans', params=params) - - async def get_wireless_lan(self, id: int) -> Dict: - """Get a specific wireless LAN by ID.""" - return self.client.get(f'{self.base_endpoint}/wireless-lans', id) - - async def create_wireless_lan( - self, - ssid: str, - status: str = 'active', - group: Optional[int] = None, - vlan: Optional[int] = None, - tenant: Optional[int] = None, - auth_type: Optional[str] = None, - auth_cipher: Optional[str] = None, - auth_psk: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new wireless LAN.""" - data = {'ssid': ssid, 'status': status, **kwargs} - for key, val in [ - ('group', group), ('vlan', vlan), ('tenant', tenant), - ('auth_type', auth_type), ('auth_cipher', auth_cipher), - ('auth_psk', auth_psk), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/wireless-lans', data) - - async def update_wireless_lan(self, id: int, **kwargs) -> Dict: - """Update a wireless LAN.""" - return self.client.patch(f'{self.base_endpoint}/wireless-lans', id, kwargs) - - async def delete_wireless_lan(self, id: int) -> None: - """Delete a wireless LAN.""" - self.client.delete(f'{self.base_endpoint}/wireless-lans', id) - - # ==================== Wireless Links ==================== - - async def list_wireless_links( - self, - ssid: Optional[str] = None, - status: Optional[str] = None, - tenant_id: Optional[int] = None, - **kwargs - ) -> List[Dict]: - """List all wireless links with optional filtering.""" - params = {k: v for k, v in { - 'ssid': ssid, 'status': status, 'tenant_id': tenant_id, **kwargs - }.items() if v is not None} - return self.client.list(f'{self.base_endpoint}/wireless-links', params=params) - - async def get_wireless_link(self, id: int) -> Dict: - """Get a specific wireless link by ID.""" - return self.client.get(f'{self.base_endpoint}/wireless-links', id) - - async def create_wireless_link( - self, - interface_a: int, - interface_b: int, - ssid: Optional[str] = None, - status: str = 'connected', - tenant: Optional[int] = None, - auth_type: Optional[str] = None, - auth_cipher: Optional[str] = None, - auth_psk: Optional[str] = None, - description: Optional[str] = None, - **kwargs - ) -> Dict: - """Create a new wireless link.""" - data = {'interface_a': interface_a, 'interface_b': interface_b, 'status': status, **kwargs} - for key, val in [ - ('ssid', ssid), ('tenant', tenant), ('auth_type', auth_type), - ('auth_cipher', auth_cipher), ('auth_psk', auth_psk), ('description', description) - ]: - if val is not None: - data[key] = val - return self.client.create(f'{self.base_endpoint}/wireless-links', data) - - async def update_wireless_link(self, id: int, **kwargs) -> Dict: - """Update a wireless link.""" - return self.client.patch(f'{self.base_endpoint}/wireless-links', id, kwargs) - - async def delete_wireless_link(self, id: int) -> None: - """Delete a wireless link.""" - self.client.delete(f'{self.base_endpoint}/wireless-links', id) -- 2.49.1