@calltelemetry/cucm-mcp 0.1.8 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,6 +7,7 @@ Included capabilities:
7
7
  - Query + download trace/log files via CUCM DIME Log Collection SOAP services
8
8
  - Query/download Syslog via DIME SystemLogs selections
9
9
  - Start/stop packet captures via CUCM CLI over SSH, then download the resulting `.cap` via DIME
10
+ - Analyze captured pcaps: SIP call flows, SCCP/Skinny messages, RTP quality metrics (via tshark)
10
11
 
11
12
  ## Configuration
12
13
 
@@ -24,6 +25,20 @@ Credentials are read from tool args or environment variables.
24
25
  - `CUCM_SSH_PASSWORD`
25
26
  - `CUCM_SSH_PORT` (default `22`)
26
27
 
28
+ ### tshark (Pcap Analysis)
29
+
30
+ The pcap analysis tools require **tshark** (Wireshark CLI). It is discovered automatically:
31
+
32
+ 1. `TSHARK_PATH` env var (explicit override)
33
+ 2. `tshark` in PATH
34
+ 3. `/Applications/Wireshark.app/Contents/MacOS/tshark` (macOS Wireshark install)
35
+ 4. `/usr/bin/tshark` (Linux)
36
+
37
+ If tshark is not found, pcap analysis tools return a helpful error instead of failing silently.
38
+
39
+ - `TSHARK_PATH` — override tshark binary location
40
+ - `CUCM_MCP_TSHARK_TIMEOUT_MS` — execution timeout (default: 60000ms)
41
+
27
42
  ### TLS
28
43
 
29
44
  CUCM lab environments often use self-signed certificates. By default this server sets `NODE_TLS_REJECT_UNAUTHORIZED=0` unless you opt into strict verification:
@@ -76,14 +91,36 @@ yarn test
76
91
 
77
92
  Live tests are opt-in via env vars; see `test/live.test.js`.
78
93
 
79
- ## Useful Tools
94
+ ## Tools
95
+
96
+ ### Log Collection (DIME)
97
+
98
+ | Tool | Description |
99
+ |------|-------------|
100
+ | `select_logs_minutes` | List recent ServiceLogs/SystemLogs files |
101
+ | `select_syslog_minutes` | List recent system log files (defaults to `Syslog`) |
80
102
 
81
- - `select_logs_minutes` - list recent ServiceLogs/SystemLogs files
82
- - `select_syslog_minutes` - list recent system log files (defaults to `Syslog`)
83
- - `packet_capture_start` / `packet_capture_stop` - control captures via SSH
84
- - `packet_capture_stop_and_download` - stop capture + download `.cap` via DIME
85
- - `packet_capture_state_list` - list captures from state file
86
- - `packet_capture_download_from_state` - download by captureId after restart
103
+ ### Packet Capture (SSH + DIME)
104
+
105
+ | Tool | Description |
106
+ |------|-------------|
107
+ | `packet_capture_start` | Start capture via CUCM CLI over SSH |
108
+ | `packet_capture_stop` | Stop a running capture |
109
+ | `packet_capture_stop_and_download` | Stop capture + download `.cap` via DIME |
110
+ | `packet_capture_state_list` | List captures from state file |
111
+ | `packet_capture_download_from_state` | Download by captureId after restart |
112
+
113
+ ### Pcap Analysis (tshark)
114
+
115
+ These tools analyze downloaded `.cap` files so an LLM can reason about VoIP calls without opening Wireshark. All accept either a file path or a `captureId` from the state store.
116
+
117
+ | Tool | Description |
118
+ |------|-------------|
119
+ | `pcap_call_summary` | High-level overview — protocols, endpoints, SIP call count, RTP stream count |
120
+ | `pcap_sip_calls` | SIP call flows grouped by Call-ID with setup timing, SDP codec/media info |
121
+ | `pcap_sccp_messages` | Skinny/SCCP messages with human-readable message type names |
122
+ | `pcap_rtp_streams` | RTP quality per stream — jitter, packet loss, codec, duration |
123
+ | `pcap_protocol_filter` | Arbitrary tshark display filter for deeper investigation |
87
124
 
88
125
  ## Packet Capture Notes
89
126
 
@@ -145,3 +182,13 @@ open -R "/tmp/foo.cap"
145
182
  # Open in Wireshark
146
183
  open -a Wireshark "/tmp/foo.cap"
