mcp-cohesity 2.0.0
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/LICENSE +77 -0
- package/README.md +494 -0
- package/dist/cohesity-client.d.ts +47 -0
- package/dist/cohesity-client.js +126 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +149 -0
- package/dist/tools/active-directory.d.ts +11 -0
- package/dist/tools/active-directory.js +82 -0
- package/dist/tools/alerts.d.ts +6 -0
- package/dist/tools/alerts.js +111 -0
- package/dist/tools/antivirus.d.ts +24 -0
- package/dist/tools/antivirus.js +180 -0
- package/dist/tools/audit-logs.d.ts +10 -0
- package/dist/tools/audit-logs.js +161 -0
- package/dist/tools/clones.d.ts +24 -0
- package/dist/tools/clones.js +107 -0
- package/dist/tools/cluster-reports.d.ts +10 -0
- package/dist/tools/cluster-reports.js +224 -0
- package/dist/tools/cluster.d.ts +6 -0
- package/dist/tools/cluster.js +33 -0
- package/dist/tools/external-targets.d.ts +3 -0
- package/dist/tools/external-targets.js +145 -0
- package/dist/tools/kms.d.ts +14 -0
- package/dist/tools/kms.js +164 -0
- package/dist/tools/notifications.d.ts +3 -0
- package/dist/tools/notifications.js +156 -0
- package/dist/tools/protection.d.ts +3 -0
- package/dist/tools/protection.js +514 -0
- package/dist/tools/recovery.d.ts +6 -0
- package/dist/tools/recovery.js +101 -0
- package/dist/tools/reports.d.ts +3 -0
- package/dist/tools/reports.js +346 -0
- package/dist/tools/restore.d.ts +3 -0
- package/dist/tools/restore.js +220 -0
- package/dist/tools/roles.d.ts +11 -0
- package/dist/tools/roles.js +95 -0
- package/dist/tools/run-actions.d.ts +17 -0
- package/dist/tools/run-actions.js +190 -0
- package/dist/tools/runs.d.ts +6 -0
- package/dist/tools/runs.js +94 -0
- package/dist/tools/source-registration.d.ts +11 -0
- package/dist/tools/source-registration.js +456 -0
- package/dist/tools/sources.d.ts +3 -0
- package/dist/tools/sources.js +161 -0
- package/dist/tools/stats.d.ts +3 -0
- package/dist/tools/stats.js +164 -0
- package/dist/tools/storage.d.ts +3 -0
- package/dist/tools/storage.js +191 -0
- package/dist/tools/tiering.d.ts +3 -0
- package/dist/tools/tiering.js +132 -0
- package/dist/tools/users.d.ts +13 -0
- package/dist/tools/users.js +203 -0
- package/package.json +57 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery task tools — list and inspect Cohesity data recovery operations.
|
|
3
|
+
*/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
6
|
+
export declare function registerRecoveryTools(server: McpServer, client: CohesityClient): void;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery task tools — list and inspect Cohesity data recovery operations.
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
/** Shorthand for building MCP tool return values. */
|
|
6
|
+
const reply = (text, isError = false) => ({
|
|
7
|
+
content: [{ type: "text", text }],
|
|
8
|
+
isError,
|
|
9
|
+
});
|
|
10
|
+
/** Status values the V2 recoveries endpoint accepts for filtering. */
|
|
11
|
+
const RECOVERY_STATUSES = [
|
|
12
|
+
"Accepted",
|
|
13
|
+
"Running",
|
|
14
|
+
"Canceled",
|
|
15
|
+
"Canceling",
|
|
16
|
+
"Failed",
|
|
17
|
+
"Missed",
|
|
18
|
+
"Succeeded",
|
|
19
|
+
"SucceededWithWarning",
|
|
20
|
+
"OnHold",
|
|
21
|
+
"Finalizing",
|
|
22
|
+
"Skipped",
|
|
23
|
+
"LegalHold",
|
|
24
|
+
];
|
|
25
|
+
/** Environment types supported by recovery operations. */
|
|
26
|
+
const RECOVERY_ENVIRONMENTS = [
|
|
27
|
+
"kVMware",
|
|
28
|
+
"kPhysical",
|
|
29
|
+
"kNas",
|
|
30
|
+
"kSQL",
|
|
31
|
+
"kOracle",
|
|
32
|
+
"kView",
|
|
33
|
+
"kAWS",
|
|
34
|
+
"kAzure",
|
|
35
|
+
"kGCP",
|
|
36
|
+
"kO365",
|
|
37
|
+
"kKubernetes",
|
|
38
|
+
];
|
|
39
|
+
export function registerRecoveryTools(server, client) {
|
|
40
|
+
// ── List Recovery Tasks ────────────────────────────────────────────────
|
|
41
|
+
server.tool("list_recovery_tasks", "List Cohesity recovery tasks with status, type, and progress information", {
|
|
42
|
+
status: z
|
|
43
|
+
.array(z.enum(RECOVERY_STATUSES))
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Filter recovery tasks by status"),
|
|
46
|
+
environments: z
|
|
47
|
+
.array(z.enum(RECOVERY_ENVIRONMENTS))
|
|
48
|
+
.optional()
|
|
49
|
+
.describe("Filter by source environment type"),
|
|
50
|
+
start_time_usecs: z
|
|
51
|
+
.number()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe("Only return tasks created after this timestamp (microseconds)"),
|
|
54
|
+
end_time_usecs: z
|
|
55
|
+
.number()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("Only return tasks created before this timestamp (microseconds)"),
|
|
58
|
+
max_results: z
|
|
59
|
+
.number()
|
|
60
|
+
.optional()
|
|
61
|
+
.default(25)
|
|
62
|
+
.describe("Cap on the number of results returned"),
|
|
63
|
+
}, async (args) => {
|
|
64
|
+
try {
|
|
65
|
+
const qp = { maxCount: String(args.max_results) };
|
|
66
|
+
if (args.status)
|
|
67
|
+
qp.status = args.status.join(",");
|
|
68
|
+
if (args.environments)
|
|
69
|
+
qp.environments = args.environments.join(",");
|
|
70
|
+
if (args.start_time_usecs !== undefined)
|
|
71
|
+
qp.startTimeUsecs = String(args.start_time_usecs);
|
|
72
|
+
if (args.end_time_usecs !== undefined)
|
|
73
|
+
qp.endTimeUsecs = String(args.end_time_usecs);
|
|
74
|
+
const data = await client.getV2("data-protect/recoveries", qp);
|
|
75
|
+
return reply(JSON.stringify(data, null, 2));
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
return reply(`Error fetching recovery tasks: ${err}`, true);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// ── Get Recovery Task ──────────────────────────────────────────────────
|
|
82
|
+
server.tool("get_recovery_task", "Get detailed information about a specific Cohesity recovery task including per-object restore status", {
|
|
83
|
+
id: z.string().describe("Recovery task ID to retrieve"),
|
|
84
|
+
include_tenants: z
|
|
85
|
+
.boolean()
|
|
86
|
+
.optional()
|
|
87
|
+
.default(false)
|
|
88
|
+
.describe("Include tenant information in the response"),
|
|
89
|
+
}, async (args) => {
|
|
90
|
+
try {
|
|
91
|
+
const qp = {
|
|
92
|
+
includeTenants: String(args.include_tenants),
|
|
93
|
+
};
|
|
94
|
+
const data = await client.getV2(`data-protect/recoveries/${args.id}`, qp);
|
|
95
|
+
return reply(JSON.stringify(data, null, 2));
|
|
96
|
+
}
|
|
97
|
+
catch (err) {
|
|
98
|
+
return reply(`Error fetching recovery task ${args.id}: ${err}`, true);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
function toolResult(text, isError = false) {
|
|
3
|
+
return { content: [{ type: "text", text }], isError };
|
|
4
|
+
}
|
|
5
|
+
function usecsToDayKey(usecs, tzOffsetHours = 0) {
|
|
6
|
+
const ms = usecs / 1000 + tzOffsetHours * 3600000;
|
|
7
|
+
const d = new Date(ms);
|
|
8
|
+
return d.toISOString().slice(0, 10);
|
|
9
|
+
}
|
|
10
|
+
function bytesToHuman(bytes) {
|
|
11
|
+
if (!bytes)
|
|
12
|
+
return "0 B";
|
|
13
|
+
const units = ["B", "KiB", "MiB", "GiB", "TiB"];
|
|
14
|
+
let i = 0;
|
|
15
|
+
let v = bytes;
|
|
16
|
+
while (v >= 1024 && i < units.length - 1) {
|
|
17
|
+
v /= 1024;
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
return `${v.toFixed(1)} ${units[i]}`;
|
|
21
|
+
}
|
|
22
|
+
function usecsToReadable(usecs) {
|
|
23
|
+
return new Date(usecs / 1000).toISOString().replace("T", " ").slice(0, 19) + " UTC";
|
|
24
|
+
}
|
|
25
|
+
function durationSecs(startUsecs, endUsecs) {
|
|
26
|
+
const secs = Math.round((endUsecs - startUsecs) / 1e6);
|
|
27
|
+
if (secs < 60)
|
|
28
|
+
return `${secs}s`;
|
|
29
|
+
if (secs < 3600)
|
|
30
|
+
return `${Math.floor(secs / 60)}m ${secs % 60}s`;
|
|
31
|
+
return `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`;
|
|
32
|
+
}
|
|
33
|
+
export function registerReportTools(server, client) {
|
|
34
|
+
// ── Protected Objects Heatmap ────────────────────────────────────────
|
|
35
|
+
server.tool("get_protection_heatmap", "Generate a Protected Objects Heatmap report showing per-VM/object backup status for each day in a date range — matching the Cohesity GUI Reports > Protected Objects Heatmap view. Returns a summary (total runs, success, failed, canceled) and a per-object daily grid with run details (protection group, start time, duration, logical size, data read, status).", {
|
|
36
|
+
days: z
|
|
37
|
+
.number()
|
|
38
|
+
.optional()
|
|
39
|
+
.default(7)
|
|
40
|
+
.describe("Number of days to look back (default: 7, max: 30)"),
|
|
41
|
+
protection_group_ids: z
|
|
42
|
+
.array(z.string())
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Filter to specific protection group IDs. Omit to include all groups."),
|
|
45
|
+
environment: z
|
|
46
|
+
.enum(["kVMware", "kPhysical", "kSQL", "kOracle", "kNas", "kGenericNas"])
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Filter by environment type"),
|
|
49
|
+
}, async ({ days, protection_group_ids, environment }) => {
|
|
50
|
+
try {
|
|
51
|
+
const now = Date.now() * 1000;
|
|
52
|
+
const limitedDays = Math.min(days, 30);
|
|
53
|
+
const startUsecs = now - limitedDays * 86400 * 1e6;
|
|
54
|
+
// Step 1: Get all protection groups (or filter to requested ones)
|
|
55
|
+
const groupsResult = await client.getV2("data-protect/protection-groups", {
|
|
56
|
+
maxCount: "100",
|
|
57
|
+
includeLastRunInfo: "false",
|
|
58
|
+
...(environment ? { environments: environment } : {}),
|
|
59
|
+
});
|
|
60
|
+
let groups = groupsResult.protectionGroups ?? [];
|
|
61
|
+
if (protection_group_ids?.length) {
|
|
62
|
+
const idSet = new Set(protection_group_ids);
|
|
63
|
+
groups = groups.filter(g => idSet.has(g.id));
|
|
64
|
+
}
|
|
65
|
+
if (groups.length === 0) {
|
|
66
|
+
return toolResult("No protection groups found matching the criteria.");
|
|
67
|
+
}
|
|
68
|
+
// Step 2: Fetch runs with object details for each group in parallel
|
|
69
|
+
const runFetches = groups.map(g => client.getV2(`data-protect/protection-groups/${g.id}/runs`, {
|
|
70
|
+
maxCount: "200",
|
|
71
|
+
includeObjectDetails: "true",
|
|
72
|
+
startTimeUsecs: String(Math.floor(startUsecs)),
|
|
73
|
+
endTimeUsecs: String(Math.floor(now)),
|
|
74
|
+
}).then(r => ({ group: g, result: r }))
|
|
75
|
+
.catch(() => ({ group: g, result: { runs: [] } })));
|
|
76
|
+
const allGroupRuns = await Promise.all(runFetches);
|
|
77
|
+
const objectMap = new Map();
|
|
78
|
+
let totalRuns = 0;
|
|
79
|
+
let totalSuccess = 0;
|
|
80
|
+
let totalFailed = 0;
|
|
81
|
+
let totalCanceled = 0;
|
|
82
|
+
let totalWarning = 0;
|
|
83
|
+
for (const { group, result } of allGroupRuns) {
|
|
84
|
+
const runs = (result.runs ?? []);
|
|
85
|
+
for (const run of runs) {
|
|
86
|
+
const localInfo = run.localBackupInfo;
|
|
87
|
+
if (!localInfo)
|
|
88
|
+
continue;
|
|
89
|
+
const runStatus = localInfo.status;
|
|
90
|
+
const runStartUsecs = localInfo.startTimeUsecs;
|
|
91
|
+
const runEndUsecs = localInfo.endTimeUsecs ?? runStartUsecs;
|
|
92
|
+
const runType = (localInfo.runType ?? "kRegular").replace("k", "");
|
|
93
|
+
totalRuns++;
|
|
94
|
+
if (runStatus === "Succeeded")
|
|
95
|
+
totalSuccess++;
|
|
96
|
+
else if (runStatus === "Failed")
|
|
97
|
+
totalFailed++;
|
|
98
|
+
else if (runStatus === "Canceled")
|
|
99
|
+
totalCanceled++;
|
|
100
|
+
else if (runStatus === "SucceededWithWarning")
|
|
101
|
+
totalWarning++;
|
|
102
|
+
const dayKey = usecsToDayKey(runStartUsecs);
|
|
103
|
+
const objects = (run.objects ?? []);
|
|
104
|
+
for (const obj of objects) {
|
|
105
|
+
const objInfo = obj.object;
|
|
106
|
+
if (!objInfo)
|
|
107
|
+
continue;
|
|
108
|
+
const objId = String(objInfo.id ?? "unknown");
|
|
109
|
+
const objName = String(objInfo.name ?? "unknown");
|
|
110
|
+
const snapInfo = obj.localSnapshotInfo?.snapshotInfo;
|
|
111
|
+
const objStatus = String(snapInfo?.status ?? runStatus ?? "Unknown").replace("kSuccessful", "Succeeded").replace("kFailed", "Failed").replace("kCanceled", "Canceled").replace("kWarning", "Warning");
|
|
112
|
+
const stats = snapInfo?.stats;
|
|
113
|
+
const logicalBytes = stats?.logicalSizeBytes ?? 0;
|
|
114
|
+
const bytesRead = stats?.bytesRead ?? localInfo.localSnapshotStats?.bytesRead ?? 0;
|
|
115
|
+
const objStart = snapInfo?.startTimeUsecs ?? runStartUsecs;
|
|
116
|
+
const objEnd = snapInfo?.endTimeUsecs ?? runEndUsecs;
|
|
117
|
+
if (!objectMap.has(objId)) {
|
|
118
|
+
objectMap.set(objId, { name: objName, days: new Map() });
|
|
119
|
+
}
|
|
120
|
+
const entry = objectMap.get(objId);
|
|
121
|
+
if (!entry.days.has(dayKey))
|
|
122
|
+
entry.days.set(dayKey, []);
|
|
123
|
+
entry.days.get(dayKey).push({
|
|
124
|
+
protectionGroup: group.name,
|
|
125
|
+
startTime: usecsToReadable(objStart),
|
|
126
|
+
duration: durationSecs(objStart, objEnd),
|
|
127
|
+
logicalSize: bytesToHuman(logicalBytes),
|
|
128
|
+
dataRead: bytesToHuman(bytesRead),
|
|
129
|
+
status: objStatus,
|
|
130
|
+
runType,
|
|
131
|
+
isSlaViolated: !!(localInfo.isSlaViolated),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Step 4: Build output
|
|
137
|
+
const startDate = new Date(startUsecs / 1000).toISOString().slice(0, 10);
|
|
138
|
+
const endDate = new Date(now / 1000).toISOString().slice(0, 10);
|
|
139
|
+
const output = {
|
|
140
|
+
report: "Protected Objects Heatmap",
|
|
141
|
+
dateRange: `${startDate} to ${endDate}`,
|
|
142
|
+
summary: {
|
|
143
|
+
totalRuns,
|
|
144
|
+
succeeded: totalSuccess,
|
|
145
|
+
succeededWithWarning: totalWarning,
|
|
146
|
+
failed: totalFailed,
|
|
147
|
+
canceled: totalCanceled,
|
|
148
|
+
totalObjects: objectMap.size,
|
|
149
|
+
},
|
|
150
|
+
objects: [],
|
|
151
|
+
};
|
|
152
|
+
// Build day list
|
|
153
|
+
const dayList = [];
|
|
154
|
+
for (let i = limitedDays - 1; i >= 0; i--) {
|
|
155
|
+
dayList.push(new Date((now / 1000) - i * 86400000).toISOString().slice(0, 10));
|
|
156
|
+
}
|
|
157
|
+
for (const [, objData] of objectMap) {
|
|
158
|
+
const heatmap = {};
|
|
159
|
+
let objSuccess = 0;
|
|
160
|
+
let objFailed = 0;
|
|
161
|
+
let objTotal = 0;
|
|
162
|
+
for (const day of dayList) {
|
|
163
|
+
const runs = objData.days.get(day);
|
|
164
|
+
if (runs?.length) {
|
|
165
|
+
heatmap[day] = runs;
|
|
166
|
+
objTotal += runs.length;
|
|
167
|
+
for (const r of runs) {
|
|
168
|
+
if (r.status === "Succeeded")
|
|
169
|
+
objSuccess++;
|
|
170
|
+
else
|
|
171
|
+
objFailed++;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
heatmap[day] = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
output.objects.push({
|
|
179
|
+
name: objData.name,
|
|
180
|
+
successRate: objTotal > 0 ? `${Math.round(objSuccess / objTotal * 100)}%` : "N/A",
|
|
181
|
+
totalRuns: objTotal,
|
|
182
|
+
heatmap,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return toolResult(JSON.stringify(output, null, 2));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
return toolResult(`Error generating protection heatmap: ${error}`, true);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// ── Protection Summary Report ─────────────────────────────────────────
|
|
192
|
+
server.tool("get_protection_summary", "Generate a protection job summary report showing overall backup health: success/failure counts, SLA violations, and a list of failing protection groups with their last run status. Good for daily operational review.", {
|
|
193
|
+
days: z
|
|
194
|
+
.number()
|
|
195
|
+
.optional()
|
|
196
|
+
.default(1)
|
|
197
|
+
.describe("Number of days to look back (default: 1 for daily review, max: 30)"),
|
|
198
|
+
}, async ({ days }) => {
|
|
199
|
+
try {
|
|
200
|
+
const now = Date.now() * 1000;
|
|
201
|
+
const limitedDays = Math.min(days, 30);
|
|
202
|
+
const startUsecs = now - limitedDays * 86400 * 1e6;
|
|
203
|
+
// Get all groups with last run info
|
|
204
|
+
const groupsResult = await client.getV2("data-protect/protection-groups", {
|
|
205
|
+
maxCount: "200",
|
|
206
|
+
includeLastRunInfo: "true",
|
|
207
|
+
});
|
|
208
|
+
const groups = groupsResult.protectionGroups ?? [];
|
|
209
|
+
// Get run stats for the period
|
|
210
|
+
const statsResult = await client.getV2("stats/protection-runs", {
|
|
211
|
+
startTimeUsecs: String(Math.floor(startUsecs)),
|
|
212
|
+
endTimeUsecs: String(Math.floor(now)),
|
|
213
|
+
});
|
|
214
|
+
// Aggregate stats across all time buckets
|
|
215
|
+
const statTotals = {};
|
|
216
|
+
for (const bucket of statsResult.protectionRunsStatsList ?? []) {
|
|
217
|
+
for (const s of bucket.stats ?? []) {
|
|
218
|
+
statTotals[s.protectionRunStatus] = (statTotals[s.protectionRunStatus] ?? 0) + s.protectionRunsCount;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Identify problem groups from last run info
|
|
222
|
+
const failingGroups = [];
|
|
223
|
+
const warningGroups = [];
|
|
224
|
+
const slaViolations = [];
|
|
225
|
+
let activeGroups = 0;
|
|
226
|
+
let pausedGroups = 0;
|
|
227
|
+
for (const g of groups) {
|
|
228
|
+
if (g.isPaused) {
|
|
229
|
+
pausedGroups++;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
activeGroups++;
|
|
233
|
+
const lastRun = g.lastRun;
|
|
234
|
+
const localInfo = lastRun?.localBackupInfo;
|
|
235
|
+
if (!localInfo)
|
|
236
|
+
continue;
|
|
237
|
+
const status = localInfo.status;
|
|
238
|
+
const lastRunStart = localInfo.startTimeUsecs ?? 0;
|
|
239
|
+
if (lastRunStart < startUsecs)
|
|
240
|
+
continue; // outside the window
|
|
241
|
+
if (status === "Failed") {
|
|
242
|
+
failingGroups.push({
|
|
243
|
+
name: g.name,
|
|
244
|
+
id: g.id,
|
|
245
|
+
environment: g.environment,
|
|
246
|
+
lastRunStatus: status,
|
|
247
|
+
lastRunTime: usecsToReadable(lastRunStart),
|
|
248
|
+
failedObjects: localInfo.failedObjectsCount ?? 0,
|
|
249
|
+
successfulObjects: localInfo.successfulObjectsCount ?? 0,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
else if (status === "SucceededWithWarning") {
|
|
253
|
+
warningGroups.push({
|
|
254
|
+
name: g.name,
|
|
255
|
+
id: g.id,
|
|
256
|
+
lastRunTime: usecsToReadable(lastRunStart),
|
|
257
|
+
failedObjects: localInfo.failedObjectsCount ?? 0,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (localInfo.isSlaViolated) {
|
|
261
|
+
slaViolations.push({
|
|
262
|
+
name: g.name,
|
|
263
|
+
lastRunTime: usecsToReadable(lastRunStart),
|
|
264
|
+
status,
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const total = Object.values(statTotals).reduce((a, b) => a + b, 0);
|
|
269
|
+
const output = {
|
|
270
|
+
report: "Protection Summary",
|
|
271
|
+
dateRange: `Last ${limitedDays} day(s)`,
|
|
272
|
+
summary: {
|
|
273
|
+
totalRuns: total,
|
|
274
|
+
succeeded: statTotals["Succeeded"] ?? 0,
|
|
275
|
+
succeededWithWarning: statTotals["SucceededWithWarning"] ?? 0,
|
|
276
|
+
failed: statTotals["Failed"] ?? 0,
|
|
277
|
+
canceled: statTotals["Canceled"] ?? 0,
|
|
278
|
+
running: statTotals["Running"] ?? 0,
|
|
279
|
+
successRate: total > 0 ? `${Math.round(((statTotals["Succeeded"] ?? 0) + (statTotals["SucceededWithWarning"] ?? 0)) / total * 100)}%` : "N/A",
|
|
280
|
+
},
|
|
281
|
+
protectionGroups: {
|
|
282
|
+
active: activeGroups,
|
|
283
|
+
paused: pausedGroups,
|
|
284
|
+
total: groups.length,
|
|
285
|
+
},
|
|
286
|
+
slaViolations: { count: slaViolations.length, groups: slaViolations },
|
|
287
|
+
failingGroups: { count: failingGroups.length, groups: failingGroups },
|
|
288
|
+
warningGroups: { count: warningGroups.length, groups: warningGroups },
|
|
289
|
+
};
|
|
290
|
+
return toolResult(JSON.stringify(output, null, 2));
|
|
291
|
+
}
|
|
292
|
+
catch (error) {
|
|
293
|
+
return toolResult(`Error generating protection summary: ${error}`, true);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
// ── Recovery Summary Report ───────────────────────────────────────────
|
|
297
|
+
server.tool("get_recovery_summary", "Generate a recovery activity summary showing how many objects were recovered, recovery task statuses, and recent recovery operations in a time window.", {
|
|
298
|
+
days: z
|
|
299
|
+
.number()
|
|
300
|
+
.optional()
|
|
301
|
+
.default(7)
|
|
302
|
+
.describe("Number of days to look back (default: 7)"),
|
|
303
|
+
}, async ({ days }) => {
|
|
304
|
+
try {
|
|
305
|
+
const now = Date.now() * 1000;
|
|
306
|
+
const limitedDays = Math.min(days, 90);
|
|
307
|
+
const startUsecs = now - limitedDays * 86400 * 1e6;
|
|
308
|
+
const [statsResult, tasksResult] = await Promise.all([
|
|
309
|
+
client.getV2("stats/recoveries", {
|
|
310
|
+
startTimeUsecs: String(Math.floor(startUsecs)),
|
|
311
|
+
endTimeUsecs: String(Math.floor(now)),
|
|
312
|
+
}),
|
|
313
|
+
client.getV2("data-protect/recoveries", {
|
|
314
|
+
maxCount: "25",
|
|
315
|
+
startTimeUsecs: String(Math.floor(startUsecs)),
|
|
316
|
+
}),
|
|
317
|
+
]);
|
|
318
|
+
const recoveries = tasksResult.recoveries ?? [];
|
|
319
|
+
const statusCounts = {};
|
|
320
|
+
for (const rec of recoveries) {
|
|
321
|
+
const s = String(rec.status ?? "Unknown");
|
|
322
|
+
statusCounts[s] = (statusCounts[s] ?? 0) + 1;
|
|
323
|
+
}
|
|
324
|
+
const output = {
|
|
325
|
+
report: "Recovery Summary",
|
|
326
|
+
dateRange: `Last ${limitedDays} day(s)`,
|
|
327
|
+
stats: statsResult,
|
|
328
|
+
taskStatusBreakdown: statusCounts,
|
|
329
|
+
recentTasks: recoveries.map(r => ({
|
|
330
|
+
id: r.id,
|
|
331
|
+
name: r.name,
|
|
332
|
+
status: r.status,
|
|
333
|
+
environment: r.snapshotEnvironment,
|
|
334
|
+
recoveryAction: r.recoveryAction,
|
|
335
|
+
startTime: r.startTimeUsecs ? usecsToReadable(r.startTimeUsecs) : null,
|
|
336
|
+
objectCount: r.numObjects,
|
|
337
|
+
createdBy: r.creationInfo?.userName,
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
return toolResult(JSON.stringify(output, null, 2));
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
return toolResult(`Error generating recovery summary: ${error}`, true);
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|