mcp-hayabusa

mcp-hayabusa

An MCP server that wraps the Hayabusa CLI, enabling analysis of Windows EVTX event log files and browsing of its detection rule set.

Category
访问服务器

README

mcp-hayabusa

An MCP server with two layers: it wraps the Hayabusa CLI, exposing a scan_evtx tool for analyzing Windows EVTX event log files and a get_hayabusa_rules tool for browsing its detection rule set; and it doubles as a detection-engineering knowledge base, exposing a curated Sigma rule set and MITRE ATT&CK technique/coverage lookups as detection:// resources, plus an analyze_coverage tool for querying that coverage directly (by technique ID or by tactic).

Requirements

  • Python 3.10+ (uses the X | None type-hint syntax)
  • The mcp library (pip install -r requirements.txt)
  • The Hayabusa CLI, extracted to ./hayabusa/ (see Setup below)
  • The MITRE ATT&CK Enterprise STIX bundle, extracted to ./attack/ (see Setup below) — only required for the detection://attack/techniques/{technique_id} resource

Setup

pip install -r requirements.txt
python scripts/download_hayabusa.py

download_hayabusa.py detects your OS/architecture, downloads the matching release asset from Hayabusa's latest GitHub release, and extracts it into ./hayabusa/ (binary, rules/, config/). This directory is gitignored — re-run the script after cloning, or whenever you want to pick up a newer Hayabusa release.

Optional, for manual testing:

python scripts/download_sample_evtx.py

Downloads one real attack-technique sample (4794_DSRM_password_change_t1098.evtx) from EVTX-ATTACK-SAMPLES into ./samples/ (also gitignored).

Required for the detection://attack/techniques/{technique_id} resource:

python scripts/download_attack_data.py

Downloads the MITRE ATT&CK Enterprise STIX bundle (~50MB) from attack-stix-data into ./attack/enterprise-attack.json (gitignored). Re-run it whenever you want to pick up a newer ATT&CK release — the server caches the parsed data in memory for the life of the process, so restart server.py afterward to pick up the change.

Running the server

python server.py

The server communicates over stdio, so it's meant to be launched by an MCP client (e.g. Claude Code), not run interactively. With no client attached, it reads EOF from stdin and exits immediately — that's expected, not a bug.

To register it with Claude Code locally, add it to .mcp.json at the project root:

{
  "mcpServers": {
    "hayabusa": {
      "command": "python",
      "args": ["server.py"],
      "cwd": "C:/path/to/mcp-hayabusa"
    }
  }
}

MCP server definitions aren't read from settings.json/settings.local.json (those are for permissions/hooks/env) — .mcp.json is the file Claude Code actually checks. It's gitignored here because cwd is an absolute, machine-specific path; each collaborator creates their own.

Registering with Claude Desktop

Claude Desktop uses a separate config file from .mcp.json, and doesn't support a cwd key, so pass an absolute path to server.py in args instead:

{
  "mcpServers": {
    "hayabusa": {
      "command": "python",
      "args": ["C:/path/to/mcp-hayabusa/server.py"]
    }
  }
}

Finding the right file to edit takes a bit of care on Windows:

  • On a standard (non-Store) install, it's %APPDATA%\Claude\claude_desktop_config.json.
  • On the MSIX-packaged Store build, Windows redirects the app's %APPDATA% to a virtualized path — editing %APPDATA%\Claude\... from outside the app (e.g. a terminal) touches an inert file the app never reads. The real file is %LOCALAPPDATA%\Packages\<Claude package ID>\LocalCache\Roaming\Claude\claude_desktop_config.json. Check %LOCALAPPDATA%\Packages for a folder starting with Claude_ if you're not sure which build you have.

Fully quit and relaunch Claude Desktop after editing (not just close the window). Per-server connection logs land in logs\mcp-server-hayabusa.log next to the config file — check there first if a server shows as disconnected; it logs the exact command/args/cwd used to launch it and any stderr from the process.

The scan_evtx tool

scan_evtx(
    file_path: str,
    min_severity: str | None = None,
    rule_filter: str | None = None,
    output_format: str = "summary",
    max_results: int | None = None,
) -> dict
Parameter Required Description
file_path yes Path to the .evtx file to scan
min_severity no Minimum severity to include: informational, low, medium, high, or critical. Filtering happens inside Hayabusa itself (--min-level).
rule_filter no Case-insensitive substring matched against each finding's rule title (e.g. "lateral" or "mimikatz"). Only matching findings are returned.
output_format no "summary" (default) returns a trimmed set of fields per finding; "full" returns every field Hayabusa produced.
max_results no Caps the number of findings returned.

Success response