147
184
  ```
185
+
186
+ ### Analyzing the Capture (LLM)
187
+
188
+ After downloading, use the pcap analysis tools to query the capture without leaving the MCP session:
189
+
190
+ 1. **Quick triage** — `pcap_call_summary` to see what protocols/calls are in the file
191
+ 2. **SIP drill-down** — `pcap_sip_calls` to trace INVITE → 200 OK → BYE flows
192
+ 3. **SCCP drill-down** — `pcap_sccp_messages` for Cisco phone ↔ CallManager signaling
193
+ 4. **Audio quality** — `pcap_rtp_streams` for jitter, packet loss, codec per RTP stream
194
+ 5. **Custom query** — `pcap_protocol_filter` with any tshark display filter (e.g., `sip.Method == INVITE`)
package/dist/axl.js CHANGED
@@ -1,130 +1,235 @@
1
1
  import { XMLParser } from "fast-xml-parser";
2
2
  const parser = new XMLParser({
3
- ignoreAttributes: false,
4
- attributeNamePrefix: "@",
5
- removeNSPrefix: true,
6
- trimValues: true,
3
+ ignoreAttributes: false,
4
+ attributeNamePrefix: "@",
5
+ removeNSPrefix: true,
6
+ trimValues: true
7
7
  });
8
8
  function basicAuthHeader(username, password) {
9
- return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
9
+ return `Basic ${Buffer.from(`${username}:${password}`, "utf8").toString("base64")}`;
10
10
  }
11
11
  function escapeXml(s) {
12
- return s
13
- .replaceAll("&", "&")
14
- .replaceAll("<", "&lt;")
15
- .replaceAll(">", "&gt;")
16
- .replaceAll('"', "&quot;")
17
- .replaceAll("'", "&apos;");
18
- }
19
- export function normalizeHost(hostOrUrl) {
20
- const s = String(hostOrUrl || "").trim();
21
- if (!s)
22
- throw new Error("host is required");
23
- if (s.includes("://")) {
24
- const u = new URL(s);
25
- return u.hostname;
12
+ return s.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
13
+ }
14
+ function formatUnknownError(e) {
15
+ if (e instanceof Error) return e.message;
16
+ if (typeof e === "string") return e;
17
+ try {
18
+ return JSON.stringify(e);
19
+ } catch {
20
+ return String(e);
21
+ }
22
+ }
23
+ function isPlainObject(v) {
24
+ return typeof v === "object" && v !== null && !Array.isArray(v);
25
+ }
26
+ function expandReturnedTagsPaths(paths) {
27
+ const root = {};
28
+ for (const raw of paths) {
29
+ const p = String(raw || "").trim();
30
+ if (!p) continue;
31
+ const parts = p.split(".").filter(Boolean);
32
+ if (parts.length === 0) continue;
33
+ let cur = root;
34
+ for (let i = 0; i < parts.length; i++) {
35
+ const key = parts[i];
36
+ const isLeaf = i === parts.length - 1;
37
+ const next = cur[key];
38
+ if (isLeaf) {
39
+ if (next == null) cur[key] = null;
40
+ } else {
41
+ if (!isPlainObject(next)) cur[key] = {};
42
+ cur = cur[key];
43
+ }
26
44
  }
27
- return s.replace(/^https?:\/\//, "").replace(/\/+$/, "").split("/")[0];
45
+ }
46
+ return root;
28
47
  }
29
- export function resolveAxlAuth(auth) {
30
- const username = auth?.username || process.env.CUCM_AXL_USERNAME || process.env.CUCM_USERNAME || process.env.CUCM_DIME_USERNAME;
31
- const password = auth?.password || process.env.CUCM_AXL_PASSWORD || process.env.CUCM_PASSWORD || process.env.CUCM_DIME_PASSWORD;
32
- if (!username || !password) {
33
- throw new Error("Missing AXL credentials (provide auth or set CUCM_AXL_USERNAME/CUCM_AXL_PASSWORD)");
48
+ function objectToXml(tag, value) {
49
+ if (Array.isArray(value)) {
50
+ return value.map((v) => objectToXml(tag, v)).join("");
51
+ }
52
+ if (value == null) {
53
+ return `<${tag}/>`;
54
+ }
55
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
56
+ return `<${tag}>${escapeXml(String(value))}</${tag}>`;
57
+ }
58
+ if (!isPlainObject(value)) {
59
+ return `<${tag}>${escapeXml(String(value))}</${tag}>`;
60
+ }
61
+ const attrs = [];
62
+ const children = [];
63
+ let text = null;
64
+ for (const [k, v] of Object.entries(value)) {
65
+ if (k === "#text") {
66
+ text = v == null ? "" : String(v);
67
+ continue;
34
68
  }
35
- return { username, password };
69
+ if (k.startsWith("@")) {
70
+ const attrName = k.slice(1);
71
+ if (!attrName) continue;
72
+ if (v == null) continue;
73
+ attrs.push(`${attrName}="${escapeXml(String(v))}"`);
74
+ continue;
75
+ }
76
+ children.push(objectToXml(k, v));
77
+ }
78
+ const attrStr = attrs.length ? " " + attrs.join(" ") : "";
79
+ const childStr = children.join("");
80
+ const textStr = text != null ? escapeXml(text) : "";
81
+ if (!childStr && !textStr) {
82
+ return `<${tag}${attrStr}/>`;
83
+ }
84
+ return `<${tag}${attrStr}>${textStr}${childStr}</${tag}>`;
85
+ }
86
+ function normalizeHost(hostOrUrl) {
87
+ const s = String(hostOrUrl || "").trim();
88
+ if (!s) throw new Error("host is required");
89
+ if (s.includes("://")) {
90
+ const u = new URL(s);
91
+ return u.hostname;
92
+ }
93
+ return s.replace(/^https?:\/\//, "").replace(/\/+$/, "").split("/")[0];
94
+ }
95
+ function resolveAxlAuth(auth) {
96
+ const username = auth?.username || process.env.CUCM_AXL_USERNAME || process.env.CUCM_USERNAME || process.env.CUCM_DIME_USERNAME;
97
+ const password = auth?.password || process.env.CUCM_AXL_PASSWORD || process.env.CUCM_PASSWORD || process.env.CUCM_DIME_PASSWORD;
98
+ if (!username || !password) {
99
+ throw new Error("Missing AXL credentials (provide auth or set CUCM_AXL_USERNAME/CUCM_AXL_PASSWORD)");
100
+ }
101
+ return { username, password };
36
102
  }
37
- export function resolveAxlTarget(hostOrUrl, port, version) {
38
- const host = normalizeHost(hostOrUrl);
39
- const envPort = process.env.CUCM_AXL_PORT ? Number.parseInt(process.env.CUCM_AXL_PORT, 10) : undefined;
40
- const resolvedPort = port ?? envPort ?? 8443;
41
- const resolvedVersion = version || process.env.CUCM_AXL_VERSION || "15.0";
42
- return { host, port: resolvedPort, version: resolvedVersion };
103
+ function resolveAxlTarget(hostOrUrl, port, version) {
104
+ const host = normalizeHost(hostOrUrl);
105
+ const envPort = process.env.CUCM_AXL_PORT ? Number.parseInt(process.env.CUCM_AXL_PORT, 10) : void 0;
106
+ const resolvedPort = port ?? envPort ?? 8443;
107
+ const resolvedVersion = version || process.env.CUCM_AXL_VERSION || "15.0";
108
+ return { host, port: resolvedPort, version: resolvedVersion };
43
109
  }
44
110
  function soapEnvelope(axlVersion, innerXml) {
45
- const ns = `http://www.cisco.com/AXL/API/${escapeXml(axlVersion)}`;
46
- return (`<?xml version="1.0" encoding="UTF-8"?>` +
47
- `<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="${ns}">` +
48
- `<soapenv:Header/>` +
49
- `<soapenv:Body>` +
50
- innerXml +
51
- `</soapenv:Body>` +
52
- `</soapenv:Envelope>`);
53
- }
54
- async function fetchAxl(target, auth, operation, innerXml, timeoutMs = 30_000) {
55
- const url = `https://${target.host}:${target.port}/axl/`;
56
- const xmlBody = soapEnvelope(target.version, innerXml);
57
- const res = await fetch(url, {
58
- method: "POST",
59
- headers: {
60
- Authorization: basicAuthHeader(auth.username, auth.password),
61
- "Content-Type": "text/xml; charset=utf-8",
62
- SOAPAction: `CUCM:DB ver=${target.version} ${operation}`,
63
- Accept: "*/*",
64
- },
65
- body: Buffer.from(xmlBody, "utf8"),
66
- signal: AbortSignal.timeout(timeoutMs),
67
- });
68
- const contentType = res.headers.get("content-type");
69
- const text = await res.text().catch(() => "");
70
- if (!res.ok) {
71
- throw new Error(`CUCM AXL HTTP ${res.status}: ${text || res.statusText}`);
72
- }
73
- return { contentType, xml: text };
111
+ const ns = `http://www.cisco.com/AXL/API/${escapeXml(axlVersion)}`;
112
+ return `<?xml version="1.0" encoding="UTF-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="${ns}"><soapenv:Header/><soapenv:Body>` + innerXml + `</soapenv:Body></soapenv:Envelope>`;
113
+ }
114
+ async function fetchAxl(target, auth, operation, innerXml, timeoutMs = 3e4) {
115
+ const url = `https://${target.host}:${target.port}/axl/`;
116
+ const xmlBody = soapEnvelope(target.version, innerXml);
117
+ const res = await fetch(url, {
118
+ method: "POST",
119
+ headers: {
120
+ Authorization: basicAuthHeader(auth.username, auth.password),
121
+ "Content-Type": "text/xml; charset=utf-8",
122
+ SOAPAction: `CUCM:DB ver=${target.version} ${operation}`,
123
+ Accept: "*/*"
124
+ },
125
+ body: Buffer.from(xmlBody, "utf8"),
126
+ signal: AbortSignal.timeout(timeoutMs)
127
+ });
128
+ const contentType = res.headers.get("content-type");
129
+ const text = await res.text().catch(() => "");
130
+ if (!res.ok) {
131
+ throw new Error(`CUCM AXL HTTP ${res.status}: ${text || res.statusText}`);
132
+ }
133
+ return { contentType, xml: text };
74
134
  }
