wazuh-mcp 1.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 +21 -0
- package/README.md +291 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1941 -0
- package/dist/index.js.map +1 -0
- package/package.json +57 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1941 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// src/config.ts
|
|
8
|
+
function getConfig() {
|
|
9
|
+
const url = process.env.WAZUH_URL || process.env.WAZUH_BASE_URL;
|
|
10
|
+
if (!url) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"WAZUH_URL environment variable is required. Set it to your Wazuh manager API URL (e.g., https://localhost:55000)"
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
const username = process.env.WAZUH_USERNAME || process.env.WAZUH_USER;
|
|
16
|
+
if (!username) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"WAZUH_USERNAME environment variable is required. Set it to your Wazuh API username."
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const password = process.env.WAZUH_PASSWORD;
|
|
22
|
+
if (!password) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
"WAZUH_PASSWORD environment variable is required. Set it to your Wazuh API password."
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
const verifySslStr = process.env.WAZUH_VERIFY_SSL ?? "false";
|
|
28
|
+
const verifySsl = verifySslStr.toLowerCase() === "true";
|
|
29
|
+
const timeout = parseInt(process.env.WAZUH_TIMEOUT ?? "30", 10) * 1e3;
|
|
30
|
+
let indexer;
|
|
31
|
+
const indexerUrl = process.env.WAZUH_INDEXER_URL;
|
|
32
|
+
if (indexerUrl) {
|
|
33
|
+
indexer = {
|
|
34
|
+
url: indexerUrl.replace(/\/+$/, ""),
|
|
35
|
+
username: process.env.WAZUH_INDEXER_USERNAME ?? "admin",
|
|
36
|
+
password: process.env.WAZUH_INDEXER_PASSWORD ?? "",
|
|
37
|
+
verifySsl: (process.env.WAZUH_INDEXER_VERIFY_SSL ?? "false").toLowerCase() === "true"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return { url: url.replace(/\/+$/, ""), username, password, verifySsl, timeout, indexer };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/client.ts
|
|
44
|
+
var WazuhClientError = class extends Error {
|
|
45
|
+
constructor(message, statusCode) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.statusCode = statusCode;
|
|
48
|
+
this.name = "WazuhClientError";
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
var WazuhAuthenticationError = class extends WazuhClientError {
|
|
52
|
+
constructor(message, statusCode) {
|
|
53
|
+
super(message, statusCode);
|
|
54
|
+
this.name = "WazuhAuthenticationError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
var WazuhClient = class {
|
|
58
|
+
token = null;
|
|
59
|
+
baseUrl;
|
|
60
|
+
username;
|
|
61
|
+
password;
|
|
62
|
+
verifySsl;
|
|
63
|
+
timeout;
|
|
64
|
+
constructor(config) {
|
|
65
|
+
this.baseUrl = config.url;
|
|
66
|
+
this.username = config.username;
|
|
67
|
+
this.password = config.password;
|
|
68
|
+
this.verifySsl = config.verifySsl;
|
|
69
|
+
this.timeout = config.timeout;
|
|
70
|
+
}
|
|
71
|
+
get fetchOptions() {
|
|
72
|
+
const opts = {};
|
|
73
|
+
if (!this.verifySsl) {
|
|
74
|
+
}
|
|
75
|
+
return opts;
|
|
76
|
+
}
|
|
77
|
+
createAbortSignal() {
|
|
78
|
+
const controller = new AbortController();
|
|
79
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
80
|
+
return {
|
|
81
|
+
signal: controller.signal,
|
|
82
|
+
clear: () => clearTimeout(timeoutId)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
async authenticate() {
|
|
86
|
+
const credentials = Buffer.from(
|
|
87
|
+
`${this.username}:${this.password}`
|
|
88
|
+
).toString("base64");
|
|
89
|
+
const { signal, clear } = this.createAbortSignal();
|
|
90
|
+
let response;
|
|
91
|
+
try {
|
|
92
|
+
response = await fetch(
|
|
93
|
+
`${this.baseUrl}/security/user/authenticate`,
|
|
94
|
+
{
|
|
95
|
+
method: "POST",
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Basic ${credentials}`,
|
|
98
|
+
"Content-Type": "application/json"
|
|
99
|
+
},
|
|
100
|
+
signal,
|
|
101
|
+
...this.fetchOptions
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
106
|
+
throw new WazuhClientError(`Wazuh API authentication timeout after ${this.timeout}ms`);
|
|
107
|
+
}
|
|
108
|
+
throw error;
|
|
109
|
+
} finally {
|
|
110
|
+
clear();
|
|
111
|
+
}
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new WazuhAuthenticationError(
|
|
114
|
+
`Authentication failed: ${response.status} ${response.statusText}`,
|
|
115
|
+
response.status
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
const body = await response.json();
|
|
119
|
+
if (!body.data?.token) {
|
|
120
|
+
throw new WazuhAuthenticationError(
|
|
121
|
+
"Authentication response missing token"
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
this.token = body.data.token;
|
|
125
|
+
return this.token;
|
|
126
|
+
}
|
|
127
|
+
async ensureAuthenticated() {
|
|
128
|
+
if (!this.token) {
|
|
129
|
+
await this.authenticate();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async request(method, endpoint, params, body) {
|
|
133
|
+
await this.ensureAuthenticated();
|
|
134
|
+
const url = new URL(`${this.baseUrl}${endpoint}`);
|
|
135
|
+
if (params) {
|
|
136
|
+
for (const [key, value] of Object.entries(params)) {
|
|
137
|
+
if (value !== void 0 && value !== null) {
|
|
138
|
+
url.searchParams.set(key, String(value));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const headers = {
|
|
143
|
+
Authorization: `Bearer ${this.token}`,
|
|
144
|
+
"Content-Type": "application/json"
|
|
145
|
+
};
|
|
146
|
+
const { signal, clear } = this.createAbortSignal();
|
|
147
|
+
let response;
|
|
148
|
+
try {
|
|
149
|
+
response = await fetch(url.toString(), {
|
|
150
|
+
method,
|
|
151
|
+
headers,
|
|
152
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
153
|
+
signal,
|
|
154
|
+
...this.fetchOptions
|
|
155
|
+
});
|
|
156
|
+
} catch (error) {
|
|
157
|
+
clear();
|
|
158
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
159
|
+
throw new WazuhClientError(`Wazuh API timeout after ${this.timeout}ms`);
|
|
160
|
+
}
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
clear();
|
|
164
|
+
if (response.status === 401) {
|
|
165
|
+
this.token = null;
|
|
166
|
+
await this.authenticate();
|
|
167
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
168
|
+
const { signal: retrySignal, clear: retryClear } = this.createAbortSignal();
|
|
169
|
+
let retryResponse;
|
|
170
|
+
try {
|
|
171
|
+
retryResponse = await fetch(url.toString(), {
|
|
172
|
+
method,
|
|
173
|
+
headers,
|
|
174
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
175
|
+
signal: retrySignal,
|
|
176
|
+
...this.fetchOptions
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
retryClear();
|
|
180
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
181
|
+
throw new WazuhClientError(`Wazuh API timeout after ${this.timeout}ms`);
|
|
182
|
+
}
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
retryClear();
|
|
186
|
+
if (!retryResponse.ok) {
|
|
187
|
+
throw new WazuhClientError(
|
|
188
|
+
`Request failed after re-auth: ${retryResponse.status} ${retryResponse.statusText}`,
|
|
189
|
+
retryResponse.status
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
return await retryResponse.json();
|
|
193
|
+
}
|
|
194
|
+
if (!response.ok) {
|
|
195
|
+
let errorMsg = `${response.status} ${response.statusText}`;
|
|
196
|
+
try {
|
|
197
|
+
const errorBody = await response.json();
|
|
198
|
+
if (errorBody.message) {
|
|
199
|
+
errorMsg = `${errorMsg}: ${errorBody.message}`;
|
|
200
|
+
}
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
throw new WazuhClientError(`Request failed: ${errorMsg}`, response.status);
|
|
204
|
+
}
|
|
205
|
+
return await response.json();
|
|
206
|
+
}
|
|
207
|
+
async get(endpoint, params) {
|
|
208
|
+
return this.request("GET", endpoint, params);
|
|
209
|
+
}
|
|
210
|
+
// --- Agent methods ---
|
|
211
|
+
async getAgents(params = {}) {
|
|
212
|
+
return this.get("/agents", params);
|
|
213
|
+
}
|
|
214
|
+
async getAgent(agentId) {
|
|
215
|
+
return this.get(`/agents`, { agents_list: agentId });
|
|
216
|
+
}
|
|
217
|
+
async getAgentStats(agentId) {
|
|
218
|
+
return this.get(`/agents/${agentId}/stats/agent`);
|
|
219
|
+
}
|
|
220
|
+
// --- Alert methods ---
|
|
221
|
+
async getAlerts(params = {}) {
|
|
222
|
+
return this.get("/alerts", params);
|
|
223
|
+
}
|
|
224
|
+
// --- Rule methods ---
|
|
225
|
+
async getRules(params = {}) {
|
|
226
|
+
return this.get("/rules", params);
|
|
227
|
+
}
|
|
228
|
+
async getRule(ruleId) {
|
|
229
|
+
return this.get("/rules", { rule_ids: ruleId });
|
|
230
|
+
}
|
|
231
|
+
// --- Decoder methods ---
|
|
232
|
+
async getDecoders(params = {}) {
|
|
233
|
+
return this.get("/decoders", params);
|
|
234
|
+
}
|
|
235
|
+
// --- Version method ---
|
|
236
|
+
async getVersion() {
|
|
237
|
+
return this.get("/");
|
|
238
|
+
}
|
|
239
|
+
// --- SCA methods ---
|
|
240
|
+
async getScaPolicies(agentId) {
|
|
241
|
+
return this.get(`/sca/${agentId}`);
|
|
242
|
+
}
|
|
243
|
+
async getScaChecks(agentId, policyId, params = {}) {
|
|
244
|
+
return this.get(`/sca/${agentId}/checks/${policyId}`, params);
|
|
245
|
+
}
|
|
246
|
+
// --- Syscollector methods ---
|
|
247
|
+
async getAgentOs(agentId) {
|
|
248
|
+
return this.get(`/syscollector/${agentId}/os`);
|
|
249
|
+
}
|
|
250
|
+
async getAgentPackages(agentId, params = {}) {
|
|
251
|
+
return this.get(`/syscollector/${agentId}/packages`, params);
|
|
252
|
+
}
|
|
253
|
+
async getAgentProcesses(agentId, params = {}) {
|
|
254
|
+
return this.get(`/syscollector/${agentId}/processes`, params);
|
|
255
|
+
}
|
|
256
|
+
async getAgentPorts(agentId, params = {}) {
|
|
257
|
+
return this.get(`/syscollector/${agentId}/ports`, params);
|
|
258
|
+
}
|
|
259
|
+
async getAgentNetwork(agentId) {
|
|
260
|
+
return this.get(`/syscollector/${agentId}/netiface`);
|
|
261
|
+
}
|
|
262
|
+
async getAgentHotfixes(agentId, params = {}) {
|
|
263
|
+
return this.get(`/syscollector/${agentId}/hotfixes`, params);
|
|
264
|
+
}
|
|
265
|
+
// --- Rootcheck methods ---
|
|
266
|
+
async getRootcheck(agentId, params = {}) {
|
|
267
|
+
return this.get(`/rootcheck/${agentId}`, params);
|
|
268
|
+
}
|
|
269
|
+
// --- Syscheck / FIM methods ---
|
|
270
|
+
async getFimFiles(agentId, params = {}) {
|
|
271
|
+
return this.get(`/syscheck/${agentId}`, params);
|
|
272
|
+
}
|
|
273
|
+
// --- Manager methods ---
|
|
274
|
+
async getManagerLogs(params = {}) {
|
|
275
|
+
return this.get("/manager/logs", params);
|
|
276
|
+
}
|
|
277
|
+
async getManagerConfig(params = {}) {
|
|
278
|
+
return this.get("/manager/configuration", params);
|
|
279
|
+
}
|
|
280
|
+
// --- Group methods ---
|
|
281
|
+
async getGroups(params = {}) {
|
|
282
|
+
return this.get("/groups", params);
|
|
283
|
+
}
|
|
284
|
+
async getGroupAgents(groupId, params = {}) {
|
|
285
|
+
return this.get(`/groups/${groupId}/agents`, params);
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
// src/indexer-client.ts
|
|
290
|
+
var WazuhIndexerError = class extends Error {
|
|
291
|
+
constructor(message, statusCode) {
|
|
292
|
+
super(message);
|
|
293
|
+
this.statusCode = statusCode;
|
|
294
|
+
this.name = "WazuhIndexerError";
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
var WazuhIndexerClient = class {
|
|
298
|
+
baseUrl;
|
|
299
|
+
authHeader;
|
|
300
|
+
verifySsl;
|
|
301
|
+
timeout = 3e4;
|
|
302
|
+
constructor(config) {
|
|
303
|
+
this.baseUrl = config.url;
|
|
304
|
+
this.authHeader = "Basic " + Buffer.from(`${config.username}:${config.password}`).toString("base64");
|
|
305
|
+
this.verifySsl = config.verifySsl;
|
|
306
|
+
}
|
|
307
|
+
createAbortSignal() {
|
|
308
|
+
const controller = new AbortController();
|
|
309
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
310
|
+
return { signal: controller.signal, clear: () => clearTimeout(timeoutId) };
|
|
311
|
+
}
|
|
312
|
+
async post(path, body) {
|
|
313
|
+
const url = `${this.baseUrl}${path}`;
|
|
314
|
+
const { signal, clear } = this.createAbortSignal();
|
|
315
|
+
let response;
|
|
316
|
+
try {
|
|
317
|
+
response = await fetch(url, {
|
|
318
|
+
method: "POST",
|
|
319
|
+
headers: {
|
|
320
|
+
Authorization: this.authHeader,
|
|
321
|
+
"Content-Type": "application/json"
|
|
322
|
+
},
|
|
323
|
+
body: JSON.stringify(body),
|
|
324
|
+
signal
|
|
325
|
+
});
|
|
326
|
+
} catch (error) {
|
|
327
|
+
clear();
|
|
328
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
329
|
+
throw new WazuhIndexerError(`Wazuh Indexer request timeout after ${this.timeout}ms`);
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
}
|
|
333
|
+
clear();
|
|
334
|
+
if (!response.ok) {
|
|
335
|
+
let errorMsg = `${response.status} ${response.statusText}`;
|
|
336
|
+
try {
|
|
337
|
+
const errorBody = await response.json();
|
|
338
|
+
if (errorBody.error) {
|
|
339
|
+
errorMsg = `${errorMsg}: ${JSON.stringify(errorBody.error)}`;
|
|
340
|
+
}
|
|
341
|
+
} catch {
|
|
342
|
+
}
|
|
343
|
+
throw new WazuhIndexerError(`Indexer request failed: ${errorMsg}`, response.status);
|
|
344
|
+
}
|
|
345
|
+
return await response.json();
|
|
346
|
+
}
|
|
347
|
+
mapHitToAlert(hit) {
|
|
348
|
+
const s = hit._source;
|
|
349
|
+
const rule = s.rule;
|
|
350
|
+
const agent = s.agent;
|
|
351
|
+
const manager = s.manager;
|
|
352
|
+
const decoder = s.decoder;
|
|
353
|
+
const mitre = rule?.mitre;
|
|
354
|
+
return {
|
|
355
|
+
id: hit._id,
|
|
356
|
+
timestamp: s.timestamp,
|
|
357
|
+
rule: rule ? {
|
|
358
|
+
id: rule.id,
|
|
359
|
+
level: rule.level,
|
|
360
|
+
description: rule.description,
|
|
361
|
+
groups: rule.groups,
|
|
362
|
+
pci_dss: rule.pci_dss,
|
|
363
|
+
gdpr: rule.gdpr,
|
|
364
|
+
hipaa: rule.hipaa,
|
|
365
|
+
nist_800_53: rule.nist_800_53,
|
|
366
|
+
mitre: mitre ? {
|
|
367
|
+
id: mitre.id,
|
|
368
|
+
tactic: mitre.tactic,
|
|
369
|
+
technique: mitre.technique
|
|
370
|
+
} : void 0
|
|
371
|
+
} : void 0,
|
|
372
|
+
agent: agent ? {
|
|
373
|
+
id: agent.id,
|
|
374
|
+
name: agent.name,
|
|
375
|
+
ip: agent.ip
|
|
376
|
+
} : void 0,
|
|
377
|
+
manager: manager ? { name: manager.name } : void 0,
|
|
378
|
+
location: s.location,
|
|
379
|
+
decoder: decoder ? { name: decoder.name } : void 0,
|
|
380
|
+
full_log: s.full_log,
|
|
381
|
+
data: s.data
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
async searchAlerts(query, size, from) {
|
|
385
|
+
const body = {
|
|
386
|
+
query,
|
|
387
|
+
size,
|
|
388
|
+
from,
|
|
389
|
+
sort: [{ timestamp: { order: "desc" } }]
|
|
390
|
+
};
|
|
391
|
+
const result = await this.post("/wazuh-alerts-*/_search", body);
|
|
392
|
+
return {
|
|
393
|
+
alerts: result.hits.hits.map((h) => this.mapHitToAlert(h)),
|
|
394
|
+
total: result.hits.total.value
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
async getRecentAlerts(limit, offset, filters) {
|
|
398
|
+
const must = [];
|
|
399
|
+
if (filters?.level !== void 0) {
|
|
400
|
+
must.push({ range: { "rule.level": { gte: filters.level } } });
|
|
401
|
+
}
|
|
402
|
+
if (filters?.agent_id) {
|
|
403
|
+
must.push({ term: { "agent.id": filters.agent_id } });
|
|
404
|
+
}
|
|
405
|
+
if (filters?.rule_id) {
|
|
406
|
+
must.push({ term: { "rule.id": filters.rule_id } });
|
|
407
|
+
}
|
|
408
|
+
if (filters?.search) {
|
|
409
|
+
must.push({
|
|
410
|
+
multi_match: {
|
|
411
|
+
query: filters.search,
|
|
412
|
+
fields: ["full_log", "rule.description", "agent.name"]
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
const query = must.length > 0 ? { bool: { must } } : { match_all: {} };
|
|
417
|
+
return this.searchAlerts(query, limit, offset);
|
|
418
|
+
}
|
|
419
|
+
async getAlert(id) {
|
|
420
|
+
const body = {
|
|
421
|
+
query: { ids: { values: [id] } },
|
|
422
|
+
size: 1
|
|
423
|
+
};
|
|
424
|
+
const result = await this.post("/wazuh-alerts-*/_search", body);
|
|
425
|
+
if (result.hits.hits.length === 0) return null;
|
|
426
|
+
return this.mapHitToAlert(result.hits.hits[0]);
|
|
427
|
+
}
|
|
428
|
+
async fullTextSearch(query, limit, offset, filters) {
|
|
429
|
+
const must = [
|
|
430
|
+
{
|
|
431
|
+
multi_match: {
|
|
432
|
+
query,
|
|
433
|
+
fields: ["full_log", "rule.description", "agent.name"]
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
];
|
|
437
|
+
if (filters?.level !== void 0) {
|
|
438
|
+
must.push({ range: { "rule.level": { gte: filters.level } } });
|
|
439
|
+
}
|
|
440
|
+
if (filters?.agent_id) {
|
|
441
|
+
must.push({ term: { "agent.id": filters.agent_id } });
|
|
442
|
+
}
|
|
443
|
+
return this.searchAlerts({ bool: { must } }, limit, offset);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
// src/tools/agents.ts
|
|
448
|
+
import { z } from "zod";
|
|
449
|
+
function registerAgentTools(server, client) {
|
|
450
|
+
server.tool(
|
|
451
|
+
"list_agents",
|
|
452
|
+
"List all Wazuh agents with optional status filtering",
|
|
453
|
+
{
|
|
454
|
+
status: z.enum(["active", "disconnected", "never_connected", "pending"]).optional().describe(
|
|
455
|
+
"Filter by agent status: active, disconnected, never_connected, or pending"
|
|
456
|
+
),
|
|
457
|
+
limit: z.number().int().min(1).max(100).default(10).describe("Maximum number of agents to return (1-100)"),
|
|
458
|
+
offset: z.number().int().min(0).default(0).describe("Pagination offset"),
|
|
459
|
+
sort: z.string().optional().describe("Sort field with direction prefix (e.g., '-name', '+id')")
|
|
460
|
+
},
|
|
461
|
+
async ({ status, limit, offset, sort }) => {
|
|
462
|
+
try {
|
|
463
|
+
const params = { limit, offset };
|
|
464
|
+
if (status) params.status = status;
|
|
465
|
+
if (sort) params.sort = sort;
|
|
466
|
+
const response = await client.getAgents(params);
|
|
467
|
+
const data = response.data;
|
|
468
|
+
const result = {
|
|
469
|
+
agents: data.affected_items.map((agent) => ({
|
|
470
|
+
id: agent.id,
|
|
471
|
+
name: agent.name,
|
|
472
|
+
ip: agent.ip,
|
|
473
|
+
status: agent.status,
|
|
474
|
+
group: agent.group,
|
|
475
|
+
os_name: agent.os?.name,
|
|
476
|
+
os_version: agent.os?.version,
|
|
477
|
+
os_platform: agent.os?.platform,
|
|
478
|
+
version: agent.version,
|
|
479
|
+
manager: agent.manager,
|
|
480
|
+
node_name: agent.node_name,
|
|
481
|
+
date_add: agent.dateAdd,
|
|
482
|
+
last_keepalive: agent.lastKeepAlive
|
|
483
|
+
})),
|
|
484
|
+
total: data.total_affected_items,
|
|
485
|
+
limit,
|
|
486
|
+
offset
|
|
487
|
+
};
|
|
488
|
+
return {
|
|
489
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
490
|
+
};
|
|
491
|
+
} catch (error) {
|
|
492
|
+
return {
|
|
493
|
+
content: [
|
|
494
|
+
{
|
|
495
|
+
type: "text",
|
|
496
|
+
text: JSON.stringify({
|
|
497
|
+
error: error instanceof Error ? error.message : String(error)
|
|
498
|
+
})
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
isError: true
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
server.tool(
|
|
507
|
+
"get_agent",
|
|
508
|
+
"Get detailed information about a specific Wazuh agent by ID",
|
|
509
|
+
{
|
|
510
|
+
agent_id: z.string().describe("Agent identifier (e.g., '001')")
|
|
511
|
+
},
|
|
512
|
+
async ({ agent_id }) => {
|
|
513
|
+
try {
|
|
514
|
+
const response = await client.getAgent(agent_id);
|
|
515
|
+
const agents = response.data.affected_items;
|
|
516
|
+
if (agents.length === 0) {
|
|
517
|
+
return {
|
|
518
|
+
content: [
|
|
519
|
+
{
|
|
520
|
+
type: "text",
|
|
521
|
+
text: JSON.stringify({ error: `Agent '${agent_id}' not found` })
|
|
522
|
+
}
|
|
523
|
+
],
|
|
524
|
+
isError: true
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
const agent = agents[0];
|
|
528
|
+
const result = {
|
|
529
|
+
id: agent.id,
|
|
530
|
+
name: agent.name,
|
|
531
|
+
ip: agent.ip,
|
|
532
|
+
status: agent.status,
|
|
533
|
+
group: agent.group,
|
|
534
|
+
os_name: agent.os?.name,
|
|
535
|
+
os_version: agent.os?.version,
|
|
536
|
+
os_platform: agent.os?.platform,
|
|
537
|
+
version: agent.version,
|
|
538
|
+
manager: agent.manager,
|
|
539
|
+
node_name: agent.node_name,
|
|
540
|
+
date_add: agent.dateAdd,
|
|
541
|
+
last_keepalive: agent.lastKeepAlive,
|
|
542
|
+
register_ip: agent.registerIP
|
|
543
|
+
};
|
|
544
|
+
return {
|
|
545
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
546
|
+
};
|
|
547
|
+
} catch (error) {
|
|
548
|
+
return {
|
|
549
|
+
content: [
|
|
550
|
+
{
|
|
551
|
+
type: "text",
|
|
552
|
+
text: JSON.stringify({
|
|
553
|
+
error: error instanceof Error ? error.message : String(error)
|
|
554
|
+
})
|
|
555
|
+
}
|
|
556
|
+
],
|
|
557
|
+
isError: true
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
);
|
|
562
|
+
server.tool(
|
|
563
|
+
"get_agent_stats",
|
|
564
|
+
"Get system statistics (CPU, memory, disk) for a specific Wazuh agent",
|
|
565
|
+
{
|
|
566
|
+
agent_id: z.string().describe("Agent identifier (e.g., '001')")
|
|
567
|
+
},
|
|
568
|
+
async ({ agent_id }) => {
|
|
569
|
+
try {
|
|
570
|
+
const agentResponse = await client.getAgent(agent_id);
|
|
571
|
+
const agents = agentResponse.data.affected_items;
|
|
572
|
+
if (agents.length === 0) {
|
|
573
|
+
return {
|
|
574
|
+
content: [
|
|
575
|
+
{
|
|
576
|
+
type: "text",
|
|
577
|
+
text: JSON.stringify({ error: `Agent '${agent_id}' not found` })
|
|
578
|
+
}
|
|
579
|
+
],
|
|
580
|
+
isError: true
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
const agent = agents[0];
|
|
584
|
+
const statsResponse = await client.getAgentStats(agent_id);
|
|
585
|
+
const stats = statsResponse.data.affected_items[0] ?? {};
|
|
586
|
+
const result = {
|
|
587
|
+
agent_id: agent.id,
|
|
588
|
+
agent_name: agent.name,
|
|
589
|
+
cpu: stats.cpu,
|
|
590
|
+
memory: stats.memory,
|
|
591
|
+
disk: stats.disk
|
|
592
|
+
};
|
|
593
|
+
return {
|
|
594
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
595
|
+
};
|
|
596
|
+
} catch (error) {
|
|
597
|
+
return {
|
|
598
|
+
content: [
|
|
599
|
+
{
|
|
600
|
+
type: "text",
|
|
601
|
+
text: JSON.stringify({
|
|
602
|
+
error: error instanceof Error ? error.message : String(error)
|
|
603
|
+
})
|
|
604
|
+
}
|
|
605
|
+
],
|
|
606
|
+
isError: true
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/tools/alerts.ts
|
|
614
|
+
import { z as z2 } from "zod";
|
|
615
|
+
var NO_INDEXER_MSG = "Alerts require WAZUH_INDEXER_URL configuration. Wazuh 4.x stores alerts in the Wazuh Indexer (OpenSearch), not the REST API.";
|
|
616
|
+
function registerAlertTools(server, _client, indexerClient) {
|
|
617
|
+
server.tool(
|
|
618
|
+
"get_alerts",
|
|
619
|
+
"Retrieve recent security alerts from Wazuh with optional filtering",
|
|
620
|
+
{
|
|
621
|
+
limit: z2.number().int().min(1).max(100).default(10).describe("Maximum number of alerts to return (1-100)"),
|
|
622
|
+
offset: z2.number().int().min(0).default(0).describe("Pagination offset"),
|
|
623
|
+
level: z2.number().int().min(0).optional().describe("Minimum rule severity level"),
|
|
624
|
+
agent_id: z2.string().optional().describe("Filter by agent ID"),
|
|
625
|
+
rule_id: z2.string().optional().describe("Filter by specific rule ID"),
|
|
626
|
+
sort: z2.string().optional().describe("Sort field with direction prefix (e.g., '-timestamp')"),
|
|
627
|
+
search: z2.string().optional().describe("Search term for full_log text")
|
|
628
|
+
},
|
|
629
|
+
async ({ limit, offset, level, agent_id, rule_id, search }) => {
|
|
630
|
+
if (!indexerClient) {
|
|
631
|
+
return {
|
|
632
|
+
content: [{ type: "text", text: JSON.stringify({ error: NO_INDEXER_MSG }) }],
|
|
633
|
+
isError: true
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
try {
|
|
637
|
+
const { alerts, total } = await indexerClient.getRecentAlerts(limit, offset, {
|
|
638
|
+
level,
|
|
639
|
+
agent_id,
|
|
640
|
+
rule_id,
|
|
641
|
+
search
|
|
642
|
+
});
|
|
643
|
+
const result = {
|
|
644
|
+
alerts: alerts.map((alert) => ({
|
|
645
|
+
id: alert.id,
|
|
646
|
+
timestamp: alert.timestamp,
|
|
647
|
+
rule_id: alert.rule?.id,
|
|
648
|
+
rule_level: alert.rule?.level,
|
|
649
|
+
rule_description: alert.rule?.description,
|
|
650
|
+
rule_groups: alert.rule?.groups,
|
|
651
|
+
agent_id: alert.agent?.id,
|
|
652
|
+
agent_name: alert.agent?.name,
|
|
653
|
+
location: alert.location,
|
|
654
|
+
decoder: alert.decoder?.name,
|
|
655
|
+
full_log: alert.full_log,
|
|
656
|
+
mitre: alert.rule?.mitre
|
|
657
|
+
})),
|
|
658
|
+
total,
|
|
659
|
+
limit,
|
|
660
|
+
offset
|
|
661
|
+
};
|
|
662
|
+
return {
|
|
663
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
664
|
+
};
|
|
665
|
+
} catch (error) {
|
|
666
|
+
return {
|
|
667
|
+
content: [
|
|
668
|
+
{
|
|
669
|
+
type: "text",
|
|
670
|
+
text: JSON.stringify({
|
|
671
|
+
error: error instanceof Error ? error.message : String(error)
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
],
|
|
675
|
+
isError: true
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
);
|
|
680
|
+
server.tool(
|
|
681
|
+
"get_alert",
|
|
682
|
+
"Retrieve a single security alert by its ID",
|
|
683
|
+
{
|
|
684
|
+
alert_id: z2.string().describe("Alert identifier")
|
|
685
|
+
},
|
|
686
|
+
async ({ alert_id }) => {
|
|
687
|
+
if (!indexerClient) {
|
|
688
|
+
return {
|
|
689
|
+
content: [{ type: "text", text: JSON.stringify({ error: NO_INDEXER_MSG }) }],
|
|
690
|
+
isError: true
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
const alert = await indexerClient.getAlert(alert_id);
|
|
695
|
+
if (!alert) {
|
|
696
|
+
return {
|
|
697
|
+
content: [
|
|
698
|
+
{
|
|
699
|
+
type: "text",
|
|
700
|
+
text: JSON.stringify({ error: `Alert '${alert_id}' not found` })
|
|
701
|
+
}
|
|
702
|
+
],
|
|
703
|
+
isError: true
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
const result = {
|
|
707
|
+
id: alert.id,
|
|
708
|
+
timestamp: alert.timestamp,
|
|
709
|
+
rule_id: alert.rule?.id,
|
|
710
|
+
rule_level: alert.rule?.level,
|
|
711
|
+
rule_description: alert.rule?.description,
|
|
712
|
+
rule_groups: alert.rule?.groups,
|
|
713
|
+
agent_id: alert.agent?.id,
|
|
714
|
+
agent_name: alert.agent?.name,
|
|
715
|
+
location: alert.location,
|
|
716
|
+
decoder: alert.decoder?.name,
|
|
717
|
+
full_log: alert.full_log,
|
|
718
|
+
mitre: alert.rule?.mitre,
|
|
719
|
+
data: alert.data
|
|
720
|
+
};
|
|
721
|
+
return {
|
|
722
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
723
|
+
};
|
|
724
|
+
} catch (error) {
|
|
725
|
+
return {
|
|
726
|
+
content: [
|
|
727
|
+
{
|
|
728
|
+
type: "text",
|
|
729
|
+
text: JSON.stringify({
|
|
730
|
+
error: error instanceof Error ? error.message : String(error)
|
|
731
|
+
})
|
|
732
|
+
}
|
|
733
|
+
],
|
|
734
|
+
isError: true
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
);
|
|
739
|
+
server.tool(
|
|
740
|
+
"search_alerts",
|
|
741
|
+
"Perform full-text search across Wazuh security alerts",
|
|
742
|
+
{
|
|
743
|
+
query: z2.string().describe("Search query string"),
|
|
744
|
+
limit: z2.number().int().min(1).max(100).default(10).describe("Maximum number of alerts to return (1-100)"),
|
|
745
|
+
offset: z2.number().int().min(0).default(0).describe("Pagination offset"),
|
|
746
|
+
level: z2.number().int().min(0).optional().describe("Minimum rule severity level"),
|
|
747
|
+
agent_id: z2.string().optional().describe("Filter by agent ID")
|
|
748
|
+
},
|
|
749
|
+
async ({ query, limit, offset, level, agent_id }) => {
|
|
750
|
+
if (!indexerClient) {
|
|
751
|
+
return {
|
|
752
|
+
content: [{ type: "text", text: JSON.stringify({ error: NO_INDEXER_MSG }) }],
|
|
753
|
+
isError: true
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
const { alerts, total } = await indexerClient.fullTextSearch(query, limit, offset, {
|
|
758
|
+
level,
|
|
759
|
+
agent_id
|
|
760
|
+
});
|
|
761
|
+
const result = {
|
|
762
|
+
alerts: alerts.map((alert) => ({
|
|
763
|
+
id: alert.id,
|
|
764
|
+
timestamp: alert.timestamp,
|
|
765
|
+
rule_id: alert.rule?.id,
|
|
766
|
+
rule_level: alert.rule?.level,
|
|
767
|
+
rule_description: alert.rule?.description,
|
|
768
|
+
rule_groups: alert.rule?.groups,
|
|
769
|
+
agent_id: alert.agent?.id,
|
|
770
|
+
agent_name: alert.agent?.name,
|
|
771
|
+
location: alert.location,
|
|
772
|
+
decoder: alert.decoder?.name,
|
|
773
|
+
full_log: alert.full_log,
|
|
774
|
+
mitre: alert.rule?.mitre
|
|
775
|
+
})),
|
|
776
|
+
total,
|
|
777
|
+
query,
|
|
778
|
+
limit,
|
|
779
|
+
offset
|
|
780
|
+
};
|
|
781
|
+
return {
|
|
782
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
783
|
+
};
|
|
784
|
+
} catch (error) {
|
|
785
|
+
return {
|
|
786
|
+
content: [
|
|
787
|
+
{
|
|
788
|
+
type: "text",
|
|
789
|
+
text: JSON.stringify({
|
|
790
|
+
error: error instanceof Error ? error.message : String(error)
|
|
791
|
+
})
|
|
792
|
+
}
|
|
793
|
+
],
|
|
794
|
+
isError: true
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// src/tools/rules.ts
|
|
802
|
+
import { z as z3 } from "zod";
|
|
803
|
+
function registerRuleTools(server, client) {
|
|
804
|
+
server.tool(
|
|
805
|
+
"list_rules",
|
|
806
|
+
"List all Wazuh rules with optional level and group filtering",
|
|
807
|
+
{
|
|
808
|
+
level: z3.number().int().min(0).optional().describe("Filter by rule severity level"),
|
|
809
|
+
group: z3.string().optional().describe("Filter by rule group name"),
|
|
810
|
+
limit: z3.number().int().min(1).max(100).default(10).describe("Maximum number of rules to return (1-100)"),
|
|
811
|
+
offset: z3.number().int().min(0).default(0).describe("Pagination offset"),
|
|
812
|
+
sort: z3.string().optional().describe("Sort field with direction prefix (e.g., '-level')")
|
|
813
|
+
},
|
|
814
|
+
async ({ level, group, limit, offset, sort }) => {
|
|
815
|
+
try {
|
|
816
|
+
const params = { limit, offset };
|
|
817
|
+
if (level !== void 0) params.level = level;
|
|
818
|
+
if (group) params.group = group;
|
|
819
|
+
if (sort) params.sort = sort;
|
|
820
|
+
const response = await client.getRules(params);
|
|
821
|
+
const data = response.data;
|
|
822
|
+
const result = {
|
|
823
|
+
rules: data.affected_items.map((rule) => ({
|
|
824
|
+
id: rule.id,
|
|
825
|
+
description: rule.description,
|
|
826
|
+
level: rule.level,
|
|
827
|
+
groups: rule.groups,
|
|
828
|
+
pci_dss: rule.pci_dss,
|
|
829
|
+
gdpr: rule.gdpr,
|
|
830
|
+
gpg13: rule.gpg13,
|
|
831
|
+
hipaa: rule.hipaa,
|
|
832
|
+
nist_800_53: rule.nist_800_53,
|
|
833
|
+
tsc: rule.tsc,
|
|
834
|
+
mitre: rule.mitre,
|
|
835
|
+
details: rule.details
|
|
836
|
+
})),
|
|
837
|
+
total: data.total_affected_items,
|
|
838
|
+
limit,
|
|
839
|
+
offset
|
|
840
|
+
};
|
|
841
|
+
return {
|
|
842
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
843
|
+
};
|
|
844
|
+
} catch (error) {
|
|
845
|
+
return {
|
|
846
|
+
content: [
|
|
847
|
+
{
|
|
848
|
+
type: "text",
|
|
849
|
+
text: JSON.stringify({
|
|
850
|
+
error: error instanceof Error ? error.message : String(error)
|
|
851
|
+
})
|
|
852
|
+
}
|
|
853
|
+
],
|
|
854
|
+
isError: true
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
);
|
|
859
|
+
server.tool(
|
|
860
|
+
"get_rule",
|
|
861
|
+
"Get detailed information about a specific Wazuh rule by ID",
|
|
862
|
+
{
|
|
863
|
+
rule_id: z3.number().int().describe("Rule identifier (e.g., 5710)")
|
|
864
|
+
},
|
|
865
|
+
async ({ rule_id }) => {
|
|
866
|
+
try {
|
|
867
|
+
const response = await client.getRule(rule_id);
|
|
868
|
+
const rules = response.data.affected_items;
|
|
869
|
+
if (rules.length === 0) {
|
|
870
|
+
return {
|
|
871
|
+
content: [
|
|
872
|
+
{
|
|
873
|
+
type: "text",
|
|
874
|
+
text: JSON.stringify({ error: `Rule '${rule_id}' not found` })
|
|
875
|
+
}
|
|
876
|
+
],
|
|
877
|
+
isError: true
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
const rule = rules[0];
|
|
881
|
+
const result = {
|
|
882
|
+
id: rule.id,
|
|
883
|
+
description: rule.description,
|
|
884
|
+
level: rule.level,
|
|
885
|
+
groups: rule.groups,
|
|
886
|
+
filename: rule.filename,
|
|
887
|
+
relative_dirname: rule.relative_dirname,
|
|
888
|
+
status: rule.status,
|
|
889
|
+
pci_dss: rule.pci_dss,
|
|
890
|
+
gdpr: rule.gdpr,
|
|
891
|
+
gpg13: rule.gpg13,
|
|
892
|
+
hipaa: rule.hipaa,
|
|
893
|
+
nist_800_53: rule.nist_800_53,
|
|
894
|
+
tsc: rule.tsc,
|
|
895
|
+
mitre: rule.mitre,
|
|
896
|
+
details: rule.details
|
|
897
|
+
};
|
|
898
|
+
return {
|
|
899
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
900
|
+
};
|
|
901
|
+
} catch (error) {
|
|
902
|
+
return {
|
|
903
|
+
content: [
|
|
904
|
+
{
|
|
905
|
+
type: "text",
|
|
906
|
+
text: JSON.stringify({
|
|
907
|
+
error: error instanceof Error ? error.message : String(error)
|
|
908
|
+
})
|
|
909
|
+
}
|
|
910
|
+
],
|
|
911
|
+
isError: true
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
);
|
|
916
|
+
server.tool(
|
|
917
|
+
"search_rules",
|
|
918
|
+
"Search Wazuh rules by description text",
|
|
919
|
+
{
|
|
920
|
+
description: z3.string().describe("Search term to match against rule descriptions"),
|
|
921
|
+
limit: z3.number().int().min(1).max(100).default(10).describe("Maximum number of rules to return (1-100)"),
|
|
922
|
+
offset: z3.number().int().min(0).default(0).describe("Pagination offset"),
|
|
923
|
+
level: z3.number().int().min(0).optional().describe("Minimum severity level filter")
|
|
924
|
+
},
|
|
925
|
+
async ({ description, limit, offset, level }) => {
|
|
926
|
+
try {
|
|
927
|
+
const params = {
|
|
928
|
+
search: description,
|
|
929
|
+
limit,
|
|
930
|
+
offset
|
|
931
|
+
};
|
|
932
|
+
if (level !== void 0) params.level = level;
|
|
933
|
+
const response = await client.getRules(params);
|
|
934
|
+
const data = response.data;
|
|
935
|
+
const result = {
|
|
936
|
+
rules: data.affected_items.map((rule) => ({
|
|
937
|
+
id: rule.id,
|
|
938
|
+
description: rule.description,
|
|
939
|
+
level: rule.level,
|
|
940
|
+
groups: rule.groups,
|
|
941
|
+
pci_dss: rule.pci_dss,
|
|
942
|
+
gdpr: rule.gdpr,
|
|
943
|
+
gpg13: rule.gpg13,
|
|
944
|
+
hipaa: rule.hipaa,
|
|
945
|
+
nist_800_53: rule.nist_800_53,
|
|
946
|
+
tsc: rule.tsc,
|
|
947
|
+
mitre: rule.mitre,
|
|
948
|
+
details: rule.details
|
|
949
|
+
})),
|
|
950
|
+
total: data.total_affected_items,
|
|
951
|
+
description,
|
|
952
|
+
limit,
|
|
953
|
+
offset
|
|
954
|
+
};
|
|
955
|
+
return {
|
|
956
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
957
|
+
};
|
|
958
|
+
} catch (error) {
|
|
959
|
+
return {
|
|
960
|
+
content: [
|
|
961
|
+
{
|
|
962
|
+
type: "text",
|
|
963
|
+
text: JSON.stringify({
|
|
964
|
+
error: error instanceof Error ? error.message : String(error)
|
|
965
|
+
})
|
|
966
|
+
}
|
|
967
|
+
],
|
|
968
|
+
isError: true
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/tools/decoders.ts
|
|
976
|
+
import { z as z4 } from "zod";
|
|
977
|
+
function registerDecoderTools(server, client) {
|
|
978
|
+
server.tool(
|
|
979
|
+
"list_decoders",
|
|
980
|
+
"List all available Wazuh decoders with optional name filtering",
|
|
981
|
+
{
|
|
982
|
+
name: z4.string().optional().describe("Filter by decoder name"),
|
|
983
|
+
limit: z4.number().int().min(1).max(100).default(10).describe("Maximum number of decoders to return (1-100)"),
|
|
984
|
+
offset: z4.number().int().min(0).default(0).describe("Pagination offset"),
|
|
985
|
+
sort: z4.string().optional().describe("Sort field with direction prefix (e.g., '-name')")
|
|
986
|
+
},
|
|
987
|
+
async ({ name, limit, offset, sort }) => {
|
|
988
|
+
try {
|
|
989
|
+
const params = { limit, offset };
|
|
990
|
+
if (name) params.name = name;
|
|
991
|
+
if (sort) params.sort = sort;
|
|
992
|
+
const response = await client.getDecoders(params);
|
|
993
|
+
const data = response.data;
|
|
994
|
+
const result = {
|
|
995
|
+
decoders: data.affected_items.map((decoder) => ({
|
|
996
|
+
name: decoder.name,
|
|
997
|
+
filename: decoder.filename,
|
|
998
|
+
relative_dirname: decoder.relative_dirname,
|
|
999
|
+
status: decoder.status,
|
|
1000
|
+
position: decoder.position,
|
|
1001
|
+
details: decoder.details
|
|
1002
|
+
})),
|
|
1003
|
+
total: data.total_affected_items,
|
|
1004
|
+
limit,
|
|
1005
|
+
offset
|
|
1006
|
+
};
|
|
1007
|
+
return {
|
|
1008
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1009
|
+
};
|
|
1010
|
+
} catch (error) {
|
|
1011
|
+
return {
|
|
1012
|
+
content: [
|
|
1013
|
+
{
|
|
1014
|
+
type: "text",
|
|
1015
|
+
text: JSON.stringify({
|
|
1016
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1017
|
+
})
|
|
1018
|
+
}
|
|
1019
|
+
],
|
|
1020
|
+
isError: true
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
);
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// src/tools/version.ts
|
|
1028
|
+
function registerVersionTools(server, client) {
|
|
1029
|
+
server.tool(
|
|
1030
|
+
"get_wazuh_version",
|
|
1031
|
+
"Get the Wazuh manager version and API information",
|
|
1032
|
+
{},
|
|
1033
|
+
async () => {
|
|
1034
|
+
try {
|
|
1035
|
+
const response = await client.getVersion();
|
|
1036
|
+
const info = response.data;
|
|
1037
|
+
const result = {
|
|
1038
|
+
title: info.title,
|
|
1039
|
+
api_version: info.api_version,
|
|
1040
|
+
revision: info.revision,
|
|
1041
|
+
license: info.license_name,
|
|
1042
|
+
hostname: info.hostname,
|
|
1043
|
+
timestamp: info.timestamp
|
|
1044
|
+
};
|
|
1045
|
+
return {
|
|
1046
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1047
|
+
};
|
|
1048
|
+
} catch (error) {
|
|
1049
|
+
return {
|
|
1050
|
+
content: [
|
|
1051
|
+
{
|
|
1052
|
+
type: "text",
|
|
1053
|
+
text: JSON.stringify({
|
|
1054
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1055
|
+
})
|
|
1056
|
+
}
|
|
1057
|
+
],
|
|
1058
|
+
isError: true
|
|
1059
|
+
};
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// src/tools/sca.ts
|
|
1066
|
+
import { z as z5 } from "zod";
|
|
1067
|
+
function registerScaTools(server, client) {
|
|
1068
|
+
server.tool(
|
|
1069
|
+
"get_sca_policies",
|
|
1070
|
+
"List Security Configuration Assessment (SCA) policies evaluated on a Wazuh agent",
|
|
1071
|
+
{
|
|
1072
|
+
agent_id: z5.string().describe("Agent identifier (e.g., '001')")
|
|
1073
|
+
},
|
|
1074
|
+
async ({ agent_id }) => {
|
|
1075
|
+
try {
|
|
1076
|
+
const response = await client.getScaPolicies(agent_id);
|
|
1077
|
+
const data = response.data;
|
|
1078
|
+
const result = {
|
|
1079
|
+
agent_id,
|
|
1080
|
+
policies: data.affected_items.map((policy) => ({
|
|
1081
|
+
policy_id: policy.policy_id,
|
|
1082
|
+
name: policy.name,
|
|
1083
|
+
description: policy.description,
|
|
1084
|
+
score: policy.score,
|
|
1085
|
+
pass: policy.pass,
|
|
1086
|
+
fail: policy.fail,
|
|
1087
|
+
invalid: policy.invalid,
|
|
1088
|
+
total_checks: policy.total_checks,
|
|
1089
|
+
hash_file: policy.hash_file,
|
|
1090
|
+
end_scan: policy.end_scan
|
|
1091
|
+
})),
|
|
1092
|
+
total: data.total_affected_items
|
|
1093
|
+
};
|
|
1094
|
+
return {
|
|
1095
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1096
|
+
};
|
|
1097
|
+
} catch (error) {
|
|
1098
|
+
return {
|
|
1099
|
+
content: [
|
|
1100
|
+
{
|
|
1101
|
+
type: "text",
|
|
1102
|
+
text: JSON.stringify({
|
|
1103
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1104
|
+
})
|
|
1105
|
+
}
|
|
1106
|
+
],
|
|
1107
|
+
isError: true
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
);
|
|
1112
|
+
server.tool(
|
|
1113
|
+
"get_sca_checks",
|
|
1114
|
+
"Get individual check results for a specific SCA policy on a Wazuh agent",
|
|
1115
|
+
{
|
|
1116
|
+
agent_id: z5.string().describe("Agent identifier (e.g., '001')"),
|
|
1117
|
+
policy_id: z5.string().describe("SCA policy identifier (e.g., 'cis_debian10')"),
|
|
1118
|
+
result: z5.enum(["passed", "failed", "not applicable"]).optional().describe("Filter by check result: passed, failed, or not applicable"),
|
|
1119
|
+
limit: z5.number().int().min(1).max(500).default(25).describe("Maximum number of checks to return (1-500)"),
|
|
1120
|
+
offset: z5.number().int().min(0).default(0).describe("Pagination offset")
|
|
1121
|
+
},
|
|
1122
|
+
async ({ agent_id, policy_id, result, limit, offset }) => {
|
|
1123
|
+
try {
|
|
1124
|
+
const params = { limit, offset };
|
|
1125
|
+
if (result) params.result = result;
|
|
1126
|
+
const response = await client.getScaChecks(agent_id, policy_id, params);
|
|
1127
|
+
const data = response.data;
|
|
1128
|
+
const mapped = {
|
|
1129
|
+
agent_id,
|
|
1130
|
+
policy_id,
|
|
1131
|
+
checks: data.affected_items.map((check) => ({
|
|
1132
|
+
id: check.id,
|
|
1133
|
+
title: check.title,
|
|
1134
|
+
description: check.description,
|
|
1135
|
+
rationale: check.rationale,
|
|
1136
|
+
remediation: check.remediation,
|
|
1137
|
+
result: check.result,
|
|
1138
|
+
condition: check.condition,
|
|
1139
|
+
command: check.command,
|
|
1140
|
+
references: check.references,
|
|
1141
|
+
compliance: check.compliance,
|
|
1142
|
+
reason: check.reason
|
|
1143
|
+
})),
|
|
1144
|
+
total: data.total_affected_items,
|
|
1145
|
+
limit,
|
|
1146
|
+
offset
|
|
1147
|
+
};
|
|
1148
|
+
return {
|
|
1149
|
+
content: [{ type: "text", text: JSON.stringify(mapped, null, 2) }]
|
|
1150
|
+
};
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
return {
|
|
1153
|
+
content: [
|
|
1154
|
+
{
|
|
1155
|
+
type: "text",
|
|
1156
|
+
text: JSON.stringify({
|
|
1157
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1158
|
+
})
|
|
1159
|
+
}
|
|
1160
|
+
],
|
|
1161
|
+
isError: true
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
);
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
// src/tools/syscollector.ts
|
|
1169
|
+
import { z as z6 } from "zod";
|
|
1170
|
+
function registerSyscollectorTools(server, client) {
|
|
1171
|
+
server.tool(
|
|
1172
|
+
"get_agent_os",
|
|
1173
|
+
"Get operating system information collected from a Wazuh agent",
|
|
1174
|
+
{
|
|
1175
|
+
agent_id: z6.string().describe("Agent identifier (e.g., '001')")
|
|
1176
|
+
},
|
|
1177
|
+
async ({ agent_id }) => {
|
|
1178
|
+
try {
|
|
1179
|
+
const response = await client.getAgentOs(agent_id);
|
|
1180
|
+
const items = response.data.affected_items;
|
|
1181
|
+
const result = {
|
|
1182
|
+
agent_id,
|
|
1183
|
+
os: items[0] ?? null
|
|
1184
|
+
};
|
|
1185
|
+
return {
|
|
1186
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1187
|
+
};
|
|
1188
|
+
} catch (error) {
|
|
1189
|
+
return {
|
|
1190
|
+
content: [
|
|
1191
|
+
{
|
|
1192
|
+
type: "text",
|
|
1193
|
+
text: JSON.stringify({
|
|
1194
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1195
|
+
})
|
|
1196
|
+
}
|
|
1197
|
+
],
|
|
1198
|
+
isError: true
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
);
|
|
1203
|
+
server.tool(
|
|
1204
|
+
"get_agent_packages",
|
|
1205
|
+
"List software packages installed on a Wazuh agent",
|
|
1206
|
+
{
|
|
1207
|
+
agent_id: z6.string().describe("Agent identifier (e.g., '001')"),
|
|
1208
|
+
limit: z6.number().int().min(1).max(500).default(25).describe("Maximum number of packages to return (1-500)"),
|
|
1209
|
+
offset: z6.number().int().min(0).default(0).describe("Pagination offset"),
|
|
1210
|
+
search: z6.string().optional().describe("Filter packages by name")
|
|
1211
|
+
},
|
|
1212
|
+
async ({ agent_id, limit, offset, search }) => {
|
|
1213
|
+
try {
|
|
1214
|
+
const params = { limit, offset };
|
|
1215
|
+
if (search) params.search = search;
|
|
1216
|
+
const response = await client.getAgentPackages(agent_id, params);
|
|
1217
|
+
const data = response.data;
|
|
1218
|
+
const result = {
|
|
1219
|
+
agent_id,
|
|
1220
|
+
packages: data.affected_items.map((pkg) => ({
|
|
1221
|
+
name: pkg.name,
|
|
1222
|
+
version: pkg.version,
|
|
1223
|
+
architecture: pkg.architecture,
|
|
1224
|
+
description: pkg.description,
|
|
1225
|
+
format: pkg.format,
|
|
1226
|
+
vendor: pkg.vendor,
|
|
1227
|
+
install_time: pkg.install_time,
|
|
1228
|
+
size: pkg.size
|
|
1229
|
+
})),
|
|
1230
|
+
total: data.total_affected_items,
|
|
1231
|
+
limit,
|
|
1232
|
+
offset
|
|
1233
|
+
};
|
|
1234
|
+
return {
|
|
1235
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1236
|
+
};
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
return {
|
|
1239
|
+
content: [
|
|
1240
|
+
{
|
|
1241
|
+
type: "text",
|
|
1242
|
+
text: JSON.stringify({
|
|
1243
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1244
|
+
})
|
|
1245
|
+
}
|
|
1246
|
+
],
|
|
1247
|
+
isError: true
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
server.tool(
|
|
1253
|
+
"get_agent_processes",
|
|
1254
|
+
"List running processes on a Wazuh agent",
|
|
1255
|
+
{
|
|
1256
|
+
agent_id: z6.string().describe("Agent identifier (e.g., '001')"),
|
|
1257
|
+
limit: z6.number().int().min(1).max(500).default(25).describe("Maximum number of processes to return (1-500)"),
|
|
1258
|
+
offset: z6.number().int().min(0).default(0).describe("Pagination offset"),
|
|
1259
|
+
search: z6.string().optional().describe("Filter processes by name or command")
|
|
1260
|
+
},
|
|
1261
|
+
async ({ agent_id, limit, offset, search }) => {
|
|
1262
|
+
try {
|
|
1263
|
+
const params = { limit, offset };
|
|
1264
|
+
if (search) params.search = search;
|
|
1265
|
+
const response = await client.getAgentProcesses(agent_id, params);
|
|
1266
|
+
const data = response.data;
|
|
1267
|
+
const result = {
|
|
1268
|
+
agent_id,
|
|
1269
|
+
processes: data.affected_items.map((proc) => ({
|
|
1270
|
+
pid: proc.pid,
|
|
1271
|
+
name: proc.name,
|
|
1272
|
+
state: proc.state,
|
|
1273
|
+
ppid: proc.ppid,
|
|
1274
|
+
cmd: proc.cmd,
|
|
1275
|
+
argvs: proc.argvs,
|
|
1276
|
+
euser: proc.euser,
|
|
1277
|
+
vm_size: proc.vm_size
|
|
1278
|
+
})),
|
|
1279
|
+
total: data.total_affected_items,
|
|
1280
|
+
limit,
|
|
1281
|
+
offset
|
|
1282
|
+
};
|
|
1283
|
+
return {
|
|
1284
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1285
|
+
};
|
|
1286
|
+
} catch (error) {
|
|
1287
|
+
return {
|
|
1288
|
+
content: [
|
|
1289
|
+
{
|
|
1290
|
+
type: "text",
|
|
1291
|
+
text: JSON.stringify({
|
|
1292
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1293
|
+
})
|
|
1294
|
+
}
|
|
1295
|
+
],
|
|
1296
|
+
isError: true
|
|
1297
|
+
};
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
);
|
|
1301
|
+
server.tool(
|
|
1302
|
+
"get_agent_ports",
|
|
1303
|
+
"List open network ports on a Wazuh agent",
|
|
1304
|
+
{
|
|
1305
|
+
agent_id: z6.string().describe("Agent identifier (e.g., '001')"),
|
|
1306
|
+
limit: z6.number().int().min(1).max(500).default(25).describe("Maximum number of ports to return (1-500)"),
|
|
1307
|
+
offset: z6.number().int().min(0).default(0).describe("Pagination offset")
|
|
1308
|
+
},
|
|
1309
|
+
async ({ agent_id, limit, offset }) => {
|
|
1310
|
+
try {
|
|
1311
|
+
const response = await client.getAgentPorts(agent_id, { limit, offset });
|
|
1312
|
+
const data = response.data;
|
|
1313
|
+
const result = {
|
|
1314
|
+
agent_id,
|
|
1315
|
+
ports: data.affected_items.map((port) => ({
|
|
1316
|
+
protocol: port.protocol,
|
|
1317
|
+
local_ip: port.local_ip,
|
|
1318
|
+
local_port: port.local_port,
|
|
1319
|
+
remote_ip: port.remote_ip,
|
|
1320
|
+
remote_port: port.remote_port,
|
|
1321
|
+
state: port.state,
|
|
1322
|
+
pid: port.pid,
|
|
1323
|
+
process: port.process
|
|
1324
|
+
})),
|
|
1325
|
+
total: data.total_affected_items,
|
|
1326
|
+
limit,
|
|
1327
|
+
offset
|
|
1328
|
+
};
|
|
1329
|
+
return {
|
|
1330
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1331
|
+
};
|
|
1332
|
+
} catch (error) {
|
|
1333
|
+
return {
|
|
1334
|
+
content: [
|
|
1335
|
+
{
|
|
1336
|
+
type: "text",
|
|
1337
|
+
text: JSON.stringify({
|
|
1338
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1339
|
+
})
|
|
1340
|
+
}
|
|
1341
|
+
],
|
|
1342
|
+
isError: true
|
|
1343
|
+
};
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
);
|
|
1347
|
+
server.tool(
|
|
1348
|
+
"get_agent_network",
|
|
1349
|
+
"List network interfaces and their IP addresses on a Wazuh agent",
|
|
1350
|
+
{
|
|
1351
|
+
agent_id: z6.string().describe("Agent identifier (e.g., '001')")
|
|
1352
|
+
},
|
|
1353
|
+
async ({ agent_id }) => {
|
|
1354
|
+
try {
|
|
1355
|
+
const response = await client.getAgentNetwork(agent_id);
|
|
1356
|
+
const data = response.data;
|
|
1357
|
+
const result = {
|
|
1358
|
+
agent_id,
|
|
1359
|
+
interfaces: data.affected_items.map((iface) => ({
|
|
1360
|
+
name: iface.name,
|
|
1361
|
+
type: iface.type,
|
|
1362
|
+
state: iface.state,
|
|
1363
|
+
mac: iface.mac,
|
|
1364
|
+
mtu: iface.mtu,
|
|
1365
|
+
ipv4: iface.ipv4,
|
|
1366
|
+
ipv6: iface.ipv6,
|
|
1367
|
+
tx_packets: iface.tx_packets,
|
|
1368
|
+
rx_packets: iface.rx_packets
|
|
1369
|
+
})),
|
|
1370
|
+
total: data.total_affected_items
|
|
1371
|
+
};
|
|
1372
|
+
return {
|
|
1373
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1374
|
+
};
|
|
1375
|
+
} catch (error) {
|
|
1376
|
+
return {
|
|
1377
|
+
content: [
|
|
1378
|
+
{
|
|
1379
|
+
type: "text",
|
|
1380
|
+
text: JSON.stringify({
|
|
1381
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1382
|
+
})
|
|
1383
|
+
}
|
|
1384
|
+
],
|
|
1385
|
+
isError: true
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
);
|
|
1390
|
+
server.tool(
|
|
1391
|
+
"get_agent_hotfixes",
|
|
1392
|
+
"List Windows hotfixes/patches installed on a Wazuh agent",
|
|
1393
|
+
{
|
|
1394
|
+
agent_id: z6.string().describe("Agent identifier (e.g., '001')"),
|
|
1395
|
+
limit: z6.number().int().min(1).max(500).default(25).describe("Maximum number of hotfixes to return (1-500)"),
|
|
1396
|
+
offset: z6.number().int().min(0).default(0).describe("Pagination offset")
|
|
1397
|
+
},
|
|
1398
|
+
async ({ agent_id, limit, offset }) => {
|
|
1399
|
+
try {
|
|
1400
|
+
const response = await client.getAgentHotfixes(agent_id, { limit, offset });
|
|
1401
|
+
const data = response.data;
|
|
1402
|
+
const result = {
|
|
1403
|
+
agent_id,
|
|
1404
|
+
hotfixes: data.affected_items.map((h) => h.hotfix),
|
|
1405
|
+
total: data.total_affected_items,
|
|
1406
|
+
limit,
|
|
1407
|
+
offset
|
|
1408
|
+
};
|
|
1409
|
+
return {
|
|
1410
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1411
|
+
};
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
return {
|
|
1414
|
+
content: [
|
|
1415
|
+
{
|
|
1416
|
+
type: "text",
|
|
1417
|
+
text: JSON.stringify({
|
|
1418
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1419
|
+
})
|
|
1420
|
+
}
|
|
1421
|
+
],
|
|
1422
|
+
isError: true
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
// src/tools/rootcheck.ts
|
|
1430
|
+
import { z as z7 } from "zod";
|
|
1431
|
+
function registerRootcheckTools(server, client) {
|
|
1432
|
+
server.tool(
|
|
1433
|
+
"get_rootcheck",
|
|
1434
|
+
"Get rootkit detection scan results for a Wazuh agent",
|
|
1435
|
+
{
|
|
1436
|
+
agent_id: z7.string().describe("Agent identifier (e.g., '001')"),
|
|
1437
|
+
limit: z7.number().int().min(1).max(100).default(25).describe("Maximum number of results to return (1-100)"),
|
|
1438
|
+
offset: z7.number().int().min(0).default(0).describe("Pagination offset"),
|
|
1439
|
+
status: z7.enum(["outstanding", "solved"]).optional().describe("Filter by status: outstanding (active findings) or solved")
|
|
1440
|
+
},
|
|
1441
|
+
async ({ agent_id, limit, offset, status }) => {
|
|
1442
|
+
try {
|
|
1443
|
+
const params = { limit, offset };
|
|
1444
|
+
if (status) params.status = status;
|
|
1445
|
+
const response = await client.getRootcheck(agent_id, params);
|
|
1446
|
+
const data = response.data;
|
|
1447
|
+
const result = {
|
|
1448
|
+
agent_id,
|
|
1449
|
+
findings: data.affected_items.map((item) => ({
|
|
1450
|
+
status: item.status,
|
|
1451
|
+
event: item.event,
|
|
1452
|
+
day: item.day,
|
|
1453
|
+
old_day: item.old_day,
|
|
1454
|
+
cis: item.cis,
|
|
1455
|
+
pci_dss: item.pci_dss
|
|
1456
|
+
})),
|
|
1457
|
+
total: data.total_affected_items,
|
|
1458
|
+
limit,
|
|
1459
|
+
offset
|
|
1460
|
+
};
|
|
1461
|
+
return {
|
|
1462
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1463
|
+
};
|
|
1464
|
+
} catch (error) {
|
|
1465
|
+
return {
|
|
1466
|
+
content: [
|
|
1467
|
+
{
|
|
1468
|
+
type: "text",
|
|
1469
|
+
text: JSON.stringify({
|
|
1470
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1471
|
+
})
|
|
1472
|
+
}
|
|
1473
|
+
],
|
|
1474
|
+
isError: true
|
|
1475
|
+
};
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// src/tools/syscheck.ts
|
|
1482
|
+
import { z as z8 } from "zod";
|
|
1483
|
+
function registerSyscheckTools(server, client) {
|
|
1484
|
+
server.tool(
|
|
1485
|
+
"get_fim_files",
|
|
1486
|
+
"Get File Integrity Monitoring (FIM) results for a Wazuh agent \u2014 shows monitored files, registry keys, and detected changes",
|
|
1487
|
+
{
|
|
1488
|
+
agent_id: z8.string().describe("Agent identifier (e.g., '001')"),
|
|
1489
|
+
limit: z8.number().int().min(1).max(500).default(25).describe("Maximum number of results to return (1-500)"),
|
|
1490
|
+
offset: z8.number().int().min(0).default(0).describe("Pagination offset"),
|
|
1491
|
+
search: z8.string().optional().describe("Filter by file path or name"),
|
|
1492
|
+
type: z8.enum(["file", "registry_key", "registry_value"]).optional().describe("Filter by entry type: file, registry_key, or registry_value")
|
|
1493
|
+
},
|
|
1494
|
+
async ({ agent_id, limit, offset, search, type }) => {
|
|
1495
|
+
try {
|
|
1496
|
+
const params = { limit, offset };
|
|
1497
|
+
if (search) params.search = search;
|
|
1498
|
+
if (type) params.type = type;
|
|
1499
|
+
const response = await client.getFimFiles(agent_id, params);
|
|
1500
|
+
const data = response.data;
|
|
1501
|
+
const result = {
|
|
1502
|
+
agent_id,
|
|
1503
|
+
files: data.affected_items.map((entry) => ({
|
|
1504
|
+
file: entry.file,
|
|
1505
|
+
type: entry.type,
|
|
1506
|
+
date: entry.date,
|
|
1507
|
+
mtime: entry.mtime,
|
|
1508
|
+
size: entry.size,
|
|
1509
|
+
perm: entry.perm,
|
|
1510
|
+
uname: entry.uname,
|
|
1511
|
+
gname: entry.gname,
|
|
1512
|
+
md5: entry.md5,
|
|
1513
|
+
sha256: entry.sha256,
|
|
1514
|
+
changed_attributes: entry.changed_attributes
|
|
1515
|
+
})),
|
|
1516
|
+
total: data.total_affected_items,
|
|
1517
|
+
limit,
|
|
1518
|
+
offset
|
|
1519
|
+
};
|
|
1520
|
+
return {
|
|
1521
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1522
|
+
};
|
|
1523
|
+
} catch (error) {
|
|
1524
|
+
return {
|
|
1525
|
+
content: [
|
|
1526
|
+
{
|
|
1527
|
+
type: "text",
|
|
1528
|
+
text: JSON.stringify({
|
|
1529
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1530
|
+
})
|
|
1531
|
+
}
|
|
1532
|
+
],
|
|
1533
|
+
isError: true
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// src/tools/manager.ts
|
|
1541
|
+
import { z as z9 } from "zod";
|
|
1542
|
+
function registerManagerTools(server, client) {
|
|
1543
|
+
server.tool(
|
|
1544
|
+
"get_manager_logs",
|
|
1545
|
+
"Retrieve Wazuh manager logs with optional filtering by severity level or module tag",
|
|
1546
|
+
{
|
|
1547
|
+
limit: z9.number().int().min(1).max(100).default(25).describe("Maximum number of log entries to return (1-100)"),
|
|
1548
|
+
offset: z9.number().int().min(0).default(0).describe("Pagination offset"),
|
|
1549
|
+
level: z9.enum(["info", "warning", "error", "critical", "debug"]).optional().describe("Filter by log severity level"),
|
|
1550
|
+
tag: z9.string().optional().describe("Filter by module/tag name (e.g., 'wazuh-modulesd', 'ossec-analysisd')")
|
|
1551
|
+
},
|
|
1552
|
+
async ({ limit, offset, level, tag }) => {
|
|
1553
|
+
try {
|
|
1554
|
+
const params = { limit, offset };
|
|
1555
|
+
if (level) params.level = level;
|
|
1556
|
+
if (tag) params.tag = tag;
|
|
1557
|
+
const response = await client.getManagerLogs(params);
|
|
1558
|
+
const data = response.data;
|
|
1559
|
+
const result = {
|
|
1560
|
+
logs: data.affected_items.map((entry) => ({
|
|
1561
|
+
timestamp: entry.timestamp,
|
|
1562
|
+
tag: entry.tag,
|
|
1563
|
+
level: entry.level,
|
|
1564
|
+
description: entry.description
|
|
1565
|
+
})),
|
|
1566
|
+
total: data.total_affected_items,
|
|
1567
|
+
limit,
|
|
1568
|
+
offset
|
|
1569
|
+
};
|
|
1570
|
+
return {
|
|
1571
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1572
|
+
};
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
return {
|
|
1575
|
+
content: [
|
|
1576
|
+
{
|
|
1577
|
+
type: "text",
|
|
1578
|
+
text: JSON.stringify({
|
|
1579
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1580
|
+
})
|
|
1581
|
+
}
|
|
1582
|
+
],
|
|
1583
|
+
isError: true
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
);
|
|
1588
|
+
server.tool(
|
|
1589
|
+
"get_manager_config",
|
|
1590
|
+
"Get the active Wazuh manager configuration for a specific section",
|
|
1591
|
+
{
|
|
1592
|
+
section: z9.string().optional().describe(
|
|
1593
|
+
"Configuration section to retrieve (e.g., 'vulnerability-detection', 'syscheck', 'rootcheck', 'alerts'). Omit to get the full configuration."
|
|
1594
|
+
)
|
|
1595
|
+
},
|
|
1596
|
+
async ({ section }) => {
|
|
1597
|
+
try {
|
|
1598
|
+
const params = {};
|
|
1599
|
+
if (section) params.section = section;
|
|
1600
|
+
const response = await client.getManagerConfig(params);
|
|
1601
|
+
return {
|
|
1602
|
+
content: [{ type: "text", text: JSON.stringify(response.data, null, 2) }]
|
|
1603
|
+
};
|
|
1604
|
+
} catch (error) {
|
|
1605
|
+
return {
|
|
1606
|
+
content: [
|
|
1607
|
+
{
|
|
1608
|
+
type: "text",
|
|
1609
|
+
text: JSON.stringify({
|
|
1610
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1611
|
+
})
|
|
1612
|
+
}
|
|
1613
|
+
],
|
|
1614
|
+
isError: true
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// src/tools/groups.ts
|
|
1622
|
+
import { z as z10 } from "zod";
|
|
1623
|
+
function registerGroupTools(server, client) {
|
|
1624
|
+
server.tool(
|
|
1625
|
+
"list_groups",
|
|
1626
|
+
"List all Wazuh agent groups",
|
|
1627
|
+
{
|
|
1628
|
+
limit: z10.number().int().min(1).max(100).default(25).describe("Maximum number of groups to return (1-100)"),
|
|
1629
|
+
offset: z10.number().int().min(0).default(0).describe("Pagination offset")
|
|
1630
|
+
},
|
|
1631
|
+
async ({ limit, offset }) => {
|
|
1632
|
+
try {
|
|
1633
|
+
const response = await client.getGroups({ limit, offset });
|
|
1634
|
+
const data = response.data;
|
|
1635
|
+
const result = {
|
|
1636
|
+
groups: data.affected_items.map((group) => ({
|
|
1637
|
+
name: group.name,
|
|
1638
|
+
count: group.count,
|
|
1639
|
+
config_sum: group.configSum,
|
|
1640
|
+
merged_sum: group.mergedSum
|
|
1641
|
+
})),
|
|
1642
|
+
total: data.total_affected_items,
|
|
1643
|
+
limit,
|
|
1644
|
+
offset
|
|
1645
|
+
};
|
|
1646
|
+
return {
|
|
1647
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1648
|
+
};
|
|
1649
|
+
} catch (error) {
|
|
1650
|
+
return {
|
|
1651
|
+
content: [
|
|
1652
|
+
{
|
|
1653
|
+
type: "text",
|
|
1654
|
+
text: JSON.stringify({
|
|
1655
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1656
|
+
})
|
|
1657
|
+
}
|
|
1658
|
+
],
|
|
1659
|
+
isError: true
|
|
1660
|
+
};
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
);
|
|
1664
|
+
server.tool(
|
|
1665
|
+
"get_group_agents",
|
|
1666
|
+
"List agents belonging to a specific Wazuh group",
|
|
1667
|
+
{
|
|
1668
|
+
group_id: z10.string().describe("Group name/identifier (e.g., 'default', 'linux-servers')"),
|
|
1669
|
+
limit: z10.number().int().min(1).max(100).default(25).describe("Maximum number of agents to return (1-100)"),
|
|
1670
|
+
offset: z10.number().int().min(0).default(0).describe("Pagination offset")
|
|
1671
|
+
},
|
|
1672
|
+
async ({ group_id, limit, offset }) => {
|
|
1673
|
+
try {
|
|
1674
|
+
const response = await client.getGroupAgents(group_id, { limit, offset });
|
|
1675
|
+
const data = response.data;
|
|
1676
|
+
const result = {
|
|
1677
|
+
group_id,
|
|
1678
|
+
agents: data.affected_items.map((agent) => ({
|
|
1679
|
+
id: agent.id,
|
|
1680
|
+
name: agent.name,
|
|
1681
|
+
ip: agent.ip,
|
|
1682
|
+
status: agent.status,
|
|
1683
|
+
os_name: agent.os?.name,
|
|
1684
|
+
os_platform: agent.os?.platform,
|
|
1685
|
+
version: agent.version,
|
|
1686
|
+
last_keepalive: agent.lastKeepAlive
|
|
1687
|
+
})),
|
|
1688
|
+
total: data.total_affected_items,
|
|
1689
|
+
limit,
|
|
1690
|
+
offset
|
|
1691
|
+
};
|
|
1692
|
+
return {
|
|
1693
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1694
|
+
};
|
|
1695
|
+
} catch (error) {
|
|
1696
|
+
return {
|
|
1697
|
+
content: [
|
|
1698
|
+
{
|
|
1699
|
+
type: "text",
|
|
1700
|
+
text: JSON.stringify({
|
|
1701
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1702
|
+
})
|
|
1703
|
+
}
|
|
1704
|
+
],
|
|
1705
|
+
isError: true
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
// src/resources.ts
|
|
1713
|
+
function registerResources(server, client, indexerClient) {
|
|
1714
|
+
server.resource(
|
|
1715
|
+
"wazuh-agents",
|
|
1716
|
+
"wazuh://agents",
|
|
1717
|
+
{
|
|
1718
|
+
description: "List of all registered Wazuh agents and their current status",
|
|
1719
|
+
mimeType: "application/json"
|
|
1720
|
+
},
|
|
1721
|
+
async () => {
|
|
1722
|
+
const response = await client.getAgents({ limit: 100 });
|
|
1723
|
+
const agents = response.data.affected_items.map((agent) => ({
|
|
1724
|
+
id: agent.id,
|
|
1725
|
+
name: agent.name,
|
|
1726
|
+
ip: agent.ip,
|
|
1727
|
+
status: agent.status,
|
|
1728
|
+
group: agent.group,
|
|
1729
|
+
os: agent.os?.name,
|
|
1730
|
+
version: agent.version,
|
|
1731
|
+
last_keepalive: agent.lastKeepAlive
|
|
1732
|
+
}));
|
|
1733
|
+
return {
|
|
1734
|
+
contents: [
|
|
1735
|
+
{
|
|
1736
|
+
uri: "wazuh://agents",
|
|
1737
|
+
mimeType: "application/json",
|
|
1738
|
+
text: JSON.stringify(
|
|
1739
|
+
{ agents, total: response.data.total_affected_items },
|
|
1740
|
+
null,
|
|
1741
|
+
2
|
|
1742
|
+
)
|
|
1743
|
+
}
|
|
1744
|
+
]
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
);
|
|
1748
|
+
server.resource(
|
|
1749
|
+
"wazuh-alerts-recent",
|
|
1750
|
+
"wazuh://alerts/recent",
|
|
1751
|
+
{
|
|
1752
|
+
description: "Recent security alerts from Wazuh (last 25)",
|
|
1753
|
+
mimeType: "application/json"
|
|
1754
|
+
},
|
|
1755
|
+
async () => {
|
|
1756
|
+
if (!indexerClient) {
|
|
1757
|
+
return {
|
|
1758
|
+
contents: [
|
|
1759
|
+
{
|
|
1760
|
+
uri: "wazuh://alerts/recent",
|
|
1761
|
+
mimeType: "application/json",
|
|
1762
|
+
text: JSON.stringify({
|
|
1763
|
+
error: "Alerts require WAZUH_INDEXER_URL configuration. Wazuh 4.x stores alerts in the Wazuh Indexer (OpenSearch), not the REST API."
|
|
1764
|
+
})
|
|
1765
|
+
}
|
|
1766
|
+
]
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
const { alerts: rawAlerts, total } = await indexerClient.getRecentAlerts(25, 0);
|
|
1770
|
+
const alerts = rawAlerts.map((alert) => ({
|
|
1771
|
+
id: alert.id,
|
|
1772
|
+
timestamp: alert.timestamp,
|
|
1773
|
+
rule_id: alert.rule?.id,
|
|
1774
|
+
rule_level: alert.rule?.level,
|
|
1775
|
+
rule_description: alert.rule?.description,
|
|
1776
|
+
agent_id: alert.agent?.id,
|
|
1777
|
+
agent_name: alert.agent?.name
|
|
1778
|
+
}));
|
|
1779
|
+
return {
|
|
1780
|
+
contents: [
|
|
1781
|
+
{
|
|
1782
|
+
uri: "wazuh://alerts/recent",
|
|
1783
|
+
mimeType: "application/json",
|
|
1784
|
+
text: JSON.stringify({ alerts, total }, null, 2)
|
|
1785
|
+
}
|
|
1786
|
+
]
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1789
|
+
);
|
|
1790
|
+
server.resource(
|
|
1791
|
+
"wazuh-rules-summary",
|
|
1792
|
+
"wazuh://rules/summary",
|
|
1793
|
+
{
|
|
1794
|
+
description: "Summary of Wazuh detection rules by severity level",
|
|
1795
|
+
mimeType: "application/json"
|
|
1796
|
+
},
|
|
1797
|
+
async () => {
|
|
1798
|
+
const response = await client.getRules({ limit: 100, sort: "-level" });
|
|
1799
|
+
const rules = response.data.affected_items.map((rule) => ({
|
|
1800
|
+
id: rule.id,
|
|
1801
|
+
description: rule.description,
|
|
1802
|
+
level: rule.level,
|
|
1803
|
+
groups: rule.groups
|
|
1804
|
+
}));
|
|
1805
|
+
return {
|
|
1806
|
+
contents: [
|
|
1807
|
+
{
|
|
1808
|
+
uri: "wazuh://rules/summary",
|
|
1809
|
+
mimeType: "application/json",
|
|
1810
|
+
text: JSON.stringify(
|
|
1811
|
+
{ rules, total: response.data.total_affected_items },
|
|
1812
|
+
null,
|
|
1813
|
+
2
|
|
1814
|
+
)
|
|
1815
|
+
}
|
|
1816
|
+
]
|
|
1817
|
+
};
|
|
1818
|
+
}
|
|
1819
|
+
);
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
// src/prompts.ts
|
|
1823
|
+
import { z as z11 } from "zod";
|
|
1824
|
+
function registerPrompts(server) {
|
|
1825
|
+
server.prompt(
|
|
1826
|
+
"investigate-alert",
|
|
1827
|
+
"Investigate a Wazuh security alert and provide analysis with remediation steps",
|
|
1828
|
+
{ alert_id: z11.string().describe("The alert ID to investigate") },
|
|
1829
|
+
({ alert_id }) => ({
|
|
1830
|
+
messages: [
|
|
1831
|
+
{
|
|
1832
|
+
role: "user",
|
|
1833
|
+
content: {
|
|
1834
|
+
type: "text",
|
|
1835
|
+
text: [
|
|
1836
|
+
`Investigate Wazuh alert ${alert_id}. Please:`,
|
|
1837
|
+
"",
|
|
1838
|
+
"1. Use get_alert to retrieve the full alert details",
|
|
1839
|
+
"2. Use get_rule to look up the triggered rule",
|
|
1840
|
+
"3. Use get_agent to check the affected agent's status",
|
|
1841
|
+
"4. Analyze the alert context (MITRE ATT&CK mapping, compliance frameworks)",
|
|
1842
|
+
"5. Determine severity and potential impact",
|
|
1843
|
+
"6. Suggest specific remediation steps",
|
|
1844
|
+
"7. Recommend additional monitoring or detection rules if needed"
|
|
1845
|
+
].join("\n")
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
]
|
|
1849
|
+
})
|
|
1850
|
+
);
|
|
1851
|
+
server.prompt(
|
|
1852
|
+
"agent-health-check",
|
|
1853
|
+
"Perform a comprehensive health check on a Wazuh agent",
|
|
1854
|
+
{ agent_id: z11.string().describe("The agent ID to check") },
|
|
1855
|
+
({ agent_id }) => ({
|
|
1856
|
+
messages: [
|
|
1857
|
+
{
|
|
1858
|
+
role: "user",
|
|
1859
|
+
content: {
|
|
1860
|
+
type: "text",
|
|
1861
|
+
text: [
|
|
1862
|
+
`Perform a health check on Wazuh agent ${agent_id}. Please:`,
|
|
1863
|
+
"",
|
|
1864
|
+
"1. Use get_agent to retrieve agent details and connection status",
|
|
1865
|
+
"2. Use get_agent_stats to check CPU, memory, and disk usage",
|
|
1866
|
+
"3. Use get_alerts with agent_id filter to find recent alerts for this agent",
|
|
1867
|
+
"4. Identify any resource concerns (high CPU, low disk, memory pressure)",
|
|
1868
|
+
"5. Flag any critical or high-severity alerts",
|
|
1869
|
+
"6. Provide an overall health assessment and recommendations"
|
|
1870
|
+
].join("\n")
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
]
|
|
1874
|
+
})
|
|
1875
|
+
);
|
|
1876
|
+
server.prompt(
|
|
1877
|
+
"security-overview",
|
|
1878
|
+
"Generate a security overview of the Wazuh environment",
|
|
1879
|
+
{},
|
|
1880
|
+
() => ({
|
|
1881
|
+
messages: [
|
|
1882
|
+
{
|
|
1883
|
+
role: "user",
|
|
1884
|
+
content: {
|
|
1885
|
+
type: "text",
|
|
1886
|
+
text: [
|
|
1887
|
+
"Generate a security overview of the Wazuh environment. Please:",
|
|
1888
|
+
"",
|
|
1889
|
+
"1. Use get_wazuh_version to confirm the Wazuh version",
|
|
1890
|
+
"2. Use list_agents to see all agents and their status",
|
|
1891
|
+
"3. Use get_alerts with sort=-timestamp to find the most recent alerts",
|
|
1892
|
+
"4. Use get_alerts with level >= 12 to find critical alerts",
|
|
1893
|
+
"5. Summarize:",
|
|
1894
|
+
" - Total agents (active vs disconnected)",
|
|
1895
|
+
" - Alert volume and severity distribution",
|
|
1896
|
+
" - Top triggered rules",
|
|
1897
|
+
" - Any MITRE ATT&CK techniques detected",
|
|
1898
|
+
" - Compliance coverage (PCI-DSS, HIPAA, GDPR)",
|
|
1899
|
+
"6. Highlight any immediate concerns requiring attention"
|
|
1900
|
+
].join("\n")
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
]
|
|
1904
|
+
})
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
// src/index.ts
|
|
1909
|
+
async function main() {
|
|
1910
|
+
const config = getConfig();
|
|
1911
|
+
if (!config.verifySsl) {
|
|
1912
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
1913
|
+
}
|
|
1914
|
+
const client = new WazuhClient(config);
|
|
1915
|
+
const indexerClient = config.indexer ? new WazuhIndexerClient(config.indexer) : void 0;
|
|
1916
|
+
const server = new McpServer({
|
|
1917
|
+
name: "wazuh-mcp",
|
|
1918
|
+
version: "1.0.0",
|
|
1919
|
+
description: "MCP server for the Wazuh SIEM/XDR platform - query agents, alerts, rules, and decoders"
|
|
1920
|
+
});
|
|
1921
|
+
registerAgentTools(server, client);
|
|
1922
|
+
registerAlertTools(server, client, indexerClient);
|
|
1923
|
+
registerRuleTools(server, client);
|
|
1924
|
+
registerDecoderTools(server, client);
|
|
1925
|
+
registerVersionTools(server, client);
|
|
1926
|
+
registerScaTools(server, client);
|
|
1927
|
+
registerSyscollectorTools(server, client);
|
|
1928
|
+
registerRootcheckTools(server, client);
|
|
1929
|
+
registerSyscheckTools(server, client);
|
|
1930
|
+
registerManagerTools(server, client);
|
|
1931
|
+
registerGroupTools(server, client);
|
|
1932
|
+
registerResources(server, client, indexerClient);
|
|
1933
|
+
registerPrompts(server);
|
|
1934
|
+
const transport = new StdioServerTransport();
|
|
1935
|
+
await server.connect(transport);
|
|
1936
|
+
}
|
|
1937
|
+
main().catch((error) => {
|
|
1938
|
+
console.error("Fatal error:", error);
|
|
1939
|
+
process.exit(1);
|
|
1940
|
+
});
|
|
1941
|
+
//# sourceMappingURL=index.js.map
|