A FastMCP server that exposes Cisco Catalyst Center automation as secure MCP tools
for AI agents. It runs the cisco.catalystcenter Ansible collection through
ansible-runner, tracks work as async tasks in Redis, and serves everything over
HTTPS with API-key authentication.
- 70 MCP tools for Catalyst Center workflows (sites, SD-Access fabric, provisioning, assurance, discovery, inventory, and more)
- Async task execution with Redis persistence and artifact tracking
- Multi-cluster support through a YAML catalog
- Secure by default — HTTPS + API key, with credentials protected by Ansible Vault
- Secure golden path — the 6 steps to a running server
- Tools · Endpoints · Architecture
- Configuration reference
- Running and deployment — CLI, systemd, NGINX, Docker
- Other platforms — macOS, Debian, RHEL, WSL2, dev
- Multiple Catalyst Centers
- Ansible Vault details
- Troubleshooting · Support · License
New here? Follow the six steps below from top to bottom. They take you from a clean machine to a running, authenticated, HTTPS server.
flowchart LR
A[1. Install] --> B[2. Create<br/>environment]
B --> C[3. Set up<br/>secure keys]
C --> D[4. Add credentials<br/>via Ansible Vault]
D --> E[5. Run server<br/>over HTTPS]
E --> F[6. Try it<br/>demo call]
- OS: Linux (recommended) or macOS. On Windows, use WSL2 or Docker — see Other platforms.
- Python: 3.11 or higher
- Redis: 6.0 or higher (
redis-cli pingreturnsPONG) - Ansible collection:
cisco.catalystcenter2.6.0+ (installed in Step 2) - Network: reachability to your Cisco Catalyst Center instance
git clone https://github.com/cisco-en-programmability/catalyst_center_iac_mcpserver.git
cd catalyst_center_iac_mcpserverCreate a virtual environment and install the server, the Ansible runtime, and the collection:
python3 -m venv .venv
source .venv/bin/activate # Windows (WSL2) uses the same command
pip install --upgrade pip setuptools wheel
pip install "ansible-core>=2.16,<2.19"
pip install .
ansible-galaxy collection install -r requirements.ymlCreate your non-secret configuration file. Secrets (passwords, API keys) are added later in Step 4, so leave them out here:
cp deploy/env/catalyst-center-iac-mcp.env.example .envEdit .env and set the non-secret values. Keep HTTPS_ONLY=true:
CATALYSTCENTER_HOST=your-catalyst-center.example.com
CATALYSTCENTER_USERNAME=automation
CATALYSTCENTER_VERSION=2.6.0
CATALYSTCENTER_VERIFY_SSL=false
REDIS_URL=redis://127.0.0.1:6379/0
SERVER_HOST=127.0.0.1
SERVER_PORT=8443
HTTPS_ONLY=trueThe server auto-loads .env. Do not put CATALYSTCENTER_PASSWORD or
API_KEYS in this file — they go into the vault in Step 4.
The server requires TLS (HTTPS_ONLY=true is the default) and rejects requests
without a valid API key when API-key auth is enabled.
a. TLS certificate. Use a certificate from your PKI/Let's Encrypt in production. For a lab, generate a self-signed certificate:
mkdir -p deploy/certs
openssl req -x509 -newkey rsa:4096 -nodes \
-keyout deploy/certs/privkey.pem \
-out deploy/certs/fullchain.pem \
-days 365 -subj "/CN=localhost"Point .env at the certificate and enable API-key auth:
TLS_CERTFILE=deploy/certs/fullchain.pem
TLS_KEYFILE=deploy/certs/privkey.pem
API_KEY_ENABLED=trueb. API key. Generate a strong key and copy the value — you'll store it in the vault in the next step:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"Keep the Catalyst Center password and API key encrypted at rest. (Full reference in Ansible Vault details.)
# One-time: create a vault password, readable only by you
mkdir -p ~/.config/catalyst-center-iac-mcp
openssl rand -base64 32 > ~/.config/catalyst-center-iac-mcp/.vault_pass
chmod 600 ~/.config/catalyst-center-iac-mcp/.vault_pass
# Put your secrets in a file (use the API key you generated in Step 3)
cat > secrets.env << 'EOF'
CATALYSTCENTER_PASSWORD=your-catalyst-center-password
API_KEYS=your-generated-api-key
EOF
# Encrypt it — the plaintext is now gone from disk
ansible-vault encrypt secrets.env \
--vault-password-file ~/.config/catalyst-center-iac-mcp/.vault_passsecrets.env is now ciphertext. Keep it out of version control.
Decrypt the secrets into memory, export them, and start the server. The server
already reads the rest of its config from .env:
set -a
source <(ansible-vault view secrets.env \
--vault-password-file ~/.config/catalyst-center-iac-mcp/.vault_pass)
set +a
catalyst-center-iac-mcpThe server is now available at https://127.0.0.1:8443.
Verify it (use your API key; -k accepts the self-signed lab certificate):
export API_KEY="your-generated-api-key"
curl -k https://127.0.0.1:8443/healthz -H "X-API-Key: $API_KEY"
# {"status": "ok", "subject": "apikey:..."}The server from Step 5 runs in the foreground, so open a new terminal for the demo. It performs a secure health check and lists your configured Catalyst Centers.
Linux / macOS / WSL2 / Git Bash (bash):
export MCP_URL="https://127.0.0.1:8443"
export API_KEY="your-generated-api-key"
chmod +x scripts/demo.sh # first time only
./scripts/demo.shWindows (PowerShell):
$env:MCP_URL = "https://127.0.0.1:8443"
$env:API_KEY = "your-generated-api-key"
./scripts/demo.ps1Or make the same authenticated tool call directly:
curl -k -X POST https://127.0.0.1:8443/mcp \
-H "Content-Type: application/json" \
-H "X-API-Key: $API_KEY" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": { "name": "list_catalyst_centers", "arguments": {} }
}'Register the server with an MCP client (for example Claude Desktop) by pointing it at the HTTPS endpoint:
{
"mcpServers": {
"catalyst-center": {
"url": "https://your-hostname:8443/mcp",
"transport": "http"
}
}
}The server exposes 70 tools in three groups:
- Direct tools (3) — simplified interfaces:
provision_site,delete_site,configure_network_settings. - Workflow tools (38) — full control per module (site management, ISE/AAA, templates, PnP, wireless, network profiles, assurance, application policy, discovery, inventory, provisioning, reports, SD-Access fabric, LAN automation, RMA).
- Config generators (29) — read-only tools (
<domain>_config) that query current state and default tostate: gathered.
Call tools/list on the running server, or see tool_catalog.yaml,
for the complete inventory.
Most tools accept a target selector:
catalyst_center— pick a cluster from catalyst_center_clusters.yaml by name, label, location, or host.tenant_id— legacy tenant-scoped environment variable routing.
If both are given, catalyst_center wins. If neither is given, the enabled cluster
marked default: true is used. See Multiple Catalyst Centers.
GET /healthz— health checkPOST /mcp— MCP protocol endpointGET /iactasks/get/{task_id}— task statusGET /iactasks/get/{task_id}/logs— task logs and artifacts
Long operations return a task ID; poll /iactasks/get/{task_id} for progress, or
use the get_iac_task and get_task_stdout tools.
FastMCP— MCP tool registration and request handlingFastAPI— HTTP app for/mcp,/healthz, and/iactasks/get/{task_id}ansible-runner— the only execution path for Catalyst Center operationsRedis— task state, progress, results, and artifact indexing
The server is stateless by design; Redis and runner artifacts hold operational state, which makes multi-instance deployment practical.
The golden path above sets only what you need. This is the full variable reference.
CATALYSTCENTER_HOST=catalyst-center.example.com
CATALYSTCENTER_USERNAME=automation
CATALYSTCENTER_PASSWORD=change-me # keep in the vault, not in .env
CATALYSTCENTER_VERIFY_SSL=false
CATALYSTCENTER_PORT=443
CATALYSTCENTER_VERSION=2.6.0SERVER_HOST=127.0.0.1
SERVER_PORT=8443
SERVER_WORKERS=2
HTTPS_ONLY=true # server refuses to start without TLS files
PROXY_HEADERS=true
FORWARDED_ALLOW_IPS=127.0.0.1
TLS_CERTFILE=deploy/certs/fullchain.pem
TLS_KEYFILE=deploy/certs/privkey.pem
# TLS_CA_CERTS=/etc/ssl/certs/ca-certificates.crt
REDIS_URL=redis://127.0.0.1:6379/0
RUNNER_ARTIFACT_ROOT=/var/lib/catalyst-center-iac-mcp/artifacts
RUNNER_TIMEOUT_SECONDS=3600
TASK_TTL_SECONDS=86400
MCP_PATH=/mcp
MCP_TRANSPORT=http
MCP_STATELESS_HTTP=true # correct for app-level MCP registrations
CATALYST_CENTER_CLUSTERS_FILE=./catalyst_center_clusters.yamlHTTPS_ONLY=true is the default; the server will not start unless TLS_CERTFILE
and TLS_KEYFILE are set. Set HTTPS_ONLY=false only for local development behind
another TLS terminator.
MCP_STATELESS_HTTP=true is correct for app-level MCP registrations. Set it to
false only if your client supports user-specific MCP sessions.
For the
reportsworkflow tool, eachgenerate_reportentry must include a non-emptyviewfield. If the view is unknown, ask the user to choose it rather than guessing.
API-key authentication is the recommended mode and the default in the golden path.
API_KEY_ENABLED=true
API_KEYS=key1_abc123,key2_xyz789 # comma-separated; keep in the vault
API_KEY_HEADER=X-API-Key # optional custom header nameGenerate keys with python3 -c "import secrets; print(secrets.token_urlsafe(32))"
and use a different key per client so they can be rotated independently. Full guide:
docs/API_KEY_AUTHENTICATION.md.
OAuth (JWT) is also supported for enterprise SSO:
OAUTH_ENABLED=true
OAUTH_ISSUER=https://auth.example.com
OAUTH_AUDIENCE=catalyst-center-mcp
OAUTH_JWKS_URL=https://auth.example.com/.well-known/jwks.jsonAnonymous (no-auth) access and plain HTTP are for local development only and are intentionally not documented as a supported flow. Always run with TLS and either API-key or OAuth authentication in shared or production environments.
The server normalizes Catalyst Center versions to the nearest supported release:
- 2.3.7.10 or 2.3.7 normalize to 2.3.7.9
- 3.1.3.1 or 3.1.3.7 normalize to 3.1.3.6
- Unsupported versions remain unchanged
Any version within the same major.minor.patch family works with the nearest supported version.
. /opt/catalyst-center-iac-mcp/.venv/bin/activate
export $(grep -v '^#' /etc/catalyst-center-iac-mcp/catalyst-center-iac-mcp.env | xargs)
catalyst-center-iac-mcpRun the app on 127.0.0.1:8000, NGINX (or another reverse proxy) for HTTPS
termination, and systemd for process supervision. Provided examples:
- systemd unit: deploy/systemd/catalyst-center-iac-mcp.service
- NGINX config: deploy/nginx/catalyst-center-iac-mcp.conf
sudo cp deploy/env/catalyst-center-iac-mcp.env.example /etc/catalyst-center-iac-mcp/catalyst-center-iac-mcp.env
sudo vi /etc/catalyst-center-iac-mcp/catalyst-center-iac-mcp.env
sudo cp deploy/systemd/catalyst-center-iac-mcp.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now catalyst-center-iac-mcp
sudo systemctl status catalyst-center-iac-mcpsudo cp deploy/nginx/catalyst-center-iac-mcp.conf /etc/nginx/conf.d/
sudo nginx -t
sudo systemctl reload nginx
curl https://mcp.example.com/healthzImportant for MCP/SSE: keep proxy buffering disabled on /mcp/, set a large
proxy_read_timeout for long jobs, and pass X-Forwarded-Proto through.
docker build -t catalyst-center-iac-mcp .
docker run -d --name catalyst-center-iac-mcp -p 8000:8000 \
-e SERVER_HOST=0.0.0.0 -e SERVER_PORT=8000 \
-e REDIS_URL=redis://redis:6379/0 \
-e CATALYSTCENTER_HOST=catalyst-center.example.com \
-e CATALYSTCENTER_USERNAME=automation \
-e CATALYSTCENTER_PASSWORD=change-me \
-e CATALYSTCENTER_VERSION=2.6.0 \
-v /var/lib/catalyst-center-iac-mcp:/var/lib/catalyst-center-iac-mcp \
catalyst-center-iac-mcpPut the container behind NGINX, Caddy, HAProxy, or a Kubernetes ingress for HTTPS.
For a single host with Redis + app + NGINX HTTPS termination, use compose.yaml:
cp deploy/env/catalyst-center-iac-mcp.env.example deploy/env/catalyst-center-iac-mcp.env
vi deploy/env/catalyst-center-iac-mcp.env
# Place TLS files at deploy/certs/fullchain.pem and deploy/certs/privkey.pem
docker compose up -d --build
docker compose ps
curl https://your-hostname/healthzNGINX disables proxy buffering for /mcp/ so HTTP/SSE transport stays usable.
For local development only,
compose.dev.yamlruns Redis and the app over plain HTTP without NGINX/TLS. Do not use it for production.
Steps 2–6 of the golden path are identical everywhere. Only the OS prerequisites differ. Install the prerequisites below, then follow the golden path from Step 2.
macOS
brew install python@3.11 redis
brew services start redisUbuntu / Debian
sudo apt update
sudo apt install -y python3 python3-venv python3-pip redis-server git
sudo systemctl enable --now redis-serverRHEL / CentOS / Rocky Linux
sudo dnf install -y epel-release
sudo dnf install -y python3.11 python3.11-pip redis git
sudo systemctl enable --now redisWindows — native Windows is not supported (ansible-runner needs Unix-only
APIs). Use WSL2 (wsl --install -d Ubuntu, then follow the Ubuntu steps) or the
Docker deployment.
Development — install the dev extras and run the tests:
pip install -e ".[dev]"
pytestDefine clusters in catalyst_center_clusters.yaml
and target them by name, label, location, or host. Mark exactly one enabled
cluster with default: true so calls that omit catalyst_center use it.
catalyst_centers:
- name: "Portland"
label: "dev"
host: "Portland-center.domain.com"
version: "3.1.3.0"
location: "Portland"
enabled: true
default: true
- name: "San Jose"
label: "production"
host: "SanJose-catalyst.domain.com"
version: "2.3.7.9"
location: "San Jose"
enabled: falseCredentials stay in environment variables (keep the passwords in the vault). For a
cluster with label dev:
CC_DEV_USERNAME=svc-dev
CC_DEV_PASSWORD=dev-secret
CC_DEV_VERIFY_SSL=false
CC_DEV_PORT=443
CC_DEV_VERSION=3.1.3.0Then pass catalyst_center="Portland" or catalyst_center="dev" in a tool call.
The older CATALYSTCENTER_CLUSTER_* variable names are still accepted.
Multi-tenant (legacy). Tenant-scoped routing also works — set
CATALYSTCENTER_ACME_HOST, CATALYSTCENTER_ACME_USERNAME,
CATALYSTCENTER_ACME_PASSWORD, etc., then pass tenant_id="acme" in a tool call.
Routing rules. catalyst_center takes precedence over tenant_id. If
catalyst_center is omitted, the default: true cluster is used; if there is no
default but exactly one enabled cluster, that one is used. Use list_catalyst_centers
to inspect the inventory.
Step 4 encrypts credentials at rest. This section covers editing, rotation, and the security rationale.
How it works. The server reads credentials from environment variables at
runtime. Ansible Vault encrypts the file that holds those values on disk; at start
time you decrypt in memory (ansible-vault view), export the variables, and launch
the server, so plaintext secrets never persist on disk.
Edit or rotate:
# Edit in place (opens $EDITOR decrypted, re-encrypts on save)
ansible-vault edit secrets.env \
--vault-password-file ~/.config/catalyst-center-iac-mcp/.vault_pass
# Rotate the vault password itself
ansible-vault rekey secrets.env \
--vault-password-file ~/.config/catalyst-center-iac-mcp/.vault_passAfter rotating a Catalyst Center password or API key, edit the vaulted file and restart the server so the new value is loaded.
Security notes:
- Never decrypt secrets to a file on disk; always stream via
ansible-vault view. - Keep the vault password file at
chmod 600, owned by the service account. - In CI/CD, provide the vault password from a masked secret, not a committed file.
- Add
secrets.env,.env, and.vault_passto.gitignore. - Do not print secrets in logs,
set -xtraces, or CI output.
- Server won't start / TLS error:
HTTPS_ONLY=truerequiresTLS_CERTFILEandTLS_KEYFILE. Set them (Step 3) or useHTTPS_ONLY=falsefor local HTTP only. - 401 / missing key: send
-H "X-API-Key: $API_KEY"with a key that is present inAPI_KEYS. - Missing tools: reinstall the collection —
ansible-galaxy collection install cisco.catalystcenter:==2.6.0 --force. - Redis errors: ensure Redis is running (
redis-cli ping). - Connection issues: verify Catalyst Center reachability and credentials.
- API key authentication — full auth guide
- Module dependencies — required inputs per workflow
- Schema-driven architecture — tool registry design
- Examples — working Python samples
- Contributing · Changelog
For the certified cisco.catalystcenter collection on Red Hat Automation Hub, use
the Create issue action on the collection page. For this MCP server, open
issues in GitHub Issues.
For broader platform guidance, see Cisco DevNet.
Provided under the Cisco Sample Code License, Version 1.1.