Skip to content

Commit 136e418

Browse files
Merge pull request #65 from kanylbullen/feature/compact-output-mode
feat: compact output mode for token-efficient MCP responses
2 parents b0d2da6 + 4bf797e commit 136e418

File tree

1 file changed

+96
-10
lines changed

1 file changed

+96
-10
lines changed

src/wazuh_mcp_server/server.py

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,76 @@ def validate_protocol_version(version: Optional[str], strict: bool = False) -> s
485485

486486

487487
# MCP Protocol Handlers
488+
489+
def _compact_alert(alert: dict) -> dict:
490+
"""Strip a raw Wazuh alert to essential fields for MCP output."""
491+
compact = {}
492+
if "timestamp" in alert:
493+
compact["timestamp"] = alert["timestamp"]
494+
agent = alert.get("agent", {})
495+
if agent:
496+
compact["agent"] = {"id": agent.get("id", ""), "name": agent.get("name", "")}
497+
rule = alert.get("rule", {})
498+
if rule:
499+
compact["rule"] = {
500+
"id": rule.get("id", ""),
501+
"level": rule.get("level", 0),
502+
"description": rule.get("description", ""),
503+
"groups": rule.get("groups", []),
504+
}
505+
if rule.get("mitre"):
506+
compact["rule"]["mitre"] = rule["mitre"]
507+
src = alert.get("data", {})
508+
if src.get("srcip"):
509+
compact["srcip"] = src["srcip"]
510+
if src.get("dstip"):
511+
compact["dstip"] = src["dstip"]
512+
if alert.get("syscheck"):
513+
sc = alert["syscheck"]
514+
compact["syscheck"] = {"path": sc.get("path", ""), "event": sc.get("event", "")}
515+
if alert.get("full_log"):
516+
log = alert["full_log"]
517+
compact["full_log"] = (log[:300] + "...") if len(log) > 300 else log
518+
return compact
519+
520+
521+
def _compact_alerts_result(result: dict) -> dict:
522+
"""Apply compaction to a standard alerts result dict."""
523+
data = result.get("data", {})
524+
items = data.get("affected_items", [])
525+
data["affected_items"] = [_compact_alert(a) for a in items]
526+
return result
527+
528+
529+
def _compact_vulnerability(vuln: dict) -> dict:
530+
"""Strip a raw Wazuh vulnerability to essential fields for MCP output."""
531+
compact = {}
532+
for key in ("id", "severity"):
533+
if key in vuln:
534+
compact[key] = vuln[key]
535+
if "description" in vuln:
536+
desc = vuln["description"]
537+
compact["description"] = (desc[:120] + "...") if len(desc) > 120 else desc
538+
if "published_at" in vuln:
539+
compact["published_at"] = vuln["published_at"]
540+
pkg = vuln.get("package", {})
541+
if pkg:
542+
compact["package"] = {"name": pkg.get("name", ""), "version": pkg.get("version", "")}
543+
agent = vuln.get("agent", {})
544+
if agent:
545+
compact["agent"] = {"id": agent.get("id", ""), "name": agent.get("name", "")}
546+
return compact
547+
548+
549+
def _compact_vulns_result(result: dict) -> dict:
550+
"""Apply compaction to a standard vulnerabilities result dict."""
551+
data = result.get("data", {})
552+
items = data.get("affected_items", [])
553+
if items:
554+
data["affected_items"] = [_compact_vulnerability(v) for v in items]
555+
return result
556+
557+
488558
async def handle_initialize(params: Dict[str, Any], session: MCPSession) -> Dict[str, Any]:
489559
"""Handle MCP initialize method per MCP specification."""
490560
client_protocol_version = params.get("protocolVersion", "2025-03-26")
@@ -940,7 +1010,8 @@ async def handle_tools_list(params: Dict[str, Any], session: MCPSession) -> Dict
9401010
"level": {"type": "string", "description": "Filter by alert level (e.g., '12', '10+')"},
9411011
"agent_id": {"type": "string", "description": "Filter by agent ID"},
9421012
"timestamp_start": {"type": "string", "description": "Start timestamp (ISO format)"},
943-
"timestamp_end": {"type": "string", "description": "End timestamp (ISO format)"}
1013+
"timestamp_end": {"type": "string", "description": "End timestamp (ISO format)"},
1014+
"compact": {"type": "boolean", "default": True, "description": "Return compact alerts with essential fields only (recommended to avoid token limits)"}
9441015
},
9451016
"required": []
9461017
}
@@ -977,7 +1048,8 @@ async def handle_tools_list(params: Dict[str, Any], session: MCPSession) -> Dict
9771048
"properties": {
9781049
"query": {"type": "string", "description": "Search query or pattern"},
9791050
"time_range": {"type": "string", "enum": ["1h", "6h", "24h", "7d"], "default": "24h"},
980-
"limit": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100}
1051+
"limit": {"type": "integer", "minimum": 1, "maximum": 1000, "default": 100},
1052+
"compact": {"type": "boolean", "default": True, "description": "Return compact events with essential fields only (recommended to avoid token limits)"}
9811053
},
9821054
"required": ["query"]
9831055
}
@@ -1062,7 +1134,8 @@ async def handle_tools_list(params: Dict[str, Any], session: MCPSession) -> Dict
10621134
"properties": {
10631135
"agent_id": {"type": "string", "description": "Filter by specific agent ID"},
10641136
"severity": {"type": "string", "enum": ["low", "medium", "high", "critical"], "description": "Filter by severity level"},
1065-
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100}
1137+
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 100},
1138+
"compact": {"type": "boolean", "default": True, "description": "Return compact vulnerabilities with essential fields only (recommended to avoid token limits)"}
10661139
},
10671140
"required": []
10681141
}
@@ -1073,7 +1146,8 @@ async def handle_tools_list(params: Dict[str, Any], session: MCPSession) -> Dict
10731146
"inputSchema": {
10741147
"type": "object",
10751148
"properties": {
1076-
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 50}
1149+
"limit": {"type": "integer", "minimum": 1, "maximum": 500, "default": 50},
1150+
"compact": {"type": "boolean", "default": True, "description": "Return compact vulnerabilities with essential fields only (recommended to avoid token limits)"}
10771151
},
10781152
"required": []
10791153
}
@@ -1287,12 +1361,15 @@ async def handle_tools_call(params: Dict[str, Any], session: MCPSession) -> Dict
12871361
agent_id = arguments.get("agent_id")
12881362
timestamp_start = arguments.get("timestamp_start")
12891363
timestamp_end = arguments.get("timestamp_end")
1364+
compact = arguments.get("compact", True)
12901365
result = await wazuh_client.get_alerts(
1291-
limit=limit, rule_id=rule_id, level=level,
1292-
agent_id=agent_id, timestamp_start=timestamp_start,
1366+
limit=limit, rule_id=rule_id, level=level,
1367+
agent_id=agent_id, timestamp_start=timestamp_start,
12931368
timestamp_end=timestamp_end
12941369
)
1295-
return {"content": [{"type": "text", "text": f"Wazuh Alerts:\n{json.dumps(result, indent=2)}"}]}
1370+
if compact:
1371+
result = _compact_alerts_result(result)
1372+
return {"content": [{"type": "text", "text": f"Wazuh Alerts:\n{json.dumps(result, indent=2 if not compact else None)}"}]}
12961373

