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.
Files changed (53) hide show
  1. package/LICENSE +77 -0
  2. package/README.md +494 -0
  3. package/dist/cohesity-client.d.ts +47 -0
  4. package/dist/cohesity-client.js +126 -0
  5. package/dist/index.d.ts +6 -0
  6. package/dist/index.js +149 -0
  7. package/dist/tools/active-directory.d.ts +11 -0
  8. package/dist/tools/active-directory.js +82 -0
  9. package/dist/tools/alerts.d.ts +6 -0
  10. package/dist/tools/alerts.js +111 -0
  11. package/dist/tools/antivirus.d.ts +24 -0
  12. package/dist/tools/antivirus.js +180 -0
  13. package/dist/tools/audit-logs.d.ts +10 -0
  14. package/dist/tools/audit-logs.js +161 -0
  15. package/dist/tools/clones.d.ts +24 -0
  16. package/dist/tools/clones.js +107 -0
  17. package/dist/tools/cluster-reports.d.ts +10 -0
  18. package/dist/tools/cluster-reports.js +224 -0
  19. package/dist/tools/cluster.d.ts +6 -0
  20. package/dist/tools/cluster.js +33 -0
  21. package/dist/tools/external-targets.d.ts +3 -0
  22. package/dist/tools/external-targets.js +145 -0
  23. package/dist/tools/kms.d.ts +14 -0
  24. package/dist/tools/kms.js +164 -0
  25. package/dist/tools/notifications.d.ts +3 -0
  26. package/dist/tools/notifications.js +156 -0
  27. package/dist/tools/protection.d.ts +3 -0
  28. package/dist/tools/protection.js +514 -0
  29. package/dist/tools/recovery.d.ts +6 -0
  30. package/dist/tools/recovery.js +101 -0
  31. package/dist/tools/reports.d.ts +3 -0
  32. package/dist/tools/reports.js +346 -0
  33. package/dist/tools/restore.d.ts +3 -0
  34. package/dist/tools/restore.js +220 -0
  35. package/dist/tools/roles.d.ts +11 -0
  36. package/dist/tools/roles.js +95 -0
  37. package/dist/tools/run-actions.d.ts +17 -0
  38. package/dist/tools/run-actions.js +190 -0
  39. package/dist/tools/runs.d.ts +6 -0
  40. package/dist/tools/runs.js +94 -0
  41. package/dist/tools/source-registration.d.ts +11 -0
  42. package/dist/tools/source-registration.js +456 -0
  43. package/dist/tools/sources.d.ts +3 -0
  44. package/dist/tools/sources.js +161 -0
  45. package/dist/tools/stats.d.ts +3 -0
  46. package/dist/tools/stats.js +164 -0
  47. package/dist/tools/storage.d.ts +3 -0
  48. package/dist/tools/storage.js +191 -0
  49. package/dist/tools/tiering.d.ts +3 -0
  50. package/dist/tools/tiering.js +132 -0
  51. package/dist/tools/users.d.ts +13 -0
  52. package/dist/tools/users.js +203 -0
  53. package/package.json +57 -0