{
  "file": "samples/4794_DSRM_password_change_t1098.evtx",
  "min_severity": null,
  "rule_filter": null,
  "output_format": "summary",
  "count": 1,
  "returned": 1,
  "truncated": false,
  "findings": [
    {
      "Timestamp": "2017-06-09 15:21:26.968 -04:00",
      "RuleTitle": "Password Change on Directory Service Restore Mode (DSRM) Account",
      "Level": "high",
      "Computer": "2016dc.hqcorp.local",
      "EventID": 4794,
      "RecordID": 3139859
    }
  ]
}

count is the total matching findings after rule_filter (before any max_results cap); returned is how many are actually in findings; truncated is true if max_results cut the list short. Pass output_format="full" to get every field Hayabusa produced (Channel, Details, ExtraFieldInfo, RuleID, etc.) instead of the trimmed summary shape shown above.

Error response

Every failure mode returns {"error": "..."} (plus stderr/returncode where applicable) instead of raising:

Situation Example error
File doesn't exist EVTX file not found: <path>
Invalid min_severity Invalid min_severity 'bogus'. Must be one of: informational, low, medium, high, critical
Invalid output_format Invalid output_format 'bogus'. Must be one of: summary, full
Invalid max_results Invalid max_results '-1'. Must be >= 0.
Hayabusa binary missing Hayabusa executable not found in <dir>
Hayabusa exits non-zero Hayabusa scan failed (with returncode, stderr)
Scan takes too long Hayabusa scan timed out after 300s
Output unparseable Failed to parse Hayabusa output: ...

The get_hayabusa_rules tool

get_hayabusa_rules(keyword: str | None = None, max_results: int | None = 50) -> dict

