4 Commits

Author SHA1 Message Date
ca6956e826 Rename to mcaxl + scrub for public PyPI release
Renames the package from `mcp-cucm-axl` to `mcaxl` to fit the
operator's mc<interface> naming convention (mcusb, mcaxl, …),
and scrubs Bingham-specific defaults so the package works for
anyone, anywhere.

Rename:
  - pyproject.toml: name, scripts entry point, description
  - src/mcp_cucm_axl/ → src/mcaxl/ (git mv preserves history)
  - All Python imports updated via sed
  - Cache directory: ~/.cache/mcp-cucm-axl/ → ~/.cache/mcaxl/
  - Log prefix [mcp-cucm-axl] → [mcaxl]
  - Package version lookup: importlib.metadata.version("mcaxl")
  - .mcp.json command updated to invoke `mcaxl` script
  - All 155 tests pass under the new name (verified)

Bingham-specific scrubs:
  - docs_loader._DEFAULT_INDEX_DIR: hardcoded /home/rpm/bingham/...
    path removed; defaults to None. Operators set CISCO_DOCS_INDEX_PATH
    env var; without it, prompts gracefully degrade with a fallback
    notice instructing the LLM to use the cisco-docs MCP search_docs
    tool instead.
  - prompts/_common.docs_or_empty_msg: removed the explicit
    /home/rpm/bingham/... path from the fallback message text.
  - server.py: removed dead-code copy of _docs_or_empty_msg() that
    was leftover from before the prompts package extraction.
  - README.md: completely rewritten as a public-facing readme. Lead
    paragraph names CUCM as the target platform, install instructions
    cover uvx / pip / Claude Code MCP add. Recommends cisco-cucm-mcp
    as the operations counterpart.

PyPI metadata:
  - Initial CalVer version: 2026.04.27
  - License: MIT (LICENSE file added)
  - Project URLs: Homepage / Source / Issues / Changelog all point
    at git.supported.systems/mcp/mcaxl (newly-created Gitea repo
    in the mcp/ org for PyPI releases)
  - Classifiers: Beta / Telecommunications Industry / Topic:Telephony
  - Keywords: mcp, cisco, cucm, axl, risport, voip, sip, audit
  - sdist excludes: CLAUDE.md, .env*, axlsqltoolkit.zip, audits/,
    tests/, pytest/ruff caches. Verified clean: wheel ships only the
    mcaxl/ source tree + LICENSE + METADATA + entry_points.

CHANGELOG.md added with a 2026.04.27 initial-release entry,
documenting tool/prompt counts, structural read-only guarantees,
Hamilton review closure, live-cluster verification, and known
limitations.

Build verification:
  - `uv build` produces clean wheel + sdist
  - Wheel: 22 source files, 195KB total, no Bingham-specific files
  - Sdist excludes verified: no CLAUDE.md, no axlsqltoolkit.zip
  - Entry point: `mcaxl = mcaxl.server:main`
  - Package installs as mcaxl==2026.4.27
2026-04-27 12:53:54 -06:00
8815db06d8 Add whoami prompt — single-user role chain with AXL service-account default
Operator-suggested prompt: "what does my AXL account *actually* have
permission to do?" Resolves the user → access-control-group →
function-role chain for a single account, defaulting to the AXL service
account from AXL_USER env when no userid is given.

The prompt principle came in using table names from older Cisco
docs (`enduserauthgroupmap`, `dirgrouprolemap`) that don't exist on
CUCM 15. The shipped SQL uses the verified CUCM 15 names
(`enduserdirgroupmap`, `functionroledirgroupmap`); a regression test
asserts the deprecated names don't appear in the rendered SQL section,
so any future "fix" reverting to the older names fires red.

Live verification on cucm-pub.binghammemorial.org found the existing
AXL service account (`SupportedSystemsReadOnly`) has 4 roles via the
`ReadOnly-AXL` access control group:
  - Standard AXL API Access  (full RW — group misnamed)
  - Standard AXL Read Only API Access  (the genuinely-read-only one)
  - Standard Packet Sniffing  (PHI-relevant in healthcare)
  - Standard RealtimeAndTraceCollection

The first finding is structural: the group `ReadOnly-AXL` contains
the FULL RW role `Standard AXL API Access` despite its name. The
MCP server's structural read-only enforcement (no write methods
registered) is what prevents this from mattering — but the account
itself is over-privileged relative to what the tool needs. The
prompt's findings template surfaces this kind of misnamed-group
case explicitly.

Also discovered (and documented in the prompt body): AXL auth is
case-insensitive for usernames, but SQL `WHERE name = 'X'` is
case-sensitive. Step 3 of the prompt handles the case-mismatch
fallback so a typo like `SupportedSYstemsReadOnly` (env) vs
`SupportedSystemsReadOnly` (cluster canonical) doesn't produce a
silently-empty result.

5 new tests:
  - correct CUCM 15 table names embedded in SQL
  - explicit userid threads through to the query
  - default reads AXL_USER from env
  - missing userid AND missing env → clear instruction
  - SQL injection defense (single-quote escape)

