Metadata-Version: 2.4
Name: mccer
Version: 2026.5.8.1
Summary: Read-only MCP server for Cisco Emergency Responder (CER) — unified facade over the v15su2 Configuration API and the legacy Read-Only API for E911 location and alert auditing.
Project-URL: Homepage, https://git.supported.systems/mcp/mccer
Project-URL: Source, https://git.supported.systems/mcp/mccer
Project-URL: Issues, https://git.supported.systems/mcp/mccer/issues
Project-URL: Changelog, https://git.supported.systems/mcp/mccer/src/branch/main/CHANGELOG.md
Author-email: Ryan Malloy <ryan@supported.systems>
License: MIT
License-File: LICENSE
Keywords: audit,cer,cisco,e911,emergency-responder,mcp,telephony,voip
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: System Administrators
Classifier: Intended Audience :: Telecommunications Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Telephony
Classifier: Topic :: System :: Networking :: Monitoring
Requires-Python: >=3.11
Requires-Dist: fastmcp>=3.2
Requires-Dist: httpx>=0.27
Requires-Dist: platformdirs>=4.9
Requires-Dist: python-dotenv>=1.0
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.24; extra == 'test'
Requires-Dist: pytest-httpx>=0.30; extra == 'test'
Requires-Dist: pytest>=8.0; extra == 'test'
Description-Content-Type: text/markdown

# mccer

[![PyPI](https://img.shields.io/pypi/v/mccer.svg)](https://pypi.org/project/mccer/)
[![Python](https://img.shields.io/pypi/pyversions/mccer.svg)](https://pypi.org/project/mccer/)
[![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)

Read-only MCP server for **Cisco Emergency Responder (CER)** — a unified facade
over both of CER's administrative APIs (the v15su2 Configuration API and the
older Read-Only API) for E911 location, alert, and license auditing.

> Tested against CER 15.0.1. Compatible with CER 8.x+ (legacy API) and 14+
> (Configuration API). Some `/v2/*` Configuration endpoints require 15SU2;
> mccer transparently falls back to legacy equivalents on older builds.

## What it looks like

Invoke the `e911_coverage_audit` prompt in any MCP-aware LLM client. Sample
findings the tool surfaces from a real cluster:

> 🔴 **Smart License sync stalled.** `LastSynchronizationTime` is `Mar 5
> 2025`; today is over a year later. `LicenseStatus: Waiting`. The cluster
> still operates on its cached authorization, but new phones beyond the
> licensed count may not get tracked at the next reboot. Investigate the
> Smart License agent's outbound HTTPS path.
>
> 🟡 **CER↔CUCM CTI signaling configured plaintext.** `EnableSecureConn:
> false`. Call-setup signaling between the two systems travels unencrypted
> on the management network. For HIPAA-relevant deployments, plan CAPF
> certificate enrollment + secure CTI migration.
>
> 🟡 **Onsite-alert recipient email contains a typo.** One named entry's
> `OnsiteAlertEmail` is missing a single letter from the surname relative to
> the entry's `OnsiteAlertName` field. 911 calls from that ERL produce no
> email notification to the intended recipient.
>
> 🟢 **Two onsite-alert entries (`Default`, `Operator`) carry no contact.**
> If any ERL points at these entries, 911 calls from that ERL trigger no
> internal notification at all. Confirm intentional vs incomplete config.

Each finding ties to specific tool calls (`cer_e911_license`,
`cer_cucm_clusters`, `cer_onsite_alerts`) with severity + recommended action,
not raw API output the operator has to interpret.

## Scope and complement

`mccer` is intentionally narrow — **read-only audit of CER configuration via
both of its admin APIs**. It does NOT cover:

- CUCM-side configuration (dial plan, phones, registration) — use
  [`mcaxl`](https://pypi.org/project/mcaxl/)
- CUCM live operations (log tail, CDR, RIS, packet capture) — use
  [`mcsiphon`](https://pypi.org/project/mcsiphon/)
- *Write* operations on CER (intentionally not exposed; see below)

The three projects compose well in the same MCP-aware LLM session for
end-to-end UC audit narratives spanning ERL → phone → CUCM device → CDR.

## Why this exists

CER's admin UI is great for one-config-at-a-time work but painful for the
cross-cluster audit questions an E911 review actually needs to answer:

- *"Which phones are registered to CUCM but have no ERL match in CER?"*
- *"Are CER's onsite-alert email addresses still pointing at active staff?"*
- *"Did the Smart License agent ever stop syncing? When?"*
- *"Is the CER↔CUCM CTI channel encrypted?"*
- *"Which switches does CER even know about, via which SNMP version?"*
- *"For every 911 call placed last quarter, which ELIN was sent to the PSAP?"*
- *"Is the audit account I created actually scoped to read-only on the API
  surface I think it is?"*

mccer queries those answers programmatically and ships curated prompts that
orchestrate the tools into ready-to-act audit findings.

## Read-only by structural guarantee

The Configuration API supports full CRUD; mccer exposes only its GETs. Both
of mccer's HTTP clients implement only `.get()` — no `.post()`, `.put()`,
`.delete()`. Tools are wrappers around those reads. Defense-in-depth: even
if a future regression added a write helper, FastMCP would still need an
explicit `@mcp.tool` decorator to expose it.

This means the CER API service account mccer uses can be scoped to a
read-only audit role on CER. mccer is structurally incapable of mutating
CER state regardless of what permissions the account is granted.

## Install

```bash
# Run directly from PyPI:
uvx mccer

# Or as a pinned dev install:
pip install mccer

# Or via Claude Code's MCP registry:
claude mcp add cer -- uvx mccer
```

## Configure

```bash
# .env (in project root or anywhere on dotenv's search path)
CER_HOST=cer-pub.example.com         # bare hostname → https; or full URL
CER_USERNAME=apiuser                  # CER local or remote-on-CUCM user
CER_PASSWORD=...

# Optional:
MCCER_VERIFY_TLS=false                # self-signed certs / SSH-tunneled access
MCCER_CACHE_TTL=300                   # response cache TTL in seconds (future)
```

### CER user setup

Cisco's auth docs require the **`CER System Admin`** role for Configuration
API access. The narrower `CER Audit Admin` role is *not* sufficient on the
Configuration API — empirically (CER 15.0.1) those endpoints return
`{"status":"Account is locked..."}` with audit-admin alone. The legacy
Read-Only API has more permissive role gating but still uses Basic auth.

Cisco recommends a *dedicated* CER API account separate from the main admin
so password rotations don't disrupt unrelated operations.

### Reaching CER through an SSH tunnel

If your CER publisher isn't directly reachable from where mccer runs, the
common pattern is an SSH local-port forward through a bastion:

```bash
ssh -fN -L 18443:cer-pub.example.com:443 your-bastion
# in .env:
CER_HOST=https://localhost:18443
MCCER_VERIFY_TLS=false   # cert won't match localhost
```

## Tool surface (19 total)

Each tool returns Cisco's actual response shape — parsed from XML to dict on
the legacy API, native JSON on the Configuration API. Empty-result responses
come back in a distinct shape (e.g. `{"message":"No Manual Config Phone
Record Available","TrackingID":"..."}`) and tools propagate as-is rather than
normalizing.

### Phone-bucket inventory (the four-bucket E911 partition)

Every CUCM-registered phone falls into exactly one of these buckets:

- `cer_phones_unlocated` — registered but no ERL → 🔴 immediate 911 risk
- `cer_phones_tracked` — successful auto-location (switchport / IP-subnet / AP)
- `cer_phones_nontracked` — CER sees them but can't auto-locate
- `cer_phones_manualconfig` — operator-set ERL override

### Alert recipients (who gets notified on 911)

- `cer_onsite_alerts` — email recipients per ERL/zone
- `cer_pager_alerts` — pager addresses per ERL/zone

### Network discovery (the auto-location chain)

- `cer_switchports` — switchports CER probed via SNMP
- `cer_lan_switches` — LAN switches in CER's inventory
- `cer_snmp_v2` / `cer_snmp_v3` — SNMP credentials per target
- `cer_discovery_status` — phone-tracking scan-cycle health

### Configuration

- `cer_e911_license` — Smart License tier + utilization
- `cer_cucm_clusters` — CUCM clusters CER receives data from (fills a hole the
  Configuration API leaves: `/cluster` only supports POST/PUT/DELETE, no GET)
- `cer_call_history` — retrospective 911 call log (caller / ERL / ELIN / time)

### Admin user inventory

- `cer_list_users` / `cer_list_user_roles` / `cer_list_user_groups`

### Connectivity

- `cer_ping` — verify both API surfaces are reachable + auth works
- `mccer_health` — local introspection (no network call)

## Resources (3)

URI-addressable read-only data the LLM ingests as ambient context (vs tools,
which the LLM decides when to invoke):

- `cer://api-inventory` — full 34-endpoint reference (static)
- `cer://overview` — live cluster snapshot (license + clusters + alert
  recipients), composing several tool calls
- `cer://erls` — live ERL definitions (the location reference data every
  audit-flavored reasoning step needs)

## Prompts (4)

Multi-turn workflow templates that orchestrate tool calls into audit-ready
findings:

- `whoami` — orientation: what mccer is, what's available
- `e911_coverage_audit(focus="full")` — full E911 readiness audit walkthrough,
  ranked findings (🔴/🟡/🟢) with evidence + recommended actions
- `alert_recipient_review` — focused stale-recipient hunt for 911 contacts
- `admin_account_audit` — CER admin/API user staleness + role bloat review

## CER version compatibility

| CER version | What works |
|---|---|
| 8.x+ | Legacy Read-Only API (most tools) |
| 14+ | Configuration API GETs (users/roles/groups, ERLs, IPSubnet) |
| 15SU1 / 15.0.1 base | Above + most `/v2/*` endpoints; `/v2/unlocatedphones` and `/v2/callhistorydetails` 415 → mccer falls back to legacy `/unlocatedphones/info` and `/callhistory/info` automatically |
| 15SU2+ | Full Configuration API surface including all `/v2/*` |

Falling back is silent — operators don't see a difference except slightly
different response shapes between Config-API JSON and legacy-API XML→dict.

## Caveats

- **Both APIs run on the CER Publisher only.** There is no Subscriber-side
  read failover.
- **HTTPS required** despite some legacy docs showing `http://` — mccer
  hard-rejects plaintext at client construction.
- **No `429 Too Many Requests`** status from CER. Failure modes under load
  are 500 / 503 / TCP timeout. Retry logic should backoff on those.
- **Caching is recommended but not yet shipped.** Cisco's "no enforced rate
  throttling" warning + Publisher-only deployment means client-side caching
  matters operationally. A future release will add the SQLite-backed cache
  pattern used by the sibling `mcaxl`.
- **Cisco lockout policy is real.** Repeated bad-auth attempts will lock the
  CER user account; clear via the CER admin web UI (password reset / unlock
  user). If your API password contains shell- or `.env`-special characters
  (`#`, `!`, `&`, `$`, etc.), double-check the value reaching CER actually
  matches what's stored in `.env` — mismatch produces failed-auth attempts
  that trigger lockout, presenting as `Account is locked` in API responses.

## Sibling projects

- [mcaxl](https://pypi.org/project/mcaxl/) — CUCM AXL/RIS audit (dial plan,
  phones, registration, partition/CSS analysis)
- [mcsiphon](https://pypi.org/project/mcsiphon/) — CUCM serviceability + CDR
  (logs, real-time registration, control center, performance counters)

## License

MIT — see [LICENSE](LICENSE).

## Source

- PyPI: <https://pypi.org/project/mccer/>
- Repository: <https://git.supported.systems/mcp/mccer>
- Issues: <https://git.supported.systems/mcp/mccer/issues>
- Changelog: [CHANGELOG.md](CHANGELOG.md)
