Custom build of ida-pro-mcp: an MCP Server 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 and contributors; the custom
changes below are maintained in this repository.
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 |
- Linux, macOS or Windows
- Python 3.11+ (use
idapyswitchinside IDA to match) - IDA Pro 8.3+ (9.x recommended). IDA Free is not supported
- uv
- An MCP client (Claude Code, Cursor, Codex, Opencode, VS Code, ...)
Activate idalib for your uv environment:
# 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.
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:
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(mode0600) and survives reboots;GET /keyre-issues it once per server start - Override with
IDA_MCP_API_KEY(or relocate viaIDA_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.
| 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 |
# 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:
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 startupSession lifecycle tools: idb_open, idb_list, idb_close — open one database
per session; each session gets its own isolated worker process.
Start the plugin inside IDA (Edit -> Plugins -> MCP, or Ctrl+Alt+M), then:
uv run ida-pro-mcp # stdio proxy, auto-discovers IDA
uv run ida-pro-mcp --transport http://127.0.0.1:9999 # serve HTTP insteadGeneric JSON config (HTTP transport):
{
"mcpServers": {
"ida-instances": {
"url": "http://your-host:9999/mcp",
"headers": {
"Authorization": "Bearer <key-from-/key>"
}
}
}
}Claude Code:
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.
| 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).
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 -qMIT — inherited from upstream 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.