Skip to content

Commit e160888

Browse files
committed
several fixes and improvements around playing nice with MCP tool selection and parametrization.
1 parent b9327e0 commit e160888

2 files changed

Lines changed: 66 additions & 34 deletions

File tree

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ repository = "https://github.com/gbrigandi/mcp-server-wazuh"
99
readme = "README.md"
1010

1111
[dependencies]
12-
wazuh-client = "0.1.4"
12+
wazuh-client = "0.1.5"
1313
rmcp = { version = "0.1.5", features = ["server", "transport-io"] }
1414
tokio = { version = "1", features = ["full"] }
1515
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }

src/main.rs

Lines changed: 65 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ struct GetVulnerabilitySummaryParams {
124124
}
125125

126126
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
127-
struct GetRunningAgentsParams {
127+
struct GetAgentsParams {
128128
#[schemars(description = "Maximum number of agents to retrieve (default: 100)")]
129129
limit: Option<u32>,
130-
#[schemars(description = "Agent status filter (active, disconnected, pending, never_connected) (default: active)")]
131-
status: Option<String>,
130+
#[schemars(description = "Agent status filter (active, disconnected, pending, never_connected)")]
131+
status: String,
132132
#[schemars(description = "Agent name to search for (optional)")]
133133
name: Option<String>,
134134
#[schemars(description = "Agent IP address to filter by (optional)")]
@@ -159,14 +159,14 @@ struct GetAgentProcessesParams {
159159

160160
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
161161
struct GetAgentPortsParams {
162-
#[schemars(description = "Agent ID to get network ports for (required, e.g., \"0\", \"1\", \"001\")")]
162+
#[schemars(description = "Agent ID to get network ports for (required, e.g., \"001\", \"002\", \"003\")")]
163163
agent_id: String,
164164
#[schemars(description = "Maximum number of ports to retrieve (default: 100)")]
165165
limit: Option<u32>,
166-
#[schemars(description = "Protocol to filter by (e.g., \"tcp\", \"udp\") (optional)")]
167-
protocol: Option<String>,
168-
#[schemars(description = "State to filter by (e.g., \"LISTEN\", \"ESTABLISHED\") (optional)")]
169-
state: Option<String>,
166+
#[schemars(description = "Protocol to filter by (e.g., \"tcp\", \"udp\")")]
167+
protocol: String,
168+
#[schemars(description = "State to filter by (e.g., \"LISTENING\", \"ESTABLISHED\")")]
169+
state: String,
170170
}
171171

172172
#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
@@ -175,8 +175,8 @@ struct SearchManagerLogsParams {
175175
limit: Option<u32>,
176176
#[schemars(description = "Number of log entries to skip (default: 0)")]
177177
offset: Option<u32>,
178-
#[schemars(description = "Log level to filter by (e.g., \"error\", \"warning\", \"info\") (optional)")]
179-
level: Option<String>,
178+
#[schemars(description = "Log level to filter by (e.g., \"error\", \"warning\", \"info\")")]
179+
level: String,
180180
#[schemars(description = "Log tag to filter by (e.g., \"wazuh-modulesd\") (optional)")]
181181
tag: Option<String>,
182182
#[schemars(description = "Search term to filter log descriptions (optional)")]
@@ -801,20 +801,18 @@ impl WazuhToolsServer {
801801
}
802802

803803
#[tool(
804-
name = "get_wazuh_running_agents",
804+
name = "get_wazuh_agents",
805805
description = "Retrieves a list of Wazuh agents with their current status and details. Returns formatted agent information including ID, name, IP, status, OS details, and last activity. Supports filtering by status, name, IP, group, OS platform, and version."
806806
)]
807-
808-
async fn get_wazuh_running_agents(
807+
async fn get_wazuh_agents(
809808
&self,
810-
#[tool(aggr)] params: GetRunningAgentsParams,
809+
#[tool(aggr)] params: GetAgentsParams,
811810
) -> Result<CallToolResult, McpError> {
812811
let limit = params.limit.unwrap_or(100);
813-
let status = params.status.as_deref().unwrap_or("active"); // Default to active agents
814812

815813
tracing::info!(
816814
limit = %limit,
817-
status = %status,
815+
status = ?params.status,
818816
name = ?params.name,
819817
ip = ?params.ip,
820818
group = ?params.group,
@@ -831,7 +829,7 @@ impl WazuhToolsServer {
831829
None, // select
832830
None, // sort
833831
None, // search
834-
Some(status),
832+
Some(&params.status),
835833
None, // query
836834
None, // older_than
837835
params.os_platform.as_deref(),
@@ -851,7 +849,7 @@ impl WazuhToolsServer {
851849
if agents.is_empty() {
852850
tracing::info!("No Wazuh agents found matching criteria. Returning standard message.");
853851
return Ok(CallToolResult::success(vec![Content::text(
854-
format!("No Wazuh agents found matching the specified criteria (status: {}).", status),
852+
format!("No Wazuh agents found matching the specified criteria (status: {}).", &params.status),
855853
)]));
856854
}
857855

@@ -1232,7 +1230,7 @@ impl WazuhToolsServer {
12321230
match logs_client.get_manager_logs(
12331231
Some(limit),
12341232
Some(offset),
1235-
params.level.as_deref(),
1233+
Some(&params.level),
12361234
params.tag.as_deref(),
12371235
params.search_term.as_deref(),
12381236
).await {
@@ -1391,13 +1389,13 @@ impl WazuhToolsServer {
13911389
match logs_client.get_remoted_stats().await {
13921390
Ok(stats) => {
13931391
let formatted_text = format!(
1394-
"Wazuh Remoted Statistics:\nTotal Queue Size: {}\nTCP Sessions: {}\nEvent Count: {}\nControl Message Count: {}\nDiscarded Message Count: {}\nMessages Sent: {}\nBytes Received: {}\nDequeued After Close: {}",
1392+
"Wazuh Remoted Statistics:\nQueue Size: {}\nTotal Queue Size: {}\nTCP Sessions: {}\nControl Message Count: {}\nDiscarded Message Count: {}\nMessages Sent (Bytes): {}\nBytes Received: {}\nDequeued After Close: {}",
1393+
stats.queue_size,
13951394
stats.total_queue_size,
13961395
stats.tcp_sessions,
1397-
stats.evt_count,
1398-
stats.ctrl_count,
1396+
stats.ctrl_msg_count,
13991397
stats.discarded_count,
1400-
stats.msg_sent,
1398+
stats.sent_bytes,
14011399
stats.recv_bytes,
14021400
stats.dequeued_after_close
14031401
);
@@ -1451,15 +1449,45 @@ impl WazuhToolsServer {
14511449
&agent_id,
14521450
Some(limit * 2), // Fetch more to allow for client-side state filtering
14531451
Some(offset),
1454-
params.protocol.as_deref(),
1452+
Some(&params.protocol),
14551453
).await {
14561454
Ok(mut ports) => {
1457-
// Client-side filtering for state if provided
1458-
if let Some(state_filter) = &params.state {
1459-
ports.retain(|port| {
1460-
port.state.as_ref().is_some_and(|s| s.eq_ignore_ascii_case(state_filter))
1461-
});
1462-
}
1455+
let requested_state_is_listening = params.state.trim().eq_ignore_ascii_case("listening");
1456+
1457+
ports.retain(|port| {
1458+
tracing::debug!(
1459+
"Pre-filter port: {:?} (State: {:?}), requested_state_is_listening: {}",
1460+
port.inode, // Using inode for a concise port identifier in log
1461+
port.state,
1462+
requested_state_is_listening
1463+
);
1464+
let result = match port.state.as_ref().map(|s| s.trim()) {
1465+
Some(actual_port_state_str) => { // Port has a state string
1466+
if actual_port_state_str.is_empty() {
1467+
// Filter out ports where state is present but an empty string
1468+
false
1469+
} else if requested_state_is_listening {
1470+
// User requested "listening": keep only if actual state is "listening"
1471+
actual_port_state_str.eq_ignore_ascii_case("listening")
1472+
} else {
1473+
// User requested non-"listening": keep if actual state is not "listening"
1474+
!actual_port_state_str.eq_ignore_ascii_case("listening")
1475+
}
1476+
}
1477+
None => { // Port has no state (port.state is None)
1478+
if requested_state_is_listening {
1479+
// If user wants "listening" ports, a port with no state is not a match.
1480+
false
1481+
} else {
1482+
// If user wants non-"listening" ports, a port with no state is a match.
1483+
true
1484+
}
1485+
}
1486+
};
1487+
1488+
tracing::debug!("Post-filter decision for port: {:?}, Keep: {}", port.inode, result);
1489+
result
1490+
});
14631491

14641492
// Apply limit after client-side filtering
14651493
ports.truncate(limit as usize);
@@ -1477,11 +1505,11 @@ impl WazuhToolsServer {
14771505
.map(|port: WazuhPort| { // Explicitly type port
14781506
let mut details = vec![
14791507
format!("Protocol: {}", port.protocol),
1480-
format!("Local: {}:{}", port.local.ip, port.local.port),
1508+
format!("Local: {}:{}", port.local.ip.clone().unwrap_or("N/A".to_string()), port.local.port),
14811509
];
14821510

14831511
if let Some(remote) = &port.remote {
1484-
details.push(format!("Remote: {}:{}", remote.ip, remote.port));
1512+
details.push(format!("Remote: {}:{}", remote.ip.clone().unwrap_or("N/A".to_string()), remote.port));
14851513
}
14861514
if let Some(state) = &port.state {
14871515
details.push(format!("State: {}", state));
@@ -1589,7 +1617,11 @@ impl ServerHandler for WazuhToolsServer {
15891617
- 'get_wazuh_agent_processes': Retrieves a list of running processes for a specific Wazuh agent. \
15901618
Requires an 'agent_id' parameter (formatted as described for other agent-specific tools). Supports 'limit' (default 100) and 'search' (to filter by process name or command line) parameters.\n\
15911619
- 'get_wazuh_agent_ports': Retrieves a list of open network ports for a specific Wazuh agent. \
1592-
Requires an 'agent_id' parameter (formatted as described for other agent-specific tools). Supports 'limit' (default 100), 'protocol' (e.g., \"tcp\", \"udp\"), and 'state' (e.g., \"LISTEN\", \"ESTABLISHED\") parameters to filter the results. Note: State filtering is performed client-side by this server.\n\
1620+
Requires an 'agent_id' parameter (formatted as described for other agent-specific tools). Supports 'limit' (default 100), 'protocol' (e.g., \"tcp\", \"udp\"), and 'state' (e.g., \"LISTENING\", \"ESTABLISHED\") parameters to filter the results. Note: State filtering is performed client-side by this server.\n\
1621+
The 'state' parameter filters results:
1622+
- If 'state' is 'LISTENING' (case-insensitive): Only ports explicitly in the 'LISTENING' state are returned. Ports with other states, no state, or an empty state string are filtered out.
1623+
- If 'state' is any other value (e.g., 'ESTABLISHED'): Ports that are *not* in the 'LISTENING' state are returned. This includes ports with other defined states (like 'ESTABLISHED', 'TIME_WAIT', etc.) and ports that have *no state* defined. Ports with an empty state string are always filtered out.
1624+
Note: State filtering is performed client-side by this server. \
15931625
- 'search_wazuh_manager_logs': Searches Wazuh manager logs. \
15941626
Optional parameters: 'limit' (default 100), 'offset' (default 0), 'level' (e.g., \"error\", \"info\"), 'tag' (e.g., \"wazuh-modulesd\"), 'search_term' (for free-text search in log descriptions).\n\
15951627
- 'get_wazuh_manager_error_logs': Retrieves Wazuh manager error logs. \

0 commit comments

Comments
 (0)