MCP server that receives UniFi security events via syslog, parses CEF messages, stores them in SQLite, and exposes query tools for SIEM integration
Four tools with complete input schemas and descriptions. Naming follows verb_noun pattern (list_events, get_event, get_categories, get_event_stats). Descriptions are clear and domain-specific (10-200 chars). All parameters have types and descriptions. However, output schemas are not documented in the source code, LLMs cannot see what fields to expect from responses. Error handling is absent from tool definitions. No tool annotations (readOnlyHint, destructiveHint, idempotentHint) despite all being read-only operations.
List the distinct event categories currently present in the store (e.g. "ips_alert", "firewall_block", "honeypot", "admin_action", "unknown").
Get a single stored event by id, including its full raw syslog message.
Get aggregate event counts grouped by category, severity, or source_ip, optionally within a time range (since/until, ISO8601).
List stored UniFi SIEM events. Filters: since/until (ISO8601 timestamps, filtered on receipt time), category, severity_min, source_ip/dest_ip (exact match or CIDR, e.g. "10.0.30.0/24"), limit (default 100, max 500), offset.
Output schemas not documented. LLMs cannot see what fields list_events, get_event, get_categories, or get_event_stats return. This forces agents to guess field names for downstream processing and risks hallucinated field access.
No tool annotations despite all tools being read-only. Adding readOnlyHint=true to all four tools would signal to agents that these calls are safe to retry and have no side effects.
No error handling guidance in tool definitions. If a query returns no results, or if an invalid CIDR is passed to source_ip/dest_ip, the tool definitions do not explain what the LLM should do next.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | B | 79 | <=2025-11-25 | v2 |
list_events accepts limit (max 500) and offset for pagination, but no documentation of total count or next_cursor in the response. LLMs cannot determine if more results exist without calling again with incremented offset.
get_event_stats group_by parameter is an enum (category, severity, source_ip) but no description explains what the response structure looks like for each grouping option.