A read-only Model Context Protocol server that enables LLMs to interact with NetBox infrastructure data. Built with FastMCP and designed for use by NetBox operators.
Your role: Help contributors design and implement features within the project's stated scope (see CONTRIBUTING.md). Challenge proposals that fall outside scope before implementation begins, not after. Ask clarifying questions and challenge assumptions when needed.
- Python: >=3.11, <3.15
- Package Manager: uv
- MCP Framework: FastMCP >=3.0.0
- HTTP Client: httpx
- NetBox API: REST API via token authentication
.
├── src/
│ └── netbox_mcp_server/
│ ├── __init__.py # Package initialization with __version__
│ ├── __main__.py # Entry point for module execution
│ ├── server.py # Main MCP server with tool definitions
│ ├── netbox_client.py # NetBox REST API client abstraction
│ ├── netbox_types.py # NetBox object type mappings
│ └── config.py # Settings and logging configuration
├── tests/ # Test suite
├── .github/workflows/ # CI/CD automation
├── pyproject.toml # Dependencies and project metadata
├── README.md # User-facing documentation
├── CHANGELOG.md # Auto-generated release notes
└── LICENSE # Apache 2.0 license
Design Pattern: Clean separation between MCP server logic (server.py) and NetBox API client (netbox_client.py) to support future plugin-based implementations.
# Install dependencies (ONLY use uv, NEVER pip)
uv sync
# Run the server locally (requires env vars)
NETBOX_URL=https://netbox.example.com/ NETBOX_TOKEN=<token> uv run netbox-mcp-server
# Alternative: module execution
uv run -m netbox_mcp_server
# Add to Claude Code (for development/testing)
claude mcp add --transport stdio netbox \
--env NETBOX_URL=https://netbox.example.com/ \
--env NETBOX_TOKEN=<token> \
-- uv --directory /path/to/netbox-mcp-server run netbox-mcp-server- Simplicity over cleverness: Write simple, straightforward code that's easy to understand
- Readability first: Code is read 10x more than it's written - optimize for the reader
- Build iteratively: Start with minimal functionality, verify it works, then add complexity
- DRY (Don't Repeat Yourself): Extract common patterns, but only after the third occurrence
- Early returns: Use early returns to avoid nested conditions and improve readability
- Descriptive names: Use clear variable and function names that explain intent
- Less code = less debt: Minimize code footprint; the best code is no code at all
- Test frequently: Test with realistic inputs and validate outputs as you build
- Functional where clear: Use functional, stateless approaches when they improve clarity
- Clean core logic: Keep business logic clean; push implementation details to the edges
This project uses python-semantic-release for automated version management. Versions are automatically determined from commit messages following Conventional Commits.
Release triggers:
feat:commits trigger minor version bumps (1.0.0 → 1.1.0)fix:andperf:commits trigger patch version bumps (1.0.0 → 1.0.1)- Commits with
BREAKING CHANGE:in the body trigger major version bumps (1.0.0 → 2.0.0) docs:,test:,chore:,ci:,refactor:commits are logged but don't trigger releases
Workflow:
- Merge to
mainautomatically triggers release analysis - If commits warrant a release, version is bumped and CHANGELOG updated
- GitHub Release is created with auto-generated release notes
- Type hints required: All function parameters and return types must be annotated
- Docstrings: Use Google-style docstrings for all public functions and classes
- Line length: 88 characters maximum (Ruff/Black standard)
- Naming conventions:
- Functions and variables:
snake_case - Classes:
PascalCase - Constants:
UPPER_SNAKE_CASE
- Functions and variables:
- String formatting: Use f-strings for string formatting
- Imports: Absolute imports preferred, group standard library → third-party → local
- Error handling: Never use bare
except- specify exception types (e.g.,except ValueError:); raise descriptive exceptions and let FastMCP handle error responses
# ✅ Good: Clear types, descriptive names, proper error handling, comprehensive docstrings
def netbox_get_objects(object_type: str, filters: dict) -> list[dict]:
"""Get objects from NetBox based on their type and filters.
Args:
object_type: String representing the NetBox object type (e.g. "devices")
filters: Dictionary of filters to apply to the API call
Returns:
Either a single object dict or a list of object dicts
"""
if object_type not in NETBOX_OBJECT_TYPES:
valid_types = "\n".join(f"- {t}" for t in sorted(NETBOX_OBJECT_TYPES.keys()))
raise ValueError(f"Invalid object_type. Must be one of:\n{valid_types}")
return netbox.get(NETBOX_OBJECT_TYPES[object_type], params=filters)
# ❌ Avoid: Missing types, generic names, silent failures, no docstrings
def get_stuff(t, f):
try:
return netbox.get(types[t], params=f)
except:
return None- Abstraction layer:
NetBoxClientBasedefines interface for future ORM implementation - Read-only by design: Only GET operations exposed; no create/update/delete tools
- Environment-based config: All secrets via environment variables, never hardcoded
- Explicit object mapping:
NETBOX_OBJECT_TYPESdictionary maintains allowed types
- Define tool function with
@mcp.tooldecorator - Include comprehensive docstring with args, return types, and examples
- Validate inputs before calling NetBox client
- Return structured data (dict or list); let FastMCP handle serialization
- Use
netbox_prefix for all tools (e.g.,netbox_get_objects) - Use descriptive action verbs:
get,list,search - Keep names intuitive for LLM consumption
Tools support core NetBox object types across these modules:
- DCIM: devices, sites, racks, interfaces, cables, etc.
- IPAM: IP addresses, prefixes, VLANs, VRFs, ASNs
- Circuits: circuits, providers, circuit terminations
- Virtualization: VMs, clusters, VM interfaces
- Tenancy: tenants, contacts, contact groups
- VPN: tunnels, IPsec policies, IKE policies
- Wireless: wireless LANs, wireless links
- Extras: config contexts, custom fields, export templates, image attachments, jobs, saved filters, scripts, tags, webhooks
See NETBOX_OBJECT_TYPES in server.py for complete list.
NETBOX_URL: Base URL of NetBox instance (e.g.,https://netbox.example.com/)NETBOX_TOKEN: Read-only API token with appropriate permissionsLOG_LEVEL: Logging verbosity (default:INFO, options:DEBUG,WARNING,ERROR)
- Read-only tokens: Always use read-only API tokens with minimal required permissions
- No credential storage: Tokens passed via environment, never stored or logged
- SSL verification: Enabled by default in REST client
- No plugin support: Deliberately excludes plugin object types to limit attack surface
- Open source: All code auditable; report security issues per SECURITY.md
Currently no automated test suite. When adding tests:
- Test tool behavior with real NetBox instance (Docker-based test environment)
- Mock external NetBox API calls only when necessary
- Validate error handling (invalid object types, missing credentials, API errors)
- Test pagination handling for large result sets
- ❌ NEVER use pip - Only use
uvfor package management - ❌ NEVER use
uv pip install- This is forbidden; useuv addinstead - ❌ NEVER use
@latestsyntax - Specify versions explicitly or let uv manage them
- ❌ Add write operations (create/update/delete) without explicit project maintainer approval
- ❌ Add support for plugin object types (scope limited to core NetBox)
- ❌ Hardcode credentials or NetBox URLs
- ❌ Bypass the
NetBoxClientBaseabstraction - ❌ Remove type hints or comprehensive docstrings
- ❌ Use print statements (use proper logging via
LOG_LEVEL)
- Professional communication: Be courteous and constructive
- Documentation is mandatory: A user-facing feature doesn't exist unless documented in README.md
- Licensing: Apache 2.0; ensure all contributions are compatible
- Issue-driven development: Reference issues in commits and PRs
- Be direct: State what things ARE (avoid "This isn't X, it's Y" constructions)
- Be brief: Optimize for reader comprehension, not writer expression
- Be clear: Avoid defensive writing patterns and ambiguous language
- Always use feature branches - NEVER commit directly to
main - Branch naming: Use descriptive names with prefixes:
fix/auth-timeout- Bug fixesfeat/api-pagination- New featureschore/ruff-fixes- Maintenance tasks
- One logical change per branch - Simplifies review and rollback
-
Conventional commit format (preferred):
type(scope): short description Examples: feat(tools): add netbox_search_objects tool fix(client): handle pagination correctly chore(deps): upgrade fastmcp to 2.13.0
-
Commit trailers for attribution:
# Link to GitHub issue git commit --trailer "Github-Issue:#123" # Credit bug reporter git commit --trailer "Reported-by:Jane Doe"
-
Make atomic commits - One logical change per commit
-
Keep granular history on feature branch; squash only when merging to
main
- Create or reference an issue before starting work
- Create feature branch:
git checkout -b feat/issue-123-description - Commit in small, logical increments as you work
- Push and open draft PR early for visibility
- Convert to ready PR when functionally complete and tests pass
- Wait for reviews and address feedback
- Merge after approval and CI checks pass
PR Description Format:
- 1-2 paragraphs: State the problem/need and your solution
- Include a focused code example if adding new functionality
- Keep it concise for minor fixes (1-2 sentences is fine)
What to Avoid:
- Bullet-point summaries of every file changed
- Marketing language or excessive justification
- Repeating what the code diff already shows
What to Include:
- Link related issues:
Fixes #123orRelates to #456 - Why this change matters (not just what changed)
- ❌ NEVER commit directly to
main- Always use feature branches - ✅ DO keep commits professional and concise and focused on the change
✅ Add if:
- Exposes core NetBox functionality not currently accessible
- Has clear use case for LLM-driven queries
- Maintains read-only contract
- Follows existing tool patterns
❌ Don't add if:
- Duplicates existing tool functionality
- Requires write operations
- Only benefits niche use cases
- Adds complexity without clear value
Only modify NetBoxClientBase if:
- Planning to add plugin-based ORM implementation
- All CRUD methods must remain in sync
- Changes must not break REST implementation
Level 1 (New Contributors): Add tools, fix bugs, improve documentation
Level 2 (Maintainers): Modify client abstraction, add new object type categories
Level 3 (Core Team): Architectural changes, security-sensitive code
These examples show how LLMs interact with MCP tools (conceptual format):
# Get all devices in a site
mcp_tool("netbox_get_objects", {
"object_type": "devices",
"filters": {"site": "equinix-dc14"}
})
# Get specific device by ID
mcp_tool("netbox_get_object_by_id", {
"object_type": "devices",
"object_id": 123
})
# Find recent changes
mcp_tool("netbox_get_changelogs", {
"filters": {
"action": "update",
"time_after": "2025-01-01T00:00:00Z"
}
})"NETBOX_URL and NETBOX_TOKEN environment variables must be set" → Set environment variables before running server
"Invalid object_type"
→ Check NETBOX_OBJECT_TYPES dictionary for supported types
→ Plugin object types are not supported
"Connection refused" or timeout → Verify NETBOX_URL is accessible and includes protocol (https://) → Check firewall rules and network connectivity
"Authentication failed" → Verify API token is valid and not expired → Ensure token has read permissions for requested objects
- NetBox API Documentation
- Model Context Protocol Specification
- FastMCP Documentation
- Project README - User-facing setup and usage
- Contributing Guide - Project scope, contribution workflow, and out-of-scope list
- Security Policy - Vulnerability reporting