MCP server for Oxidized network device configuration backups
Runs on your machine from its package; the steps are below.
At most: creates or changes data: records, files, settings.
Odel charges nothing per call. The publisher may charge for its own service.
Runs locally over stdio
This server isn't hosted — your MCP client launches it from a package registry. Use one of the commands below, or drop the config into your client (e.g. Claude Desktop).
Run
uvx oxidized-mcp
MCP client config
{
"mcpServers": {
"oxidized-mcp": {
"command": "uvx",
"args": [
"oxidized-mcp"
]
}
}
}Run
uvx oxidized-mcp
MCP client config
{
"mcpServers": {
"oxidized-mcp": {
"command": "uvx",
"args": [
"oxidized-mcp"
]
}
}
}Run
docker run -i --rm ghcr.io/mhajder/oxidized-mcp:0.1.0
MCP client config
{
"mcpServers": {
"oxidized-mcp": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/mhajder/oxidized-mcp:0.1.0"
]
}
}
}Oxidized MCP Server is a Python-based Model Context Protocol (MCP) server that gives AI assistants access to Oxidized, the network device configuration backup tool. It talks to the oxidized-web REST API to list devices, check backup health, read and search device configurations, browse and diff configuration history, and queue backups. It supports read-only mode, tag-based tool filtering, and bearer token authentication for HTTP transports.
group/name) or IP addressgit (or gitcrypt) outputThe easiest way to get started is to install from PyPI:
# Using UV (recommended)
uvx oxidized-mcp
# Or using pip
pip install oxidized-mcp
Remember to configure the environment variables for your Oxidized instance before running the server:
# Create environment configuration
export OXIDIZED_URL=http://localhost:8888
For more details, visit: https://pypi.org/project/oxidized-mcp/
git clone https://github.com/mhajder/oxidized-mcp.git
cd oxidized-mcp
# Using UV (recommended)
uv sync
# Or using pip
pip install -e .
cp .env.example .env
# Edit .env with your Oxidized URL (and Basic auth credentials if needed)
# Using UV (recommended)
uv run oxidized-mcp
# Or using the installed command directly
oxidized-mcp
For development with additional tools:
# Clone and install with development dependencies
git clone https://github.com/mhajder/oxidized-mcp.git
cd oxidized-mcp
uv sync --group dev
# Run tests
uv run pytest
# Run with coverage
uv run pytest --cov=src/
# Run linting and formatting
uv run ruff check .
uv run ruff format .
# Run type checking
uv run ty check .
# Setup prek hooks
uv run prek install
# Oxidized Connection Details
# Base URL of oxidized-web (include the url_prefix if one is configured)
OXIDIZED_URL=http://localhost:8888
# Optional HTTP Basic auth, e.g. for a reverse proxy in front of oxidized-web
# OXIDIZED_USERNAME=
# OXIDIZED_PASSWORD=
# SSL Configuration
OXIDIZED_VERIFY_SSL=true
OXIDIZED_TIMEOUT=30
# Timeout for search_configs (oxidized-web reads every configuration)
OXIDIZED_SEARCH_TIMEOUT=300
# Redact node vars (often device credentials) in tool output
OXIDIZED_REDACT_NODE_VARS=true
# Read-Only Mode
# Set READ_ONLY_MODE true to disable operations (trigger_node_backup, reload_nodes)
READ_ONLY_MODE=false
# Disabled Tags
# Comma-separated list of tags to disable tools for (empty by default)
# Example: OXIDIZED_DISABLED_TAGS=search,diff
OXIDIZED_DISABLED_TAGS=
# Logging Configuration
LOG_LEVEL=INFO
# Rate Limiting (requests per minute)
# Set RATE_LIMIT_ENABLED true to enable rate limiting
RATE_LIMIT_ENABLED=false
RATE_LIMIT_MAX_REQUESTS=60
RATE_LIMIT_WINDOW_MINUTES=1
# Tool Search Transform (Optional)
# Set TOOL_SEARCH_ENABLED true to replace full tool listings with search_tools + call_tool
TOOL_SEARCH_ENABLED=false
# Search strategy: bm25 (natural language) or regex (pattern match)
TOOL_SEARCH_STRATEGY=bm25
# Maximum number of tools returned by search_tools
TOOL_SEARCH_MAX_RESULTS=5
# Sentry Error Tracking (Optional)
# Set SENTRY_DSN to enable error tracking and performance monitoring
# SENTRY_DSN=https://your-key@o12345.ingest.us.sentry.io/6789
# Optional Sentry configuration
# SENTRY_TRACES_SAMPLE_RATE=1.0
# SENTRY_SEND_DEFAULT_PII=false
# SENTRY_ENVIRONMENT=production
# SENTRY_RELEASE=1.2.3
# SENTRY_PROFILE_SESSION_SAMPLE_RATE=1.0
# SENTRY_PROFILE_LIFECYCLE=trace
# SENTRY_ENABLE_LOGS=true
# MCP Transport Configuration
# Transport type: 'stdio' (default), 'sse' (Server-Sent Events), or 'http' (HTTP Streamable)
# MCP_TRANSPORT=stdio
# HTTP Transport Settings (used when MCP_TRANSPORT=sse or MCP_TRANSPORT=http)
# Host to bind the HTTP server (default: 127.0.0.1)
# MCP_HTTP_HOST=127.0.0.1
# Port to bind the HTTP server (default: 8000)
# MCP_HTTP_PORT=8000
# Optional bearer token for authentication (leave empty for no auth)
# MCP_HTTP_BEARER_TOKEN=
The server optionally supports Sentry for error tracking, performance monitoring, and debugging. Sentry integration is completely optional and only initialized if configured.
To enable Sentry monitoring, install the optional dependency:
# Using UV (recommended)
uv sync --extra sentry
Enable Sentry by setting the SENTRY_DSN environment variable in your .env file:
# Required: Sentry DSN for your project
SENTRY_DSN=https://your-key@o12345.ingest.us.sentry.io/6789
# Optional: Performance monitoring sample rate (0.0-1.0, default: 1.0)
SENTRY_TRACES_SAMPLE_RATE=1.0
# Optional: Include personally identifiable information (default: false).
# Keep it off: with the MCP integration it records tool results, which are
# full device configurations including secrets.
SENTRY_SEND_DEFAULT_PII=false
# Optional: Environment name (e.g., "production", "staging")
SENTRY_ENVIRONMENT=production
# Optional: Release version (auto-detected from package if not set)
SENTRY_RELEASE=1.2.2
# Optional: Profiling - continuous profiling sample rate (0.0-1.0, default: 1.0)
SENTRY_PROFILE_SESSION_SAMPLE_RATE=1.0
# Optional: Profiling - lifecycle mode for profiling (default: "trace")
# Options: "all", "continuation", "trace"
SENTRY_PROFILE_LIFECYCLE=trace
# Optional: Enable log capture as breadcrumbs and events (default: true)
SENTRY_ENABLE_LOGS=true
When enabled, Sentry automatically captures:
.env fileSentry is completely optional. If you don't set SENTRY_DSN, the server will run normally without any Sentry integration, and no monitoring data will be collected.
list_nodes: List devices with optional filters (group, model, last run status, name/IP substring) and paging; group/model are filtered by Oxidized itself (/nodes/<group|model>/<value>.json), so large inventories are not transferred in fullget_node: Get a single device's details and last backup run (accepts name or IP)find_nodes: Find devices by partial name, full name or IP, exact matches firstget_backup_stats: Backup health summary - counts per status, group and model, success rate, failed / never backed up / stale devices; optionally for a single group or modelget_node_config: Get the latest backed-up configuration of a device (by name or IP), paged by lines (offset / max_lines)search_configs: Search all configurations for a regex or literal text; returns matching lines with line numbers and optional contextRequire the Oxidized git or gitcrypt output.
list_node_versions: List stored configuration versions (oid, date, author, message), newest firstget_node_version: Get the configuration at a given version (full oid or unique prefix), paged by linesdiff_node_versions: Unified diff between two versions; defaults to the most recent changeHidden when READ_ONLY_MODE=true.
trigger_node_backup: Queue an immediate backup of a device, optionally recording a commit message and authorreload_nodes: Reload the whole node list from its source (a per-node reload is deliberately not offered: oxidized-web's /reload?node=X replaces the entire in-memory node list with the matching nodes)The server supports a read-only mode that disables all write operations for safe monitoring:
READ_ONLY_MODE=true
When enabled, only tools tagged read-only are exposed: trigger_node_backup and reload_nodes are hidden, while every node, configuration and history tool stays available.
You can disable specific categories of tools by setting disabled tags:
OXIDIZED_DISABLED_TAGS=search,diff
Available tags include:
node - Node listing and lookup tools (and the backup/reload operations)stats - Backup health statisticsconfig - Configuration read toolssearch - Configuration search and node search toolsversion - Version history toolsdiff - Version diff toolbackup - Backup trigger operationreload - Node list reload operationread-only - Every tool that does not change Oxidized stateThe server supports rate limiting to control API usage and prevent abuse. If enabled, requests are limited per client using a sliding window algorithm.
Enable rate limiting by setting the following environment variables in your .env file:
RATE_LIMIT_ENABLED=true
RATE_LIMIT_MAX_REQUESTS=60 # Maximum requests allowed per window
RATE_LIMIT_WINDOW_MINUTES=1 # Window size in minutes
If RATE_LIMIT_ENABLED is set to true, the server will apply rate limiting middleware. Adjust RATE_LIMIT_MAX_REQUESTS and RATE_LIMIT_WINDOW_MINUTES as needed for your environment.
FastMCP tool search can reduce prompt size for servers with many tools.
When enabled, list_tools returns two synthetic tools:
search_tools: Finds matching tools and returns their full schemascall_tool: Executes any discovered tool by nameEnable it with:
TOOL_SEARCH_ENABLED=true
TOOL_SEARCH_STRATEGY=bm25 # bm25 or regex
TOOL_SEARCH_MAX_RESULTS=8 # optional, default is 5
bm25 supports natural language queries, while regex uses a regex
pattern input for deterministic matching.
Tool search respects existing visibility controls (read-only mode and disabled tags).
The server supports SSL certificate verification and custom timeout settings:
OXIDIZED_VERIFY_SSL=true # Enable SSL certificate verification
OXIDIZED_TIMEOUT=30 # Request timeout in seconds
oxidized-web has no authentication of its own and is usually published behind a reverse proxy with HTTP Basic auth. Set both variables to send Basic auth with every request:
OXIDIZED_USERNAME=oxidized
OXIDIZED_PASSWORD=your-password
Node vars from the Oxidized source (router.db, SQL, HTTP) often contain per-device credentials, and oxidized-web returns them unredacted from /nodes.json. The server replaces every var value with <redacted> (keeping the keys) unless you opt out:
OXIDIZED_REDACT_NODE_VARS=false
The server supports multiple transport protocols for different deployment scenarios:
The default transport uses standard input/output for communication. This is ideal for local usage and integration with tools that communicate via stdin/stdout:
MCP_TRANSPORT=stdio
For network-based deployments, you can use HTTP with Server-Sent Events. This allows the MCP server to be accessed over HTTP with real-time streaming:
MCP_TRANSPORT=sse
MCP_HTTP_HOST=127.0.0.1 # Localhost
MCP_HTTP_PORT=8000 # Port to listen on
MCP_HTTP_BEARER_TOKEN=your-secret-token # Optional authentication token
When using SSE transport with a bearer token, clients must include the token in their requests:
curl -H "Authorization: Bearer your-secret-token" http://localhost:8000/sse
The HTTP Streamable transport provides HTTP-based communication with request/response streaming. This is ideal for web integrations and tools that need HTTP endpoints:
MCP_TRANSPORT=http
MCP_HTTP_HOST=127.0.0.1 # Localhost
MCP_HTTP_PORT=8000 # Port to listen on
MCP_HTTP_BEARER_TOKEN=your-secret-token # Optional authentication token
When using streamable transport with a bearer token:
curl -H "Authorization: Bearer your-secret-token" \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' \
http://localhost:8000/mcp
Note: The HTTP transport requires proper JSON-RPC formatting with jsonrpc and id fields. The server may also require session initialization for some operations.
list_node_versions, get_node_version, diff_node_versions) needs the git or gitcrypt output. With the file output Oxidized reports no versions.group to disambiguate; get_node_config and the history tools look the group up automatically when it is omitted (with the git output's single_repo the group is part of the stored path, so it is required by oxidized-web). Nodes without a group are shown as group default, and passing default back is treated as "no group". search_configs filters groups the same way as list_nodes (exact match). trigger_node_backup takes no group because Oxidized queues backups by node name or IP only.group and model in list_nodes / get_backup_stats are filtered by Oxidized itself. The group must match exactly (case-sensitive, as shown by list_nodes), so a misspelt group returns nothing instead of downloading the whole inventory. Built-in model names are case-insensitive (ios and the IOSXE alias are sent as IOS, from a catalog of Oxidized's models); custom models must be spelled exactly. Neither filter ever falls back to downloading the whole inventory.conf_search finds matching devices (it reads every configuration server-side, so it can be slow on large installations), then only those configurations are fetched to extract matching lines. max_nodes caps the fetches; nodes that were never backed up (whose placeholder text can match) are skipped without counting. The server-side step uses OXIDIZED_SEARCH_TIMEOUT (default 300 s) instead of OXIDIZED_TIMEOUT. Patterns run as Ruby regexps on the server and Python regexps locally, line by line, so stick to common syntax.trigger_node_backup moves the device to the head of the queue. Check get_node for the result.RACK_ENV=production the reason is hidden, so a 500 on a node route is reported with a hint that it may be an unknown node name.A Docker image is available on GitHub Packages for easy deployment.
docker pull ghcr.io/mhajder/oxidized-mcp:latest
docker run --rm -p 8000:8000 \
-e OXIDIZED_URL=http://oxidized:8888 \
-e MCP_HTTP_BEARER_TOKEN=your-secret-token \
ghcr.io/mhajder/oxidized-mcp:latest
The image defaults to the HTTP Streamable transport on port 8000 (http://localhost:8000/mcp).
git checkout -b feature/amazing-feature)uv run pytest && uv run ruff check .)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)MIT License - see LICENSE file for details.