Skip to main content
Glama
Claude-Reverser

IDA-instances-MCP

README.md
# IDA-instances-MCP

Custom build of [ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp): an
[MCP Server](https://modelcontextprotocol.io/introduction) that lets MCP clients
drive IDA Pro — reworked with a stability pass aimed at hosting **multiple
headless idalib instances** (the Ida-Instances setup).

This is a fork of mrexodia's `ida-pro-mcp` (MIT). All upstream credit goes to
[Duncan Ogilvie](https://github.com/mrexodia) and contributors; the custom
changes below are maintained in this repository.

## What's custom

Stability hardening on top of upstream v2.0.0:

| Area | Change |
|------|--------|
| Data safety | Partial-database cleanup is ownership-aware: it never deletes `.id0/.id1/.id2/.nam/.til` files claimed by another live instance, and fails safe when discovery is unavailable |
| Save reliability | `idb_save` runs with a 600s budget (was 60s); `idb_close` refuses to kill a worker after a failed save instead of silently discarding changes |
| Concurrency | Supervisor lock no longer spans worker spawn (~120s), health probes (~2.5s/session) or stale-worker termination — one slow open can't freeze every session |
| Robustness | Malformed JSON-RPC over stdio returns `-32700`/`-32600` instead of crashing the proxy |
| Timeouts | Proxy timeout raised 30s → 900s and made configurable (`IDA_MCP_PROXY_TIMEOUT`) so long decompiles don't "fail" client-side while still running server-side |
| Networking | Worker port TOCTOU now retries; SSE writes and teardown are lock-guarded; CORS policy reads are cached off the IDA main thread |
| Resource limits | IDB trace log capped at 64 MB (`IDA_MCP_TRACE_MAX_MB`) with oversized arguments/results clipped; flush-failure queue bounded |
| Authentication | Every HTTP request requires an instance API key; issued **exactly once** via `GET /key`, then burned. The key persists on disk so it survives restarts and reboots |
| Hosting endpoints | `GET /health` (unauthenticated readiness probe), `GET /sessions` (open databases + last-accessed), `POST /upload` (direct binary upload with size cap) |
| Idle reaping | Sessions untouched for `IDA_MCP_IDLE_TIMEOUT` minutes are auto-closed (saved first) |
| Graceful shutdown | On `SIGTERM`/`SIGINT` every open database is saved before exit — no more lost work on `pct stop` |
| Self-update | Startup checks GitHub releases; interactive sessions are offered a one-keypress auto-update |
| Hosting defaults | Headless supervisor listens on **0.0.0.0:9999**; the GUI plugin also binds **0.0.0.0** and now requires the same API key |

## Requirements

- Linux, macOS or Windows
- [Python](https://www.python.org/downloads/) **3.11+** (use `idapyswitch` inside IDA to match)
- [IDA Pro](https://hex-rays.com/ida-pro) **8.3+** (9.x recommended). **IDA Free is not supported**
- [uv](https://astral.sh/uv)
- An MCP client (Claude Code, Cursor, Codex, Opencode, VS Code, ...)

## Setup

Activate idalib for your uv environment:

```bash
# linux (server deployment)
uv run "/opt/idapro-9.x/idalib/python/py-activate-idalib.py"
# macos
uv run "/Applications/IDA Professional 9.x.app/Contents/MacOS/idalib/python/py-activate-idalib.py"
# windows
uv run "C:\Program Files\IDA Professional 9.x\idalib\python\py-activate-idalib.py"
```

Headless runs additionally need the IDA install directory in `IDADIR`.

### Run the headless supervisor (hosting mode)

```bash
uv run idalib-mcp                # serves http://0.0.0.0:9999/mcp (new default)
```

On startup an **API key** is generated (or loaded from disk) and every request
without it gets `401 Unauthorized` — no exceptions.

Fetch the key once, from the machine/network that owns the instance:

```bash
curl http://your-host:9999/key
# {"key": "3f2b..."}   <- save it; this endpoint is now burned until restart
```

- The key persists at `~/.idapro/mcp/api_key` (mode `0600`) and survives
  reboots; `GET /key` re-issues it once per server start
- Override with `IDA_MCP_API_KEY` (or relocate via `IDA_MCP_API_KEY_FILE`)
- All other endpoints require it on every call:

```
Authorization: Bearer <key>
# or equivalently
X-API-Key: <key>
```

The key is never logged.

#### Hosting endpoints

| Endpoint | Auth | Purpose |
|----------|------|---------|
| `GET /health` | none | Readiness probe: `{"status":"ok","uptime":N,"sessions":N}` — poll instead of sleeping |
| `GET /sessions` | key | Open databases incl. last-accessed timestamps |
| `POST /upload?filename=NAME` | key | Raw-body binary upload, streamed to `IDA_MCP_UPLOAD_DIR`; returns the path to feed `idb_open` |

```bash
# provisioning flow
while ! curl -sf http://10.0.0.4:9999/health >/dev/null; do sleep 1; done
KEY=$(curl -s http://10.0.0.4:9999/key | jq -r .key)
curl -H "Authorization: Bearer $KEY" --data-binary @sample.elf \
     "http://10.0.0.4:9999/upload?filename=sample.elf"
```

Useful flags:

```bash
uv run idalib-mcp --host 127.0.0.1 --port 9999   # override bind address
uv run idalib-mcp --max-workers 8                # concurrent databases (default 4, 0 = unlimited)
uv run idalib-mcp --unsafe                       # enable destructive/debugger tools (DANGEROUS)
uv run idalib-mcp --profile tools.txt            # restrict worker tools to a profile file
uv run idalib-mcp --stdio                        # stdio transport instead of HTTP
uv run idalib-mcp path/to/binary                 # open a binary at startup
```

Session lifecycle tools: `idb_open`, `idb_list`, `idb_close` — open one database
per session; each session gets its own isolated worker process.

### Run the GUI proxy (interactive IDA)

Start the plugin inside IDA (`Edit -> Plugins -> MCP`, or `Ctrl+Alt+M`), then:

```bash
uv run ida-pro-mcp                                   # stdio proxy, auto-discovers IDA
uv run ida-pro-mcp --transport http://127.0.0.1:9999 # serve HTTP instead
```

### Connect an MCP client

Generic JSON config (HTTP transport):

```json
{
  "mcpServers": {
    "ida-instances": {
      "url": "http://your-host:9999/mcp",
      "headers": {
        "Authorization": "Bearer <key-from-/key>"
      }
    }
  }
}
```

Claude Code:

```bash
claude mcp add --transport http ida-instances http://your-host:9999/mcp \
  --header "Authorization: Bearer <key-from-/key>"
```

For the GUI proxy over stdio, run `uv run ida-pro-mcp --config` and paste the
JSON into your client.

## Configuration (environment variables)

| Variable | Default | Meaning |
|----------|---------|---------|
| `IDA_MCP_OPEN_TIMEOUT` | `1800` | Max seconds for open + auto-analysis before reap (`0` = unlimited) |
| `IDA_MCP_WORKER_CALL_TIMEOUT` | `900` | Backstop per forwarded tool call |
| `IDA_MCP_PROXY_TIMEOUT` | `900` | GUI-proxy → IDA upstream timeout (`0` = unlimited) |
| `IDA_MCP_TRACE_MAX_MB` | `64` | Trace log cap stored inside the IDB |
| `IDA_MCP_MAX_WORKERS` | `4` | Default `--max-workers` |
| `IDA_MCP_HEALTH_TCP_TIMEOUT` / `IDA_MCP_HEALTH_RPC_TIMEOUT` | `2.0` / `10.0` | Health probe budgets |
| `IDA_MCP_IDLE_TIMEOUT` | `60` | Minutes before an untouched session is auto-closed (`0` disables) |
| `IDA_MCP_MAX_UPLOAD_MB` | `100` | Cap for `POST /upload` bodies |
| `IDA_MCP_UPLOAD_DIR` | `~/.idapro/mcp/uploads` | Where uploads are stored |
| `IDA_MCP_API_KEY` / `IDA_MCP_API_KEY_FILE` | – / `~/.idapro/mcp/api_key` | Inject or relocate the instance key |
| `IDA_MCP_SHUTDOWN_SAVE_TIMEOUT` / `IDA_MCP_SHUTDOWN_SAVE_BUDGET` | `30` / `120` | Per-database / total save budget on SIGTERM |
| `IDA_MCP_NO_UPDATE_CHECK` | unset | Set `1` to disable the GitHub release check |

> **Security notes:** the API key gates every HTTP endpoint on both the
> supervisor and the GUI plugin (the plugin can execute arbitrary code via
> `py_eval`, so it is keyed even on loopback). It is a bearer token — anyone
> who obtains it owns the instance; add a TLS reverse proxy if it leaves your
> trusted network. Browsers can pass the key as `?key=<key>` (handy for
> `/config.html`).

## Testing

```bash
IDADIR=/path/to/ida uv run ida-mcp-test tests/crackme03.elf -q
IDADIR=/path/to/ida uv run ida-mcp-test tests/typed_fixture.elf -q
IDADIR=/path/to/ida uv run ida-mcp-test tests/crackme03.elf -c api_analysis   # one category
IDADIR=/path/to/ida uv run coverage run -m ida_pro_mcp.test tests/crackme03.elf -q
```

## License

MIT — inherited from upstream [`ida-pro-mcp`](https://github.com/mrexodia/ida-pro-mcp).
Upstream product names (IDA Pro, Hex-Rays) are trademarks of Hex-Rays SA; this
project requires your own valid IDA Pro license.

Maintenance

ActivityActive
ResponsivenessNo issues