123 → 128 tests; 9 → 10 prompts. Prompt registration smoke test
updated to assert the new shim is wired.
2026-04-26 00:05:31 -06:00
8aaeb04417 Add 4 audit prompts: phone_inventory, user_audit, inbound_did_audit, hunt_pilot_audit
Builds on the prompts-package extraction. Each new prompt embeds
schema-verified SQL plus a findings template tuned to surface
audit-actionable issues (orphans, drift, capacity outliers, security
posture).

phone_inventory_report(filter=None):
  Aggregates by model / device pool / CSS, then anomaly queries for
  phones with no description, phones whose description echoes their
  MAC-based name, phones with no owner, phones in non-default CSS.
  Cross-references owner status (phones owned by inactive users
  surface as findings).

user_audit(focus=full|admin|inactive|app_users):
  End user + application user inventory, role/group assignments via
  the enduserdirgroupmap → dirgroup → functionroledirgroupmap →
  functionrole join chain. Security-critical findings: app users
  with admin-grade role memberships, local-user accounts with admin
  privileges, phones owned by inactive users.

inbound_did_audit():
  Reusable form of today's cucm-inbound-did-inventory work. XFORM-
  Inbound-DNIS curated list categorized (pass-through, block-trans,
  specific renames, wildcards, catch-all hazard). Cross-checked
  against Internal-PT route patterns and the operator-curated
  PSTN-Screen-PT spam blocklist. Findings for orphan target
  extensions and the silent !-catch-all risk.

hunt_pilot_audit():
  Hunt pilot inventory with queue settings, line group membership,
  and distribution algorithm decoding. Schema knowledge already
  Hamilton-verified: huntpilotqueue joins via fknumplan_pilot, NOT
  fknumplan (the test asserts the correct column appears in the
  rendered prompt). Findings: queue misconfigurations (NULL
  destinations, infinite max-wait), empty line groups, dead pilots
  with no route-list destination.

Implementation notes:
  - Each prompt's SQL was validated against the live cluster
    (cucm-pub.binghammemorial.org, CUCM 15.0.1.12900-234).
  - user_audit originally used UNION ALL with NULL-typed status
    column for the headcounts query; Informix rejected it. Split
    into two simpler queries (commented in the prompt body).
  - phone_inventory_report uses a Hamilton-style SQL escape for
    the optional name_filter (single quotes doubled).
  - All four prompts gracefully degrade when the docs index isn't
    loaded (verified by test_all_new_prompts_render_without_docs).

114 → 123 tests; 5 → 9 prompts. Full live-cluster verification:
  - 12 phone models, 629 Cisco 7841 phones (largest model)
  - 1,246 active end users, 25 application users
  - Hunt pilots with named distribution algorithms (Broadcast, Top
    Down, etc.) — confirms typedistributealgorithm join works
  - Hamilton-fixed huntpilotqueue.fknumplan_pilot column verified
    in the embedded SQL via dedicated regression test.
2026-04-25 23:57:01 -06:00
e6aa075793 Extract prompts into a package + add sip_trunk_report
Refactor: the four existing inline prompts in server.py move into
individual modules under src/mcp_cucm_axl/prompts/. Server.py keeps
thin @mcp.prompt-decorated shims that delegate to the corresponding
render() function — FastMCP needs the shims because it introspects
their signatures to expose parameters to the LLM, but the prompt
*content* now lives one-prompt-per-file.

Why: server.py's prompt section had grown to ~200 lines of inline
markdown. As more query patterns get documented (see
docs/query-patterns/) this would only worsen. Per-module bodies are
easier to diff, review, and unit-test in isolation.

Layout:
  src/mcp_cucm_axl/prompts/
    __init__.py
    _common.py             — shared helpers, keyword sets, render_schema_block
    route_plan_overview.py
    investigate_pattern.py
    audit_routing.py
    cucm_sql_help.py
    sip_trunk_report.py    — NEW

Each prompt module exports a `render(docs, *args) -> str` function
that takes the DocsIndex as a parameter (no module globals). The
shim in server.py grabs the runtime `_docs` and passes it in. Pure
functions = trivially unit-testable.

NEW prompt: sip_trunk_report.

Implementation reference: docs/query-patterns/sip-trunk-report.md
(written separately as a query-pattern doc, validated against the
live cluster). The prompt embeds:
  - Step 1: trunk inventory SQL (device + sipdevice + 5 LEFT JOINs)
  - Step 2: per-destination SQL (siptrunkdestination)
  - Step 3: pointer to existing route_lists_and_groups() tool
  - Step 4: findings template (SPOF, profile sprawl, CSS asymmetry,
    codec heterogeneity, DNS-vs-IP, security posture)

Optional `name_filter` parameter narrows the inventory via LIKE; the
filter value is escaped for SQL safety (single quotes doubled per
Informix convention).

Tests: 14 new in tests/test_prompts_package.py covering each
prompt's render() with and without docs, plus a registration smoke
test that confirms the FastMCP shim set matches the prompts package
exports (catches the case where a new module is added without its
shim).

Total: 100 → 114 tests; 5 prompts registered; live verification
against cucm-pub.binghammemorial.org confirms the embedded SQL
produces real inventory data. The four original prompts are
behaviorally identical to before — same content, just relocated.
2026-04-25 23:29:05 -06:00