@@ -0,0 +1,456 @@
1
+ /**
2
+ * Source registration tools — register, update, and unregister protection sources.
3
+ * Covers VMware (vCenter / ESXi / vCloud), Physical, Azure, AWS, M365, and Generic NAS.
4
+ *
5
+ * All payload shapes are derived from the cluster's OpenAPI v2 spec
6
+ * (cluster_v2_api.yaml). Field names match the spec exactly; deviating from
7
+ * the spec produces KValidationError from the cluster.
8
+ */
9
+ import { z } from "zod";
10
+ const reply = (text, isError = false) => ({
11
+ content: [{ type: "text", text }],
12
+ isError,
13
+ });
14
+ /**
15
+ * POST a registration body and best-effort kick off an inventory discovery
16
+ * so subsequent search_objects calls find the new source's objects.
17
+ */
18
+ async function postRegistration(client, body, label) {
19
+ const result = await client.postV2("data-protect/sources/registrations", body);
20
+ const id = result.id;
21
+ if (id) {
22
+ try {
23
+ await client.postV2(`data-protect/sources/${id}/refresh`, {});
24
+ }
25
+ catch {
26
+ /* discovery refresh is best-effort */
27
+ }
28
+ }
29
+ return reply(`${label} registered.\n${JSON.stringify(result, null, 2)}`);
30
+ }
31
+ export function registerSourceRegistrationTools(server, client) {
32
+ // ── VMware vCenter / ESXi standalone / vCloud Director ──────────────────
33
+ // Schema: VmwareSourceRegistrationParams { type, vCenterParams | esxiParams | vcdParams }
34
+ // vCenter and ESXi inherit CommonSourceRegistrationParams which is
35
+ // Credentials { username, password } + { endpoint (required) }.
36
+ server.tool("register_vmware_source", "Register a VMware vCenter, ESXi standalone host, or vCloud Director endpoint as a backup source", {
37
+ vmware_type: z
38
+ .enum(["kVCenter", "kStandaloneHost", "kvCloudDirector"])
39
+ .default("kVCenter")
40
+ .describe("VMware source type"),
41
+ endpoint: z
42
+ .string()
43
+ .describe("Hostname or IP of the vCenter, ESXi host, or vCloud endpoint (not used for vCloud — provide vcd_vcenters instead)"),
44
+ username: z.string().describe("Username for the source"),
45
+ password: z.string().describe("Password for the source"),
46
+ vcd_vcenters: z
47
+ .array(z.object({
48
+ endpoint: z.string(),
49
+ username: z.string(),
50
+ password: z.string(),
51
+ name: z.string().optional(),
52
+ }))
53
+ .optional()
54
+ .describe("vCenters backing the vCloud Director (only when vmware_type=kvCloudDirector)"),
55
+ }, async (args) => {
56
+ try {
57
+ const cred = {
58
+ endpoint: args.endpoint,
59
+ username: args.username,
60
+ password: args.password,
61
+ };
62
+ const vmwareParams = { type: args.vmware_type };
63
+ if (args.vmware_type === "kVCenter") {
64
+ vmwareParams.vCenterParams = cred;
65
+ }
66
+ else if (args.vmware_type === "kStandaloneHost") {
67
+ vmwareParams.esxiParams = cred;
68
+ }
69
+ else {
70
+ if (!args.vcd_vcenters?.length) {
71
+ return reply("vCloud Director registration requires vcd_vcenters with at least one vCenter credential set.", true);
72
+ }
73
+ vmwareParams.vcdParams = {
74
+ vcenterCredentialInfoList: args.vcd_vcenters,
75
+ };
76
+ }
77
+ return await postRegistration(client, { environment: "kVMware", vmwareParams }, `VMware ${args.vmware_type} ${args.endpoint}`);
78
+ }
79
+ catch (err) {
80
+ return reply(`Error registering VMware source: ${err}`, true);
81
+ }
82
+ });
83
+ // ── Physical Server ────────────────────────────────────────────────────
84
+ // Schema: PhysicalSourceRegistrationParams { endpoint (required), hostType?, physicalType?, forceRegister?, applications? }
85
+ server.tool("register_physical_source", "Register a physical server (Linux, Windows, AIX, Solaris, SAP HANA) as a backup source. Requires the Cohesity agent to be installed and reachable on the host.", {
86
+ endpoint: z.string().describe("Hostname or IP of the physical server"),
87
+ host_type: z
88
+ .enum(["kLinux", "kWindows", "kAix", "kSolaris", "kSapHana", "kOther", "kHPUX", "kVOS"])
89
+ .optional()
90
+ .describe("Operating system of the physical host"),
91
+ physical_type: z
92
+ .enum(["kGroup", "kHost", "kWindowsCluster", "kOracleRACCluster", "kOracleAPCluster", "kUnixCluster", "kOracleCluster"])
93
+ .optional()
94
+ .describe("Physical source topology"),
95
+ force_register: z
96
+ .boolean()
97
+ .optional()
98
+ .describe("Force registration even if the host is already registered with another cluster"),
99
+ applications: z
100
+ .array(z.enum(["kSQL", "kOracle"]))
101
+ .optional()
102
+ .describe("Applications to register with the physical source"),
103
+ }, async (args) => {
104
+ try {
105
+ const physicalParams = { endpoint: args.endpoint };
106
+ if (args.host_type)
107
+ physicalParams.hostType = args.host_type;
108
+ if (args.physical_type)
109
+ physicalParams.physicalType = args.physical_type;
110
+ if (args.force_register !== undefined)
111
+ physicalParams.forceRegister = args.force_register;
112
+ if (args.applications?.length)
113
+ physicalParams.applications = args.applications;
114
+ return await postRegistration(client, { environment: "kPhysical", physicalParams }, `Physical host ${args.endpoint}`);
115
+ }
116
+ catch (err) {
117
+ return reply(`Error registering physical source: ${err}`, true);
118
+ }
119
+ });
120
+ // ── Azure Subscription ─────────────────────────────────────────────────
121
+ // Schema: AzureSourceRegistrationParams {
122
+ // registrationLevel (REQUIRED, kTenant|kSubscription),
123
+ // registrationWorkflow (REQUIRED, kExpress|kManual),
124
+ // azureTenantId?, subscriptionDetails?[{ subscriptionId }],
125
+ // applicationCredentials?[{ applicationId, applicationObjectId?, encryptedApplicationKey? }],
126
+ // useCases?
127
+ // }
128
+ //
129
+ // KNOWN CLUSTER-SIDE BUG (some Cohesity builds, observed on 7.x as of
130
+ // 2026-05): the V2 endpoint's kAzure handler returns
131
+ // HTTP 403 "Azure credentials does not have Subscription Id"
132
+ // for any payload, even with valid creds and a Contributor role on the
133
+ // subscription. The error is fixed text and returned in <300ms — the
134
+ // cluster never contacts Azure. The same V2 endpoint works correctly
135
+ // for kAWS, kS3Compatible, and kVMware on the same cluster, so this is
136
+ // an Azure-specific implementation gap on the cluster side. The schema
137
+ // and payload below are correct against the documented OpenAPI v2 spec
138
+ // and have been validated against Azure directly via Microsoft's OAuth
139
+ // and Resource Management APIs.
140
+ //
141
+ // Workaround: register the Azure source through the Cohesity GUI; all
142
+ // other Azure MCP tools (search_objects, refresh_source, create
143
+ // protection group, run, etc.) work normally on the GUI-created source.
144
+ server.tool("register_azure_source", "Register an Azure tenant or subscription as a backup source for protecting Azure VMs, SQL, Files, Blob, and other workloads. NOTE: some Cohesity cluster builds have a V2 Azure handler bug that returns 'Azure credentials does not have Subscription Id' even for valid credentials. If you hit this error, register the source via the Cohesity GUI instead — all other Azure MCP tools work normally on a GUI-created source.", {
145
+ registration_level: z
146
+ .enum(["kTenant", "kSubscription"])
147
+ .describe("Whether registering at tenant level or specific subscription level"),
148
+ registration_workflow: z
149
+ .enum(["kExpress", "kManual"])
150
+ .describe("kExpress uses Cohesity-managed app, kManual uses your own Azure AD app"),
151
+ tenant_id: z
152
+ .string()
153
+ .describe("Azure AD tenant ID (GUID or domain name)"),
154
+ subscription_ids: z
155
+ .array(z.string())
156
+ .optional()
157
+ .describe("Azure subscription IDs to register. REQUIRED when registration_level=kSubscription"),
158
+ application_id: z
159
+ .string()
160
+ .describe("Azure AD app registration (client) ID — applicationCredentials is required by the cluster"),
161
+ application_key: z
162
+ .string()
163
+ .describe("Azure AD app registration client secret"),
164
+ use_cases: z
165
+ .array(z.enum([
166
+ "kVirtualMachine",
167
+ "kSQL",
168
+ "kEntraID",
169
+ "kFileShare",
170
+ "kKubernetes",
171
+ "kBlobStorage",
172
+ "kAzureSQLMI",
173
+ "kAzureSQLDB",
174
+ ]))
175
+ .optional()
176
+ .describe("Azure workload types to enable for this registration"),
177
+ }, async (args) => {
178
+ try {
179
+ if (args.registration_level === "kSubscription" && !args.subscription_ids?.length) {
180
+ return reply("registration_level=kSubscription requires at least one subscription_id.", true);
181
+ }
182
+ const azureParams = {
183
+ registrationLevel: args.registration_level,
184
+ registrationWorkflow: args.registration_workflow,
185
+ azureTenantId: args.tenant_id,
186
+ applicationCredentials: [
187
+ {
188
+ applicationId: args.application_id,
189
+ encryptedApplicationKey: args.application_key,
190
+ },
191
+ ],
192
+ };
193
+ if (args.subscription_ids?.length) {
194
+ azureParams.subscriptionDetails = args.subscription_ids.map((id) => ({
195
+ subscriptionId: id,
196
+ }));
197
+ }
198
+ if (args.use_cases?.length)
199
+ azureParams.useCases = args.use_cases;
200
+ return await postRegistration(client, { environment: "kAzure", azureParams }, `Azure ${args.registration_level} ${args.tenant_id ?? ""}`);
201
+ }
202
+ catch (err) {
203
+ return reply(`Error registering Azure source: ${err}`, true);
204
+ }
205
+ });
206
+ // ── AWS Account ────────────────────────────────────────────────────────
207
+ // Schema: AwsSourceRegistrationParams {
208
+ // subscriptionType (REQUIRED, kAWSCommercial|kAWSGovCloud|kAWSC2S),
209
+ // standardParams?: { authMethodType, iamUserAwsCredentials? | iamRoleAwsCredentials? },
210
+ // useCases?
211
+ // }
212
+ // IamUserAwsCredentials requires { accessKey, secretAccessKey, arn }.
213
+ server.tool("register_aws_source", "Register an AWS account as a backup source for protecting EC2, RDS, S3, DynamoDB, and other AWS workloads", {
214
+ subscription_type: z
215
+ .enum(["kAWSCommercial", "kAWSGovCloud", "kAWSC2S"])
216
+ .default("kAWSCommercial")
217
+ .describe("AWS partition type"),
218
+ auth_method: z
219
+ .enum(["kUseIAMUser", "kUseIAMRole", "kUseInstanceProfile"])
220
+ .default("kUseIAMUser")
221
+ .describe("Authentication method"),
222
+ access_key: z
223
+ .string()
224
+ .optional()
225
+ .describe("IAM user access key ID (required when auth_method=kUseIAMUser)"),
226
+ secret_access_key: z
227
+ .string()
228
+ .optional()
229
+ .describe("IAM user secret access key (required when auth_method=kUseIAMUser)"),
230
+ iam_user_arn: z
231
+ .string()
232
+ .optional()
233
+ .describe("IAM user ARN (required when auth_method=kUseIAMUser)"),
234
+ iam_role_arn: z
235
+ .string()
236
+ .optional()
237
+ .describe("IAM role ARN (required when auth_method=kUseIAMRole)"),
238
+ use_cases: z
239
+ .array(z.enum(["kEC2", "kRDS", "kPostgres", "kDynamoDB", "kS3", "kDocumentDB", "kRedshift"]))
240
+ .optional()
241
+ .describe("AWS workload types to enable"),
242
+ }, async (args) => {
243
+ try {
244
+ const standardParams = { authMethodType: args.auth_method };
245
+ if (args.auth_method === "kUseIAMUser") {
246
+ if (!args.access_key || !args.secret_access_key || !args.iam_user_arn) {
247
+ return reply("kUseIAMUser requires access_key, secret_access_key, and iam_user_arn.", true);
248
+ }
249
+ standardParams.iamUserAwsCredentials = {
250
+ accessKey: args.access_key,
251
+ secretAccessKey: args.secret_access_key,
252
+ arn: args.iam_user_arn,
253
+ };
254
+ }
255
+ else if (args.auth_method === "kUseIAMRole") {
256
+ if (!args.iam_role_arn) {
257
+ return reply("kUseIAMRole requires iam_role_arn.", true);
258
+ }
259
+ standardParams.iamRoleAwsCredentials = { iamRoleArn: args.iam_role_arn };
260
+ }
261
+ const awsParams = {
262
+ subscriptionType: args.subscription_type,
263
+ standardParams,
264
+ };
265
+ if (args.use_cases?.length)
266
+ awsParams.useCases = args.use_cases;
267
+ return await postRegistration(client, { environment: "kAWS", awsParams }, `AWS ${args.subscription_type} (${args.auth_method})`);
268
+ }
269
+ catch (err) {
270
+ return reply(`Error registering AWS source: ${err}`, true);
271
+ }
272
+ });
273
+ // ── Microsoft 365 Tenant ───────────────────────────────────────────────
274
+ // Schema: Office365SourceRegistrationParams extends CommonSourceRegistrationParams
275
+ // (which is Credentials + endpoint).
276
+ // Plus optional: office365AppCredentialsList[{ clientId, clientSecret }],
277
+ // office365Region, useOAuthForExchangeOnline.
278
+ server.tool("register_m365_source", "Register a Microsoft 365 tenant as a backup source for protecting Exchange Online, OneDrive, SharePoint, Teams, and Groups", {
279
+ endpoint: z
280
+ .string()
281
+ .describe("M365 endpoint, typically the tenant domain (e.g., contoso.onmicrosoft.com)"),
282
+ username: z.string().describe("Global admin username for the M365 tenant"),
283
+ password: z.string().describe("Password for the admin account"),
284
+ app_credentials: z
285
+ .array(z.object({
286
+ client_id: z.string().describe("Azure AD app registration client ID"),
287
+ client_secret: z.string().describe("Azure AD app registration client secret"),
288
+ }))
289
+ .min(1)
290
+ .describe("Azure AD application credentials used for Graph API access (at least one required)"),
291
+ region: z
292
+ .enum(["Default", "China", "Germany", "UsDoD", "UsGccHigh"])
293
+ .optional()
294
+ .describe("M365 region (Default for commercial, UsGccHigh for GCC High, etc.)"),
295
+ use_oauth_for_exchange: z
296
+ .boolean()
297
+ .optional()
298
+ .describe("Use OAuth for Exchange Online authentication"),
299
+ }, async (args) => {
300
+ try {
301
+ const office365Params = {
302
+ endpoint: args.endpoint,
303
+ username: args.username,
304
+ password: args.password,
305
+ };
306
+ office365Params.office365AppCredentialsList = args.app_credentials.map((c) => ({
307
+ clientId: c.client_id,
308
+ clientSecret: c.client_secret,
309
+ }));
310
+ if (args.region)
311
+ office365Params.office365Region = args.region;
312
+ if (args.use_oauth_for_exchange !== undefined) {
313
+ office365Params.useOAuthForExchangeOnline = args.use_oauth_for_exchange;
314
+ }
315
+ return await postRegistration(client, { environment: "kO365", office365Params }, `M365 tenant ${args.endpoint}`);
316
+ }
317
+ catch (err) {
318
+ return reply(`Error registering M365 source: ${err}`, true);
319
+ }
320
+ });
321
+ // ── Generic NAS (NFS or SMB) ───────────────────────────────────────────
322
+ // Schema: GenericNasRegistrationParams {
323
+ // mountPoint (REQUIRED),
324
+ // mode (REQUIRED, kNfs4_1|kNfs3|kCifs1),
325
+ // smbMountCredentials?: { username, password }, skipValidation?, description?
326
+ // }
327
+ server.tool("register_nas_source", "Register a generic NAS mount point (NFS3, NFS4.1, or SMB/CIFS) as a backup source", {
328
+ mount_point: z
329
+ .string()
330
+ .describe("Mount point of the NAS share (e.g., server:/export for NFS or \\\\server\\share for SMB)"),
331
+ mode: z
332
+ .enum(["kNfs3", "kNfs4_1", "kCifs1"])
333
+ .describe("Mount protocol mode"),
334
+ username: z
335
+ .string()
336
+ .optional()
337
+ .describe("Username for SMB / CIFS authentication (required when mode=kCifs1)"),
338
+ password: z
339
+ .string()
340
+ .optional()
341
+ .describe("Password for SMB / CIFS authentication"),
342
+ skip_validation: z
343
+ .boolean()
344
+ .optional()
345
+ .describe("Skip share-reachability validation during registration"),
346
+ description: z.string().optional().describe("Description for the NAS source"),
347
+ }, async (args) => {
348
+ try {
349
+ const genericNasParams = {
350
+ mountPoint: args.mount_point,
351
+ mode: args.mode,
352
+ };
353
+ if (args.mode === "kCifs1") {
354
+ if (!args.username) {
355
+ return reply("SMB / CIFS mode requires a username (and typically a password).", true);
356
+ }
357
+ genericNasParams.smbMountCredentials = {
358
+ username: args.username,
359
+ password: args.password ?? "",
360
+ };
361
+ }
362
+ if (args.skip_validation !== undefined)
363
+ genericNasParams.skipValidation = args.skip_validation;
364
+ if (args.description)
365
+ genericNasParams.description = args.description;
366
+ return await postRegistration(client, { environment: "kGenericNas", genericNasParams }, `NAS share ${args.mount_point}`);
367
+ }
368
+ catch (err) {
369
+ return reply(`Error registering NAS source: ${err}`, true);
370
+ }
371
+ });
372
+ // ── Update Registration ────────────────────────────────────────────────
373
+ // PATCH lets callers splice in new credentials/endpoint on an existing
374
+ // registration without restating the whole body.
375
+ server.tool("update_source_registration", "Update an existing source registration (rotate credentials, change endpoint, etc.). For VMware vCenter only — for other env types, use the raw API.", {
376
+ registration_id: z.number().describe("Registration ID to update"),
377
+ username: z.string().optional().describe("New username (when applicable)"),
378
+ password: z.string().optional().describe("New password / secret (when applicable)"),
379
+ endpoint: z.string().optional().describe("New endpoint / hostname (when applicable)"),
380
+ }, async (args) => {
381
+ try {
382
+ const current = (await client.getV2(`data-protect/sources/registrations/${args.registration_id}`));
383
+ const env = current.environment;
384
+ if (env === "kVMware") {
385
+ const vmware = (current.vmwareParams ?? {});
386
+ const cred = vmware.vCenterParams ?? vmware.esxiParams ?? vmware.vcdParams;
387
+ if (cred) {
388
+ if (args.username !== undefined)
389
+ cred.username = args.username;
390
+ if (args.password !== undefined)
391
+ cred.password = args.password;
392
+ if (args.endpoint !== undefined)
393
+ cred.endpoint = args.endpoint;
394
+ }
395
+ }
396
+ else {
397
+ return reply(`update_source_registration currently only covers kVMware. Got environment=${env}. For other env types, use the API directly.`, true);
398
+ }
399
+ const result = await client.putV2(`data-protect/sources/registrations/${args.registration_id}`, current);
400
+ return reply(`Registration ${args.registration_id} updated.\n${JSON.stringify(result, null, 2)}`);
401
+ }
402
+ catch (err) {
403
+ return reply(`Error updating source registration: ${err}`, true);
404
+ }
405
+ });
406
+ // ── S3 Compatible Source ───────────────────────────────────────────────
407
+ // Schema: S3CompatibleSourceRegistrationParams {
408
+ // endpoint (REQUIRED, IP-only — no protocol prefix, no port),
409
+ // port (REQUIRED, int),
410
+ // accessKeyId (REQUIRED),
411
+ // secretAccessKey (REQUIRED)
412
+ // }
413
+ // Used to register AWS S3, MinIO, Ceph, Wasabi, Cloudian, on-prem ECS, etc.
414
+ // For AWS S3 specifically the endpoint is the region's S3 endpoint
415
+ // (e.g. s3.us-east-1.amazonaws.com) — Cohesity resolves the hostname.
416
+ server.tool("register_s3_compatible_source", "Register an S3-compatible object storage endpoint as a backup source. Use for AWS S3, MinIO, Ceph, Wasabi, on-prem ECS, or any S3 API-compatible target.", {
417
+ endpoint: z
418
+ .string()
419
+ .describe("S3 endpoint (e.g. s3.us-east-1.amazonaws.com or 10.0.0.50). Do NOT include https:// prefix or port — those are separate fields."),
420
+ port: z
421
+ .number()
422
+ .int()
423
+ .default(443)
424
+ .describe("TCP port (443 for AWS S3 / TLS, 9000 for default MinIO, etc.)"),
425
+ access_key_id: z.string().describe("S3 access key ID"),
426
+ secret_access_key: z.string().describe("S3 secret access key"),
427
+ }, async (args) => {
428
+ try {
429
+ const body = {
430
+ environment: "kS3Compatible",
431
+ s3CompatibleParams: {
432
+ endpoint: args.endpoint,
433
+ port: args.port,
434
+ accessKeyId: args.access_key_id,
435
+ secretAccessKey: args.secret_access_key,
436
+ },
437
+ };
438
+ return await postRegistration(client, body, `S3-compatible ${args.endpoint}:${args.port}`);
439
+ }
440
+ catch (err) {
441
+ return reply(`Error registering S3-compatible source: ${err}`, true);
442
+ }
443
+ });
444
+ // ── Unregister Source ──────────────────────────────────────────────────
445
+ server.tool("unregister_source", "Unregister (delete) a source registration. WARNING: removes the source from Cohesity but does not delete existing backups.", {
446
+ registration_id: z.number().describe("Registration ID to delete"),
447
+ }, async (args) => {
448
+ try {
449
+ await client.deleteV2(`data-protect/sources/registrations/${args.registration_id}`);
450
+ return reply(`Registration ${args.registration_id} unregistered.`);
451
+ }
452
+ catch (err) {
453
+ return reply(`Error unregistering source: ${err}`, true);
454
+ }
455
+ });
456
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CohesityClient } from "../cohesity-client.js";
3
+ export declare function registerSourcesTools(server: McpServer, client: CohesityClient): void;
@@ -0,0 +1,161 @@
1
+ import { z } from "zod";
2
+ function toolResult(text, isError = false) {
3
+ return { content: [{ type: "text", text }], isError };
4
+ }
5
+ export function registerSourcesTools(server, client) {
6
+ // ── List Sources ─────────────────────────────────────────────────────
7
+ server.tool("list_sources", "List all registered Cohesity protection sources such as vSphere, Physical servers, NAS, SQL, and more", {
8
+ environments: z
9
+ .array(z.enum([
10
+ "kVMware",
11
+ "kPhysical",
12
+ "kNas",
13
+ "kSQL",
14
+ "kOracle",
15
+ "kView",
16
+ "kPuppeteer",
17
+ "kGenericNas",
18
+ "kAcropolis",
19
+ "kPhysicalFiles",
20
+ "kIsilon",
21
+ "kNetapp",
22
+ "kAgent",
23
+ "kGenericNas",
24
+ "kAD",
25
+ "kAWS",
26
+ "kAzure",
27
+ "kGCP",
28
+ "kKVM",
29
+ "kAWSNative",
30
+ "kO365",
31
+ "kO365Outlook",
32
+ "kHyperFlex",
33
+ "kGCPNative",
34
+ "kAzureNative",
35
+ "kKubernetes",
36
+ "kElastifile",
37
+ "kFlashBlade",
38
+ "kRDSSnapshotManager",
39
+ "kCassandra",
40
+ "kMongoDB",
41
+ "kCouchbase",
42
+ "kHdfs",
43
+ "kHBase",
44
+ "kUDA",
45
+ "kSfdc",
46
+ "kO365Teams",
47
+ "kO365Group",
48
+ "kO365Exchange",
49
+ "kO365OneDrive",
50
+ "kO365Sharepoint",
51
+ ]))
52
+ .optional()
53
+ .describe("Filter by environment type"),
54
+ include_data_store_details: z
55
+ .boolean()
56
+ .optional()
57
+ .default(false)
58
+ .describe("Include datastore details for VMware sources"),
59
+ }, async ({ environments, include_data_store_details }) => {
60
+ try {
61
+ const params = {};
62
+ if (environments)
63
+ params.environments = environments.join(",");
64
+ if (include_data_store_details)
65
+ params.includeApplicationsTreeInfo = "true";
66
+ // /v2/data-protect/sources is not exposed on all cluster builds (some
67
+ // return 404). /sources/registrations is reliably present and returns
68
+ // the same logical set with richer per-source metadata.
69
+ const result = await client.getV2("data-protect/sources/registrations", params);
70
+ return toolResult(JSON.stringify(result, null, 2));
71
+ }
72
+ catch (error) {
73
+ return toolResult(`Error fetching protection sources: ${error}`, true);
74
+ }
75
+ });
76
+ // ── Search Objects ───────────────────────────────────────────────────
77
+ server.tool("search_objects", "Search for Cohesity protectable objects (VMs, physical servers, databases, etc.) by name or environment. The object ID used for protection groups is found in objectProtectionInfos[0].objectId in the response. For a full list of all VMware objects with IDs, use list_sources then get_source.", {
78
+ search_string: z
79
+ .string()
80
+ .optional()
81
+ .describe("Search by object name. Supports wildcard '*' suffix (e.g. 'web-*')"),
82
+ environments: z
83
+ .array(z.enum([
84
+ "kVMware", "kHyperV", "kAWS", "kAzure", "kGCP", "kPhysical",
85
+ "kPhysicalFiles", "kSQL", "kOracle", "kView", "kNetapp", "kGenericNas",
86
+ "kIsilon", "kFlashBlade", "kKubernetes", "kO365", "kO365Exchange",
87
+ "kO365OneDrive", "kO365Sharepoint", "kO365Teams",
88
+ ]))
89
+ .optional()
90
+ .describe("Filter by environment type (e.g. kVMware, kPhysical, kSQL)"),
91
+ source_ids: z
92
+ .array(z.number())
93
+ .optional()
94
+ .describe("Filter by registered source IDs (e.g. vCenter ID)"),
95
+ max_results: z
96
+ .number()
97
+ .optional()
98
+ .default(50)
99
+ .describe("Maximum number of results to return"),
100
+ }, async ({ search_string, environments, source_ids, max_results }) => {
101
+ try {
102
+ await client.refreshAllSources();
103
+ const params = {
104
+ maxResultsCount: String(max_results),
105
+ };
106
+ if (search_string)
107
+ params.searchString = search_string;
108
+ if (environments)
109
+ params.environments = environments.join(",");
110
+ if (source_ids)
111
+ params.sourceIds = source_ids.join(",");
112
+ const result = await client.getV2("data-protect/search/objects", params);
113
+ return toolResult(JSON.stringify(result, null, 2));
114
+ }
115
+ catch (error) {
116
+ return toolResult(`Error searching objects: ${error}`, true);
117
+ }
118
+ });
119
+ // ── Get Source ───────────────────────────────────────────────────────
120
+ server.tool("get_source", "Get detailed information about a specific Cohesity protection source including its object hierarchy", {
121
+ id: z
122
+ .number()
123
+ .describe("Source ID to retrieve details for"),
124
+ environment: z
125
+ .string()
126
+ .optional()
127
+ .describe("Environment type of the source (e.g., kVMware, kPhysical, kNas)"),
128
+ include_entity_permission_info: z
129
+ .boolean()
130
+ .optional()
131
+ .default(false)
132
+ .describe("Include entity permission information"),
133
+ }, async ({ id, environment, include_entity_permission_info }) => {
134
+ try {
135
+ const params = {
136
+ includeEntityPermissionInfo: String(include_entity_permission_info),
137
+ };
138
+ if (environment)
139
+ params.environment = environment;
140
+ const result = await client.getV2(`data-protect/sources/registrations/${id}`);
141
+ return toolResult(JSON.stringify(result, null, 2));
142
+ }
143
+ catch (error) {
144
+ return toolResult(`Error fetching source ${id}: ${error}`, true);
145
+ }
146
+ });
147
+ // ── Refresh Source ───────────────────────────────────────────────────
148
+ server.tool("refresh_source", "Refresh a registered protection source to sync the latest inventory from it. For vCenter, this re-discovers VMs, datastores, hosts, and clusters. Use list_sources to get the source ID.", {
149
+ id: z
150
+ .number()
151
+ .describe("ID of the protection source to refresh (e.g. vCenter source ID from list_sources)"),
152
+ }, async ({ id }) => {
153
+ try {
154
+ await client.postV2(`data-protect/sources/${id}/refresh`, {});
155
+ return toolResult(`Source ${id} refresh initiated successfully. The source inventory will be updated shortly.`);
156
+ }
157
+ catch (error) {
158
+ return toolResult(`Error refreshing source ${id}: ${error}`, true);
159
+ }
160
+ });
161
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CohesityClient } from "../cohesity-client.js";
3
+ export declare function registerStatsTools(server: McpServer, client: CohesityClient): void;