@calltelemetry/cucm-mcp 0.1.6 → 0.1.7
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 +48 -0
- package/dist/index.js +30 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,3 +90,51 @@ Live tests are opt-in via env vars; see `test/live.test.js`.
|
|
|
90
90
|
- Use the platform/OS admin for SSH (`administrator` user on most lab systems)
|
|
91
91
|
- To request a high packet count without specifying an exact number, pass `maxPackets: true` to `packet_capture_start`
|
|
92
92
|
- If traffic is low, a small `count` can still run “forever” waiting for packets; use `packet_capture_stop` to cancel, or set `maxDurationMs` to auto-stop
|
|
93
|
+
|
|
94
|
+
### Auth Note (DIME vs SSH)
|
|
95
|
+
|
|
96
|
+
CUCM deployments vary:
|
|
97
|
+
|
|
98
|
+
- SSH and DIME may accept different usernames/passwords.
|
|
99
|
+
- Quick check: the right DIME user returns HTTP 200 for the WSDL.
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
curl -k -u "<user>:<pass>" \
|
|
103
|
+
"https://<cucm-host>:8443/logcollectionservice2/services/LogCollectionPortTypeService?wsdl" \
|
|
104
|
+
-o /dev/null -w "%{http_code}\n"
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Recommended Workflow
|
|
108
|
+
|
|
109
|
+
1) Start capture (returns quickly; capture continues on CUCM):
|
|
110
|
+
|
|
111
|
+
Tool: `packet_capture_start`
|
|
112
|
+
|
|
113
|
+
Useful options:
|
|
114
|
+
|
|
115
|
+
- `count`: stop after N packets (can wait indefinitely if traffic is low)
|
|
116
|
+
- `maxDurationMs`: stop after a fixed time even if packet count isn’t reached
|
|
117
|
+
- `startTimeoutMs`: fail fast if the CUCM CLI prompt isn’t reachable
|
|
118
|
+
- `maxPackets: true`: sets a high capture count (1,000,000) when `count` is omitted
|
|
119
|
+
|
|
120
|
+
2) Stop + download the capture:
|
|
121
|
+
|
|
122
|
+
Tool: `packet_capture_stop_and_download`
|
|
123
|
+
|
|
124
|
+
This:
|
|
125
|
+
|
|
126
|
+
- stops the SSH capture (best-effort)
|
|
127
|
+
- retries DIME downloads until the file appears
|
|
128
|
+
- tries rolled filenames (`.cap01`, `.cap02`, ...)
|
|
129
|
+
|
|
130
|
+
### Viewing the Capture (macOS)
|
|
131
|
+
|
|
132
|
+
After download, you’ll get a `savedPath` like `/tmp/foo.cap`.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Reveal in Finder
|
|
136
|
+
open -R "/tmp/foo.cap"
|
|
137
|
+
|
|
138
|
+
# Open in Wireshark
|
|
139
|
+
open -a Wireshark "/tmp/foo.cap"
|
|
140
|
+
```
|
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ const strictTls = tlsMode === "strict" || tlsMode === "verify";
|
|
|
14
14
|
// Set CUCM_MCP_TLS_MODE=strict to enforce verification.
|
|
15
15
|
if (!strictTls)
|
|
16
16
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
17
|
-
const server = new McpServer({ name: "cucm", version: "0.1.
|
|
17
|
+
const server = new McpServer({ name: "cucm", version: "0.1.7" });
|
|
18
18
|
const captures = new PacketCaptureManager();
|
|
19
19
|
const captureState = defaultStateStore();
|
|
20
20
|
const dimeAuthSchema = z
|
|
@@ -187,14 +187,20 @@ server.tool("packet_capture_stop_and_download", "Stop a packet capture and downl
|
|
|
187
187
|
dimePort: z.number().int().min(1).max(65535).optional(),
|
|
188
188
|
auth: dimeAuthSchema.describe("DIME auth (optional; defaults to CUCM_DIME_USERNAME/CUCM_DIME_PASSWORD)"),
|
|
189
189
|
outFile: z.string().optional().describe("Optional output path for the downloaded .cap file"),
|
|
190
|
-
stopTimeoutMs: z
|
|
190
|
+
stopTimeoutMs: z
|
|
191
|
+
.number()
|
|
192
|
+
.int()
|
|
193
|
+
.min(1000)
|
|
194
|
+
.max(10 * 60_000)
|
|
195
|
+
.optional()
|
|
196
|
+
.describe("How long to wait for SSH capture stop (default 300000)"),
|
|
191
197
|
downloadTimeoutMs: z
|
|
192
198
|
.number()
|
|
193
199
|
.int()
|
|
194
200
|
.min(1000)
|
|
195
201
|
.max(10 * 60_000)
|
|
196
202
|
.optional()
|
|
197
|
-
.describe("How long to wait for the capture file to appear in DIME"),
|
|
203
|
+
.describe("How long to wait for the capture file to appear in DIME (default 300000)"),
|
|
198
204
|
downloadPollIntervalMs: z
|
|
199
205
|
.number()
|
|
200
206
|
.int()
|
|
@@ -203,15 +209,29 @@ server.tool("packet_capture_stop_and_download", "Stop a packet capture and downl
|
|
|
203
209
|
.optional()
|
|
204
210
|
.describe("How often to retry DIME GetOneFile when the file isn't there yet"),
|
|
205
211
|
}, async ({ captureId, dimePort, auth, outFile, stopTimeoutMs, downloadTimeoutMs, downloadPollIntervalMs }) => {
|
|
206
|
-
const
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
212
|
+
const stopTimeout = stopTimeoutMs ?? 300_000;
|
|
213
|
+
const dlTimeout = downloadTimeoutMs ?? 300_000;
|
|
214
|
+
const dlPoll = downloadPollIntervalMs ?? 2000;
|
|
215
|
+
let stopped;
|
|
216
|
+
let stopError;
|
|
217
|
+
try {
|
|
218
|
+
stopped = await captures.stop(captureId, stopTimeout);
|
|
219
|
+
}
|
|
220
|
+
catch (e) {
|
|
221
|
+
stopError = e instanceof Error ? e.message : String(e || "");
|
|
222
|
+
// Fall back to state file (useful if stop failed or MCP restarted).
|
|
223
|
+
const pruned = captureState.pruneExpired(captureState.load());
|
|
224
|
+
const rec = pruned.captures[captureId];
|
|
225
|
+
if (!rec)
|
|
226
|
+
throw new Error(`Failed to stop capture and capture not found in state: ${captureId}. stopError=${stopError}`);
|
|
227
|
+
stopped = rec;
|
|
228
|
+
}
|
|
229
|
+
const candidates = (stopped.remoteFileCandidates || []).length ? stopped.remoteFileCandidates : [stopped.remoteFilePath];
|
|
210
230
|
const dl = await getOneFileAnyWithRetry(stopped.host, candidates, {
|
|
211
231
|
auth: auth,
|
|
212
232
|
port: dimePort,
|
|
213
|
-
timeoutMs:
|
|
214
|
-
pollIntervalMs:
|
|
233
|
+
timeoutMs: dlTimeout,
|
|
234
|
+
pollIntervalMs: dlPoll,
|
|
215
235
|
});
|
|
216
236
|
const saved = writeDownloadedFile(dl, outFile);
|
|
217
237
|
return {
|
|
@@ -223,6 +243,7 @@ server.tool("packet_capture_stop_and_download", "Stop a packet capture and downl
|
|
|
223
243
|
host: stopped.host,
|
|
224
244
|
remoteFilePath: dl.filename,
|
|
225
245
|
stopTimedOut: stopped.stopTimedOut || false,
|
|
246
|
+
stopError,
|
|
226
247
|
savedPath: saved.filePath,
|
|
227
248
|
bytes: saved.bytes,
|
|
228
249
|
dimeAttempts: dl.attempts,
|