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,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cohesity REST API client supporting V1 and V2 endpoints
|
|
3
|
+
* with automatic bearer token authentication and retry on expiry.
|
|
4
|
+
*/
|
|
5
|
+
export class CohesityClient {
|
|
6
|
+
v2Base;
|
|
7
|
+
v1Base;
|
|
8
|
+
cfg;
|
|
9
|
+
bearer = null;
|
|
10
|
+
constructor(cfg) {
|
|
11
|
+
this.cfg = cfg;
|
|
12
|
+
this.v2Base = `https://${cfg.cluster}/v2`;
|
|
13
|
+
this.v1Base = `https://${cfg.cluster}/irisservices/api/v1/public`;
|
|
14
|
+
if (cfg.allowSelfSigned) {
|
|
15
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/** Obtain a bearer token from the V2 access-tokens endpoint. */
|
|
19
|
+
async authenticate() {
|
|
20
|
+
const resp = await fetch(`${this.v2Base}/access-tokens`, {
|
|
21
|
+
method: "POST",
|
|
22
|
+
headers: { "Content-Type": "application/json" },
|
|
23
|
+
body: JSON.stringify({
|
|
24
|
+
username: this.cfg.username,
|
|
25
|
+
password: this.cfg.password,
|
|
26
|
+
domain: this.cfg.domain,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
if (!resp.ok) {
|
|
30
|
+
throw new Error(`Authentication failed (${resp.status}): ${await resp.text()}`);
|
|
31
|
+
}
|
|
32
|
+
const data = (await resp.json());
|
|
33
|
+
const tok = data.accessToken ?? data.token;
|
|
34
|
+
if (!tok)
|
|
35
|
+
throw new Error("No access token in authentication response");
|
|
36
|
+
this.bearer = tok;
|
|
37
|
+
}
|
|
38
|
+
/** Return current token, authenticating first if needed. */
|
|
39
|
+
async token() {
|
|
40
|
+
if (!this.bearer)
|
|
41
|
+
await this.authenticate();
|
|
42
|
+
return this.bearer;
|
|
43
|
+
}
|
|
44
|
+
/** Build headers for an authenticated request. */
|
|
45
|
+
authHeaders(tok) {
|
|
46
|
+
return {
|
|
47
|
+
Authorization: `Bearer ${tok}`,
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
Accept: "application/json",
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** Parse a fetch Response into JSON, text, or null (for 204). */
|
|
53
|
+
async parseResponse(resp) {
|
|
54
|
+
if (resp.status === 204)
|
|
55
|
+
return null;
|
|
56
|
+
const ct = resp.headers.get("content-type") ?? "";
|
|
57
|
+
return ct.includes("application/json") ? resp.json() : resp.text();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Core HTTP method. Builds URL with query params, attaches auth,
|
|
61
|
+
* and retries once on 401 (token expiry).
|
|
62
|
+
*/
|
|
63
|
+
async http(method, url, body, params) {
|
|
64
|
+
const target = new URL(url);
|
|
65
|
+
if (params) {
|
|
66
|
+
for (const [k, v] of Object.entries(params))
|
|
67
|
+
target.searchParams.set(k, v);
|
|
68
|
+
}
|
|
69
|
+
const buildOpts = (tok) => {
|
|
70
|
+
const opts = { method, headers: this.authHeaders(tok) };
|
|
71
|
+
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
72
|
+
opts.body = JSON.stringify(body);
|
|
73
|
+
}
|
|
74
|
+
return opts;
|
|
75
|
+
};
|
|
76
|
+
let tok = await this.token();
|
|
77
|
+
let resp = await fetch(target.toString(), buildOpts(tok));
|
|
78
|
+
// Retry once on 401 — token may have expired
|
|
79
|
+
if (resp.status === 401) {
|
|
80
|
+
this.bearer = null;
|
|
81
|
+
tok = await this.token();
|
|
82
|
+
resp = await fetch(target.toString(), buildOpts(tok));
|
|
83
|
+
}
|
|
84
|
+
if (!resp.ok) {
|
|
85
|
+
throw new Error(`Cohesity API ${method} ${target.pathname} failed (${resp.status}): ${await resp.text()}`);
|
|
86
|
+
}
|
|
87
|
+
return this.parseResponse(resp);
|
|
88
|
+
}
|
|
89
|
+
// ── V2 convenience methods ──────────────────────────────────────────
|
|
90
|
+
getV2(path, params) {
|
|
91
|
+
return this.http("GET", `${this.v2Base}/${path}`, undefined, params);
|
|
92
|
+
}
|
|
93
|
+
postV2(path, body, params) {
|
|
94
|
+
return this.http("POST", `${this.v2Base}/${path}`, body, params);
|
|
95
|
+
}
|
|
96
|
+
putV2(path, body) {
|
|
97
|
+
return this.http("PUT", `${this.v2Base}/${path}`, body);
|
|
98
|
+
}
|
|
99
|
+
patchV2(path, body) {
|
|
100
|
+
return this.http("PATCH", `${this.v2Base}/${path}`, body);
|
|
101
|
+
}
|
|
102
|
+
deleteV2(path) {
|
|
103
|
+
return this.http("DELETE", `${this.v2Base}/${path}`);
|
|
104
|
+
}
|
|
105
|
+
// ── V1 convenience methods ──────────────────────────────────────────
|
|
106
|
+
getV1(path, params) {
|
|
107
|
+
return this.http("GET", `${this.v1Base}/${path}`, undefined, params);
|
|
108
|
+
}
|
|
109
|
+
postV1(path, body) {
|
|
110
|
+
return this.http("POST", `${this.v1Base}/${path}`, body);
|
|
111
|
+
}
|
|
112
|
+
putV1(path, body) {
|
|
113
|
+
return this.http("PUT", `${this.v1Base}/${path}`, body);
|
|
114
|
+
}
|
|
115
|
+
// ── Source refresh ──────────────────────────────────────────────────
|
|
116
|
+
/**
|
|
117
|
+
* Refresh every registered protection source in parallel.
|
|
118
|
+
* Called automatically before CRUD operations to ensure current inventory.
|
|
119
|
+
*/
|
|
120
|
+
async refreshAllSources() {
|
|
121
|
+
const data = (await this.getV2("data-protect/sources/registrations"));
|
|
122
|
+
const ids = (data.registrations ?? []).map((r) => r.id).filter(Boolean);
|
|
123
|
+
await Promise.allSettled(ids.map((id) => this.postV2(`data-protect/sources/${id}/refresh`, {})));
|
|
124
|
+
return { refreshed: ids.length, sourceIds: ids };
|
|
125
|
+
}
|
|
126
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* mcp-cohesity — MCP server for Cohesity DataProtect
|
|
4
|
+
* Entry point: validates configuration, wires up tool modules, launches STDIO transport.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { homedir } from "node:os";
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
11
|
+
import { CohesityClient } from "./cohesity-client.js";
|
|
12
|
+
import { registerClusterTools } from "./tools/cluster.js";
|
|
13
|
+
import { registerProtectionTools } from "./tools/protection.js";
|
|
14
|
+
import { registerRunsTools } from "./tools/runs.js";
|
|
15
|
+
import { registerSourcesTools } from "./tools/sources.js";
|
|
16
|
+
import { registerSourceRegistrationTools } from "./tools/source-registration.js";
|
|
17
|
+
import { registerAuditLogTools } from "./tools/audit-logs.js";
|
|
18
|
+
import { registerClusterReportTools } from "./tools/cluster-reports.js";
|
|
19
|
+
import { registerRunActionTools } from "./tools/run-actions.js";
|
|
20
|
+
import { registerUserTools } from "./tools/users.js";
|
|
21
|
+
import { registerRoleTools } from "./tools/roles.js";
|
|
22
|
+
import { registerActiveDirectoryTools } from "./tools/active-directory.js";
|
|
23
|
+
import { registerAntivirusTools } from "./tools/antivirus.js";
|
|
24
|
+
import { registerKmsTools } from "./tools/kms.js";
|
|
25
|
+
import { registerCloneTools } from "./tools/clones.js";
|
|
26
|
+
import { registerRecoveryTools } from "./tools/recovery.js";
|
|
27
|
+
import { registerAlertsTools } from "./tools/alerts.js";
|
|
28
|
+
import { registerStorageTools } from "./tools/storage.js";
|
|
29
|
+
import { registerRestoreTools } from "./tools/restore.js";
|
|
30
|
+
import { registerReportTools } from "./tools/reports.js";
|
|
31
|
+
import { registerExternalTargetTools } from "./tools/external-targets.js";
|
|
32
|
+
import { registerTieringTools } from "./tools/tiering.js";
|
|
33
|
+
import { registerNotificationTools } from "./tools/notifications.js";
|
|
34
|
+
import { registerStatsTools } from "./tools/stats.js";
|
|
35
|
+
/**
|
|
36
|
+
* Load credentials from an external JSON file if one is provided or found
|
|
37
|
+
* at a default location. Reading is best-effort — if the file is missing,
|
|
38
|
+
* unreadable, or malformed we just return an empty object and let env
|
|
39
|
+
* vars do the work.
|
|
40
|
+
*
|
|
41
|
+
* Resolution order:
|
|
42
|
+
* 1. $COHESITY_CONFIG_FILE if set (absolute or ~/-expanded path)
|
|
43
|
+
* 2. ~/.cohesity-mcp/config.json (if it exists)
|
|
44
|
+
*/
|
|
45
|
+
function loadConfigFile() {
|
|
46
|
+
const home = homedir();
|
|
47
|
+
const expand = (p) => (p.startsWith("~") ? resolve(home, p.slice(p.startsWith("~/") ? 2 : 1)) : p);
|
|
48
|
+
const explicit = process.env.COHESITY_CONFIG_FILE;
|
|
49
|
+
const candidates = [
|
|
50
|
+
explicit && expand(explicit),
|
|
51
|
+
resolve(home, ".cohesity-mcp", "config.json"),
|
|
52
|
+
].filter((p) => !!p);
|
|
53
|
+
for (const path of candidates) {
|
|
54
|
+
try {
|
|
55
|
+
const data = readFileSync(path, "utf8");
|
|
56
|
+
const parsed = JSON.parse(data);
|
|
57
|
+
console.error(`Loaded Cohesity credentials from ${path}`);
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
// ENOENT (file missing) at the default path is silent; explicit
|
|
62
|
+
// file path being missing or malformed is a hard error.
|
|
63
|
+
const e = err;
|
|
64
|
+
if (path === explicit) {
|
|
65
|
+
console.error(`Error reading COHESITY_CONFIG_FILE=${path}: ${e.message}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
if (e.code !== "ENOENT") {
|
|
69
|
+
console.error(`Error reading ${path}: ${e.message}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return {};
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Merge external config file and process env vars, with env vars taking
|
|
77
|
+
* precedence so users can override individual fields without editing the
|
|
78
|
+
* file. Returns the typed config object or exits with a clear message
|
|
79
|
+
* if required fields are still missing.
|
|
80
|
+
*/
|
|
81
|
+
function loadConfig() {
|
|
82
|
+
const file = loadConfigFile();
|
|
83
|
+
const cluster = process.env.COHESITY_CLUSTER ?? file.cluster;
|
|
84
|
+
const username = process.env.COHESITY_USERNAME ?? file.username;
|
|
85
|
+
const password = process.env.COHESITY_PASSWORD ?? file.password;
|
|
86
|
+
const domain = process.env.COHESITY_DOMAIN ?? file.domain ?? "LOCAL";
|
|
87
|
+
// allowSelfSigned defaults to true unless explicitly disabled
|
|
88
|
+
const envAllow = process.env.COHESITY_ALLOW_SELF_SIGNED;
|
|
89
|
+
const allowSelfSigned = envAllow !== undefined ? envAllow !== "false" : file.allowSelfSigned !== false;
|
|
90
|
+
const missing = [];
|
|
91
|
+
if (!cluster)
|
|
92
|
+
missing.push("cluster (COHESITY_CLUSTER)");
|
|
93
|
+
if (!username)
|
|
94
|
+
missing.push("username (COHESITY_USERNAME)");
|
|
95
|
+
if (!password)
|
|
96
|
+
missing.push("password (COHESITY_PASSWORD)");
|
|
97
|
+
if (missing.length > 0) {
|
|
98
|
+
console.error(`Missing required Cohesity credentials: ${missing.join(", ")}.\n` +
|
|
99
|
+
`Provide them via env vars, or via a JSON file pointed to by COHESITY_CONFIG_FILE\n` +
|
|
100
|
+
`(or the default ~/.cohesity-mcp/config.json). See README for details.`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
return { cluster: cluster, username: username, password: password, domain, allowSelfSigned };
|
|
104
|
+
}
|
|
105
|
+
/** Wire every tool module to the MCP server instance. */
|
|
106
|
+
function wireTools(server, client) {
|
|
107
|
+
const registrations = [
|
|
108
|
+
registerClusterTools,
|
|
109
|
+
registerProtectionTools,
|
|
110
|
+
registerRunsTools,
|
|
111
|
+
registerSourcesTools,
|
|
112
|
+
registerSourceRegistrationTools,
|
|
113
|
+
registerRecoveryTools,
|
|
114
|
+
registerAlertsTools,
|
|
115
|
+
registerStorageTools,
|
|
116
|
+
registerRestoreTools,
|
|
117
|
+
registerReportTools,
|
|
118
|
+
registerExternalTargetTools,
|
|
119
|
+
registerTieringTools,
|
|
120
|
+
registerNotificationTools,
|
|
121
|
+
registerStatsTools,
|
|
122
|
+
registerAuditLogTools,
|
|
123
|
+
registerClusterReportTools,
|
|
124
|
+
registerRunActionTools,
|
|
125
|
+
registerUserTools,
|
|
126
|
+
registerRoleTools,
|
|
127
|
+
registerActiveDirectoryTools,
|
|
128
|
+
registerAntivirusTools,
|
|
129
|
+
registerKmsTools,
|
|
130
|
+
registerCloneTools,
|
|
131
|
+
];
|
|
132
|
+
for (const register of registrations) {
|
|
133
|
+
register(server, client);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Bootstrap the MCP server and begin listening on STDIO. */
|
|
137
|
+
async function bootstrap() {
|
|
138
|
+
const cfg = loadConfig();
|
|
139
|
+
const client = new CohesityClient(cfg);
|
|
140
|
+
const mcp = new McpServer({ name: "cohesity", version: "2.0.0" });
|
|
141
|
+
wireTools(mcp, client);
|
|
142
|
+
const transport = new StdioServerTransport();
|
|
143
|
+
await mcp.connect(transport);
|
|
144
|
+
console.error("Cohesity MCP server running");
|
|
145
|
+
}
|
|
146
|
+
bootstrap().catch((err) => {
|
|
147
|
+
console.error("Fatal error:", err);
|
|
148
|
+
process.exit(1);
|
|
149
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active Directory management tools — join, list, and remove AD domains from
|
|
3
|
+
* the Cohesity cluster.
|
|
4
|
+
*
|
|
5
|
+
* GET /v2/active-directories — list
|
|
6
|
+
* POST /v2/active-directories — CreateActiveDirectoryRequest
|
|
7
|
+
* DELETE /v2/active-directories/{id} — leave domain
|
|
8
|
+
*/
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
11
|
+
export declare function registerActiveDirectoryTools(server: McpServer, client: CohesityClient): void;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Active Directory management tools — join, list, and remove AD domains from
|
|
3
|
+
* the Cohesity cluster.
|
|
4
|
+
*
|
|
5
|
+
* GET /v2/active-directories — list
|
|
6
|
+
* POST /v2/active-directories — CreateActiveDirectoryRequest
|
|
7
|
+
* DELETE /v2/active-directories/{id} — leave domain
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
const reply = (text, isError = false) => ({
|
|
11
|
+
content: [{ type: "text", text }],
|
|
12
|
+
isError,
|
|
13
|
+
});
|
|
14
|
+
export function registerActiveDirectoryTools(server, client) {
|
|
15
|
+
// ── List Active Directories ────────────────────────────────────────────
|
|
16
|
+
server.tool("list_active_directories", "List Active Directory domains joined to the Cohesity cluster", {
|
|
17
|
+
domain_names: z.array(z.string()).optional().describe("Filter by domain names"),
|
|
18
|
+
ids: z.array(z.number()).optional().describe("Filter by AD IDs"),
|
|
19
|
+
tenant_ids: z.array(z.string()).optional().describe("Restrict to these tenant IDs"),
|
|
20
|
+
include_tenants: z.boolean().optional().describe("Include nested-tenant ADs"),
|
|
21
|
+
}, async (args) => {
|
|
22
|
+
try {
|
|
23
|
+
const qp = {};
|
|
24
|
+
if (args.domain_names?.length)
|
|
25
|
+
qp.domainNames = args.domain_names.join(",");
|
|
26
|
+
if (args.ids?.length)
|
|
27
|
+
qp.ids = args.ids.join(",");
|
|
28
|
+
if (args.tenant_ids?.length)
|
|
29
|
+
qp.tenantIds = args.tenant_ids.join(",");
|
|
30
|
+
if (args.include_tenants !== undefined)
|
|
31
|
+
qp.includeTenants = String(args.include_tenants);
|
|
32
|
+
const data = await client.getV2("active-directories", qp);
|
|
33
|
+
return reply(JSON.stringify(data, null, 2));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
return reply(`Error listing active directories: ${err}`, true);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
// ── Join Active Directory ──────────────────────────────────────────────
|
|
40
|
+
// CreateActiveDirectoryRequest requires { domainName, activeDirectoryAdminParams }.
|
|
41
|
+
server.tool("join_active_directory", "Join the cluster to an Active Directory domain using admin credentials with rights to add a computer to the domain.", {
|
|
42
|
+
domain_name: z
|
|
43
|
+
.string()
|
|
44
|
+
.describe("AD domain FQDN (e.g. corp.example.com)"),
|
|
45
|
+
admin_username: z
|
|
46
|
+
.string()
|
|
47
|
+
.describe("Username of an AD admin able to join machines to the domain"),
|
|
48
|
+
admin_password: z.string().describe("Password for the admin account"),
|
|
49
|
+
overwrite_machine_accounts: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Overwrite existing computer accounts in the domain if needed"),
|
|
53
|
+
}, async (args) => {
|
|
54
|
+
try {
|
|
55
|
+
const body = {
|
|
56
|
+
domainName: args.domain_name,
|
|
57
|
+
activeDirectoryAdminParams: {
|
|
58
|
+
username: args.admin_username,
|
|
59
|
+
password: args.admin_password,
|
|
60
|
+
},
|
|
61
|
+
...(args.overwrite_machine_accounts !== undefined && {
|
|
62
|
+
overwriteMachineAccounts: args.overwrite_machine_accounts,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
65
|
+
const result = await client.postV2("active-directories", body);
|
|
66
|
+
return reply(`Joined AD ${args.domain_name}.\n${JSON.stringify(result, null, 2)}`);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return reply(`Error joining Active Directory: ${err}`, true);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
// ── Leave Active Directory ─────────────────────────────────────────────
|
|
73
|
+
server.tool("leave_active_directory", "Remove an Active Directory from the Cohesity cluster", { id: z.number().describe("AD ID to remove") }, async (args) => {
|
|
74
|
+
try {
|
|
75
|
+
await client.deleteV2(`active-directories/${args.id}`);
|
|
76
|
+
return reply(`Active Directory ${args.id} removed.`);
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
return reply(`Error removing AD: ${err}`, true);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert management tools — list and resolve Cohesity cluster alerts.
|
|
3
|
+
*/
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
6
|
+
export declare function registerAlertsTools(server: McpServer, client: CohesityClient): void;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alert management tools — list and resolve Cohesity cluster alerts.
|
|
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
|
+
/** Valid alert state values accepted by the Cohesity V2 API. */
|
|
11
|
+
const ALERT_STATES = ["kOpen", "kSuppressed", "kResolved", "kNote"];
|
|
12
|
+
/** Valid severity levels for alert filtering. */
|
|
13
|
+
const ALERT_SEVERITIES = ["kCritical", "kWarning", "kInfo"];
|
|
14
|
+
/** Full set of alert category identifiers supported by the V2 alerts endpoint. */
|
|
15
|
+
const ALERT_CATEGORIES = [
|
|
16
|
+
"kDisk",
|
|
17
|
+
"kNode",
|
|
18
|
+
"kCluster",
|
|
19
|
+
"kNodeHealth",
|
|
20
|
+
"kClusterHealth",
|
|
21
|
+
"kBackupRestore",
|
|
22
|
+
"kEncryption",
|
|
23
|
+
"kArchivalRestore",
|
|
24
|
+
"kRemoteReplication",
|
|
25
|
+
"kQuota",
|
|
26
|
+
"kLicense",
|
|
27
|
+
"kHeliosProActiveWellness",
|
|
28
|
+
"kHeliosAnalyticsJobs",
|
|
29
|
+
"kHeliosSignatureJobs",
|
|
30
|
+
"kSecurity",
|
|
31
|
+
"kAppsInfra",
|
|
32
|
+
"kAntivirus",
|
|
33
|
+
"kArchivalCopy",
|
|
34
|
+
];
|
|
35
|
+
export function registerAlertsTools(server, client) {
|
|
36
|
+
// ── List Alerts ────────────────────────────────────────────────────────
|
|
37
|
+
server.tool("list_alerts", "List Cohesity cluster alerts with severity, description, and resolution status", {
|
|
38
|
+
alert_states: z
|
|
39
|
+
.array(z.enum(ALERT_STATES))
|
|
40
|
+
.optional()
|
|
41
|
+
.default(["kOpen"])
|
|
42
|
+
.describe("Filter by alert state (default: open alerts only)"),
|
|
43
|
+
alert_severities: z
|
|
44
|
+
.array(z.enum(ALERT_SEVERITIES))
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Filter by severity level"),
|
|
47
|
+
alert_categories: z
|
|
48
|
+
.array(z.enum(ALERT_CATEGORIES))
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Filter by alert category"),
|
|
51
|
+
start_date_usecs: z
|
|
52
|
+
.number()
|
|
53
|
+
.optional()
|
|
54
|
+
.describe("Only return alerts created after this timestamp (microseconds)"),
|
|
55
|
+
end_date_usecs: z
|
|
56
|
+
.number()
|
|
57
|
+
.optional()
|
|
58
|
+
.describe("Only return alerts created before this timestamp (microseconds)"),
|
|
59
|
+
max_results: z
|
|
60
|
+
.number()
|
|
61
|
+
.optional()
|
|
62
|
+
.default(50)
|
|
63
|
+
.describe("Cap on the number of alerts returned"),
|
|
64
|
+
}, async (args) => {
|
|
65
|
+
try {
|
|
66
|
+
const qp = { maxAlerts: String(args.max_results) };
|
|
67
|
+
if (args.alert_states)
|
|
68
|
+
qp.alertStates = args.alert_states.join(",");
|
|
69
|
+
if (args.alert_severities)
|
|
70
|
+
qp.alertSeverityList = args.alert_severities.join(",");
|
|
71
|
+
if (args.alert_categories)
|
|
72
|
+
qp.alertCategoryList = args.alert_categories.join(",");
|
|
73
|
+
if (args.start_date_usecs !== undefined)
|
|
74
|
+
qp.startDateUsecs = String(args.start_date_usecs);
|
|
75
|
+
if (args.end_date_usecs !== undefined)
|
|
76
|
+
qp.endDateUsecs = String(args.end_date_usecs);
|
|
77
|
+
const data = await client.getV2("alerts", qp);
|
|
78
|
+
return reply(JSON.stringify(data, null, 2));
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
return reply(`Error fetching alerts: ${err}`, true);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// ── Resolve Alert ──────────────────────────────────────────────────────
|
|
85
|
+
server.tool("resolve_alert", "Mark a Cohesity alert as resolved", {
|
|
86
|
+
alert_id: z.string().describe("Alert ID to resolve"),
|
|
87
|
+
resolution_summary: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe("Short summary of how the alert was resolved"),
|
|
91
|
+
resolution_details: z
|
|
92
|
+
.string()
|
|
93
|
+
.optional()
|
|
94
|
+
.describe("Detailed description of the resolution actions taken"),
|
|
95
|
+
}, async (args) => {
|
|
96
|
+
try {
|
|
97
|
+
const payload = {
|
|
98
|
+
alertIdList: [args.alert_id],
|
|
99
|
+
resolutionDetails: {
|
|
100
|
+
resolutionSummary: args.resolution_summary ?? "Resolved via MCP",
|
|
101
|
+
resolutionDetails: args.resolution_details ?? "",
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
const data = await client.postV2("alerts/resolutions", payload);
|
|
105
|
+
return reply(`Alert ${args.alert_id} resolved.\n${JSON.stringify(data, null, 2)}`);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
return reply(`Error resolving alert ${args.alert_id}: ${err}`, true);
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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 { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
23
|
+
import { CohesityClient } from "../cohesity-client.js";
|
|
24
|
+
export declare function registerAntivirusTools(server: McpServer, client: CohesityClient): void;
|