Skip to main content
Glama
sarahsl-prog

mcp-hayabusa

by sarahsl-prog
README.md
# mcp-hayabusa

MCP server wrapping [Hayabusa](https://github.com/Yamato-Security/hayabusa) for EVTX (Windows Event Log) analysis.

## Architecture

```mermaid
graph LR
    subgraph Inputs
        EVTX[EVTX file]
        HB[hayabusa/ binary +\nbundled Sigma rules]
        RULES[rules/\nSigma mirror]
        MAP[mappings/\nattack_techniques.json\nattack_tactics.json]
    end

    subgraph "MCP Server (server.py)"
        subgraph Tools
            T1[scan_evtx]
            T2[get_hayabusa_rules]
            T3[analyze_coverage]
            T4[suggest_rule]
        end
        subgraph Resources
            R1["detection://rules"]
            R2["detection://rules/{rule_name}"]
            R3["detection://rules/by-technique/{technique_id}"]
            R4["detection://attack/techniques/{technique_id}"]
        end
    end

    Client[Claude / MCP client]

    EVTX --> T1
    HB --> T1
    HB --> T2

    RULES --> R1
    RULES --> R2
    RULES --> R3
    RULES --> T3
    RULES --> T4

    MAP --> R4
    MAP --> T3
    MAP --> T4

    T4 -.writes suggested rule.-> RULES

    Client --> T1
    Client --> T2
    Client --> T3
    Client --> T4
    Client --> R1
    Client --> R2
    Client --> R3
    Client --> R4

    linkStyle 0,1,2 stroke:#e65100,stroke-width:2px
    linkStyle 3,4,5,6,7 stroke:#00695c,stroke-width:2px
    linkStyle 8,9,10 stroke:#6a1b9a,stroke-width:2px
    linkStyle 11 stroke:#c62828,stroke-width:2px
    linkStyle 12,13,14,15,16,17,18,19 stroke:#616161,stroke-width:1px
```

Edge colors: orange = raw file inputs, teal = `rules/`, purple = `mappings/`, red dashed = `suggest_rule`'s write-back, gray = client calls.

## Setup

```bash
uv sync
python3 scripts/download_hayabusa.py
```

`download_hayabusa.py` downloads the latest Hayabusa release for your platform into `./hayabusa/` and creates a stable `./hayabusa/hayabusa` (`.exe` on Windows) path. `./hayabusa/` is gitignored — re-run the script instead of committing the binary.

Sigma rules for the detection knowledge base are mirrored from [SigmaHQ/sigma](https://github.com/SigmaHQ/sigma.git) into `./rules/`:

```bash
git clone https://github.com/SigmaHQ/sigma.git sigma-rules
cp -r sigma-rules/rules ./rules
```

`./rules/` is gitignored — re-clone/pull and re-copy instead of committing the mirror.

ATT&CK technique metadata for the coverage-assessment resource is generated from the [MITRE ATT&CK STIX bundle](https://github.com/mitre-attack/attack-stix-data):

```bash
python3 scripts/download_attack_data.py
```

`download_attack_data.py` downloads the ~50MB STIX bundle and extracts compact indexes to `mappings/attack_techniques.json` (id/name/description/tactics) and `mappings/attack_tactics.json` (shortname → id/name). Re-run the script instead of committing the raw bundle.

## Configuration (.env)

Copy `.env.example` to `.env` to configure MLflow logging and transport
without touching shell env or launcher config. `.env` is gitignored;
values already set in the shell/launcher's own env take precedence over
`.env`.

```bash
cp .env.example .env
```

## Tool-call logging (MLflow)

Every call to `scan_evtx`, `get_hayabusa_rules`, `analyze_coverage`, and
`suggest_rule` is logged as an MLflow run under the `hayabusa-mcp`
experiment.

- `MLFLOW_TRACKING_URI` — where runs are stored. Defaults to a local
  `./mlruns` directory if unset. Set this to point at a shared MLflow
  tracking server instead.
- `MLFLOW_TRACKING_USER` — the user tagged on each run. Defaults to the
  local OS username (`getpass.getuser()`) if unset.

View logged runs with `uv run mlflow ui --backend-store-uri ./mlruns`.

Runs record tool arguments (e.g. `file_path`) and error text as MLflow params/tags, so for the default local `./mlruns` this carries the same trust boundary as the EVTX data itself — worth considering before pointing `MLFLOW_TRACKING_URI` at a shared or wider-audience tracking server.

## Running the server

Defaults to stdio transport (for Claude Desktop/CLI clients that spawn
the server as a subprocess):

```bash
uv run server.py
```

For HTTP testing (e.g. MCP Inspector, curl), set in `.env` or the shell:

- `MCP_TRANSPORT=streamable-http`
- `MCP_HOST` — defaults to `127.0.0.1`
- `MCP_PORT` — defaults to `8000`

```bash
MCP_TRANSPORT=streamable-http uv run server.py
# serves at http://127.0.0.1:8000/mcp
```

## Registering with Claude Code

Add to `.claude/settings.json`:

```json
{
  "mcpServers": {
    "hayabusa": {
      "command": "python",
      "args": ["server.py"],
      "cwd": "."
    }
  }
}
```

## Tools

### `scan_evtx`

Scans an EVTX file with Hayabusa and returns structured results.

| Arg | Type | Description |
|-----|------|-------------|
| `file_path` | `str` | Path to the EVTX file to scan. |
| `min_severity` | `str \| None` | Optional minimum severity filter: `informational`, `low`, `medium`, `high`, `critical`. |
| `rule_filter` | `str \| None` | Optional substring to match against rule titles (e.g. `"lateral"` or `"mimikatz"`), case-insensitive. |
| `output_format` | `str` | `"summary"` (default, key fields only) or `"full"` (all fields Hayabusa reports). |
| `max_results` | `int \| None` | Optional cap on the number of findings returned. |
| `tag_filter` | `str \| None` | Optional comma-separated MITRE ATT&CK / rule tags to restrict which rules run (e.g. `"attack.credential-access"` or `"attack.credential-access,attack.lateral-movement"`). Use `get_hayabusa_rules` to discover available tags. |

Returns a dict with `finding_count` (total matches after filtering, before `max_results`), `returned_count` (findings actually returned), and `findings`.

### `get_hayabusa_rules`

Lists available Hayabusa detection rules, optionally filtered by keyword.

| Arg | Type | Description |
|-----|------|-------------|
| `keyword` | `str \| None` | Optional substring to match against a rule's title, description, or tags, case-insensitive. |

Returns a dict with `rule_count` and `rules` (each with `title`, `id`, `level`, `description`, `tags`, `path`).

### `analyze_coverage`

Analyzes detection coverage for an ATT&CK technique ID or tactic name, combining `mappings/attack_techniques.json`/`attack_tactics.json` with the Sigma rules under `./rules/`.

| Arg | Type | Description |
|-----|------|-------------|
| `identifier` | `str` | An ATT&CK technique ID (e.g. `"T1078"`, `"1003.001"`) or a tactic name (e.g. `"Credential Access"`, `"privilege-escalation"`). |

For a technique ID, returns a single-technique report. For a tactic name, returns a report across every technique in that tactic: `query`, `query_type`, `tactic`, `tactic_id`, `technique_count`, `covered_count`, `partial_count`, `gap_count`, `gaps` (technique entries with 0 detecting rules), and `techniques` (full per-technique breakdown, each with `technique_id`, `name`, `rule_count`, `coverage`). Returns an `error` key for an unrecognized ID or tactic name.

### `suggest_rule`

Checks coverage for a single ATT&CK technique and, if it's not already covered, suggests a detection approach — optionally writing a Sigma rule skeleton to `rules/suggested/` for a human to fill in.

| Arg | Type | Description |
|-----|------|-------------|
| `technique_id` | `str` | An ATT&CK technique ID (e.g. `"T1078"`, `"1003.001"`). |
| `create_template` | `bool` | If `True`, write a Sigma rule skeleton to `rules/suggested/{technique_id}_suggested.yml` when coverage is partial or a gap. Default `False`. Fails if a template already exists for the technique — it won't overwrite. |

Returns `technique_id`, `name`, `coverage`, `rule_count`, `detecting_rules`. If already `"covered"`, returns a `message` and stops there — no suggestion generated. Otherwise returns `suggested_approach` (tactic-based detection guidance) and `template_path` (relative to `rules/`, or `null` if `create_template` was `False`). Returns an `error` key for an unknown technique ID or a template that already exists.

The generated template is a starting point, not a working rule — its `logsource` and `detection.selection` fields are `TODO` placeholders that need real telemetry fields before the rule can run.

## Resources

### `detection://rules`

Lists all Sigma rules under `./rules/`.

Returns a dict with `rule_count` and `rules` (each with `rule_name`, `title`, `id`, `level`, `tags`, `path`).

### `detection://rules/{rule_name}`

Gets a specific Sigma rule's full YAML content by rule name (filename stem, e.g. `lnx_clear_syslog`).

Returns a dict with `rule_name`, `path`, and `rule` (the full parsed YAML). Returns an `error` key if no rule matches.

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

Lists Sigma rules tagged with a given MITRE ATT&CK technique (e.g. `T1078`, `t1003.001`, or bare `1078`).

Returns a dict with `technique_id`, `rule_count`, and `rules` (same shape as `detection://rules`).

### `detection://attack/techniques/{technique_id}`

Gets an ATT&CK technique's name/description plus our detection coverage for it, cross-referencing `attack.tXXXX` tags on our Sigma rules. Requires `mappings/attack_techniques.json` (see Setup).

Returns a dict with `technique_id`, `name`, `description`, `tactics`, `detecting_rules`, `rule_count`, and `coverage` (`"covered"` at 2+ rules, `"partial"` at 1, `"gap"` at 0). Returns an `error` key for unknown technique IDs.