12971374
elif tool_name == "get_wazuh_alert_summary":
12981375
time_range = arguments.get("time_range", "24h")
@@ -1310,8 +1387,11 @@ async def handle_tools_call(params: Dict[str, Any], session: MCPSession) -> Dict
13101387
query = arguments.get("query")
13111388
time_range = arguments.get("time_range", "24h")
13121389
limit = arguments.get("limit", 100)
1390+
compact = arguments.get("compact", True)
13131391
result = await wazuh_client.search_security_events(query, time_range, limit)
1314-
return {"content": [{"type": "text", "text": f"Security Events:\n{json.dumps(result, indent=2)}"}]}
1392+
if compact:
1393+
result = _compact_alerts_result(result)
1394+
return {"content": [{"type": "text", "text": f"Security Events:\n{json.dumps(result, indent=2 if not compact else None)}"}]}
13151395

13161396
# Agent Management Tools
13171397
elif tool_name == "get_wazuh_agents":
@@ -1352,13 +1432,19 @@ async def handle_tools_call(params: Dict[str, Any], session: MCPSession) -> Dict
13521432
agent_id = arguments.get("agent_id")
13531433
severity = arguments.get("severity")
13541434
limit = arguments.get("limit", 100)
1435+
compact = arguments.get("compact", True)
13551436
result = await wazuh_client.get_vulnerabilities(agent_id=agent_id, severity=severity, limit=limit)
1356-
return {"content": [{"type": "text", "text": f"Vulnerabilities:\n{json.dumps(result, indent=2)}"}]}
1437+
if compact:
1438+
result = _compact_vulns_result(result)
1439+
return {"content": [{"type": "text", "text": f"Vulnerabilities:\n{json.dumps(result, indent=2 if not compact else None)}"}]}
13571440

13581441
elif tool_name == "get_wazuh_critical_vulnerabilities":
13591442
limit = arguments.get("limit", 50)
1443+
compact = arguments.get("compact", True)
13601444
result = await wazuh_client.get_critical_vulnerabilities(limit)
1361-
return {"content": [{"type": "text", "text": f"Critical Vulnerabilities:\n{json.dumps(result, indent=2)}"}]}
1445+
if compact:
1446+
result = _compact_vulns_result(result)
1447+
return {"content": [{"type": "text", "text": f"Critical Vulnerabilities:\n{json.dumps(result, indent=2 if not compact else None)}"}]}
13621448

13631449
elif tool_name == "get_wazuh_vulnerability_summary":
13641450
time_range = arguments.get("time_range", "7d")

0 commit comments

Comments
 (0)