automates objects, rules, NAT, and compliance reporting on FMC-managed FTD using Python, Ansible, Terraform, and MCP servers for AI agents
Firewall change work is repetitive, error-prone, and hard to audit: an engineer reads a
ticket, hand-creates objects in the FMC GUI, writes a rule, hopes nothing shadows it, and
leaves no machine-readable record of what happened. This starter pack replaces that loop
with reviewed automation. It takes CSV input, validates it before anything is sent to
the firewall, creates network objects, service objects, access rules, and NAT rules
through the Cisco Secure Firewall Management Center (FMC) REST API, and produces
compliance and inventory reports you can attach to a change record.
It teaches the same six tasks three ways — Python, Ansible, and Terraform — so you can
see how imperative, procedural, and declarative automation differ against the same
device, and then adopt whichever matches how your team already works. It also ships
three Model Context Protocol (MCP) servers so an AI agent can drive that automation
without being able to change a firewall in a single unreviewed step.
Technology stack: Python 3.11+ (requests, pandas), Ansible with the
cisco.fmcansible collection, Terraform with the CiscoDevNet/fmc provider, and
FastMCP for the MCP servers. Standalone — no
framework required. Docker images provided for the MCP servers.
Status: 1.0.0. Lab-ready and CI-tested (193 unit tests, no live FMC required). Treat
the write paths as beta until you have validated the payloads against your own FMC
version.
Not a Cisco product. Independent and community-maintained, distributed under the
Apache License 2.0. Not supported by Cisco TAC. See NOTICE and
SUPPORT.md.
A mid-size enterprise runs FMC-managed FTD firewalls and processes 20–40 firewall change
requests a month. Each one is done by hand. The problems that creates:
This repository addresses each of those:
| Problem | What this provides | Outcome |
|---|---|---|
| No pre-flight validation | validate_*.py scripts check every CSV row — column presence, IP/CIDR correctness, port ranges, duplicate names, and whether referenced zones and objects actually exist in FMC |
Bad input fails in seconds, at your desk, not in the change window |
| Object sprawl | search_objects with IP-containment matching, and duplicate-value detection in compliance_report.py |
Find the object that already covers an address before creating a sixth one |
| Unsafe deletions | find_object_usage returns every access rule referencing an object plus a safe_to_delete flag |
Clean up stale objects with evidence |
| Weak audit trail | Every run writes a per-row CSV result to outputs/reports/ with CREATED / SKIP / FAILED and a reason |
Attach a machine-readable record to the change ticket |
| Inconsistent hygiene | compliance_report.py flags naming-convention breaches, duplicate values, rules without logEnd, and rules without comments |
Measurable policy hygiene instead of opinion |
Challenges overcome. FMC payload schemas vary between releases, so every write script
documents which endpoint it uses and tells you to confirm it in API Explorer. FMC
paginates and silently truncates large object lists, so the shared client implements
get_all() to follow pagination rather than passing limit=1000 and hoping. Manual NAT
payloads differ most across versions, so that script is explicitly marked as
version-sensitive.
Where it could go next. Wiring the MCP servers to an ITSM system so a change ticket
produces the plan automatically, and extending the compliance report to shadowed-rule and
hit-count analysis. See mcp_servers/IDEAS.md.
This kit talks to a firewall management plane, so it is configured to fail safe:
| Control | Default |
|---|---|
| TLS certificate verification | Enabled (VERIFY_SSL=true). Trust a private CA with FMC_CA_BUNDLE rather than turning it off. |
| Plaintext HTTP to FMC | Rejected |
| Credentials in source files | None. All credentials come from the environment or an ansible-vault file |
| Credentials in git | Blocked by .gitignore, pre-commit hooks, and CI secret scanning |
| Credentials in logs | Redacted automatically |
| MCP server writes | Disabled until an explicit environment flag is set |
Read SECURITY.md before pointing any of this at an environment you care
about.
mcp_servers/ contains three independently deployable Model Context
Protocol servers, one per automation style:
| Server | What an agent gets |
|---|---|
| rest-api-mcp | FMC REST tooling: inventory, object search with IP containment, object-usage tracing, rule listing, and a preview → confirm → apply change pipeline |
| ansible-mcp | Allowlisted cisco.fmcansible playbooks with syntax check, --check dry run, and gated execution |
| terraform-mcp | init / validate / plan / explain / drift detection, with apply off by default |
All three are read-only by default, split every mutation into a preview and an apply
half bound by a signed confirmation token, and redact secrets before anything reaches a
model. See mcp_servers/README.md.
fmc-automation-mcp/
├── docs/ Testing method, advanced use cases, references
├── inputs/ CSV templates (RFC 1918 sample data only)
├── outputs/ Generated reports, logs, backups (gitignored)
├── python/ Scripts and the shared common/ package
├── ansible/ Playbooks, inventory, vars
├── terraform/ Provider config and resource examples
├── mcp_servers/ Three MCP servers for AI agents
└── tests/ Unit tests (no live FMC required)
| Requirement | Version | Where to get it |
|---|---|---|
| Python | 3.11 or later | https://www.python.org/downloads/ |
| Git | any recent | https://git-scm.com/downloads |
| Ansible (optional) | ansible-core 2.21+, needs Python 3.12+ | https://docs.ansible.com/ansible/latest/installation_guide/index.html |
| Terraform (optional) | 1.6 or later | https://developer.hashicorp.com/terraform/install |
| Docker (optional, for the MCP servers) | any recent | https://docs.docker.com/get-docker/ |
| An FMC | 7.0+ with REST API enabled | Your lab, or a DevNet Sandbox |
You also need a dedicated FMC API user with least privilege. Do not use a shared
administrator account.
git clone https://github.com/CiscoDevNet/fmc-automation-mcp.git
cd fmc-automation-mcpLinux / macOS
python3 -m venv .venv
source .venv/bin/activate
pip install -r python/requirements.txtWindows PowerShell
python -m venv .venv .venv\Scripts\Activate.ps1 pip install -r python\requirements.txt
Windows Command Prompt
python -m venv .venv .venv\Scripts\activate.bat pip install -r python\requirements.txt
This runs the unit tests, which need no FMC and no credentials:
pip install -r requirements-dev.txt pytest
You should see all tests pass. If they do, your Python environment is correct and any
later failure is a configuration or connectivity problem, not an install problem.
All configuration is environment based. No credential is ever stored in a source file.
Copy the template:
Linux / macOS
cp python/.env.example python/.env
Windows PowerShell
Copy-Item python\.env.example python\.envThen edit python/.env:
| Variable | Required | Format | Notes |
|---|---|---|---|
FMC_HOST |
yes | https://host-or-ip — scheme included, no trailing slash, no path |
Plaintext http:// is rejected |
FMC_USERNAME |
yes | plain string | A dedicated least-privilege API user |
FMC_PASSWORD |
yes | plain string | Prefer exporting it in your shell over writing it to the file |
VERIFY_SSL |
no | true / false |
Defaults to true. Leave it on |
FMC_CA_BUNDLE |
no | absolute path to a PEM file | The correct way to handle a self-signed lab FMC |
FMC_DOMAIN_UUID |
no | UUID | Leave blank to auto-discover the global domain |
ACCESS_POLICY_ID |
for rule scripts | UUID | Get it from outputs/reports/access_policies.json |
NAT_POLICY_ID |
for NAT scripts | UUID | From FMC |
LOG_LEVEL |
no | DEBUG/INFO/WARNING/ERROR |
Defaults to INFO |
FMC_HOST format matters: write https://fmc.example.local, not fmc.example.local
and not https://fmc.example.local/.
To keep the password off disk entirely, leave FMC_PASSWORD blank in the file and export
it instead:
read -rs FMC_PASSWORD && export FMC_PASSWORD # Linux/macOS
$env:FMC_PASSWORD = Read-Host -AsSecureString | ConvertFrom-SecureString -AsPlainText # PowerShell 7+
ansible/group_vars/all.yml contains no secrets — it reads the same environment
variables. Alternatively supply an ansible-vault file:
ansible-vault create ansible/vars/vault.yml # add: fmc_password: "..."Supply credentials as TF_VAR_* environment variables so nothing lands on disk. See
terraform/terraform.tfvars.example for the
non-secret settings.
Each server has its own .env.example. See the per-server README.
Every script supports --help, which documents its arguments and the environment
variables it needs:
python python/objects/validate_objects.py --help
usage: validate_objects.py [-h] [--log-level {DEBUG,INFO,WARNING,ERROR}] [input]
Validate an object CSV before anything is sent to FMC.
positional arguments:
input Path to the input CSV. Defaults to inputs/objects.csv
options:
-h, --help show this help message and exit
--log-level {DEBUG,INFO,WARNING,ERROR}
Override LOG_LEVEL for this run.
The input argument is optional — omit it and the script uses the matching sample in
inputs/. A path that does not exist is reported immediately with exit code 2. Run the
steps in the order below; each validate_* step is read-only and safe.
Always run the
validate_*script first. It is the only step that catches a bad
CSV before FMC sees it.
python python/inventory/get_inventory.py
Writes domains.json, devices.json, network_objects.json, host_objects.json,
security_zones.json, and access_policies.json to outputs/reports/. Open
access_policies.json to find the id you need for ACCESS_POLICY_ID.
python python/objects/validate_objects.py inputs/objects.csv python python/objects/create_objects.py inputs/objects.csv
inputs/objects.csv format:
name,type,value,description APP1_HOST,Host,10.10.10.10,Application host APP1_NET,Network,10.10.20.0/24,Application subnet
type is Host or Network. value is a bare IP for Host and CIDR for Network.
Objects that already exist by name are reported as SKIP, not overwritten.
python python/services/validate_services.py inputs/services.csv python python/services/create_services.py inputs/services.csv
name,protocol,port,description HTTPS_TCP,TCP,443,HTTPS
protocol is TCP or UDP. port is 1–65535.
Requires ACCESS_POLICY_ID in python/.env.
python python/policy/validate_rules.py inputs/rules.csv
python python/policy/create_rules.py inputs/rules.csv
python python/policy/get_rules.py # read back what existsrule_name,src_zones,dst_zones,src_networks,dst_networks,service_objects,action,enabled,log_begin,log_end,comment ALLOW_APP1_DB,inside,server,APP1_HOST,DB1_HOST,MYSQL_TCP,ALLOW,true,false,true,Approved CHG12345
Multiple zones, networks, or services in one field are semicolon separated:
inside;dmz. action is ALLOW, BLOCK, TRUST, or MONITOR. A row referencing an
object that does not exist in FMC is reported as SKIP with the missing name — it does
not abort the run.
Requires NAT_POLICY_ID. Manual NAT payloads vary most between FMC releases — confirm
yours in API Explorer first.
python python/nat/validate_nat.py inputs/nat.csv python python/nat/create_manual_nat.py inputs/nat.csv
python python/reports/compliance_report.py
Writes outputs/reports/compliance_report.csv flagging naming-convention breaches,
duplicate object values, rules without logEnd, and rules without comments.
ansible-galaxy collection install -r ansible/requirements.yml export FMC_HOST=https://fmc.example.local export FMC_USERNAME=apiuser read -rs FMC_PASSWORD && export FMC_PASSWORD ansible-playbook -i ansible/inventory.yml ansible/playbooks/get_domain.yml ansible-playbook -i ansible/inventory.yml ansible/playbooks/get_network_objects.yml ansible-playbook -i ansible/inventory.yml ansible/playbooks/create_network_objects.yml
Objects created by the last playbook are defined in ansible/vars/network_objects.yml,
not inline in the playbook — edit that file, not the playbook.
cd terraform export TF_VAR_fmc_url=https://fmc.example.local export TF_VAR_fmc_username=apiuser read -rs TF_VAR_fmc_password && export TF_VAR_fmc_password terraform init terraform validate terraform plan # review every change before applying
Resource names depend on your provider version. Confirm them first:
terraform providers schema -json | jq '.provider_schemas[].resource_schemas | keys'
terraform.tfstate records object values in cleartext and is gitignored. Use an
encrypted remote backend for anything beyond a local lab.
cd mcp_servers/rest-api-mcp # or ansible-mcp / terraform-mcp pip install -r requirements.txt cp .env.example .env python client/test_client.py # interactive smoke test
Or with Docker:
docker compose up -d --build
Each server's README has a ready-to-paste mcpServers client configuration block for
Claude Desktop, VS Code, Cursor, and other MCP-aware agents.
For each use case, verify in three places:
outputs/reports/GET result from a follow-up script or PostmanFor policy and NAT changes, also verify:
See docs/TESTING.md for the full method.
You need an FMC to run this against. If you do not have a lab, Cisco DevNet provides free
sandboxes:
Once you have a sandbox, set FMC_HOST, FMC_USERNAME, and FMC_PASSWORD to the
sandbox values. Sandbox FMCs normally present a self-signed certificate — download its CA
and set FMC_CA_BUNDLE rather than setting VERIFY_SSL=false.
Start with python python/inventory/get_inventory.py. If that writes files to
outputs/reports/, your connectivity and credentials are correct.
https://<fmc-host>/api/api-explorer) before relying on a write path.create_manual_nat.py builds anFTDManualNatRule payload whose accepted fields differ noticeably between releases.terraform/main.tf ships withIssues are tracked in
GitHub Issues. Please use the
provided templates and include your FMC version.
| I need... | Go to |
|---|---|
| Help with this repo, a bug, or a feature idea | GitHub Issues |
| To report a security vulnerability in this repo | SECURITY.md — do not open a public issue |
| Help with Cisco Secure Firewall itself | Cisco TAC |
| A vulnerability in a Cisco product | Cisco PSIRT |
| FMC REST API questions | Cisco DevNet Secure Firewall |
| Community discussion | Cisco Code Exchange Community |
Before opening an issue, re-run with LOG_LEVEL=DEBUG and confirm the endpoint in API
Explorer. Full guidance is in SUPPORT.md.
Contributions are welcome. This project is judged on clarity and safety as much as
functionality, because the code talks to a firewall.
Current focus areas where help is most useful:
compliance_report.py with shadowed-rule and hit-count analysisDevelopment environment (differs from the general install by adding the dev tooling):
pip install -r python/requirements.txt pip install -r requirements-dev.txt pre-commit install
Run the same checks CI runs:
ruff check . # lint ruff format --check . # formatting pytest # 193 unit tests, no live FMC needed bandit -c pyproject.toml -r python mcp_servers ansible-lint ansible/ terraform -chdir=terraform fmt -check
Every pull request additionally runs mypy, pip-audit, gitleaks, CodeQL, and OSSF
Scorecard, and starts each MCP server with docker compose to confirm it answers on
/mcp. See .github/workflows/ci.yml.
Full instructions on how to contribute are in CONTRIBUTING.md, and
all participation is governed by the Code of Conduct.
Projects that inspired this
Related projects
cisco.fmcansible collection (GPL-3.0)fmc provider (MPL-2.0)References
This code is licensed under the Apache License, Version 2.0. See LICENSE for details.
Third-party attribution, Cisco trademark acknowledgement, and the generative-AI
disclosure are in NOTICE. Note in particular that the cisco.fmcansible
collection is GPL-3.0-or-later, the Terraform fmc provider is MPL-2.0, and the
Terraform CLI is BUSL-1.1 — all are invoked by this project, never vendored or
redistributed, so no copyleft obligation attaches to this Apache-2.0-licensed code.
| Document | Purpose |
|---|---|
| LICENSE | Apache-2.0 licence |
| NOTICE | Trademarks, third-party licences, AI disclosure |
| SECURITY.md | Vulnerability reporting and credential guidance |
| SUPPORT.md | Where to get help — and where not to |
| CONTRIBUTING.md | How to contribute |
| CODE_OF_CONDUCT.md | Contributor Covenant 2.1 |
| CHANGELOG.md | Release history |
| docs/TESTING.md | Validation method for every use case |
| docs/ADVANCED_USE_CASES.md | VPN and device onboarding guidance |
| docs/REFERENCES.md | External documentation links |
Owner
Contributors
Categories
SecurityProducts
Secure EndpointLicense
Code Exchange Community
Get help, share code, and collaborate with other developers in the Code Exchange community.View Community