ACI MCP Server
by realpaul3907
README.md
# ACI MCP Server
A Model Context Protocol (MCP) server that gives Claude (or any
MCP-compatible AI client) natural-language tools to read and configure a
live Cisco ACI fabric through the APIC REST API — with confirmation
required before any state-changing action, and structural safety checks
that can't be bypassed by confirmation alone.
Built and tested against Cisco's free DevNet "ACI Simulator Always-On"
sandbox. Full build write-up: [How to Connect Claude AI to Cisco ACI Using
MCP: A Step-by-Step Guide](https://hackernoon.com/how-to-connect-claude-ai-to-cisco-aci-using-mcp-a-step-by-step-guide)
on HackerNoon.
## Why this exists
Most AI assistants can explain Cisco ACI's object model. This gives Claude
the ability to actually *operate* one — read fabric state, build a real
Tenant → Application Profile → EPG → Bridge Domain → VRF hierarchy, apply
contracts between EPGs, and audit the result — through conversation, with
every write gated behind explicit confirmation.
## Features
- **22 tools** spanning fabric visibility, tenant/application management,
the full ACI object hierarchy, fabric access policy (VLAN pools, domains,
AAEPs, port bindings), and policy enforcement via contracts
- **Dry-run by default** — every state-changing tool describes what it
would do and requires a second call with `confirm=True` to actually
apply it
- **Unconditional guardrails where they matter** — `delete_tenant` refuses
to touch `common`, `infra`, or `mgmt` regardless of `confirm`
- **Pre-flight dependency checks** — `create_bridge_domain` and
`apply_contract` verify their target VRF/contract actually exists before
creating a reference to it, rather than silently accepting a dangling
one (see [CHANGELOG.md](CHANGELOG.md) for the real bug this fixed)
- **A structural audit tool**, not just a lister — `get_tenant_detail`
walks an entire tenant's object tree in one call and flags anything
incomplete
## Tool reference
| Tool | Type | Description |
|---|---|---|
| `list_tenants` | Read | List every tenant on the fabric |
| `get_fabric_health` | Read | Fabric-wide health score |
| `get_faults` | Read | Active faults filtered by severity |
| `get_port_config` | Read | EPG static binding + AAEP for a pod/node/interface |
| `get_tenant_detail` | Read | Full-subtree audit of a tenant, flags incomplete objects |
| `create_tenant` | Write | Create a tenant |
| `delete_tenant` | Write | Delete a tenant — hard-refuses `common`/`infra`/`mgmt` |
| `create_application_profile` | Write | Create an AP inside a tenant |
| `create_epg` | Write | Create an EPG inside an AP, optional BD binding |
| `create_bridge_domain` | Write | Create a BD with a VRF (pre-flight checked) |
| `associate_epg_bridge_domain` | Write | Bind an existing EPG to an existing BD |
| `create_vrf` | Write | Create a VRF (Context) inside a tenant |
| `create_contract` | Write | Create a contract + filter, optional port restriction |
| `apply_contract` | Write | Apply a contract between provider and consumer EPGs (pre-flight checked) |
| `add_static_path_binding` | Write | Bind an EPG to a physical port with a VLAN |
| `remove_static_path_binding` | Write | Undo one EPG-to-port binding (precise, doesn't touch shared infra objects) |
| `add_subnet` | Write | Give a Bridge Domain a Layer 3 gateway |
| `create_vlan_pool` | Write | Create a fabric-wide static-allocation VLAN pool |
| `create_physical_domain` | Write | Create a domain, tied to a VLAN pool (pre-flight checked) |
| `create_aaep` | Write | Create an AAEP, tied to a domain (pre-flight checked) |
| `associate_epg_domain` | Write | Tie an EPG to a domain (pre-flight checked) |
| `bind_port_to_aaep` | Write | Chain a port to an AAEP via Interface Policy Group + Leaf/Interface profiles — reuses an existing policy group by name instead of duplicating it |
All **Write** tools require `confirm=True` to actually execute; called
without it, they return a description of the intended change instead.
## Requirements
- Python 3.10+ (the `mcp` SDK requires it — 3.9 will fail to install it)
- Access to an APIC controller (a free DevNet sandbox works fine — get
current credentials at [devnetsandbox.cisco.com](https://devnetsandbox.cisco.com),
search "ACI Simulator")
- An MCP-compatible client (Claude Code, Claude Desktop, etc.)
## Setup
```bash
git clone https://github.com/realpaul3907/aci-mcp-server.git
cd aci-mcp-server
pip install -r requirements.txt
cp .env.example .env
# edit .env with your real APIC_URL / APIC_USER / APIC_PASSWORD
```
**Sanity check before wiring in an AI client** — confirm the server starts
cleanly on its own:
```bash
python3 aci_mcp_server.py
```
It should hang silently (waiting on stdio) with no traceback — that's
success. `Ctrl+C` to stop.
## Registering with Claude Code
```bash
claude mcp add aci-lab --scope user -- python3 /full/path/to/aci_mcp_server.py
```
If your APIC/environment is only reachable from a remote host (e.g. behind
SSH, or inside a container), pass credentials explicitly and route through
that instead:
```bash
claude mcp add aci-lab --scope user -- ssh user@host docker exec -i \
-e APIC_URL=https://your-apic \
-e APIC_USER=admin \
-e "APIC_PASSWORD=your-password" \
container-name python3 /path/in/container/aci_mcp_server.py
```
Start a fresh Claude Code session, run `/mcp` to confirm `aci-lab` shows
connected with 22 tools, then try:
> "List all tenants"
>
> "Check fabric health"
## A worked example
[`docs/testcase_app_to_db.md`](docs/testcase_app_to_db.md) walks through a
complete test case: building a fresh tenant from nothing — VRF, two Bridge
Domains, an Application Profile, an App-tier EPG and a DB-tier EPG — then
applying a contract that permits only TCP/5432 between them, with the DB
tier correctly configured as the provider and the App tier as the
consumer.
## Design notes
- **MCP is a protocol, not an AI feature.** This server works unmodified
with any MCP-compatible client — Claude, ChatGPT via its Agents SDK,
Gemini, or a local model — since nothing in the code is Claude-specific.
- **The dry-run/confirm pattern is enforced in code, not by the model's
judgment.** A write tool called without `confirm=True` cannot reach
APIC — the check happens before any network call, not after.
- **Not every guardrail is the same strength on purpose.** Most writes are
soft-gated (confirm and proceed); deleting a system tenant is hard-gated
(no confirmation path exists at all). See `delete_tenant`.
## Security
- `.env` is git-ignored — never commit real credentials.
- This is built and tested against a free, shared DevNet sandbox. If you
point this at a production APIC, review the credential-handling pattern
first — storing a real password as a plaintext environment variable
passed on a command line (as shown in the SSH example above) is
acceptable for a lab, not for production. Use a proper secrets manager
instead.
## License
[MIT](LICENSE)
## Author
Built by [Real Paul](https://www.linkedin.com/in/realpaul67) — network
automation engineer, NetDevOps. More at
[GitHub](https://github.com/realpaul3907) and
[HackerNoon](https://hackernoon.com/u/RealPaul_2komqcs8).This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues