Drive IDA Pro's decompiler and disassembler from an AI agent over the Model Context Protocol — headless, fast, and token-frugal.
Headless IDA Pro analysis server exposing MCP tools (28 with py_eval enabled, 27 without). Designed for AI agents — tiered tool loading, multi-layer caching, batch operations, and compact payloads minimize token cost per turn.
Go orchestrates sessions and caching. Python workers run IDA via idalib. Communication uses Connect RPC over TCP.
- Built for agents, not humans. Every response is compact — hex addresses, sparse fields,
[address, type]xref tuples,"ok"instead of{"success": true}— to spend the fewest tokens per turn. - One call, not ten. Composite tools like
survey_binaryandanalyze_functionreturn decompilation + metadata + xrefs + comments in a single round-trip;analyze_functionsbatches up to 10 at once. - Stays responsive on huge binaries. Loading and auto-analysis run detached with a live progress heartbeat, so a 200 MB binary won't stall or time out your MCP client — you poll until
ready=trueand keep working. - Tiered tool surface. Agents boot with 3 tools; read/analysis and write tools promote in as the workflow advances, keeping the per-turn schema small.
- Multi-binary. Open several databases and
cross_reference/cross_searchacross them. - Safe by default. Loopback-only bind,
py_evaloff,Origin/Hostguard, optional path allowlist (details below).
IDA Pilot has no built-in authentication. The defaults are tuned for a single trusted MCP client on the same machine:
- Bound to
127.0.0.1by default. Only callers on the local host can reach it. Override with--bind 0.0.0.0(orIDA_PILOT_BIND) only if you are putting an authenticated reverse proxy in front. Origin/Hostvalidation on every request when bound to loopback — blocks DNS-rebinding and browser cross-origin attacks.py_evalis OFF by default. It runs arbitrary Python inside the IDA worker (full host filesystem + network access). Enable explicitly with--enable-py-eval(orIDA_PILOT_ENABLE_PY_EVAL=1) and treat the resulting endpoint as an RCE primitive — anyone who can reach the port can execute code.- Filesystem allowlist (opt-in). By default a client can open any file the server user can read (
open_binary,import_metadata). Set--allowed-roots(orIDA_PILOT_ALLOWED_ROOTS) to confine those paths to specific directories — recommended whenever you expose the port. Paths are symlink-resolved before the check, so a link inside a root can't escape it.
The truncated-output cache (get_cached_output) is process-global with no per-session ACL — the server has no client identity to bind entries to — so access rests on unguessable 128-bit cache IDs plus the loopback/Origin guards above, not on session ownership.
In short: a fresh ./bin/ida-pilot run is safe to leave on; opening the port to the network or enabling py_eval is an explicit choice and your responsibility to protect.
MCP Client (Claude, Cursor, etc.)
│
│ Streamable HTTP / SSE
▼
Go Server (:17300)
│ session registry, tool tiers, caching, output pagination
│
│ Connect RPC (TCP)
▼
Python Worker (one per binary)
│ idalib + Hex-Rays decompiler
▼
IDA database (.i64)
- IDA Pro 9.0+ with idalib activated (docs)
- Go 1.25+
- Python 3.10+ (the supported floor; CI and
misepin 3.12)
git clone https://github.com/wklwkl115/ida-pilot.git
cd ida-pilot
pip install -r python/requirements.txt
go build -o bin/ida-pilot ./cmd/ida-pilot./bin/ida-pilot --debugThe server listens on http://localhost:17300/ (Streamable HTTP) and http://localhost:17300/sse (SSE fallback).
Add to your MCP client configuration:
{
"mcpServers": {
"ida-pilot": {
"type": "http",
"url": "http://127.0.0.1:17300/"
}
}
}Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows)
Claude Code: .mcp.json in project root or ~/.claude/settings.json
Cursor: .cursor/mcp.json in project root
IDA Pilot is built for agents, not humans. Every design decision targets lower token cost and fewer round-trips.
Agents start with 3 tools. More appear as needed — the schema payload per turn stays small.
| Tier | Trigger | Tools | Purpose |
|---|---|---|---|
| 0 | Server boot | 3 | open_binary, list_sessions, get_cached_output |
| 1 | Binary opened | +19 = 22 | Read, query, composite, analysis, context & cross-session tools |
| 2 | First analysis | +5 = 27 | Write, annotate, and import tools |
When the last session closes, tools demote back to Tier 0. Adding --enable-py-eval registers one more Tier-1 tool, taking the full surface to 28.
One call replaces many. These are the primary entry points for analysis:
survey_binary— segments, function/import/export/string counts with top entries, entry point, decompiler status. One call to orient.analyze_function— pseudocode + metadata + xrefs + comments for a single function. All cached.analyze_functions— batch-analyze up to 10 functions in parallel.annotate_function— batch rename, retype, and comment in a single call per function.
| Cache | Scope | Capacity | Invalidation |
|---|---|---|---|
| List (functions, imports, exports, strings) | Session | Unbounded | rename / make_function |
| Decompilation (pseudocode) | Per-function | 200 entries | targeted: function renames invalidate only that function; data-label renames search and evict only entries containing the old name; lvar/comments invalidate that function |
| Xrefs (to + from) | Per-address | Unbounded | make_function |
| Segments | Session | Static | Never |
| Output (truncated responses) | Global | 200 entries, 1h TTL | FIFO eviction |
| Analysis context (visited + notes) | Session | Unbounded | Never |
Background cache warming starts automatically when a binary is opened — survey_binary returns instantly on its first call.
- Addresses are hex (
0x...) on both input and output, so the agent pastes back exactly what it sees in disassembly/listings (decimal is also accepted on input) - Sparse fields: zero/false/empty values omitted
- Xrefs as
[address, type]tuples instead of objects - Write operations return
"ok"instead of{"success": true} - Responses over 8KB auto-truncated with pagination via
get_cached_output - Session ID auto-detected when only one session is active
Agents lose context on long conversations. IDA Pilot tracks it server-side:
set_analysis_note— attach notes to addresses during analysisget_analysis_context— retrieve all visited functions and notes to recover state
The surface is intentionally small: related operations are consolidated behind a few dispatcher tools that take a discriminator field (category, mode, action, target, format). 28 tools across three tiers.
| Tool | Description |
|---|---|
open_binary |
Open a binary. Returns immediately; loads + auto-analyzes in the background |
close_binary |
Close session and save database |
save_database |
Save without closing |
list_sessions |
List active sessions |
get_session_progress |
Load/analysis progress and readiness — poll until ready=true |
get_cached_output |
Paginate a truncated response via its _cache_id |
open_binary does not block: loading and auto-analysis run in a background worker thread so the call returns at once (large binaries take minutes to analyze). Poll get_session_progress until ready=true before issuing analysis tools — they are gated and return a not ready error until then. Status RPCs stay responsive throughout because the worker serializes IDA access on a single thread while answering progress queries from cached state.
| Tool | Description |
|---|---|
survey_binary |
Full binary overview in one call |
analyze_function |
Decompile + metadata + xrefs + comments for one function |
analyze_functions |
Batch-analyze up to 10 functions; per-item errors so one bad address won't fail the batch |
| Tool | Description |
|---|---|
query |
Browse by category: functions, imports, exports, strings, globals, segments, structs, enums, entry_point. Regex, pagination, and category filters (named_only, module, name) |
get_references |
Cross-references. mode=code (direction=to/from/both), data, or string |
search |
mode=text (needle) or binary (IDA pattern), over a start/end range |
inspect |
Name + type + comment + function bounds + instruction length at an address |
get_disasm |
Disassembly at an address, or the whole function |
read_memory |
Raw memory: format=bytes/dword/qword/byte/string |
| Tool | Description |
|---|---|
cross_reference |
Look up a symbol from one binary (source_session_id + address) and find where it appears in another (target_session_id) — searches imports, exports, function names, and strings |
cross_search |
Search across multiple sessions at once. mode=functions/imports/exports/strings, regex or exact needle. Omit session_ids to search all active sessions |
| Tool | Description |
|---|---|
run_auto_analysis |
Trigger IDA auto-analysis (for binaries opened with skip_analysis). Returns once analysis finishes, or — for a long analysis that outlasts the call — promptly with status:"analyzing" while it continues in the background; poll get_session_progress until ready=true |
watch_auto_analysis |
Observe auto-analysis progress (does not drive or cancel it) |
set_analysis_note / get_analysis_context |
Server-side context tracking for state recovery |
prune_context |
Clear cached outputs + noted-function caches to free memory |
py_eval (opt-in) |
Execute Python in IDA — registered only with --enable-py-eval. Invalidates caches after each call (read_only=true to keep them warm). See Security model. |
| Tool | Description |
|---|---|
annotate_function |
Batch rename + retype + comment for a function → returns updated pseudocode |
set_metadata |
action=set_name, delete_name, or set_comment (scope=address/function/decompiler) |
set_type |
target=function (prototype), global (C type), or lvar (C type + function_address + lvar_name) |
make_function |
Create a function at an address |
import_metadata |
format=il2cpp (script.json + il2cpp.h) or flutter (flutter_meta.json) |
--bind Bind interface (default 127.0.0.1; use 0.0.0.0 only behind an auth proxy)
--port HTTP port (default: from config.json)
--config Config file path (default: config.json)
--worker Python worker script path
--max-sessions Max concurrent sessions
--session-timeout Session idle timeout (e.g. 4h)
--debug Verbose logging
--enable-py-eval Register the py_eval tool (arbitrary Python in the IDA worker — RCE primitive)
--allowed-roots Restrict agent-supplied paths to these dirs (OS-path-list separated; empty = unrestricted)
IDA_PILOT_BIND=127.0.0.1
IDA_PILOT_PORT=17300
IDA_PILOT_SESSION_TIMEOUT_MIN=240
IDA_PILOT_MAX_SESSIONS=10
IDA_PILOT_WORKER=/path/to/worker.py
IDA_PILOT_DEBUG=1
IDA_PILOT_ENABLE_PY_EVAL=0
IDA_PILOT_ALLOWED_ROOTS=/srv/samples:/data/binaries # OS-path-list separated; empty = unrestricted
{
"bind": "127.0.0.1",
"port": 17300,
"session_timeout_minutes": 240,
"max_concurrent_sessions": 4,
"database_directory": "./databases",
"python_worker_path": "python/worker/server.py",
"debug": false,
"enable_py_eval": false,
"allowed_roots": []
}make build # Build server
make test # Run tests
make proto # Regenerate protobuf (requires protoc)
make clean # Clean artifacts
make inspector # Launch MCP Inspector at localhost:5173ida-pilot/
├── cmd/ida-pilot/ # Server entry point
├── internal/
│ ├── server/ # MCP tool handlers, caching, tiers
│ ├── session/ # Session registry + persistence
│ └── worker/ # Python worker process manager
├── proto/ # Protobuf service definitions
└── python/worker/ # idalib wrapper + Connect RPC server
Contributions are welcome — see CONTRIBUTING.md for setup, the local test gates, and conventions. To report a security issue privately, see SECURITY.md.
MIT