# SIP Trunk Report — Query Pattern **Goal:** Produce a comprehensive inventory of every SIP trunk on a CUCM cluster, with destinations, profile assignments, and downstream route-group/route-list membership. Useful for handoff documentation, post-migration cleanup, and identifying single-points-of-failure on specific trunks. **Status:** Validated against CUCM 15. Empty `prompts/` directory at `src/mcaxl/prompts/` is the intended home for extracting this into a `@mcp.prompt` function. For now, prompts live inline in `server.py` (see `route_plan_overview`, `investigate_pattern`, `audit_routing`). --- ## Source-of-truth tables | Table | Holds | |---|---| | `device` | Trunk row: name, description, FKs to profiles/CSS/pool/location | | `sipdevice` | SIP-specific config: codec, calling-party selection, RDNIS handling, security, UR I domain | | `siptrunkdestination` | One row per destination IP/port (a trunk can have multiple, ordered by `sortorder`) | | `typeclass` | Device class enum — filter `tc.name = 'Trunk'` | | `sipprofile` | SIP Profile name (joined via `device.fksipprofile`) | | `callingsearchspace` | CSS name (joined via `device.fkcallingsearchspace`) | | `devicepool` | Device Pool name (joined via `device.fkdevicepool`) | | `location` | Location name for CAC/RSVP (joined via `device.fklocation`) | | `typesipcodec` | Codec name enum (joined via `sipdevice.tksipcodec`) | **Not directly relevant but worth knowing:** - `sipsecurityprofile` — name lookup for `device.fksecurityprofile`. Skipped in the query below because the security profile name is rarely informative on a routine trunk inventory; add the join if security posture matters for the use case. - `siptrunkoauth` — additional auth config for OAuth-authenticated trunks. --- ## Query 1 — Trunk inventory (one row per trunk) Joins `device` + `sipdevice` and pulls the human-readable names of every FK field that operators typically want when scanning trunks. ```sql SELECT d.name AS trunk_name, d.description, sp.name AS sip_profile, css.name AS calling_search_space, dp.name AS device_pool, loc.name AS location, tsc.name AS preferred_codec, sd.requesturidomainname AS sip_domain, sd.isanonymous AS anon_caller_id, sd.preferrouteheaderdestination AS prefer_route_header, sd.acceptinboundrdnis AS accept_inbound_rdnis, sd.acceptoutboundrdnis AS accept_outbound_rdnis FROM device d JOIN typeclass tc ON d.tkclass = tc.enum JOIN sipdevice sd ON sd.fkdevice = d.pkid LEFT JOIN sipprofile sp ON d.fksipprofile = sp.pkid LEFT JOIN callingsearchspace css ON d.fkcallingsearchspace = css.pkid LEFT JOIN devicepool dp ON d.fkdevicepool = dp.pkid LEFT JOIN location loc ON d.fklocation = loc.pkid LEFT JOIN typesipcodec tsc ON sd.tksipcodec = tsc.enum WHERE tc.name = 'Trunk' ORDER BY d.name; ``` **Why these specific columns:** - `description` — operator's free-form annotation; almost always names the upstream device + IP, useful when the trunk name itself is opaque. - `sip_profile` — drives transport (UDP/TCP/TLS), early offer, OPTIONS ping, 100rel, etc. Trunks sharing a SIP profile share *all* of those settings. - `calling_search_space` — the CSS used when this trunk *originates* a call (typical for inbound from a SIP carrier hitting the CUCM trunk). - `device_pool` + `location` — clustering and CAC/RSVP grouping. In a single-site cluster these are usually homogeneous. - `preferred_codec` — the codec CUCM advertises first in SDP from this trunk. - `accept_inbound_rdnis` / `accept_outbound_rdnis` — does the trunk pass RDNIS (Redirected Dialed Number Identification Service) on diversions/forwards? Voicemail trunks need both `t`; PSTN-facing trunks usually `f`. **LVARCHAR(1) flag fields** (`anon_caller_id`, `prefer_route_header`, `accept_inbound_rdnis`, `accept_outbound_rdnis`) return `'t'` or `'f'` — not booleans. Render appropriately in any output. --- ## Query 2 — Destinations (one row per destination IP/port) A trunk can have multiple destinations (active/active or active/standby — sortorder controls retry order). Separate query because of the one-to-many relationship. ```sql SELECT d.name AS trunk_name, std.address, std.port, std.sortorder FROM siptrunkdestination std JOIN sipdevice sd ON std.fksipdevice = sd.pkid JOIN device d ON sd.fkdevice = d.pkid ORDER BY d.name, std.sortorder; ``` **Notes:** - `address` is `VARCHAR(255)` — IP literal *or* DNS name. Expressway-C trunks often use FQDNs (e.g., `exp-c-p.example.com`) so SRV resolution can shift the actual destination. - `addressipv6` exists on the same table but is empty on most clusters. - `port` is `INTEGER` — defaults to 5060 (SIP over UDP/TCP) or 5061 (TLS), but custom ports are common for non-standard integrations (RightFax, recording platforms). --- ## Query 3 — Route-group / route-list membership **Don't write raw SQL for this** — the relevant join table is `devicenumplanmap`-adjacent and its name has shifted across CUCM versions. Use the existing MCP tool: ``` route_lists_and_groups() ``` Filter the result for `route_groups[].devices[].class == "Trunk"` to get the set of `(trunk → route group → route list)` triples. Note that some route lists have route groups with **no static device members** — those resolve to a Local Route Group via the calling phone's device-pool `fkroutegroup_local` mapping at call-time (the CUCM Standard Local Route Group feature). Trunks reachable only through Local Route Groups won't appear in the static result and require a follow-up call to `route_device_pool_route_groups()` to enumerate. --- ## Common gotchas 1. **`routelistdetail` doesn't exist.** I tried it; it fails. The actual table name varies, and the join logic for route-list → route-group → device is non-obvious. Use the MCP tool above. 2. **`securityprofile` is `sipsecurityprofile`** for SIP trunks (not the generic `phonesecurityprofile`). If you add the security profile join, use the SIP-specific table. 3. **`tkclass` filters by class enum, not text** — but `typeclass.name` provides the human-readable label. The query above filters on `tc.name = 'Trunk'` which matches all SIP and ICT trunks. To narrow to SIP-only, also require `EXISTS (SELECT 1 FROM sipdevice sd WHERE sd.fkdevice = d.pkid)` (or the inner `JOIN sipdevice` already does that). 4. **Trunks without a primary CSS** are valid — Expressway-C trunks on this cluster have `fkcallingsearchspace = NULL`. Use `LEFT JOIN` and render NULL as "(none)" rather than treating it as a finding. --- ## Suggested follow-up tool calls After running Query 1+2 and `route_lists_and_groups()`, the audit narrative usually wants: 1. `route_devices_using_css(css_name=)` — see what else uses the same CSS as a particular trunk; helps identify shared blast-radius dependencies. 2. `route_inspect_pattern(pattern, partition)` — for each route pattern that targets a trunk-bearing route list, walk the call path. 3. `axl_sql("SELECT name, description FROM sipprofile WHERE pkid IN (...)")` — if multiple trunks share a SIP profile, look up the profile's full detail (transport, early-offer, ping, etc.) once. --- ## Findings template (what to call out) When this query is wrapped in a `@mcp.prompt`, the prompt should ask the LLM to surface: - **Single-point-of-failure trunks**: any route group with one trunk member where that route group is the only path for a critical pattern (911, voicemail, fax). Cross-reference with `route_lists_and_groups()` device counts. - **Profile sprawl vs. consolidation**: are 11 trunks using 11 different SIP profiles, or do most share a small number? Sprawl = harder to audit transport/timing settings consistently. - **CSS asymmetry**: are PSTN-facing inbound trunks using a restrictive CSS that prevents them from reaching internal extensions? Are internal-facing trunks (voicemail) using a permissive CSS? Mismatches can cause one-way audio or routing failures. - **Codec heterogeneity**: most clusters standardize on G.711 µ-law. Trunks advertising G.722 or G.729 first warrant explanation. - **DNS-vs-IP destinations**: trunks using FQDNs depend on cluster DNS; flag if the FQDN resolution path adds a SPOF the audit hadn't surfaced (e.g., single DNS server). - **Security posture**: trunks using `Non Secure SIP Trunk Profile` for carrier-facing connections are a finding worth noting (typical for premise-equipment SIP carriers, but document the deliberate choice). --- ## Proposed prompt name and signature ```python @mcp.prompt def sip_trunk_report() -> str: """Comprehensive SIP trunk inventory: profiles, destinations, downstream route-group membership, with findings template. """ ... ``` Or with optional filtering: ```python @mcp.prompt def sip_trunk_report(name_filter: str | None = None) -> str: """SIP trunk inventory. Pass `name_filter` to narrow to one trunk (substring match against device.name).""" ... ``` The body should embed the queries above, the follow-up tool-call list, and the findings template — same pattern as `route_plan_overview`. --- ## Related - Existing prompts (inline in `server.py`): `route_plan_overview`, `investigate_pattern`, `audit_routing` - Existing tool: `route_lists_and_groups()` — the right way to traverse the trunk → RG → RL chain - Existing tool: `route_devices_using_css(css_name)` — for follow-up blast-radius analysis on each trunk's CSS - Cisco data dictionary for CUCM 15: search via `cisco-docs` MCP for "SIPDevice", "SIPTrunkDestination", "Device" tables