# mcp-cucm-axl Read-only MCP server for **Cisco Unified CM 15** AXL — built for LLM-driven cluster auditing, with a particular focus on the **Route Plan Report**: partitions, calling search spaces, route patterns, translation patterns, called/calling party transformations, and digit-discard instructions. ## Why this exists CUCM's admin UI is great for one-config-at-a-time work but painful for audit/discovery questions like: - "Which translation patterns rewrite the calling party number, and why?" - "Which CSSs include the `Internal_PT` partition, in what order?" - "Show me every route pattern targeting the SIP trunk to the carrier." - "Are there partitions defined but unreachable from any CSS?" This server gives an LLM SQL access to CUCM's Informix data dictionary, plus focused tools that bake in the right joins for routing-audit work. Pair it with the sibling [`mcp-cisco-docs`](../docs/) server and the LLM gets vendor documentation alongside live cluster state — answering "is our config consistent with Cisco's recommended baseline?" in a single conversation. ## Read-only by structural guarantee The server **never registers** AXL write methods. There is no `executeSQLUpdate`, no `add*`/`update*`/`remove*`/`apply*`/`reset*`/ `restart*` tool. Read-only is enforced by *absence* of write operations, not by runtime sanitization. Defense-in-depth: SQL queries are also client-side validated to begin with `SELECT` or `WITH`. ## Setup ### 1. Configure environment Edit `.env` (already gitignored): ```env AXL_URL=https://cucm-pub:8443/axl AXL_USER=AxlUser AXL_PASS=... AXL_VERIFY_TLS=false # CUCM ships self-signed certs; default off AXL_CACHE_TTL=3600 # 1 hour; 0 disables caching AXL_WSDL_PATH= # optional explicit WSDL location CISCO_DOCS_INDEX_PATH= # optional override for prompt enrichment ``` ### 2. Bootstrap the AXL WSDL Download the **Cisco AXL Toolkit** from your CUCM admin UI: > Application → Plugins → Find → "Cisco AXL Toolkit" → Download Drop the resulting `axlsqltoolkit.zip` into the project directory. On first launch, the server auto-extracts `schema/15.0/` into `~/.cache/mcp-cucm-axl/wsdl/15.0/`. The zip is gitignored (Cisco-licensed; not redistributable). Alternatives (in resolution order): ```bash # A: explicit zip elsewhere export AXL_WSDL_ZIP=/path/to/axlsqltoolkit.zip # B: explicit WSDL file export AXL_WSDL_PATH=/path/to/schema/15.0/AXLAPI.wsdl # C: pre-populated cache directory mkdir -p ~/.cache/mcp-cucm-axl/wsdl/15.0/ cp /path/to/schema/15.0/* ~/.cache/mcp-cucm-axl/wsdl/15.0/ ``` ### 3. Install + run ```bash uv sync uv run mcp-cucm-axl ``` Or via the bundled `.mcp.json`, automatically registered when Claude Code opens this directory. ## Tool surface ### Foundational | Tool | Purpose | |---|---| | `axl_version()` | Cluster version sanity check | | `axl_sql(query)` | Execute a SELECT against Informix data dictionary | | `axl_list_tables(pattern=None)` | Discover Informix tables | | `axl_describe_table(name)` | Column metadata for one table | | `cache_stats()`, `cache_clear(pattern=None)` | Cache plumbing | | `health()` | Subsystem self-check (cache / AXL / docs / RisPort init state) | ### Real-time device registration (RisPort70) Complementary to AXL — AXL tells you what's *configured*, RisPort tells you what's *currently registered*. The audit-relevant cross-reference is "configured but unregistered" (orphan signal). | Tool | Purpose | |---|---| | `device_registration_status(device_class, status, name_filter, page_size)` | Page through CUCM's RisPort `selectCmDevice` for live registration state | | `device_registration_summary()` | Cluster-wide breakdown: registered / unregistered / rejected counts across Phone, Gateway, SIPTrunk, HuntList, etc. | ### Route plan | Tool | Purpose | |---|---| | `route_partitions()` | All partitions with pattern + CSS-member counts | | `route_calling_search_spaces(name=None)` | CSS list with ordered partitions | | `route_patterns(kind=None, partition=None, filter=None)` | Route Plan Report — patterns + transformations | | `route_inspect_pattern(pattern, partition=None)` | Deep dive: transforms, route filter, reachable-from CSS, full destination chain (route list → groups → gateways) | | `route_lists_and_groups(name=None)` | Route list → route group → gateway chain (annotates Local Route Group placeholders) | | `route_translation_chain(number, css_name=None)` | Wildcard-aware pattern matcher: evaluates X / ! / [0-9] / @ / \\+ against the number and returns matches sorted by specificity | | `route_digit_discard_instructions()` | DDI catalog | | `route_device_pool_route_groups(device_pool_name=None)` | How each device pool resolves Local Route Group placeholders to actual gateway-bearing groups | | `route_devices_using_css(css_name)` | Impact analysis: every reference to a CSS across line CFA/CFB/CFNA/CFUR/translation/MWI/shared, device-level CSSs, voicemail pilots, route lists | | `route_filters(name=None)` | Route filter clauses + member rules (composed with @-pattern routes) | ## Prompts Schema-grounded conversation seeds. They pull relevant chunks from the sibling `cisco-docs` index and embed them inline: - `route_plan_overview` — fresh audit conversation seed - `investigate_pattern(pattern, partition=None)` — deep-dive a specific pattern - `audit_routing(focus="full")` — comprehensive audit walkthrough - `cucm_sql_help(question)` — catch-all for arbitrary SQL questions - `sip_trunk_report(name_filter=None)` — SIP trunk inventory + findings - `phone_inventory_report(filter=None)` — phone fleet aggregates with anomaly findings; cross-references RisPort registration state - `user_audit(focus="full")` — end users + application users + role assignments - `inbound_did_audit()` — XFORM-Inbound-DNIS inventory + screening pipeline - `hunt_pilot_audit()` — hunt pilots, queue settings, line group membership - `whoami(userid=None)` — single-user role chain (defaults to AXL service account) ## Scope and complement This server is **audit-focused**: read-only queries against AXL plus RisPort cross-reference for registration state. It does *not* cover operational debugging (logs, packet capture, perfmon counters, service control, certificates, backups). For those, install [`@calltelemetry/cisco-cucm-mcp`](https://github.com/calltelemetry/cisco-cucm-mcp) alongside this server: ```bash claude mcp add cucm-ops -- npx -y @calltelemetry/cisco-cucm-mcp@latest ``` The two servers are **complementary**, not competing — they answer different questions and use different CUCM APIs (AXL + RisPort here; DIME + RisPort + PerfMon + ControlCenter + SSH there). An LLM with both servers can compose audit findings (this server) with operational state (theirs) — e.g., *"audit found CSS X has 0 references AND RisPort shows zero phones currently registered against any device pool that inherits it → confirmed safe to delete."* ## Cache Responses are cached in SQLite at `~/.cache/mcp-cucm-axl/responses/axl_responses.sqlite`. Cache survives restarts. Clear with `cache_clear()` after a known config change. ## Notes - `route_translation_chain` does literal/prefix matching only. CUCM's actual matcher evaluates wildcards (`X`, `!`, `[0-9]`, etc.) and selects the longest match. Treat results as "patterns to investigate" rather than "definitive route." - Pattern type codes (`tkpatternusage`) used by `route_patterns(kind=...)` are stable across CUCM versions but enumerated against the `typepatternusage` table at query time, so any cluster-specific custom types still work.