Lists detection rules from the local ./hayabusa/rules/ checkout (~5,000 Sigma + Hayabusa-native rules) — useful for discovering what rules exist, and their exact titles/tags, before scanning (e.g. to pick a value for scan_evtx's rule_filter).

Parameter Required Description
keyword no Case-insensitive substring matched against each rule's title, description, and tags.
max_results no Caps the number of rules returned. Defaults to 50; pass null for unlimited.

Success response

{
  "keyword": "mimikatz",
  "count": 24,
  "returned": 24,
  "truncated": false,
  "rules": [
    {
      "title": "Mimikatz Use",
      "id": "06d71506-7beb-4f22-8888-e2e5e2ca7fd8",
      "level": null,
      "status": "test",
      "ruletype": "sigma",
      "tags": ["attack.s0002", "attack.lateral-movement", "attack.t1003.002"],
      "description": "This method detects mimikatz keywords in different Eventlogs..."
    }
  ]
}

Rule fields are extracted with a lightweight line-scan, not a full YAML parser (see Notes below), so level/status/tags/description are null/empty when a given rule doesn't define that field at the top level.

Error response

Situation Example error
Rules directory missing Hayabusa rules directory not found: <dir>
Invalid max_results Invalid max_results '-1'. Must be >= 0.

Detection engineering knowledge base resources

Alongside the two tools above, the server exposes a curated Sigma rule set (./rules/, checked into git — distinct from the full ./hayabusa/rules/ checkout used by get_hayabusa_rules) and MITRE ATT&CK lookups as four detection:// MCP resources. Resources are browsable/discoverable rather than invoked with arguments, and a not-found lookup raises an MCP ResourceError instead of returning a {"error": ...} dict (that convention is tool-specific — see the scan_evtx/get_hayabusa_rules sections above). A third tool, analyze_coverage, wraps this same data for direct technique/tactic coverage queries — see its own section below.

detection://rules

Lists all rules in ./rules/ (currently 24: hand-authored plus a curated selection copied from upstream SigmaHQ/sigma, covering credential-access, lateral-movement, and persistence techniques).

{
  "count": 24,
  "rules": [
    {
      "rule_name": "lsass_process_access",
      "title": "Suspicious Process Access to LSASS Memory",
      "id": "fe41d923-d63b-45bb-8c85-bbfb6886b6b3",
      "level": "high",
      "status": "test",
      "tags": ["attack.credential-access", "attack.t1003.001", "attack.s0002"],
      "techniques": ["T1003.001"],
      "description": "Detects non-standard processes requesting access to lsass.exe with access"
    }
  ]
}

detection://rules/{rule_name}

Returns one rule's raw YAML content, looked up by filename stem (case-insensitive, extension optional — lsass_process_access, lsass_process_access.yml, and LSASS_Process_Access all resolve the same file). Raises if rule_name doesn't match any file in ./rules/.

detection://rules/by-technique/{technique_id}

Lists rules tagged with a given ATT&CK technique ID (case-insensitive, T prefix optional — t1003.001 and T1003.001 are equivalent). An unmatched technique returns count: 0, not an error.

{
  "technique_id": "T1021.002",
  "count": 2,
  "rules": [ /* ... matching rule summaries, same shape as detection://rules ... */ ]
}

detection://attack/techniques/{technique_id}

Looks up a technique in the downloaded MITRE ATT&CK data and cross-references it against ./rules/ in one call: what the technique is, whether we detect it, and how well.

{
  "technique_id": "T1003.001",
  "name": "LSASS Memory",
  "description": "Adversaries may attempt to access credential material stored in the process memory of the Local Security Authority Subsystem Service (LSASS)...",
  "is_subtechnique": true,
  "url": "https://attack.mitre.org/techniques/T1003/001",
  "rules": [ /* ... matching rule summaries ... */ ],
  "rule_count": 3,
  "coverage": "covered"
}

coverage is one of:

Value Meaning
covered At least one rule is tagged with this exact technique ID.
partial No exact-match rule, but the parent technique (for a sub-technique ID) or a sibling sub-technique (for a parent ID) is covered — related detection logic may catch some, but not all, variants.
gap Nothing in ./rules/ references this technique at all, directly or via parent/child.

Raises if the ATT&CK data hasn't been downloaded yet (run scripts/download_attack_data.py first) or if technique_id isn't a real ATT&CK technique.

The analyze_coverage tool

analyze_coverage(target: str) -> dict

A tool (not a resource) that answers the same "what's our coverage?" question as detection://attack/techniques/{technique_id}, but takes either a single technique ID or a whole tactic, and — for a tactic — reports coverage across every technique in it in one call, rather than requiring one lookup per technique. Combines the same two sources as the detection:// resources above: the downloaded ATT&CK STIX data and the curated ./rules/ Sigma set.

Parameter Required Description
target yes Either an ATT&CK technique ID ("T1003.001", "T1003", or bare "1003.001" — the T prefix is optional), or a tactic name/shortname, case- and spacing-insensitive ("Credential Access", "credential-access").

Success response — technique

{
  "target_type": "technique",
  "technique_id": "T1558.003",
  "name": "Kerberoasting",
  "tactics": ["credential-access"],
  "coverage": "covered",
  "rule_count": 2,
  "rules": [ /* ... matching rule summaries, same shape as detection://rules ... */ ]
}

Success response — tactic

{
  "target_type": "tactic",
  "tactic": "Credential Access",
  "technique_count": 67,
  "covered_count": 8,
  "partial_count": 17,
  "gap_count": 42,
  "covered": [ {"technique_id": "T1003.001", "name": "LSASS Memory", "rule_count": 3}, "..." ],
  "partial": [ /* same shape as covered */ ],
  "gaps": [ /* same shape, rule_count is always 0 */ ]
}

coverage (technique form) and each technique's bucket placement (tactic form) use the same covered/partial/gap logic documented under detection://attack/techniques/{technique_id} above.

Error response

Like scan_evtx/get_hayabusa_rules (and unlike the detection:// resources), failures return {"error": ...} rather than raising:

Situation Example error
Empty/blank target target must be a non-empty technique ID or tactic name.
ATT&CK data not downloaded ATT&CK data not found at <path>. Run scripts/download_attack_data.py first.
Unknown technique ID Unknown ATT&CK technique: T9999
Unrecognized tactic name Unknown tactic '<target>'. Known tactics: Collection, Command and Control, ... (lists all 15)

Testing

python tests/test_scan_evtx.py

A manual script (not a pytest suite) that exercises the original two tools: scan_evtx against the sample downloaded by download_sample_evtx.py (default/full output_format, min_severity, rule_filter, max_results, and error cases), and get_hayabusa_rules against the local rule set (default cap, keyword filtering, and error cases). It does not cover the detection:// resources or analyze_coverage — those were verified manually via mcp.read_resource() / direct calls to analyze_coverage().

Notes

  • Severity filtering is delegated to Hayabusa's own -m/--min-level flag rather than reimplemented in Python; rule_filter, output_format, and max_results have no Hayabusa CLI equivalent, so they're applied as post-processing in Python.
  • Output parsing uses Hayabusa's -L/JSONL mode. Hayabusa's default -o (non--L) output is pretty-printed JSON objects concatenated with no array wrapper — not valid JSON or JSONL — so -L is required for reliable parsing.
  • get_hayabusa_rules parses rule YAML with regex line-scanning instead of a full YAML parser, to avoid adding a PyYAML dependency for what's just a fuzzy listing tool — Hayabusa itself does the real YAML parsing when a rule is actually used to scan.
  • ./rules/ is a deliberately curated cross-section of upstream Sigma, not a full mirror (~4,700 files across all platforms) — that was considered and rejected: it would duplicate ./hayabusa/rules/, blow up repo size, and (given ./rules/'s flat, non-hayabusa/sigma-subdirectory layout) risk filename collisions in the by-stem rule lookup.
  • The MITRE ATT&CK STIX bundle (~50MB) is parsed once and cached in memory for the server process's lifetime, not re-parsed per request — it's static data that doesn't change while the server runs.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选