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,224 @@
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 { z } from "zod";
9
+ const reply = (text, isError = false) => ({
10
+ content: [{ type: "text", text }],
11
+ isError,
12
+ });
13
+ /** Convert bytes to a human-readable string. */
14
+ function fmtBytes(n) {
15
+ if (n == null)
16
+ return "—";
17
+ const units = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
18
+ let v = n;
19
+ let i = 0;
20
+ while (v >= 1024 && i < units.length - 1) {
21
+ v /= 1024;
22
+ i++;
23
+ }
24
+ return `${v.toFixed(2)} ${units[i]}`;
25
+ }
26
+ /** Convert microseconds since epoch to an ISO date string. */
27
+ function fmtUsecs(usecs) {
28
+ if (!usecs)
29
+ return "—";
30
+ return new Date(usecs / 1000).toISOString();
31
+ }
32
+ export function registerClusterReportTools(server, client) {
33
+ // ── Protected Objects Trend Report (V1) ────────────────────────────────
34
+ // /irisservices/api/v1/public/reports/protectedObjectsTrends
35
+ // Returns per-object backup success/fail history rolled up by day/week.
36
+ server.tool("get_protected_objects_trend_report", "Get a per-object backup success/failure trend report over a time window. Returns each protected object with daily or weekly rollups of total, successful, running, cancelled, and failed runs.", {
37
+ start_time_msecs: z
38
+ .number()
39
+ .describe("Window start in unix milliseconds (NOT microseconds — V1 endpoint uses msec)"),
40
+ end_time_msecs: z.number().describe("Window end in unix milliseconds"),
41
+ rollup_interval_days: z
42
+ .number()
43
+ .default(1)
44
+ .describe("Days per rollup bucket (1=daily, 7=weekly, 30=monthly)"),
45
+ timezone: z
46
+ .string()
47
+ .default("UTC")
48
+ .describe("IANA timezone for date bucketing (e.g. UTC, America/New_York)"),
49
+ }, async (args) => {
50
+ try {
51
+ const qp = {
52
+ startTimeMsecs: String(args.start_time_msecs),
53
+ endTimeMsecs: String(args.end_time_msecs),
54
+ rollupIntervalDays: String(args.rollup_interval_days),
55
+ timezone: args.timezone,
56
+ };
57
+ const data = await client.getV1("reports/protectedObjectsTrends", qp);
58
+ return reply(JSON.stringify(data, null, 2));
59
+ }
60
+ catch (err) {
61
+ return reply(`Error fetching trend report: ${err}`, true);
62
+ }
63
+ });
64
+ // ── Sources × Jobs Summary Report (V1) ─────────────────────────────────
65
+ // /irisservices/api/v1/public/reports/protectionSourcesJobsSummary
66
+ // Which sources are in which protection groups, with run counts.
67
+ server.tool("get_sources_jobs_summary_report", "Get a report mapping protection sources to the protection groups (jobs) covering them, with run counts per source.", {}, async () => {
68
+ try {
69
+ const data = await client.getV1("reports/protectionSourcesJobsSummary");
70
+ return reply(JSON.stringify(data, null, 2));
71
+ }
72
+ catch (err) {
73
+ return reply(`Error fetching sources/jobs summary: ${err}`, true);
74
+ }
75
+ });
76
+ // ── Archival Data Transfer Report (V1) ─────────────────────────────────
77
+ // /irisservices/api/v1/public/reports/dataTransferToVaults
78
+ server.tool("get_archival_transfer_report", "Get a report of data transferred to external archival targets (vaults) in a given time window.", {
79
+ start_time_msecs: z.number().describe("Window start in unix milliseconds"),
80
+ end_time_msecs: z.number().describe("Window end in unix milliseconds"),
81
+ timezone: z.string().default("UTC").describe("IANA timezone for date bucketing"),
82
+ }, async (args) => {
83
+ try {
84
+ const qp = {
85
+ startTimeMsecs: String(args.start_time_msecs),
86
+ endTimeMsecs: String(args.end_time_msecs),
87
+ timezone: args.timezone,
88
+ };
89
+ const data = await client.getV1("reports/dataTransferToVaults", qp);
90
+ return reply(JSON.stringify(data, null, 2));
91
+ }
92
+ catch (err) {
93
+ return reply(`Error fetching archival transfer report: ${err}`, true);
94
+ }
95
+ });
96
+ // ── Synthesized Protection Summary Report ──────────────────────────────
97
+ // Combines protection-groups + last-run-info + alerts into a Markdown
98
+ // summary suitable for an LLM to relay to the operator.
99
+ server.tool("generate_protection_summary_report", "Generate a synthesized Markdown protection summary report: protection groups, their last run status, and any active alerts. Combines multiple V2 endpoints into one human-readable view.", {
100
+ include_resolved_alerts: z
101
+ .boolean()
102
+ .default(false)
103
+ .describe("Include resolved alerts (default: only open alerts)"),
104
+ }, async (args) => {
105
+ try {
106
+ const [groupsResp, alertsResp] = await Promise.all([
107
+ client.getV2("data-protect/protection-groups", {
108
+ includeLastRunInfo: "true",
109
+ }),
110
+ client.getV2("alerts", {
111
+ alertStates: args.include_resolved_alerts ? "kOpen,kResolved" : "kOpen",
112
+ maxAlerts: "100",
113
+ }),
114
+ ]);
115
+ const groups = groupsResp.protectionGroups ?? [];
116
+ const alerts = alertsResp.alerts ?? [];
117
+ const lines = [];
118
+ lines.push("# Protection Summary Report");
119
+ lines.push(`Generated: ${new Date().toISOString()}`);
120
+ lines.push("");
121
+ lines.push(`## Protection Groups (${groups.length})`);
122
+ lines.push("");
123
+ lines.push("| Name | Environment | Policy | Last Run | Status | Logical |");
124
+ lines.push("|---|---|---|---|---|---|");
125
+ for (const g of groups) {
126
+ const lastRun = g.lastRun ?? {};
127
+ const localRun = lastRun.localBackupInfo ?? {};
128
+ lines.push(`| ${g.name ?? "—"} | ${g.environment ?? "—"} | ${g.policyId ?? "—"} ` +
129
+ `| ${fmtUsecs(localRun.startTimeUsecs)} | ${localRun.status ?? "—"} ` +
130
+ `| ${fmtBytes(localRun.localSnapshotStats?.logicalSizeBytes)} |`);
131
+ }
132
+ lines.push("");
133
+ lines.push(`## Alerts (${alerts.length})`);
134
+ lines.push("");
135
+ if (alerts.length === 0) {
136
+ lines.push("_No alerts in the selected window._");
137
+ }
138
+ else {
139
+ lines.push("| Severity | Category | Created | Message |");
140
+ lines.push("|---|---|---|---|");
141
+ for (const a of alerts.slice(0, 50)) {
142
+ const created = fmtUsecs(a.firstTimestampUsecs);
143
+ const msg = (a.alertDocument?.alertDescription ?? a.errorMessage ?? "—")
144
+ .replace(/\|/g, "\\|")
145
+ .replace(/\n/g, " ")
146
+ .slice(0, 120);
147
+ lines.push(`| ${a.severity ?? "—"} | ${a.alertCategory ?? "—"} | ${created} | ${msg} |`);
148
+ }
149
+ }
150
+ return reply(lines.join("\n"));
151
+ }
152
+ catch (err) {
153
+ return reply(`Error generating protection summary: ${err}`, true);
154
+ }
155
+ });
156
+ // ── Synthesized Failed Backups Report ──────────────────────────────────
157
+ // Combines protection-groups + last-run-info to surface only failed/missed runs.
158
+ server.tool("generate_failed_backups_report", "Generate a Markdown report listing protection groups whose most recent run failed, was missed, or finished with warnings. Useful for daily backup health triage.", {}, async () => {
159
+ try {
160
+ const groupsResp = await client.getV2("data-protect/protection-groups", {
161
+ includeLastRunInfo: "true",
162
+ });
163
+ const groups = groupsResp.protectionGroups ?? [];
164
+ const bad = groups.filter((g) => {
165
+ const status = g.lastRun?.localBackupInfo?.status;
166
+ return ["Failed", "Missed", "SucceededWithWarning", "Canceled"].includes(status);
167
+ });
168
+ const lines = [];
169
+ lines.push("# Failed / Missed Backups Report");
170
+ lines.push(`Generated: ${new Date().toISOString()}`);
171
+ lines.push(`Failing groups: ${bad.length} of ${groups.length}`);
172
+ lines.push("");
173
+ if (bad.length === 0) {
174
+ lines.push("_All protection groups' last runs completed successfully._");
175
+ return reply(lines.join("\n"));
176
+ }
177
+ lines.push("| Name | Environment | Status | Last Run | Run ID |");
178
+ lines.push("|---|---|---|---|---|");
179
+ for (const g of bad) {
180
+ const run = g.lastRun?.localBackupInfo ?? {};
181
+ lines.push(`| ${g.name ?? "—"} | ${g.environment ?? "—"} | ${run.status ?? "—"} ` +
182
+ `| ${fmtUsecs(run.startTimeUsecs)} | ${g.lastRun?.id ?? "—"} |`);
183
+ }
184
+ return reply(lines.join("\n"));
185
+ }
186
+ catch (err) {
187
+ return reply(`Error generating failed-backups report: ${err}`, true);
188
+ }
189
+ });
190
+ // ── Synthesized Capacity Report ────────────────────────────────────────
191
+ // Combines /clusters + /stats/cluster-storage into a single capacity view.
192
+ server.tool("generate_capacity_report", "Generate a Markdown capacity report showing cluster storage usage, available capacity, deduplication ratio, and data reduction stats.", {}, async () => {
193
+ try {
194
+ const [clusterResp, storageResp] = await Promise.all([
195
+ client.getV2("clusters"),
196
+ client.getV2("stats/cluster-storage"),
197
+ ]);
198
+ const cluster = clusterResp;
199
+ const storage = storageResp;
200
+ const stats = storage.totalClusterUsage ?? storage;
201
+ const lines = [];
202
+ lines.push("# Cluster Capacity Report");
203
+ lines.push(`Generated: ${new Date().toISOString()}`);
204
+ lines.push("");
205
+ lines.push(`**Cluster:** ${cluster.name ?? "—"} (id ${cluster.id ?? "—"})`);
206
+ lines.push(`**Software:** ${cluster.softwareVersion ?? "—"}`);
207
+ lines.push(`**Nodes:** ${cluster.nodeCount ?? "—"}`);
208
+ lines.push("");
209
+ lines.push("## Storage");
210
+ lines.push("");
211
+ lines.push(`- **Total capacity:** ${fmtBytes(stats.totalPhysicalRawUsageBytes)}`);
212
+ lines.push(`- **Used:** ${fmtBytes(stats.totalPhysicalUsageBytes)}`);
213
+ lines.push(`- **Free:** ${fmtBytes(stats.totalCapacityBytes - stats.totalPhysicalUsageBytes)}`);
214
+ lines.push(`- **Logical (pre-dedup):** ${fmtBytes(stats.totalLogicalUsageBytes)}`);
215
+ if (stats.dataReductionRatio !== undefined) {
216
+ lines.push(`- **Data reduction:** ${stats.dataReductionRatio.toFixed(2)}x`);
217
+ }
218
+ return reply(lines.join("\n"));
219
+ }
220
+ catch (err) {
221
+ return reply(`Error generating capacity report: ${err}`, true);
222
+ }
223
+ });
224
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Cluster information and statistics tools.
3
+ */
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { CohesityClient } from "../cohesity-client.js";
6
+ export declare function registerClusterTools(server: McpServer, client: CohesityClient): void;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Cluster information and statistics tools.
3
+ */
4
+ /** Shorthand for building MCP tool return values. */
5
+ const reply = (text, isError = false) => ({
6
+ content: [{ type: "text", text }],
7
+ isError,
8
+ });
9
+ export function registerClusterTools(server, client) {
10
+ // ── Cluster Info ───────────────────────────────────────────────────────
11
+ server.tool("get_cluster_info", "Get Cohesity cluster information including name, ID, software version, and node count", {}, async () => {
12
+ try {
13
+ const info = await client.getV2("clusters");
14
+ return reply(JSON.stringify(info, null, 2));
15
+ }
16
+ catch (err) {
17
+ return reply(`Error fetching cluster info: ${err}`, true);
18
+ }
19
+ });
20
+ // ── Cluster Stats ──────────────────────────────────────────────────────
21
+ server.tool("get_cluster_stats", "Get Cohesity cluster storage statistics including total capacity, used and available bytes, and data protection usage", {}, async () => {
22
+ try {
23
+ const [info, storage] = await Promise.all([
24
+ client.getV2("clusters"),
25
+ client.getV2("stats/cluster-storage"),
26
+ ]);
27
+ return reply(JSON.stringify({ cluster: info, storage }, null, 2));
28
+ }
29
+ catch (err) {
30
+ return reply(`Error fetching cluster stats: ${err}`, true);
31
+ }
32
+ });
33
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CohesityClient } from "../cohesity-client.js";
3
+ export declare function registerExternalTargetTools(server: McpServer, client: CohesityClient): void;
@@ -0,0 +1,145 @@
1
+ import { z } from "zod";
2
+ function toolResult(text, isError = false) {
3
+ return { content: [{ type: "text", text }], isError };
4
+ }
5
+ export function registerExternalTargetTools(server, client) {
6
+ // ── List External Targets ────────────────────────────────────────────
7
+ server.tool("list_external_targets", "List all registered external targets (cloud vaults, tape, NAS) used for archival and tiering. Returns target IDs needed for protection policy archival configuration.", {
8
+ purpose_types: z
9
+ .array(z.enum(["Archival", "Tiering", "Rpaas", "Logbackup"]))
10
+ .optional()
11
+ .describe("Filter by purpose type"),
12
+ storage_types: z
13
+ .array(z.enum(["Azure", "Google", "AWS", "Oracle", "NAS", "QStarTape", "S3Compatible", "IBM"]))
14
+ .optional()
15
+ .describe("Filter by storage type"),
16
+ name: z
17
+ .string()
18
+ .optional()
19
+ .describe("Filter by target name"),
20
+ }, async ({ purpose_types, storage_types, name }) => {
21
+ try {
22
+ const params = {};
23
+ if (purpose_types)
24
+ params.purposeTypes = purpose_types.join(",");
25
+ if (storage_types)
26
+ params.storageTypes = storage_types.join(",");
27
+ if (name)
28
+ params.names = name;
29
+ const result = await client.getV2("data-protect/external-targets", params);
30
+ return toolResult(JSON.stringify(result, null, 2));
31
+ }
32
+ catch (error) {
33
+ return toolResult(`Error listing external targets: ${error}`, true);
34
+ }
35
+ });
36
+ // ── Get External Target ──────────────────────────────────────────────
37
+ server.tool("get_external_target", "Get details of a specific external target by ID.", {
38
+ id: z.number().describe("External target ID"),
39
+ }, async ({ id }) => {
40
+ try {
41
+ const result = await client.getV2(`data-protect/external-targets/${id}`);
42
+ return toolResult(JSON.stringify(result, null, 2));
43
+ }
44
+ catch (error) {
45
+ return toolResult(`Error fetching external target ${id}: ${error}`, true);
46
+ }
47
+ });
48
+ // ── Create External Target (AWS S3) ──────────────────────────────────
49
+ server.tool("create_external_target_aws", "Register a new AWS S3 external target for archival or tiering. Requires an existing IAM role or access key credentials.", {
50
+ name: z.string().describe("Name for the external target"),
51
+ purpose_type: z
52
+ .enum(["Archival", "Tiering"])
53
+ .describe("Purpose: Archival (long-term retention) or Tiering (automated cold data movement)"),
54
+ bucket_name: z.string().describe("S3 bucket name"),
55
+ region: z.string().describe("AWS region (e.g. us-east-1)"),
56
+ storage_class: z
57
+ .enum([
58
+ "AmazonS3Standard",
59
+ "AmazonS3StandardIA",
60
+ "AmazonS3OneZoneIA",
61
+ "AmazonS3IntelligentTiering",
62
+ "AmazonS3Glacier",
63
+ "AmazonS3GlacierDeepArchive",
64
+ "AmazonS3GlacierIR",
65
+ ])
66
+ .optional()
67
+ .default("AmazonS3Standard")
68
+ .describe("S3 storage class"),
69
+ access_key_id: z.string().optional().describe("AWS access key ID (leave blank if using IAM role)"),
70
+ secret_access_key: z.string().optional().describe("AWS secret access key"),
71
+ iam_role_arn: z.string().optional().describe("IAM role ARN (alternative to access key)"),
72
+ }, async ({ name, purpose_type, bucket_name, region, storage_class, access_key_id, secret_access_key, iam_role_arn }) => {
73
+ try {
74
+ const body = {
75
+ name,
76
+ purposeType: purpose_type,
77
+ storageType: "AWS",
78
+ awsParams: {
79
+ bucketName: bucket_name,
80
+ region,
81
+ storageClass: storage_class,
82
+ ...(iam_role_arn ? { iamRoleArn: iam_role_arn } : {}),
83
+ ...(access_key_id ? { accessKeyId: access_key_id } : {}),
84
+ ...(secret_access_key ? { secretAccessKey: secret_access_key } : {}),
85
+ },
86
+ };
87
+ const result = await client.postV2("data-protect/external-targets", body);
88
+ return toolResult(JSON.stringify(result, null, 2));
89
+ }
90
+ catch (error) {
91
+ return toolResult(`Error creating AWS external target: ${error}`, true);
92
+ }
93
+ });
94
+ // ── Create External Target (Azure) ───────────────────────────────────
95
+ server.tool("create_external_target_azure", "Register a new Azure Blob Storage external target for archival or tiering.", {
96
+ name: z.string().describe("Name for the external target"),
97
+ purpose_type: z
98
+ .enum(["Archival", "Tiering"])
99
+ .describe("Purpose: Archival or Tiering"),
100
+ container_name: z.string().describe("Azure Blob container name"),
101
+ storage_account_name: z.string().describe("Azure storage account name"),
102
+ storage_account_key: z.string().describe("Azure storage account access key"),
103
+ storage_class: z
104
+ .enum(["AzureHotBlob", "AzureCoolBlob", "AzureColdBlob", "AzureArchiveBlob"])
105
+ .optional()
106
+ .default("AzureCoolBlob")
107
+ .describe("Azure storage tier"),
108
+ }, async ({ name, purpose_type, container_name, storage_account_name, storage_account_key, storage_class }) => {
109
+ try {
110
+ const body = {
111
+ name,
112
+ purposeType: purpose_type,
113
+ storageType: "Azure",
114
+ azureParams: {
115
+ containerName: container_name,
116
+ storageAccountName: storage_account_name,
117
+ storageAccountKey: storage_account_key,
118
+ storageClass: storage_class,
119
+ },
120
+ };
121
+ const result = await client.postV2("data-protect/external-targets", body);
122
+ return toolResult(JSON.stringify(result, null, 2));
123
+ }
124
+ catch (error) {
125
+ return toolResult(`Error creating Azure external target: ${error}`, true);
126
+ }
127
+ });
128
+ // ── Delete External Target ───────────────────────────────────────────
129
+ server.tool("delete_external_target", "Delete a registered external target. Use force_delete=true only if the target has no active archival jobs.", {
130
+ id: z.number().describe("External target ID to delete"),
131
+ force_delete: z
132
+ .boolean()
133
+ .optional()
134
+ .default(false)
135
+ .describe("Force delete even if the target has associated data"),
136
+ }, async ({ id, force_delete }) => {
137
+ try {
138
+ await client.deleteV2(`data-protect/external-targets/${id}?forceDelete=${force_delete}`);
139
+ return toolResult(`External target ${id} deleted successfully.`);
140
+ }
141
+ catch (error) {
142
+ return toolResult(`Error deleting external target ${id}: ${error}`, true);
143
+ }
144
+ });
145
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Key Management System (KMS) tools — list, add, update, and delete KMS
3
+ * configurations on the cluster. Cohesity supports an internal KMS plus
4
+ * external types: AwsKms, KmipKms, IbmKms, GcpKms.
5
+ *
6
+ * Endpoints (verified against cluster_v2_api.yaml):
7
+ * GET /v2/kms
8
+ * POST /v2/kms — KmsConfigurationCreateParams
9
+ * PUT /v2/kms/{id} — KmsConfigurationAddUpdateParams
10
+ * DELETE /v2/kms/{id}
11
+ */
12
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
13
+ import { CohesityClient } from "../cohesity-client.js";
14
+ export declare function registerKmsTools(server: McpServer, client: CohesityClient): void;
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Key Management System (KMS) tools — list, add, update, and delete KMS
3
+ * configurations on the cluster. Cohesity supports an internal KMS plus
4
+ * external types: AwsKms, KmipKms, IbmKms, GcpKms.
5
+ *
6
+ * Endpoints (verified against cluster_v2_api.yaml):
7
+ * GET /v2/kms
8
+ * POST /v2/kms — KmsConfigurationCreateParams
9
+ * PUT /v2/kms/{id} — KmsConfigurationAddUpdateParams
10
+ * DELETE /v2/kms/{id}
11
+ */
12
+ import { z } from "zod";
13
+ const reply = (text, isError = false) => ({
14
+ content: [{ type: "text", text }],
15
+ isError,
16
+ });
17
+ export function registerKmsTools(server, client) {
18
+ // ── List KMS Configurations ────────────────────────────────────────────
19
+ server.tool("list_kms_configurations", "List Key Management Systems (KMS) configured on the cluster. The InternalKms is always present; external types include AwsKms, KmipKms, IbmKms, and GcpKms.", {
20
+ ids: z.array(z.number()).optional().describe("Restrict to these KMS IDs"),
21
+ include_rpaas_kms: z
22
+ .boolean()
23
+ .optional()
24
+ .describe("Include KMS configured by FortKnox / RPaaS"),
25
+ }, async (args) => {
26
+ try {
27
+ const qp = {};
28
+ if (args.ids?.length)
29
+ qp.ids = args.ids.join(",");
30
+ if (args.include_rpaas_kms !== undefined)
31
+ qp.includeRpaasKms = String(args.include_rpaas_kms);
32
+ const data = await client.getV2("kms", qp);
33
+ return reply(JSON.stringify(data, null, 2));
34
+ }
35
+ catch (err) {
36
+ return reply(`Error listing KMS configurations: ${err}`, true);
37
+ }
38
+ });
39
+ // ── Add AWS KMS ────────────────────────────────────────────────────────
40
+ // KmsConfigurationCreateParams { type: "AwsKms", awsKmsParams: { ... }, name, ownershipContext?, usageType? }
41
+ server.tool("add_aws_kms", "Register an AWS KMS as an encryption key source. Used to encrypt storage domains or external targets.", {
42
+ name: z.string().describe("Display name for this KMS configuration"),
43
+ access_key_id: z.string().describe("AWS access key ID"),
44
+ secret_access_key: z.string().describe("AWS secret access key"),
45
+ region: z.string().describe("AWS region (e.g., us-east-1)"),
46
+ cmk_arn: z
47
+ .string()
48
+ .describe("ARN of the AWS KMS Customer Master Key (CMK) to use"),
49
+ storage_domain_ids: z
50
+ .array(z.number())
51
+ .optional()
52
+ .describe("Storage domain IDs to assign this KMS to (cannot change after assignment)"),
53
+ external_target_ids: z
54
+ .array(z.number())
55
+ .optional()
56
+ .describe("External target IDs to assign this KMS to (cannot change after assignment)"),
57
+ }, async (args) => {
58
+ try {
59
+ const body = {
60
+ name: args.name,
61
+ type: "AwsKms",
62
+ awsKmsParams: {
63
+ accessKeyId: args.access_key_id,
64
+ secretAccessKey: args.secret_access_key,
65
+ region: args.region,
66
+ cmkArn: args.cmk_arn,
67
+ },
68
+ ...(args.storage_domain_ids?.length && { storageDomainIds: args.storage_domain_ids }),
69
+ ...(args.external_target_ids?.length && { externalTargetIds: args.external_target_ids }),
70
+ };
71
+ const data = await client.postV2("kms", body);
72
+ return reply(`AWS KMS '${args.name}' added.\n${JSON.stringify(data, null, 2)}`);
73
+ }
74
+ catch (err) {
75
+ return reply(`Error adding AWS KMS: ${err}`, true);
76
+ }
77
+ });
78
+ // ── Add KMIP KMS ───────────────────────────────────────────────────────
79
+ server.tool("add_kmip_kms", "Register a KMIP-compliant KMS (e.g., Thales, Fortanix). Provide endpoint, port, CA cert, and client certs.", {
80
+ name: z.string().describe("Display name for this KMS configuration"),
81
+ server_ip: z.string().describe("KMIP server hostname or IP"),
82
+ server_port: z.number().describe("KMIP server port (typically 5696)"),
83
+ ca_certificate: z.string().describe("CA certificate (PEM)"),
84
+ client_certificate: z.string().describe("Client certificate (PEM)"),
85
+ client_key: z.string().describe("Client private key (PEM)"),
86
+ kmip_protocol_version: z
87
+ .enum(["v_1_0", "v_1_1", "v_1_2", "v_1_3", "v_1_4"])
88
+ .default("v_1_2")
89
+ .describe("KMIP protocol version to negotiate"),
90
+ storage_domain_ids: z.array(z.number()).optional(),
91
+ external_target_ids: z.array(z.number()).optional(),
92
+ }, async (args) => {
93
+ try {
94
+ const body = {
95
+ name: args.name,
96
+ type: "KmipKms",
97
+ kmipKmsParams: {
98
+ serverIp: args.server_ip,
99
+ serverPort: args.server_port,
100
+ caCertificate: args.ca_certificate,
101
+ clientCertificate: args.client_certificate,
102
+ clientKey: args.client_key,
103
+ kmipProtocolVersion: args.kmip_protocol_version,
104
+ },
105
+ ...(args.storage_domain_ids?.length && { storageDomainIds: args.storage_domain_ids }),
106
+ ...(args.external_target_ids?.length && { externalTargetIds: args.external_target_ids }),
107
+ };
108
+ const data = await client.postV2("kms", body);
109
+ return reply(`KMIP KMS '${args.name}' added.\n${JSON.stringify(data, null, 2)}`);
110
+ }
111
+ catch (err) {
112
+ return reply(`Error adding KMIP KMS: ${err}`, true);
113
+ }
114
+ });
115
+ // ── Update KMS ─────────────────────────────────────────────────────────
116
+ // KmsConfigurationAddUpdateParams — adds/changes assignments without
117
+ // re-supplying type-specific provider params.
118
+ server.tool("update_kms", "Update an existing KMS configuration — assign additional storage domains or external targets, or rename it. Provider credentials cannot be changed via this method; delete and re-add to rotate credentials.", {
119
+ id: z.number().describe("KMS configuration ID"),
120
+ name: z.string().describe("Display name"),
121
+ storage_domain_ids: z
122
+ .array(z.number())
123
+ .optional()
124
+ .describe("Replacement list of storage domain IDs"),
125
+ external_target_ids: z
126
+ .array(z.number())
127
+ .optional()
128
+ .describe("Replacement list of external target IDs"),
129
+ }, async (args) => {
130
+ try {
131
+ const body = {
132
+ name: args.name,
133
+ ...(args.storage_domain_ids && { storageDomainIds: args.storage_domain_ids }),
134
+ ...(args.external_target_ids && { externalTargetIds: args.external_target_ids }),
135
+ };
136
+ const data = await client.putV2(`kms/${args.id}`, body);
137
+ return reply(`KMS ${args.id} updated.\n${JSON.stringify(data, null, 2)}`);
138
+ }
139
+ catch (err) {
140
+ return reply(`Error updating KMS: ${err}`, true);
141
+ }
142
+ });
143
+ // ── Delete KMS ─────────────────────────────────────────────────────────
144
+ server.tool("delete_kms", "Delete a KMS configuration. The internal KMS (id 0) cannot be deleted.", { id: z.number().describe("KMS ID to delete") }, async (args) => {
145
+ try {
146
+ await client.deleteV2(`kms/${args.id}`);
147
+ return reply(`KMS ${args.id} deleted.`);
148
+ }
149
+ catch (err) {
150
+ return reply(`Error deleting KMS: ${err}`, true);
151
+ }
152
+ });
153
+ // ── Get External Target Encryption Key ─────────────────────────────────
154
+ // GET /v2/data-protect/external-targets/{id}/encryption-key
155
+ server.tool("get_external_target_encryption_key", "Get the encryption key associated with an external target (e.g., AWS S3 bucket, Azure container). Used to verify which KMS is providing keys for archived data.", { external_target_id: z.number().describe("External target ID") }, async (args) => {
156
+ try {
157
+ const data = await client.getV2(`data-protect/external-targets/${args.external_target_id}/encryption-key`);
158
+ return reply(JSON.stringify(data, null, 2));
159
+ }
160
+ catch (err) {
161
+ return reply(`Error fetching external target encryption key: ${err}`, true);
162
+ }
163
+ });
164
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { CohesityClient } from "../cohesity-client.js";
3
+ export declare function registerNotificationTools(server: McpServer, client: CohesityClient): void;