har-mcp
by yynag
README.md
# har-mcp
English | [简体中文](README.zh-CN.md)
An MCP server for analyzing **large HAR files** with **schema-less protobuf / gRPC decoding**, built for reverse-engineering and traffic research workflows.
It turns huge, opaque HAR captures (full of binary protobuf bodies) into targeted, structured queries — so your AI agent no longer has to "drop out to Python" to decode a single field.
## Why
Capture tools (Reqable, Charles, mitmproxy, …) export HAR. For apps that speak protobuf/gRPC (Google Play, gRPC services, …) those HAR files are large and their bodies are opaque binary. Existing options either truncate big bodies or force you into manual side-scripts.
`har-mcp` is different:
- **No silent truncation.** Bodies are stored in full. Large bodies return a sized preview by default and the whole payload on demand (`full:true`) or via structured decoding.
- **Schema-less protobuf decoding.** Bodies are decoded without any `.proto` — field numbers, wire types, nested messages, strings, bytes — so you can inspect obfuscated/proprietary protocols immediately.
- **Any path, no lock-in.** Every tool accepts an arbitrary `.har` path (or a registered name). No fixed working directory to configure.
- **Sidecar Database (<name>.har.db).** Each `data.har` gets an isolated companion SQLite file `data.har.db` in the same directory, making it straightforward for developers to inspect, query, or delete per-file databases.
- **Scales to 100 MB+ HAR.** Streaming index build + companion SQLite schema (slim metadata table vs. isolated body blobs) keep listing/filtering instant and memory flat.
- **ID-addressed, Reqable-friendly.** Entries are addressed by Reqable's own `_id` / `_uid`, mirroring the official Reqable MCP's `filter → id → operate-by-id` pattern.
## Getting started
### 1. Install Bun
har-mcp runs on [Bun](https://bun.sh) (it uses the built-in `bun:sqlite`, so there are no native dependencies to compile).
```bash
# macOS / Linux
curl -fsSL https://bun.sh/install | bash
# Windows (PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"
```
Verify with `bun --version` (requires ≥ 1.1).
### 2. Get har-mcp
Pick one:
- **From source** (recommended):
```bash
git clone https://github.com/yynag/har-mcp.git
cd har-mcp
bun install
```
- **Compiled JS from Releases**: download `index.js` from the [latest release](https://github.com/yynag/har-mcp/releases) — built by CI with all dependencies bundled, so you only need Bun to run it (`bun /path/to/index.js`).
### 3. Configure your MCP client
No directory to configure — you point at HAR files **by path** through the tools (see step 4).
**opencode** (`opencode.jsonc`):
```jsonc
"har": {
"type": "local",
"command": ["bun", "run", "/absolute/path/to/har-mcp/src/index.ts"],
"enabled": true
}
```
Using the compiled release JS instead of source:
```jsonc
"har": {
"type": "local",
"command": ["bun", "/absolute/path/to/index.js"],
"enabled": true
}
```
**Claude Desktop / Cursor / any stdio client** (`claude_desktop_config.json` / `mcp.json`):
```json
{
"mcpServers": {
"har": {
"command": "bun",
"args": ["run", "/absolute/path/to/har-mcp/src/index.ts"]
}
}
}
```
### 4. Load HARs and go
Load a single file or a whole directory, then query by ID:
```
har_index { "target": "/path/to/captures" } # starts background creation
har_files { "dir": "/path/to/captures" } # poll until status is "ready"
har_filter { "keyword": "acquire" } # -> IDs, after ready
har_decode_body { "id": 254, "target": "request" }
```
Most tools' `file` parameter also accepts an arbitrary `.har` path directly — it starts indexing on demand; while indexing, the tool returns `status: "creating"`.
## Loading & cleanup
- **Sidecar Database**: Opening or indexing `path/to/data.har` automatically creates and maintains `path/to/data.har.db` right next to the HAR file.
- **Background creation**: `har_index` starts creation and returns immediately. Use `har_files` to observe the status; query tools return `status: "creating"` until the database is ready.
- **Auto-check & reuse**: Companion `.har.db` files check `size` and `mtime` to reuse indexes without re-parsing when the file hasn't changed.
- **Manual cleanup**: `har_forget { file }` deletes the companion `data.har.db` file (or delete it directly). The HAR file itself is never touched.
## Configuration
| Variable / flag | Description | Default |
|---|---|---|
| `HAR_PREVIEW_BYTES` | Body preview size before truncation notice | `8192` |
| `HAR_DECODE_MAX_DEPTH` | Protobuf recursion depth | `12` |
| `HAR_FILTER_LIMIT` | Default result limit | `100` |
## Tools
| Tool | Description |
|---|---|
| `har_files` | List `*.har` files and their companion `.har.db` index status in a directory. |
| `har_index` | Start background creation of companion `.har.db` indexes for a file or a directory. |
| `har_forget` | Delete the companion `.har.db` database for a HAR file. |
| `har_filter` | Filter entries in a HAR file → return compact rows with IDs (no bodies). |
| `har_get_by_id` | Fetch full entry (headers + body) by Reqable ID from a HAR file. |
| `har_generate_curl` | Generate a cURL command for an entry by ID. |
| `har_decode_body` | Decode a body as schema-less protobuf (auto base64/gzip/gRPC). Optional `path` to focus a sub-field (e.g. `1.11`). |
| `har_diff` | Field-level diff of two decoded bodies (great for "working request vs. my request"). |
| `har_search` | Keyword search over url/host/headers (FTS) and optionally text bodies. |
### Typical workflow
1. `har_index { target: "/path/to/captures" }` → start loading.
2. `har_files { dir: "/path/to/captures" }` → poll until every file is `ready`.
3. `har_filter { keyword: "acquire" }` → get IDs of interest.
4. `har_decode_body { id: 254, target: "request" }` → inspect the protobuf.
5. `har_diff { idA: 3, idB: 947, target: "request" }` → see exactly which fields differ.
6. `har_generate_curl { id: 3 }` → reproduce a request.
## How it works
**Storage.** Each HAR has its own companion SQLite database. The HAR is streamed (`stream-json`) into that database during background creation:
- `meta` — the status and source-file metadata for this HAR.
- `entries` — slim, indexed metadata (method/host/path/status/kind/sizes). Filters scan only this table.
- `bodies` — headers + full body BLOBs, fetched only by ID. Never scanned during listing.
- `entries_fts` — FTS5 over url/host/path/headers for `har_search` (falls back to LIKE if unavailable).
**Body classification.** `content.encoding == "base64"` marks binary bodies. Because `mimeType` is often missing or wrong, bodies are re-classified by magic bytes: gzip (`1f 8b`), gRPC frame (`00` + matching BE length), protobuf (low tag byte), image, text, binary.
**Protobuf decoding.** A schema-less wire-format decoder walks varint / fixed32 / fixed64 / length-delimited fields. A length-delimited value is rendered as a **string** when it is valid UTF-8 with no hard control characters; otherwise it is recursively tested as a **nested message** (strict full-buffer parse); otherwise as **hex bytes**. This cleanly separates text from sub-messages without a schema. gRPC bodies have their 5-byte frame header stripped (multi-frame supported); gzip is decompressed defensively. Depth, field-count and string-length limits keep output bounded.
**IDs.** `id` is Reqable's `_id` (human-friendly); `uid` is `_uid` (globally unique). If an `id` appears in multiple files (e.g. a capture exported twice), pass `file` (a path or name) to disambiguate; results carry a `_note`.
## Development
```bash
bun install
bun run typecheck # tsc --noEmit
bun run test:ci # synthetic-fixture test: path indexing, prune, forget, decode (CI-safe)
bun run smoke # store + decoder smoke test (set HAR_DIR to real HARs)
bun run smoke:mcp # end-to-end MCP protocol test
bun run build:js # bundle to dist/index.js
bun run build # standalone binary
```
CI (`.github/workflows/build.yml`) runs typecheck + `test:ci`, bundles `dist/index.js`, uploads it as an artifact, and attaches it to a release on `v*` tags.
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues