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,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antivirus / threat-detection tools — manage antivirus service integrations
|
|
3
|
+
* (e.g., ICAP-based scanners) and review infected/quarantined files.
|
|
4
|
+
*
|
|
5
|
+
* Notes:
|
|
6
|
+
* - Cohesity's "anomaly detection" (ransomware behavioural analysis) is a
|
|
7
|
+
* Helios SaaS feature and is NOT exposed on standalone on-prem clusters.
|
|
8
|
+
* For on-prem threat detection, the cluster integrates with ICAP-compliant
|
|
9
|
+
* antivirus servers via Antivirus Service Groups. This module exposes
|
|
10
|
+
* those endpoints.
|
|
11
|
+
*
|
|
12
|
+
* Endpoints (verified against cluster_v2_api.yaml):
|
|
13
|
+
* GET /v2/antivirus-service/groups
|
|
14
|
+
* POST /v2/antivirus-service/groups
|
|
15
|
+
* GET /v2/antivirus-service/groups/{id}
|
|
16
|
+
* PUT /v2/antivirus-service/groups/{id}
|
|
17
|
+
* DELETE /v2/antivirus-service/groups/{id}
|
|
18
|
+
* GET /v2/antivirus-service/icap-uri-connection-status
|
|
19
|
+
* GET /v2/antivirus-service/infected-files
|
|
20
|
+
* POST /v2/antivirus-service/infected-files/actions (quarantine/unquarantine/delete)
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
const reply = (text, isError = false) => ({
|
|
24
|
+
content: [{ type: "text", text }],
|
|
25
|
+
isError,
|
|
26
|
+
});
|
|
27
|
+
export function registerAntivirusTools(server, client) {
|
|
28
|
+
// ── List Antivirus Service Groups ──────────────────────────────────────
|
|
29
|
+
server.tool("list_antivirus_groups", "List antivirus service groups configured on the cluster. Each group bundles one or more ICAP-compliant antivirus servers.", {}, async () => {
|
|
30
|
+
try {
|
|
31
|
+
const data = await client.getV2("antivirus-service/groups");
|
|
32
|
+
return reply(JSON.stringify(data, null, 2));
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
return reply(`Error listing antivirus groups: ${err}`, true);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
// ── Create Antivirus Service Group ─────────────────────────────────────
|
|
39
|
+
// Schema: CreateAntivirusServiceGroupParams { name, antivirusServices[], description?, state? }
|
|
40
|
+
// AntivirusService { name, icapUri, scanFileSizeKBytes?, isEnabled? }
|
|
41
|
+
server.tool("create_antivirus_group", "Create a new antivirus service group. Bundles one or more ICAP servers so the cluster can route on-access scans to them.", {
|
|
42
|
+
name: z.string().describe("Group name (unique on the cluster)"),
|
|
43
|
+
description: z.string().optional().describe("Free-form description"),
|
|
44
|
+
antivirus_services: z
|
|
45
|
+
.array(z.object({
|
|
46
|
+
name: z.string().describe("Antivirus service display name"),
|
|
47
|
+
icap_uri: z.string().describe("ICAP URI of the antivirus server (e.g. icap://av.example.com:1344/respmod)"),
|
|
48
|
+
is_enabled: z.boolean().default(true).describe("Whether this service is active"),
|
|
49
|
+
scan_file_size_kbytes: z
|
|
50
|
+
.number()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Max file size to scan in KB; larger files are skipped"),
|
|
53
|
+
}))
|
|
54
|
+
.min(1)
|
|
55
|
+
.describe("One or more ICAP antivirus services to include in the group"),
|
|
56
|
+
state: z.enum(["Enable", "Disable"]).default("Enable").describe("Initial state of the group"),
|
|
57
|
+
}, async (args) => {
|
|
58
|
+
try {
|
|
59
|
+
const body = {
|
|
60
|
+
name: args.name,
|
|
61
|
+
description: args.description,
|
|
62
|
+
state: args.state,
|
|
63
|
+
antivirusServices: args.antivirus_services.map((s) => ({
|
|
64
|
+
name: s.name,
|
|
65
|
+
icapUri: s.icap_uri,
|
|
66
|
+
isEnabled: s.is_enabled,
|
|
67
|
+
...(s.scan_file_size_kbytes !== undefined && {
|
|
68
|
+
scanFileSizeKBytes: s.scan_file_size_kbytes,
|
|
69
|
+
}),
|
|
70
|
+
})),
|
|
71
|
+
};
|
|
72
|
+
const data = await client.postV2("antivirus-service/groups", body);
|
|
73
|
+
return reply(`Antivirus group '${args.name}' created.\n${JSON.stringify(data, null, 2)}`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return reply(`Error creating antivirus group: ${err}`, true);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
// ── Get Antivirus Service Group ────────────────────────────────────────
|
|
80
|
+
server.tool("get_antivirus_group", "Get a single antivirus service group by ID", { id: z.number().describe("Antivirus group ID") }, async (args) => {
|
|
81
|
+
try {
|
|
82
|
+
const data = await client.getV2(`antivirus-service/groups/${args.id}`);
|
|
83
|
+
return reply(JSON.stringify(data, null, 2));
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
return reply(`Error fetching antivirus group ${args.id}: ${err}`, true);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// ── Update Antivirus Service Group ─────────────────────────────────────
|
|
90
|
+
server.tool("update_antivirus_group", "Update an antivirus service group's name, services, or state", {
|
|
91
|
+
id: z.number().describe("Antivirus group ID to update"),
|
|
92
|
+
name: z.string().describe("Group name"),
|
|
93
|
+
description: z.string().optional().describe("Description"),
|
|
94
|
+
antivirus_services: z
|
|
95
|
+
.array(z.object({
|
|
96
|
+
name: z.string(),
|
|
97
|
+
icap_uri: z.string(),
|
|
98
|
+
is_enabled: z.boolean().default(true),
|
|
99
|
+
scan_file_size_kbytes: z.number().optional(),
|
|
100
|
+
}))
|
|
101
|
+
.min(1)
|
|
102
|
+
.describe("Replacement list of antivirus services"),
|
|
103
|
+
state: z.enum(["Enable", "Disable"]).describe("Group state"),
|
|
104
|
+
}, async (args) => {
|
|
105
|
+
try {
|
|
106
|
+
const body = {
|
|
107
|
+
name: args.name,
|
|
108
|
+
description: args.description,
|
|
109
|
+
state: args.state,
|
|
110
|
+
antivirusServices: args.antivirus_services.map((s) => ({
|
|
111
|
+
name: s.name,
|
|
112
|
+
icapUri: s.icap_uri,
|
|
113
|
+
isEnabled: s.is_enabled,
|
|
114
|
+
...(s.scan_file_size_kbytes !== undefined && {
|
|
115
|
+
scanFileSizeKBytes: s.scan_file_size_kbytes,
|
|
116
|
+
}),
|
|
117
|
+
})),
|
|
118
|
+
};
|
|
119
|
+
const data = await client.putV2(`antivirus-service/groups/${args.id}`, body);
|
|
120
|
+
return reply(`Antivirus group ${args.id} updated.\n${JSON.stringify(data, null, 2)}`);
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
return reply(`Error updating antivirus group: ${err}`, true);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// ── Delete Antivirus Service Group ─────────────────────────────────────
|
|
127
|
+
server.tool("delete_antivirus_group", "Delete an antivirus service group", { id: z.number().describe("Antivirus group ID to delete") }, async (args) => {
|
|
128
|
+
try {
|
|
129
|
+
await client.deleteV2(`antivirus-service/groups/${args.id}`);
|
|
130
|
+
return reply(`Antivirus group ${args.id} deleted.`);
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
return reply(`Error deleting antivirus group: ${err}`, true);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
// ── ICAP URI Connection Status ─────────────────────────────────────────
|
|
137
|
+
server.tool("check_icap_connection", "Probe an ICAP antivirus URI and return its current reachability status", {
|
|
138
|
+
icap_uri: z.string().describe("ICAP URI to test (e.g. icap://av.example.com:1344/respmod)"),
|
|
139
|
+
}, async (args) => {
|
|
140
|
+
try {
|
|
141
|
+
const data = await client.getV2("antivirus-service/icap-uri-connection-status", {
|
|
142
|
+
icapUri: args.icap_uri,
|
|
143
|
+
});
|
|
144
|
+
return reply(JSON.stringify(data, null, 2));
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
return reply(`Error checking ICAP connection: ${err}`, true);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
// ── List Infected Files ────────────────────────────────────────────────
|
|
151
|
+
server.tool("list_infected_files", "List files that an antivirus service has flagged as infected on cluster Views. Filter by view, time range, or quarantine state.", {
|
|
152
|
+
view_names: z.array(z.string()).optional().describe("Restrict to these view names"),
|
|
153
|
+
include_quarantined: z.boolean().optional().describe("Include quarantined files in results"),
|
|
154
|
+
include_unquarantined: z.boolean().optional().describe("Include un-quarantined files"),
|
|
155
|
+
start_time_usecs: z.number().optional().describe("Only files detected after this timestamp (microseconds)"),
|
|
156
|
+
end_time_usecs: z.number().optional().describe("Only files detected before this timestamp (microseconds)"),
|
|
157
|
+
max_count: z.number().optional().default(100).describe("Max records to return"),
|
|
158
|
+
}, async (args) => {
|
|
159
|
+
try {
|
|
160
|
+
const qp = {};
|
|
161
|
+
if (args.view_names?.length)
|
|
162
|
+
qp.viewNameVec = args.view_names.join(",");
|
|
163
|
+
if (args.include_quarantined !== undefined)
|
|
164
|
+
qp.includeQuarantinedFiles = String(args.include_quarantined);
|
|
165
|
+
if (args.include_unquarantined !== undefined)
|
|
166
|
+
qp.includeUnquarantinedFiles = String(args.include_unquarantined);
|
|
167
|
+
if (args.start_time_usecs !== undefined)
|
|
168
|
+
qp.startTimeUsecs = String(args.start_time_usecs);
|
|
169
|
+
if (args.end_time_usecs !== undefined)
|
|
170
|
+
qp.endTimeUsecs = String(args.end_time_usecs);
|
|
171
|
+
if (args.max_count !== undefined)
|
|
172
|
+
qp.maxCount = String(args.max_count);
|
|
173
|
+
const data = await client.getV2("antivirus-service/infected-files", qp);
|
|
174
|
+
return reply(JSON.stringify(data, null, 2));
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
return reply(`Error listing infected files: ${err}`, true);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit log tools — query the cluster's `CLUSTER_AUDIT` log for who-did-what
|
|
3
|
+
* forensics, and read or update the audit log retention configuration.
|
|
4
|
+
*
|
|
5
|
+
* All shapes verified against cluster_v2_api.yaml schemas:
|
|
6
|
+
* AuditLog, AuditLogs, AuditLogConfig, GetAuditLogs operation.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
10
|
+
export declare function registerAuditLogTools(server: McpServer, client: CohesityClient): void;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit log tools — query the cluster's `CLUSTER_AUDIT` log for who-did-what
|
|
3
|
+
* forensics, and read or update the audit log retention configuration.
|
|
4
|
+
*
|
|
5
|
+
* All shapes verified against cluster_v2_api.yaml schemas:
|
|
6
|
+
* AuditLog, AuditLogs, AuditLogConfig, GetAuditLogs operation.
|
|
7
|
+
*/
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
const reply = (text, isError = false) => ({
|
|
10
|
+
content: [{ type: "text", text }],
|
|
11
|
+
isError,
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* The full entity-type list from the V2 spec is ~80 items long. We expose the
|
|
15
|
+
* most operationally useful ones; callers can still pass arbitrary strings if
|
|
16
|
+
* they really need an entity not in this list.
|
|
17
|
+
*/
|
|
18
|
+
const COMMON_ENTITY_TYPES = [
|
|
19
|
+
"ProtectionGroup",
|
|
20
|
+
"ProtectionPolicy",
|
|
21
|
+
"ProtectionRun",
|
|
22
|
+
"RecoveryTask",
|
|
23
|
+
"Source",
|
|
24
|
+
"User",
|
|
25
|
+
"Role",
|
|
26
|
+
"Group",
|
|
27
|
+
"ApiKey",
|
|
28
|
+
"AccessToken",
|
|
29
|
+
"Alert",
|
|
30
|
+
"Resolution",
|
|
31
|
+
"AlertNotificationRule",
|
|
32
|
+
"Vault",
|
|
33
|
+
"RemoteCluster",
|
|
34
|
+
"ActiveDirectory",
|
|
35
|
+
"Cluster",
|
|
36
|
+
"Node",
|
|
37
|
+
"Disk",
|
|
38
|
+
"View",
|
|
39
|
+
"StorageDomain",
|
|
40
|
+
"EncryptionKey",
|
|
41
|
+
"CloneTask",
|
|
42
|
+
"Snapshot",
|
|
43
|
+
"Tenant",
|
|
44
|
+
"SearchJob",
|
|
45
|
+
];
|
|
46
|
+
const AUDIT_ACTIONS = [
|
|
47
|
+
"Login",
|
|
48
|
+
"Logout",
|
|
49
|
+
"Create",
|
|
50
|
+
"Modify",
|
|
51
|
+
"Delete",
|
|
52
|
+
"Activate",
|
|
53
|
+
"Deactivate",
|
|
54
|
+
"Pause",
|
|
55
|
+
"Resume",
|
|
56
|
+
"RunNow",
|
|
57
|
+
"Clone",
|
|
58
|
+
"Recover",
|
|
59
|
+
"Cancel",
|
|
60
|
+
"Register",
|
|
61
|
+
"Unregister",
|
|
62
|
+
"Update",
|
|
63
|
+
"Refresh",
|
|
64
|
+
"Upgrade",
|
|
65
|
+
"Upload",
|
|
66
|
+
"Download",
|
|
67
|
+
"Rename",
|
|
68
|
+
"Accept",
|
|
69
|
+
"Mark",
|
|
70
|
+
"Close",
|
|
71
|
+
"Failover",
|
|
72
|
+
"Failback",
|
|
73
|
+
"Protect",
|
|
74
|
+
];
|
|
75
|
+
export function registerAuditLogTools(server, client) {
|
|
76
|
+
// ── List Audit Logs ────────────────────────────────────────────────────
|
|
77
|
+
// GET /v2/audit-logs — supports rich server-side filtering.
|
|
78
|
+
server.tool("list_audit_logs", "Query the cluster audit log. Filter by user, action, entity type, time window, or free-text search. Returns who-did-what-when records for compliance and forensics.", {
|
|
79
|
+
search_string: z
|
|
80
|
+
.string()
|
|
81
|
+
.optional()
|
|
82
|
+
.describe("Free-text match against entityName or details"),
|
|
83
|
+
usernames: z.array(z.string()).optional().describe("Restrict to these usernames"),
|
|
84
|
+
domains: z.array(z.string()).optional().describe("Restrict to these auth domains"),
|
|
85
|
+
entity_types: z
|
|
86
|
+
.array(z.enum(COMMON_ENTITY_TYPES))
|
|
87
|
+
.optional()
|
|
88
|
+
.describe("Restrict to these entity types (common operational subset)"),
|
|
89
|
+
actions: z.array(z.enum(AUDIT_ACTIONS)).optional().describe("Restrict to these action types"),
|
|
90
|
+
start_time_usecs: z
|
|
91
|
+
.number()
|
|
92
|
+
.optional()
|
|
93
|
+
.describe("Only logs created after this unix microsecond timestamp"),
|
|
94
|
+
end_time_usecs: z
|
|
95
|
+
.number()
|
|
96
|
+
.optional()
|
|
97
|
+
.describe("Only logs created before this unix microsecond timestamp"),
|
|
98
|
+
tenant_ids: z.array(z.string()).optional().describe("Restrict to these tenant IDs"),
|
|
99
|
+
include_tenants: z
|
|
100
|
+
.boolean()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("Include records from all tenants the caller can see"),
|
|
103
|
+
start_index: z.number().optional().describe("Pagination cursor (skip oldest N)"),
|
|
104
|
+
count: z
|
|
105
|
+
.number()
|
|
106
|
+
.optional()
|
|
107
|
+
.default(100)
|
|
108
|
+
.describe("Maximum records to return per call"),
|
|
109
|
+
}, async (args) => {
|
|
110
|
+
try {
|
|
111
|
+
const qp = {};
|
|
112
|
+
if (args.search_string)
|
|
113
|
+
qp.searchString = args.search_string;
|
|
114
|
+
if (args.usernames?.length)
|
|
115
|
+
qp.usernames = args.usernames.join(",");
|
|
116
|
+
if (args.domains?.length)
|
|
117
|
+
qp.domains = args.domains.join(",");
|
|
118
|
+
if (args.entity_types?.length)
|
|
119
|
+
qp.entityTypes = args.entity_types.join(",");
|
|
120
|
+
if (args.actions?.length)
|
|
121
|
+
qp.actions = args.actions.join(",");
|
|
122
|
+
if (args.start_time_usecs !== undefined)
|
|
123
|
+
qp.startTimeUsecs = String(args.start_time_usecs);
|
|
124
|
+
if (args.end_time_usecs !== undefined)
|
|
125
|
+
qp.endTimeUsecs = String(args.end_time_usecs);
|
|
126
|
+
if (args.tenant_ids?.length)
|
|
127
|
+
qp.tenantIds = args.tenant_ids.join(",");
|
|
128
|
+
if (args.include_tenants !== undefined)
|
|
129
|
+
qp.includeTenants = String(args.include_tenants);
|
|
130
|
+
if (args.start_index !== undefined)
|
|
131
|
+
qp.startIndex = String(args.start_index);
|
|
132
|
+
if (args.count !== undefined)
|
|
133
|
+
qp.count = String(args.count);
|
|
134
|
+
const data = await client.getV2("audit-logs", qp);
|
|
135
|
+
return reply(JSON.stringify(data, null, 2));
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
return reply(`Error fetching audit logs: ${err}`, true);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
// ── List Audit Log Actions ─────────────────────────────────────────────
|
|
142
|
+
server.tool("list_audit_log_actions", "List all action types recognized by the audit log (for building filters)", {}, async () => {
|
|
143
|
+
try {
|
|
144
|
+
const data = await client.getV2("audit-logs/actions");
|
|
145
|
+
return reply(JSON.stringify(data, null, 2));
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
return reply(`Error fetching audit log actions: ${err}`, true);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
// ── List Audit Log Entity Types ────────────────────────────────────────
|
|
152
|
+
server.tool("list_audit_log_entity_types", "List all entity types tracked in the audit log (for building filters)", {}, async () => {
|
|
153
|
+
try {
|
|
154
|
+
const data = await client.getV2("audit-logs/entity-types");
|
|
155
|
+
return reply(JSON.stringify(data, null, 2));
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
return reply(`Error fetching audit log entity types: ${err}`, true);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone tools — create and manage instant-clone tasks for test/dev workflows.
|
|
3
|
+
*
|
|
4
|
+
* Two distinct clone types exist on Cohesity:
|
|
5
|
+
*
|
|
6
|
+
* 1. VIEW CLONE — Snapshots an existing file-services View into a new View.
|
|
7
|
+
* Endpoint: POST /v2/file-services/views/{id}/clone
|
|
8
|
+
*
|
|
9
|
+
* 2. RECOVERY CLONE TASK — Created via the standard /recoveries endpoint with
|
|
10
|
+
* a clone-specific Recovery payload (recoveryAction = "CloneVMs"); the
|
|
11
|
+
* task lifecycle is then managed through /recoveries/clone/{id}.
|
|
12
|
+
*
|
|
13
|
+
* This module covers both. Recovery clones (VMs) use the same recover_vm
|
|
14
|
+
* tool but with cloneVmsParams instead of recoverVmsParams; we provide a
|
|
15
|
+
* thin shortcut here so the LLM can find it easily.
|
|
16
|
+
*
|
|
17
|
+
* Endpoints (verified against cluster_v2_api.yaml):
|
|
18
|
+
* POST /v2/file-services/views/{id}/clone — CloneViewParams
|
|
19
|
+
* DELETE /v2/data-protect/recoveries/clone/{id}
|
|
20
|
+
* GET /v2/data-protect/recoveries (filter by recoveryAction=CloneVMs)
|
|
21
|
+
*/
|
|
22
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
24
|
+
export declare function registerCloneTools(server: McpServer, client: CohesityClient): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clone tools — create and manage instant-clone tasks for test/dev workflows.
|
|
3
|
+
*
|
|
4
|
+
* Two distinct clone types exist on Cohesity:
|
|
5
|
+
*
|
|
6
|
+
* 1. VIEW CLONE — Snapshots an existing file-services View into a new View.
|
|
7
|
+
* Endpoint: POST /v2/file-services/views/{id}/clone
|
|
8
|
+
*
|
|
9
|
+
* 2. RECOVERY CLONE TASK — Created via the standard /recoveries endpoint with
|
|
10
|
+
* a clone-specific Recovery payload (recoveryAction = "CloneVMs"); the
|
|
11
|
+
* task lifecycle is then managed through /recoveries/clone/{id}.
|
|
12
|
+
*
|
|
13
|
+
* This module covers both. Recovery clones (VMs) use the same recover_vm
|
|
14
|
+
* tool but with cloneVmsParams instead of recoverVmsParams; we provide a
|
|
15
|
+
* thin shortcut here so the LLM can find it easily.
|
|
16
|
+
*
|
|
17
|
+
* Endpoints (verified against cluster_v2_api.yaml):
|
|
18
|
+
* POST /v2/file-services/views/{id}/clone — CloneViewParams
|
|
19
|
+
* DELETE /v2/data-protect/recoveries/clone/{id}
|
|
20
|
+
* GET /v2/data-protect/recoveries (filter by recoveryAction=CloneVMs)
|
|
21
|
+
*/
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
const reply = (text, isError = false) => ({
|
|
24
|
+
content: [{ type: "text", text }],
|
|
25
|
+
isError,
|
|
26
|
+
});
|
|
27
|
+
export function registerCloneTools(server, client) {
|
|
28
|
+
// ── List Clone Tasks ───────────────────────────────────────────────────
|
|
29
|
+
// Recovery clones live in /data-protect/recoveries with recoveryAction filtering.
|
|
30
|
+
server.tool("list_clone_tasks", "List active and historical clone tasks (CloneVMs, CloneView, CloneAppView). Use filters to narrow by status or time range.", {
|
|
31
|
+
status: z
|
|
32
|
+
.array(z.enum([
|
|
33
|
+
"Accepted",
|
|
34
|
+
"Running",
|
|
35
|
+
"Canceled",
|
|
36
|
+
"Canceling",
|
|
37
|
+
"Failed",
|
|
38
|
+
"Missed",
|
|
39
|
+
"Succeeded",
|
|
40
|
+
"SucceededWithWarning",
|
|
41
|
+
"OnHold",
|
|
42
|
+
"Finalizing",
|
|
43
|
+
"Skipped",
|
|
44
|
+
]))
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Filter by status"),
|
|
47
|
+
start_time_usecs: z.number().optional().describe("Only tasks created after this timestamp (μs)"),
|
|
48
|
+
end_time_usecs: z.number().optional().describe("Only tasks created before this timestamp (μs)"),
|
|
49
|
+
max_results: z.number().default(25).describe("Maximum results"),
|
|
50
|
+
}, async (args) => {
|
|
51
|
+
try {
|
|
52
|
+
const qp = {
|
|
53
|
+
maxCount: String(args.max_results),
|
|
54
|
+
recoveryActions: "CloneVMs,CloneView,CloneAppView",
|
|
55
|
+
};
|
|
56
|
+
if (args.status?.length)
|
|
57
|
+
qp.status = args.status.join(",");
|
|
58
|
+
if (args.start_time_usecs !== undefined)
|
|
59
|
+
qp.startTimeUsecs = String(args.start_time_usecs);
|
|
60
|
+
if (args.end_time_usecs !== undefined)
|
|
61
|
+
qp.endTimeUsecs = String(args.end_time_usecs);
|
|
62
|
+
const data = await client.getV2("data-protect/recoveries", qp);
|
|
63
|
+
return reply(JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
return reply(`Error listing clone tasks: ${err}`, true);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// ── Clone a View ───────────────────────────────────────────────────────
|
|
70
|
+
// POST /v2/file-services/views/{id}/clone with CloneViewParams.
|
|
71
|
+
server.tool("clone_view", "Clone a file-services View into a new View. The clone is space-efficient (snapshot-backed). Useful for spinning up read/write test copies of production data.", {
|
|
72
|
+
source_view_id: z.number().describe("ID of the View to clone"),
|
|
73
|
+
name: z.string().describe("Name for the new cloned View"),
|
|
74
|
+
description: z.string().optional().describe("Description for the cloned View"),
|
|
75
|
+
read_only: z.boolean().optional().describe("Create the clone as read-only"),
|
|
76
|
+
data_lock_expiry_usecs: z
|
|
77
|
+
.number()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe("DataLock expiry as unix microseconds (makes the clone WORM-locked)"),
|
|
80
|
+
}, async (args) => {
|
|
81
|
+
try {
|
|
82
|
+
const body = { name: args.name };
|
|
83
|
+
if (args.description)
|
|
84
|
+
body.description = args.description;
|
|
85
|
+
if (args.read_only !== undefined)
|
|
86
|
+
body.isReadOnly = args.read_only;
|
|
87
|
+
if (args.data_lock_expiry_usecs !== undefined) {
|
|
88
|
+
body.dataLockExpiryUsecs = args.data_lock_expiry_usecs;
|
|
89
|
+
}
|
|
90
|
+
const data = await client.postV2(`file-services/views/${args.source_view_id}/clone`, body);
|
|
91
|
+
return reply(`View ${args.source_view_id} cloned as '${args.name}'.\n${JSON.stringify(data, null, 2)}`);
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
return reply(`Error cloning view: ${err}`, true);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
// ── Delete Recovery Clone Task ─────────────────────────────────────────
|
|
98
|
+
server.tool("delete_clone_task", "Delete a restore clone task by ID. This tears down the clone and releases its space.", { id: z.number().describe("Clone task ID to delete") }, async (args) => {
|
|
99
|
+
try {
|
|
100
|
+
await client.deleteV2(`data-protect/recoveries/clone/${args.id}`);
|
|
101
|
+
return reply(`Clone task ${args.id} deleted.`);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
return reply(`Error deleting clone task: ${err}`, true);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cluster-local report tools — synthesize backup, recovery, and capacity
|
|
3
|
+
* reports from cluster V1 + V2 endpoints. No Helios required.
|
|
4
|
+
*
|
|
5
|
+
* Most reports are derived: we combine multiple endpoints into a single
|
|
6
|
+
* tool result that an LLM (or operator) can read at a glance.
|
|
7
|
+
*/
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
10
|
+
export declare function registerClusterReportTools(server: McpServer, client: CohesityClient): void;
|