Six surgical scrubs to clear cluster-fingerprint references before the
PyPI release. Per `~/.claude/rules/python.md`'s pre-publish PII audit
section: specific build strings (`15.0.1.12900-234`-style maintenance
release IDs) and cluster role descriptors ("production") narrow the
fingerprint of which deployment the developer tested against. Replaced
with the more accurate Cisco user-facing version ("CUCM 15.0(1)" or
"CUCM 15") and operational descriptor ("live cluster" — same trust
signal without the prod disclosure).
Files:
README.md
"Tested against CUCM 15.0.1.12900" → "Tested against CUCM 15.0(1)"
placeholder host hardened to "cucm-pub.example.com" (RFC-reserved
`.example` TLD per the rule's documented convention)
CHANGELOG.md
"production CUCM 15.0.1.12900 cluster" → "live CUCM 15 cluster"
src/mcaxl/risport.py
Comment: "verified against CUCM 15.0.1.12900 documentation" →
"verified against CUCM 15 RisPort70 docs"
src/mcaxl/route_plan.py
Comment: "the typepatternusage table in CUCM 15.0.1.12900" →
"the typepatternusage table in CUCM 15"
.env.example
Normalized to RFC-reserved values:
cucm-pub → cucm-pub.example.com
AxlUser → axl-readonly (descriptive function, not
a real-account-shape name)
TopSecret... → replace-with-your-password (clearly a placeholder)
Audit verification:
grep -rnE '15\.0\.1\.12900|bingham|SupportedSystems|CCX-AXL|CER-AXL|
CUC-AXL|TabSync|variphy|production|10\.[0-9]+\.[0-9]+\.[0-9]+|
172\.(1[6-9]|2[0-9]|3[01])\.[0-9]+\.[0-9]+|192\.168\.[0-9]+\.[0-9]+'
src/ pyproject.toml README.md CHANGELOG.md .env.example
→ returns empty (verified)
Sdist verification:
tar -tzf dist/mcaxl-2026.4.27.tar.gz | grep -iE 'CLAUDE|axlsqltoolkit|
bingham|tests/'
→ returns empty (verified)
Tests directory IS excluded from sdist via
`[tool.hatch.build.targets.sdist] exclude = ["tests/"]` — important
because test fixtures contain real cluster hostnames in mock SOAP
responses (test_risport.py SAMPLE_RESPONSE). Tests stay in the source
repo for development; they don't ship to PyPI.
Tests still pass: 155/155.
Ready for `uv publish --token …`.
189 lines
7.8 KiB
Markdown
189 lines
7.8 KiB
Markdown
# mcaxl
|
|
|
|
Read-only MCP server for **Cisco Unified Communications Manager (CUCM)** —
|
|
exposes the AXL SOAP API and RisPort70 real-time registration state to
|
|
LLMs for dial-plan analysis, configuration auditing, and impact analysis.
|
|
|
|
> Tested against CUCM 15.0(1). Should work on any CUCM 12.5+.
|
|
|
|
## 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 our PSTN carrier."*
|
|
- *"Are there partitions defined but unreachable from any CSS?"*
|
|
- *"Which phones are configured but not currently registered?"*
|
|
|
|
`mcaxl` gives an LLM SQL access to CUCM's Informix data dictionary,
|
|
schema-aware joins for common audit questions, and RisPort70
|
|
cross-reference for live registration state. Then a set of curated
|
|
prompts orchestrates the tools toward audit *findings*, not just data.
|
|
|
|
## 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`.
|
|
|
|
For operations that require write access (service control, packet capture,
|
|
log download, perfmon, etc.), install
|
|
[`@calltelemetry/cisco-cucm-mcp`](https://github.com/calltelemetry/cisco-cucm-mcp)
|
|
alongside this server. The two are complementary — `mcaxl` answers
|
|
"what does the config say?", `cisco-cucm-mcp` answers "what's happening
|
|
right now?".
|
|
|
|
## Install
|
|
|
|
```bash
|
|
# Run directly from PyPI:
|
|
uvx mcaxl
|
|
|
|
# Or as a pinned dev install:
|
|
pip install mcaxl
|
|
|
|
# Or via Claude Code's MCP registry:
|
|
claude mcp add cucm-axl -- uvx mcaxl
|
|
```
|
|
|
|
## Configure
|
|
|
|
Set these env vars (most operators use a `.env` file in the working directory):
|
|
|
|
```env
|
|
AXL_URL=https://cucm-pub.example.com:8443/axl/
|
|
AXL_USER=your-axl-service-account
|
|
AXL_PASS=your-password
|
|
|
|
# Optional:
|
|
AXL_VERIFY_TLS=false # CUCM ships self-signed certs; default off
|
|
AXL_CACHE_TTL=3600 # response cache TTL in seconds; 0 disables
|
|
AXL_RATE_LIMIT_RETRIES=3 # 502/503/504 retry count with backoff
|
|
AXL_WSDL_PATH= # explicit WSDL location override
|
|
AXL_WSDL_ZIP= # explicit toolkit zip path
|
|
CISCO_DOCS_INDEX_PATH= # for prompt enrichment (see Prompts section)
|
|
```
|
|
|
|
The AXL service account needs the **`Standard AXL Read Only API Access`**
|
|
role at minimum. It does *not* need the full `Standard AXL API Access`
|
|
role (read-write) — `mcaxl` is structurally incapable of using write
|
|
permissions even if granted.
|
|
|
|
## AXL WSDL bootstrap
|
|
|
|
CUCM's AXL toolkit is Cisco-licensed and not redistributable, so it's
|
|
not bundled. Download from your CUCM admin UI:
|
|
|
|
> Application → Plugins → Find → "Cisco AXL Toolkit" → Download
|
|
|
|
Drop the resulting `axlsqltoolkit.zip` into your working directory. On
|
|
first launch, the server auto-extracts `schema/15.0/` (or whichever
|
|
version matches your cluster) into `~/.cache/mcaxl/wsdl/15.0/`.
|
|
|
|
Alternative resolution paths (in order):
|
|
```bash
|
|
export AXL_WSDL_ZIP=/path/to/axlsqltoolkit.zip # explicit zip
|
|
export AXL_WSDL_PATH=/path/to/schema/15.0/AXLAPI.wsdl # explicit WSDL
|
|
# Or pre-populate the cache:
|
|
mkdir -p ~/.cache/mcaxl/wsdl/15.0/
|
|
cp /path/to/schema/15.0/* ~/.cache/mcaxl/wsdl/15.0/
|
|
```
|
|
|
|
## Tool surface (19 total)
|
|
|
|
### 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) |
|
|
|
|
### 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 |
|
|
| `route_translation_chain(number, css_name=None)` | Wildcard-aware pattern matcher |
|
|
| `route_digit_discard_instructions()` | DDI catalog |
|
|
| `route_device_pool_route_groups(device_pool_name=None)` | Local Route Group resolution |
|
|
| `route_devices_using_css(css_name)` | Impact analysis across 71 known fk-CSS columns |
|
|
| `route_filters(name=None, include_members=False)` | Route filter clauses + member rules |
|
|
|
|
### Real-time device registration (RisPort70)
|
|
|
|
| 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 across Phone, Gateway, SIPTrunk, HuntList, etc. |
|
|
|
|
## Prompts (10 total)
|
|
|
|
Each prompt orchestrates multiple tool calls toward a specific
|
|
audit narrative. They appear in Claude Code's slash menu under
|
|
`/mcp__cucm-axl__<name>`:
|
|
|
|
- `route_plan_overview` — fresh audit conversation seed
|
|
- `investigate_pattern(pattern, partition=None)` — single-pattern deep dive
|
|
- `audit_routing(focus="full")` — comprehensive walkthrough with checklist
|
|
- `cucm_sql_help(question)` — catch-all SQL helper
|
|
- `sip_trunk_report(name_filter=None)` — SIP trunk inventory + findings
|
|
- `phone_inventory_report(filter=None)` — phone fleet aggregates with anomaly findings (cross-references RisPort)
|
|
- `user_audit(focus="full")` — end users + app 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)
|
|
|
|
### Optional: schema-grounded prompt enrichment
|
|
|
|
Set `CISCO_DOCS_INDEX_PATH` to a directory containing `chunks.jsonl`
|
|
and `index_meta.json` (produced by the
|
|
[`mcp-cisco-docs`](https://github.com/...) indexer or any compatible
|
|
embedding pipeline) to have prompts pull relevant Cisco documentation
|
|
chunks inline. Without this, prompts gracefully degrade to a fallback
|
|
notice instructing the LLM to use the sibling cisco-docs server's
|
|
`search_docs` tool.
|
|
|
|
## Cache
|
|
|
|
Responses are cached in SQLite at
|
|
`~/.cache/mcaxl/responses/axl_responses.sqlite`. The cache is
|
|
**cluster-isolated** by SHA-256 of `AXL_URL` — pointing the server
|
|
at a different cluster never serves stale data from a previous one.
|
|
Cache survives restarts. Clear with `cache_clear()` after a known
|
|
config change.
|
|
|
|
## Caveats
|
|
|
|
- `route_translation_chain` evaluates CUCM wildcards (`X`, `!`, `[0-9]`,
|
|
`@`, `\+`) but does *not* model route-filter constraints on `@`
|
|
patterns. Use as guidance, not authoritative.
|
|
- The package's `recordingprofile` / `usageprofile` / `vipre164transformation`
|
|
reference categories were schema-verified against CUCM 15. If a future
|
|
CUCM version adds new `fkcallingsearchspace_*` columns,
|
|
`route_devices_using_css`'s coverage will lag until the package is
|
|
updated. The
|
|
`test_complete_schema_coverage_against_known_columns` test enforces
|
|
the current snapshot — failing red surfaces the drift loudly.
|
|
|
|
## License
|
|
|
|
MIT. See `LICENSE`.
|
|
|
|
## Source
|
|
|
|
- Repo: [git.supported.systems/mcp/mcaxl](https://git.supported.systems/mcp/mcaxl)
|
|
- Issues: [git.supported.systems/mcp/mcaxl/issues](https://git.supported.systems/mcp/mcaxl/issues)
|
|
- Changelog: [`CHANGELOG.md`](./CHANGELOG.md)
|