75
135
  function parseSoapReturn(operation, xmlText) {
76
- const parsed = parser.parse(xmlText);
77
- const env = parsed.Envelope || parsed;
78
- const body = env.Body || env;
79
- const fault = body?.Fault;
80
- if (fault) {
81
- const faultString = fault.faultstring || fault.faultcode || JSON.stringify(fault);
82
- return { fault: String(faultString) };
136
+ const parsed = parser.parse(xmlText);
137
+ const env = parsed.Envelope || parsed;
138
+ const body = env.Body || env;
139
+ const fault = body?.Fault;
140
+ if (fault) {
141
+ const faultString = fault.faultstring || fault.faultcode || JSON.stringify(fault);
142
+ return { fault: String(faultString) };
143
+ }
144
+ const resp = body?.[`${operation}Response`];
145
+ const ret = resp?.return;
146
+ if (ret == null) return {};
147
+ if (typeof ret === "string") return { returnValue: ret };
148
+ if (typeof ret === "object" && typeof ret["#text"] === "string") return { returnValue: ret["#text"] };
149
+ return { returnValue: String(ret) };
150
+ }
151
+ function parseSoap(operation, xmlText) {
152
+ const parsed = parser.parse(xmlText);
153
+ const env = parsed.Envelope || parsed;
154
+ const body = env.Body || env;
155
+ const fault = body?.Fault;
156
+ if (fault) {
157
+ const faultString = fault?.faultstring || fault?.faultcode || fault?.reason || void 0;
158
+ return { fault, faultString: faultString ? String(faultString) : JSON.stringify(fault) };
159
+ }
160
+ const resp = body?.[`${operation}Response`];
161
+ const ret = resp?.return;
162
+ return { returnValue: ret };
163
+ }
164
+ async function axlExecute(hostOrUrl, args) {
165
+ const operation = String(args.operation || "").trim();
166
+ if (!operation) throw new Error("operation is required");
167
+ const target = resolveAxlTarget(hostOrUrl, args.port, args.version);
168
+ const auth = resolveAxlAuth(args.auth);
169
+ let data = args.data;
170
+ if (isPlainObject(data) && Array.isArray(data.returnedTags)) {
171
+ const rt = data.returnedTags;
172
+ const paths = rt.map((p) => String(p));
173
+ const expanded = expandReturnedTagsPaths(paths);
174
+ const clone = { ...data };
175
+ clone.returnedTags = expanded;
176
+ data = clone;
177
+ }
178
+ const innerBody = isPlainObject(data) ? Object.entries(data).map(([k, v]) => objectToXml(k, v)).join("") : data == null ? "" : objectToXml("value", data);
179
+ const innerXml = `<ns:${escapeXml(operation)}>${innerBody}</ns:${escapeXml(operation)}>`;
180
+ const requestXml = soapEnvelope(target.version, innerXml);
181
+ try {
182
+ const { xml } = await fetchAxl(target, auth, operation, innerXml, args.timeoutMs ?? 3e4);
183
+ const parsed = parseSoap(operation, xml);
184
+ if (parsed.fault) {
185
+ const faultStr = parsed.faultString || "SOAP fault";
186
+ throw new Error(`AXL ${operation} fault: ${faultStr}`);
83
187
  }
84
- const resp = body?.[`${operation}Response`];
85
- const ret = resp?.return;
86
- if (ret == null)
87
- return {};
88
- if (typeof ret === "string")
89
- return { returnValue: ret };
90
- // Some AXL responses wrap the value.
91
- if (typeof ret === "object" && typeof ret["#text"] === "string")
92
- return { returnValue: ret["#text"] };
93
- return { returnValue: String(ret) };
94
- }
95
- export async function updatePhonePacketCapture(hostOrUrl, args) {
96
- const target = resolveAxlTarget(hostOrUrl, args.port, args.version);
97
- const auth = resolveAxlAuth(args.auth);
98
- const name = String(args.deviceName || "").trim();
99
- if (!name)
100
- throw new Error("deviceName is required");
101
- const mode = String(args.mode || "").trim();
102
- if (!mode)
103
- throw new Error("mode is required");
104
- const duration = Math.trunc(args.durationSeconds);
105
- if (!Number.isFinite(duration) || duration <= 0)
106
- throw new Error("durationSeconds must be > 0");
107
- const innerXml = `<ns:updatePhone>` +
108
- `<name>${escapeXml(name)}</name>` +
109
- `<packetCaptureMode>${escapeXml(mode)}</packetCaptureMode>` +
110
- `<packetCaptureDuration>${escapeXml(String(duration))}</packetCaptureDuration>` +
111
- `</ns:updatePhone>`;
112
- const { xml } = await fetchAxl(target, auth, "updatePhone", innerXml, args.timeoutMs ?? 30_000);
113
- const parsed = parseSoapReturn("updatePhone", xml);
114
- if (parsed.fault)
115
- throw new Error(`AXL updatePhone fault: ${parsed.fault}`);
116
- return { host: target.host, operation: "updatePhone", returnValue: parsed.returnValue };
117
- }
118
- export async function applyPhone(hostOrUrl, args) {
119
- const target = resolveAxlTarget(hostOrUrl, args.port, args.version);
120
- const auth = resolveAxlAuth(args.auth);
121
- const name = String(args.deviceName || "").trim();
122
- if (!name)
123
- throw new Error("deviceName is required");
124
- const innerXml = `<ns:applyPhone><name>${escapeXml(name)}</name></ns:applyPhone>`;
125
- const { xml } = await fetchAxl(target, auth, "applyPhone", innerXml, args.timeoutMs ?? 30_000);
126
- const parsed = parseSoapReturn("applyPhone", xml);
127
- if (parsed.fault)
128
- throw new Error(`AXL applyPhone fault: ${parsed.fault}`);
129
- return { host: target.host, operation: "applyPhone", returnValue: parsed.returnValue };
188
+ return {
189
+ host: target.host,
190
+ port: target.port,
191
+ version: target.version,
192
+ operation,
193
+ returnValue: parsed.returnValue,
194
+ requestXml: args.includeRequestXml ? requestXml : void 0,
195
+ responseXml: args.includeResponseXml ? xml : void 0
196
+ };
197
+ } catch (e) {
198
+ throw new Error(`AXL ${operation} failed: ${formatUnknownError(e)}`);
199
+ }
200
+ }
201
+ async function updatePhonePacketCapture(hostOrUrl, args) {
202
+ const target = resolveAxlTarget(hostOrUrl, args.port, args.version);
203
+ const auth = resolveAxlAuth(args.auth);
204
+ const name = String(args.deviceName || "").trim();
205
+ if (!name) throw new Error("deviceName is required");
206
+ const mode = String(args.mode || "").trim();
207
+ if (!mode) throw new Error("mode is required");
208
+ const duration = Math.trunc(args.durationSeconds);
209
+ if (!Number.isFinite(duration) || duration <= 0) throw new Error("durationSeconds must be > 0");
210
+ const innerXml = `<ns:updatePhone><name>${escapeXml(name)}</name><packetCaptureMode>${escapeXml(mode)}</packetCaptureMode><packetCaptureDuration>${escapeXml(String(duration))}</packetCaptureDuration></ns:updatePhone>`;
211
+ const { xml } = await fetchAxl(target, auth, "updatePhone", innerXml, args.timeoutMs ?? 3e4);
212
+ const parsed = parseSoapReturn("updatePhone", xml);
213
+ if (parsed.fault) throw new Error(`AXL updatePhone fault: ${parsed.fault}`);
214
+ return { host: target.host, operation: "updatePhone", returnValue: parsed.returnValue };
215
+ }
216
+ async function applyPhone(hostOrUrl, args) {
217
+ const target = resolveAxlTarget(hostOrUrl, args.port, args.version);
218
+ const auth = resolveAxlAuth(args.auth);
219
+ const name = String(args.deviceName || "").trim();
220
+ if (!name) throw new Error("deviceName is required");
221
+ const innerXml = `<ns:applyPhone><name>${escapeXml(name)}</name></ns:applyPhone>`;
222
+ const { xml } = await fetchAxl(target, auth, "applyPhone", innerXml, args.timeoutMs ?? 3e4);
223
+ const parsed = parseSoapReturn("applyPhone", xml);
224
+ if (parsed.fault) throw new Error(`AXL applyPhone fault: ${parsed.fault}`);
225
+ return { host: target.host, operation: "applyPhone", returnValue: parsed.returnValue };
130
226
  }
227
+ export {
228
+ applyPhone,
229
+ axlExecute,
230
+ normalizeHost,
231
+ resolveAxlAuth,
232
+ resolveAxlTarget,
233
+ updatePhonePacketCapture
234
+ };
235
+ //# sourceMappingURL=axl.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/axl.ts"],
4
+ "sourcesContent": ["import { XMLParser } from \"fast-xml-parser\";\n\nexport type AxlAuth = { username?: string; password?: string };\n\nexport type AxlTarget = {\n host: string;\n port: number;\n version: string;\n};\n\nconst parser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: \"@\",\n removeNSPrefix: true,\n trimValues: true,\n});\n\nfunction basicAuthHeader(username: string, password: string): string {\n return `Basic ${Buffer.from(`${username}:${password}`, \"utf8\").toString(\"base64\")}`;\n}\n\nfunction escapeXml(s: string): string {\n return s\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\")\n .replaceAll(\"'\", \"&apos;\");\n}\n\nfunction formatUnknownError(e: unknown): string {\n if (e instanceof Error) return e.message;\n if (typeof e === \"string\") return e;\n try {\n return JSON.stringify(e);\n } catch {\n return String(e);\n }\n}\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === \"object\" && v !== null && !Array.isArray(v);\n}\n\nfunction expandReturnedTagsPaths(paths: string[]): Record<string, unknown> {\n // Allows shorthand like:\n // [\"name\", \"model\", \"lines.line.dirn.pattern\"]\n // into a nested returnedTags object shape.\n const root: Record<string, unknown> = {};\n for (const raw of paths) {\n const p = String(raw || \"\").trim();\n if (!p) continue;\n const parts = p.split(\".\").filter(Boolean);\n if (parts.length === 0) continue;\n let cur: Record<string, unknown> = root;\n for (let i = 0; i < parts.length; i++) {\n const key = parts[i];\n const isLeaf = i === parts.length - 1;\n const next = cur[key];\n if (isLeaf) {\n // Leaf tags are empty elements (\"<foo/>\")\n if (next == null) cur[key] = null;\n } else {\n if (!isPlainObject(next)) cur[key] = {};\n cur = cur[key] as Record<string, unknown>;\n }\n }\n }\n return root;\n}\n\nfunction objectToXml(tag: string, value: unknown): string {\n // Convention:\n // - null/undefined => <tag/>\n // - primitive => <tag>value</tag>\n // - array => repeated <tag>...</tag>\n // - object => nested elements, supports:\n // - \"@attr\" keys for XML attributes\n // - \"#text\" for text nodes\n if (Array.isArray(value)) {\n return value.map((v) => objectToXml(tag, v)).join(\"\");\n }\n\n if (value == null) {\n return `<${tag}/>`;\n }\n\n if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n return `<${tag}>${escapeXml(String(value))}</${tag}>`;\n }\n\n if (!isPlainObject(value)) {\n return `<${tag}>${escapeXml(String(value))}</${tag}>`;\n }\n\n const attrs: string[] = [];\n const children: string[] = [];\n let text: string | null = null;\n for (const [k, v] of Object.entries(value)) {\n if (k === \"#text\") {\n text = v == null ? \"\" : String(v);\n continue;\n }\n if (k.startsWith(\"@\")) {\n const attrName = k.slice(1);\n if (!attrName) continue;\n if (v == null) continue;\n attrs.push(`${attrName}=\"${escapeXml(String(v))}\"`);\n continue;\n }\n children.push(objectToXml(k, v));\n }\n\n const attrStr = attrs.length ? \" \" + attrs.join(\" \") : \"\";\n const childStr = children.join(\"\");\n const textStr = text != null ? escapeXml(text) : \"\";\n\n if (!childStr && !textStr) {\n return `<${tag}${attrStr}/>`;\n }\n return `<${tag}${attrStr}>${textStr}${childStr}</${tag}>`;\n}\n\nexport function normalizeHost(hostOrUrl: string): string {\n const s = String(hostOrUrl || \"\").trim();\n if (!s) throw new Error(\"host is required\");\n if (s.includes(\"://\")) {\n const u = new URL(s);\n return u.hostname;\n }\n return s.replace(/^https?:\\/\\//, \"\").replace(/\\/+$/, \"\").split(\"/\")[0];\n}\n\nexport function resolveAxlAuth(auth?: AxlAuth): Required<AxlAuth> {\n const username = auth?.username || process.env.CUCM_AXL_USERNAME || process.env.CUCM_USERNAME || process.env.CUCM_DIME_USERNAME;\n const password = auth?.password || process.env.CUCM_AXL_PASSWORD || process.env.CUCM_PASSWORD || process.env.CUCM_DIME_PASSWORD;\n if (!username || !password) {\n throw new Error(\"Missing AXL credentials (provide auth or set CUCM_AXL_USERNAME/CUCM_AXL_PASSWORD)\");\n }\n return { username, password };\n}\n\nexport function resolveAxlTarget(hostOrUrl: string, port?: number, version?: string): AxlTarget {\n const host = normalizeHost(hostOrUrl);\n const envPort = process.env.CUCM_AXL_PORT ? Number.parseInt(process.env.CUCM_AXL_PORT, 10) : undefined;\n const resolvedPort = port ?? envPort ?? 8443;\n const resolvedVersion = version || process.env.CUCM_AXL_VERSION || \"15.0\";\n return { host, port: resolvedPort, version: resolvedVersion };\n}\n\nfunction soapEnvelope(axlVersion: string, innerXml: string): string {\n const ns = `http://www.cisco.com/AXL/API/${escapeXml(axlVersion)}`;\n return (\n `<?xml version=\"1.0\" encoding=\"UTF-8\"?>` +\n `<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ns=\"${ns}\">` +\n `<soapenv:Header/>` +\n `<soapenv:Body>` +\n innerXml +\n `</soapenv:Body>` +\n `</soapenv:Envelope>`\n );\n}\n\nasync function fetchAxl(\n target: AxlTarget,\n auth: Required<AxlAuth>,\n operation: string,\n innerXml: string,\n timeoutMs = 30_000\n): Promise<{ contentType: string | null; xml: string }> {\n const url = `https://${target.host}:${target.port}/axl/`;\n const xmlBody = soapEnvelope(target.version, innerXml);\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n Authorization: basicAuthHeader(auth.username, auth.password),\n \"Content-Type\": \"text/xml; charset=utf-8\",\n SOAPAction: `CUCM:DB ver=${target.version} ${operation}`,\n Accept: \"*/*\",\n },\n body: Buffer.from(xmlBody, \"utf8\"),\n signal: AbortSignal.timeout(timeoutMs),\n });\n\n const contentType = res.headers.get(\"content-type\");\n const text = await res.text().catch(() => \"\");\n if (!res.ok) {\n throw new Error(`CUCM AXL HTTP ${res.status}: ${text || res.statusText}`);\n }\n return { contentType, xml: text };\n}\n\nfunction parseSoapReturn(operation: string, xmlText: string): { returnValue?: string; fault?: string } {\n const parsed = parser.parse(xmlText);\n const env = parsed.Envelope || parsed;\n const body = env.Body || env;\n\n const fault = body?.Fault;\n if (fault) {\n const faultString = fault.faultstring || fault.faultcode || JSON.stringify(fault);\n return { fault: String(faultString) };\n }\n\n const resp = body?.[`${operation}Response`];\n const ret = resp?.return;\n if (ret == null) return {};\n if (typeof ret === \"string\") return { returnValue: ret };\n // Some AXL responses wrap the value.\n if (typeof ret === \"object\" && typeof ret[\"#text\"] === \"string\") return { returnValue: ret[\"#text\"] };\n return { returnValue: String(ret) };\n}\n\nfunction parseSoap(operation: string, xmlText: string): {\n fault?: unknown;\n faultString?: string;\n returnValue?: unknown;\n} {\n const parsed = parser.parse(xmlText);\n const env = parsed.Envelope || parsed;\n const body = env.Body || env;\n\n const fault = body?.Fault;\n if (fault) {\n const faultString =\n (fault as any)?.faultstring || (fault as any)?.faultcode || (fault as any)?.reason || undefined;\n return { fault, faultString: faultString ? String(faultString) : JSON.stringify(fault) };\n }\n\n const resp = body?.[`${operation}Response`];\n const ret = resp?.return;\n return { returnValue: ret };\n}\n\nexport async function axlExecute(\n hostOrUrl: string,\n args: {\n operation: string;\n data?: unknown;\n auth?: AxlAuth;\n port?: number;\n version?: string;\n timeoutMs?: number;\n includeRequestXml?: boolean;\n includeResponseXml?: boolean;\n }\n): Promise<{\n host: string;\n port: number;\n version: string;\n operation: string;\n returnValue?: unknown;\n requestXml?: string;\n responseXml?: string;\n}> {\n const operation = String(args.operation || \"\").trim();\n if (!operation) throw new Error(\"operation is required\");\n\n const target = resolveAxlTarget(hostOrUrl, args.port, args.version);\n const auth = resolveAxlAuth(args.auth);\n\n let data = args.data;\n\n // Convenience: allow returnedTags as array of strings (including dotted paths)\n // and expand it into the nested XML structure AXL expects.\n if (isPlainObject(data) && Array.isArray((data as any).returnedTags)) {\n const rt = (data as any).returnedTags as unknown[];\n const paths = rt.map((p) => String(p));\n const expanded = expandReturnedTagsPaths(paths);\n const clone: Record<string, unknown> = { ...(data as any) };\n clone.returnedTags = expanded;\n data = clone;\n }\n\n const innerBody = isPlainObject(data)\n ? Object.entries(data)\n .map(([k, v]) => objectToXml(k, v))\n .join(\"\")\n : data == null\n ? \"\"\n : objectToXml(\"value\", data);\n\n const innerXml = `<ns:${escapeXml(operation)}>${innerBody}</ns:${escapeXml(operation)}>`;\n const requestXml = soapEnvelope(target.version, innerXml);\n\n try {\n const { xml } = await fetchAxl(target, auth, operation, innerXml, args.timeoutMs ?? 30_000);\n const parsed = parseSoap(operation, xml);\n if (parsed.fault) {\n const faultStr = parsed.faultString || \"SOAP fault\";\n throw new Error(`AXL ${operation} fault: ${faultStr}`);\n }\n return {\n host: target.host,\n port: target.port,\n version: target.version,\n operation,\n returnValue: parsed.returnValue,\n requestXml: args.includeRequestXml ? requestXml : undefined,\n responseXml: args.includeResponseXml ? xml : undefined,\n };\n } catch (e) {\n // Ensure we always throw an Error with a useful message.\n throw new Error(`AXL ${operation} failed: ${formatUnknownError(e)}`);\n }\n}\n\nexport async function updatePhonePacketCapture(\n hostOrUrl: string,\n args: {\n deviceName: string;\n mode: string;\n durationSeconds: number;\n auth?: AxlAuth;\n port?: number;\n version?: string;\n timeoutMs?: number;\n }\n): Promise<{ host: string; operation: \"updatePhone\"; returnValue?: string }> {\n const target = resolveAxlTarget(hostOrUrl, args.port, args.version);\n const auth = resolveAxlAuth(args.auth);\n\n const name = String(args.deviceName || \"\").trim();\n if (!name) throw new Error(\"deviceName is required\");\n const mode = String(args.mode || \"\").trim();\n if (!mode) throw new Error(\"mode is required\");\n const duration = Math.trunc(args.durationSeconds);\n if (!Number.isFinite(duration) || duration <= 0) throw new Error(\"durationSeconds must be > 0\");\n\n const innerXml =\n `<ns:updatePhone>` +\n `<name>${escapeXml(name)}</name>` +\n `<packetCaptureMode>${escapeXml(mode)}</packetCaptureMode>` +\n `<packetCaptureDuration>${escapeXml(String(duration))}</packetCaptureDuration>` +\n `</ns:updatePhone>`;\n\n const { xml } = await fetchAxl(target, auth, \"updatePhone\", innerXml, args.timeoutMs ?? 30_000);\n const parsed = parseSoapReturn(\"updatePhone\", xml);\n if (parsed.fault) throw new Error(`AXL updatePhone fault: ${parsed.fault}`);\n return { host: target.host, operation: \"updatePhone\", returnValue: parsed.returnValue };\n}\n\nexport async function applyPhone(\n hostOrUrl: string,\n args: {\n deviceName: string;\n auth?: AxlAuth;\n port?: number;\n version?: string;\n timeoutMs?: number;\n }\n): Promise<{ host: string; operation: \"applyPhone\"; returnValue?: string }> {\n const target = resolveAxlTarget(hostOrUrl, args.port, args.version);\n const auth = resolveAxlAuth(args.auth);\n\n const name = String(args.deviceName || \"\").trim();\n if (!name) throw new Error(\"deviceName is required\");\n\n const innerXml = `<ns:applyPhone><name>${escapeXml(name)}</name></ns:applyPhone>`;\n const { xml } = await fetchAxl(target, auth, \"applyPhone\", innerXml, args.timeoutMs ?? 30_000);\n const parsed = parseSoapReturn(\"applyPhone\", xml);\n if (parsed.fault) throw new Error(`AXL applyPhone fault: ${parsed.fault}`);\n return { host: target.host, operation: \"applyPhone\", returnValue: parsed.returnValue };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }