ida-code 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ida_code/__init__.py +2 -0
- ida_code/_search_utils.py +33 -0
- ida_code/comments.py +191 -0
- ida_code/config.py +9 -0
- ida_code/doc_search.py +255 -0
- ida_code/example_search.py +570 -0
- ida_code/executor.py +145 -0
- ida_code/guidelines.py +370 -0
- ida_code/macho.py +67 -0
- ida_code/prompts.py +176 -0
- ida_code/server.py +1011 -0
- ida_code/session.py +293 -0
- ida_code/snapshots.py +110 -0
- ida_code/structures.py +227 -0
- ida_code/undo.py +102 -0
- ida_code/variables.py +206 -0
- ida_code-0.2.1.dist-info/METADATA +167 -0
- ida_code-0.2.1.dist-info/RECORD +21 -0
- ida_code-0.2.1.dist-info/WHEEL +4 -0
- ida_code-0.2.1.dist-info/entry_points.txt +2 -0
- ida_code-0.2.1.dist-info/licenses/LICENSE +21 -0
ida_code/undo.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from fastmcp.exceptions import ToolError
|
|
4
|
+
|
|
5
|
+
from ida_code import session
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_status() -> dict:
|
|
11
|
+
"""Return current undo/redo availability and action labels."""
|
|
12
|
+
session.require_open()
|
|
13
|
+
|
|
14
|
+
import ida_undo
|
|
15
|
+
|
|
16
|
+
undo_label = ida_undo.get_undo_action_label() or ""
|
|
17
|
+
redo_label = ida_undo.get_redo_action_label() or ""
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
"can_undo": bool(undo_label),
|
|
21
|
+
"undo_action": undo_label,
|
|
22
|
+
"can_redo": bool(redo_label),
|
|
23
|
+
"redo_action": redo_label,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def perform_undo(steps: int = 1) -> dict:
|
|
28
|
+
"""Undo the last action(s). Returns details of what was undone."""
|
|
29
|
+
session.require_open()
|
|
30
|
+
|
|
31
|
+
if steps < 1:
|
|
32
|
+
raise ToolError("steps must be at least 1.")
|
|
33
|
+
|
|
34
|
+
import ida_undo
|
|
35
|
+
|
|
36
|
+
undo_label = ida_undo.get_undo_action_label() or ""
|
|
37
|
+
if not undo_label:
|
|
38
|
+
raise ToolError("Nothing to undo.")
|
|
39
|
+
|
|
40
|
+
actions: list[str] = []
|
|
41
|
+
for _ in range(steps):
|
|
42
|
+
label = ida_undo.get_undo_action_label() or ""
|
|
43
|
+
if not label:
|
|
44
|
+
break
|
|
45
|
+
ida_undo.perform_undo()
|
|
46
|
+
actions.append(label)
|
|
47
|
+
|
|
48
|
+
# Database state changed — reset executor namespace.
|
|
49
|
+
from ida_code.executor import reset
|
|
50
|
+
reset()
|
|
51
|
+
|
|
52
|
+
undo_next = ida_undo.get_undo_action_label() or ""
|
|
53
|
+
redo_next = ida_undo.get_redo_action_label() or ""
|
|
54
|
+
|
|
55
|
+
log.info("Undo %d/%d steps: %s", len(actions), steps, actions)
|
|
56
|
+
return {
|
|
57
|
+
"status": "undone",
|
|
58
|
+
"steps_requested": steps,
|
|
59
|
+
"steps_performed": len(actions),
|
|
60
|
+
"actions": actions,
|
|
61
|
+
"next_undo": undo_next,
|
|
62
|
+
"next_redo": redo_next,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def perform_redo(steps: int = 1) -> dict:
|
|
67
|
+
"""Redo the last undone action(s). Returns details of what was redone."""
|
|
68
|
+
session.require_open()
|
|
69
|
+
|
|
70
|
+
if steps < 1:
|
|
71
|
+
raise ToolError("steps must be at least 1.")
|
|
72
|
+
|
|
73
|
+
import ida_undo
|
|
74
|
+
|
|
75
|
+
redo_label = ida_undo.get_redo_action_label() or ""
|
|
76
|
+
if not redo_label:
|
|
77
|
+
raise ToolError("Nothing to redo.")
|
|
78
|
+
|
|
79
|
+
actions: list[str] = []
|
|
80
|
+
for _ in range(steps):
|
|
81
|
+
label = ida_undo.get_redo_action_label() or ""
|
|
82
|
+
if not label:
|
|
83
|
+
break
|
|
84
|
+
ida_undo.perform_redo()
|
|
85
|
+
actions.append(label)
|
|
86
|
+
|
|
87
|
+
# Database state changed — reset executor namespace.
|
|
88
|
+
from ida_code.executor import reset
|
|
89
|
+
reset()
|
|
90
|
+
|
|
91
|
+
undo_next = ida_undo.get_undo_action_label() or ""
|
|
92
|
+
redo_next = ida_undo.get_redo_action_label() or ""
|
|
93
|
+
|
|
94
|
+
log.info("Redo %d/%d steps: %s", len(actions), steps, actions)
|
|
95
|
+
return {
|
|
96
|
+
"status": "redone",
|
|
97
|
+
"steps_requested": steps,
|
|
98
|
+
"steps_performed": len(actions),
|
|
99
|
+
"actions": actions,
|
|
100
|
+
"next_undo": undo_next,
|
|
101
|
+
"next_redo": redo_next,
|
|
102
|
+
}
|
ida_code/variables.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Variable inspection and modification (local/decompiler and global)."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from fastmcp.exceptions import ToolError
|
|
6
|
+
|
|
7
|
+
from ida_code import session
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _decompile_func(func_ea: int):
|
|
13
|
+
"""Decompile the function at *func_ea*, returning a cfunc_t.
|
|
14
|
+
|
|
15
|
+
Raises ToolError if Hex-Rays is unavailable or decompilation fails.
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
import ida_hexrays
|
|
19
|
+
except ImportError:
|
|
20
|
+
raise ToolError("Hex-Rays decompiler is not available.")
|
|
21
|
+
|
|
22
|
+
import ida_funcs
|
|
23
|
+
|
|
24
|
+
pfn = ida_funcs.get_func(func_ea)
|
|
25
|
+
if pfn is None:
|
|
26
|
+
raise ToolError(f"Address {func_ea:#x} is not within a recognized function.")
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
cfunc = ida_hexrays.decompile(pfn.start_ea)
|
|
30
|
+
except ida_hexrays.DecompilationFailure as e:
|
|
31
|
+
raise ToolError(f"Decompilation failed: {e}")
|
|
32
|
+
except Exception as e:
|
|
33
|
+
raise ToolError(f"{type(e).__name__}: {e}")
|
|
34
|
+
|
|
35
|
+
if cfunc is None:
|
|
36
|
+
raise ToolError("Decompilation returned no result.")
|
|
37
|
+
return cfunc
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _find_lvar(cfunc, name: str):
|
|
41
|
+
"""Find a local variable by name in *cfunc*.lvars.
|
|
42
|
+
|
|
43
|
+
Raises ToolError if not found, listing available names.
|
|
44
|
+
"""
|
|
45
|
+
for lv in cfunc.lvars:
|
|
46
|
+
if lv.name == name:
|
|
47
|
+
return lv
|
|
48
|
+
|
|
49
|
+
available = [lv.name for lv in cfunc.lvars if lv.name]
|
|
50
|
+
raise ToolError(
|
|
51
|
+
f"Local variable '{name}' not found. "
|
|
52
|
+
f"Available: {available}"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _lvar_to_dict(lv, func_ea: int) -> dict:
|
|
57
|
+
"""Convert a local variable (lvar_t) to a result dict."""
|
|
58
|
+
import ida_funcs
|
|
59
|
+
|
|
60
|
+
func_name = ida_funcs.get_func_name(func_ea) or f"sub_{func_ea:x}"
|
|
61
|
+
return {
|
|
62
|
+
"name": lv.name,
|
|
63
|
+
"type": str(lv.type()),
|
|
64
|
+
"width": lv.width,
|
|
65
|
+
"is_arg": lv.is_arg_var,
|
|
66
|
+
"function": func_name,
|
|
67
|
+
"scope": "local",
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _global_to_dict(ea: int) -> dict:
|
|
72
|
+
"""Convert a global address to a result dict."""
|
|
73
|
+
import ida_name
|
|
74
|
+
import ida_nalt
|
|
75
|
+
import ida_typeinf
|
|
76
|
+
import idc
|
|
77
|
+
|
|
78
|
+
name = ida_name.get_name(ea) or f"unk_{ea:x}"
|
|
79
|
+
|
|
80
|
+
# Try to get type info.
|
|
81
|
+
tif = ida_typeinf.tinfo_t()
|
|
82
|
+
if ida_nalt.get_tinfo(tif, ea):
|
|
83
|
+
type_str = str(tif)
|
|
84
|
+
else:
|
|
85
|
+
type_str = idc.get_type(ea) or ""
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"name": name,
|
|
89
|
+
"type": type_str,
|
|
90
|
+
"address": f"{ea:#x}",
|
|
91
|
+
"scope": "global",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _parse_type(type_str: str):
|
|
96
|
+
"""Parse a C type string into a tinfo_t.
|
|
97
|
+
|
|
98
|
+
Raises ToolError if parsing fails.
|
|
99
|
+
"""
|
|
100
|
+
import ida_typeinf
|
|
101
|
+
|
|
102
|
+
tif = ida_typeinf.tinfo_t()
|
|
103
|
+
til = ida_typeinf.get_idati()
|
|
104
|
+
decl = f"{type_str} __dummy;"
|
|
105
|
+
if not ida_typeinf.parse_decl(tif, til, decl, ida_typeinf.PT_SIL):
|
|
106
|
+
raise ToolError(f"Failed to parse type: '{type_str}'")
|
|
107
|
+
return tif
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_local_variable(func_ea: int, name: str) -> dict:
|
|
111
|
+
"""Get info about a local (decompiler) variable."""
|
|
112
|
+
session.require_open()
|
|
113
|
+
|
|
114
|
+
cfunc = _decompile_func(func_ea)
|
|
115
|
+
lv = _find_lvar(cfunc, name)
|
|
116
|
+
return _lvar_to_dict(lv, func_ea)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_global_variable(ea: int) -> dict:
|
|
120
|
+
"""Get info about a global variable/name at *ea*."""
|
|
121
|
+
session.require_open()
|
|
122
|
+
return _global_to_dict(ea)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def set_local_variable(
|
|
126
|
+
func_ea: int,
|
|
127
|
+
name: str,
|
|
128
|
+
new_name: str | None = None,
|
|
129
|
+
new_type: str | None = None,
|
|
130
|
+
) -> dict:
|
|
131
|
+
"""Rename and/or retype a local (decompiler) variable."""
|
|
132
|
+
session.require_open()
|
|
133
|
+
|
|
134
|
+
import ida_hexrays
|
|
135
|
+
import ida_funcs
|
|
136
|
+
|
|
137
|
+
pfn = ida_funcs.get_func(func_ea)
|
|
138
|
+
if pfn is None:
|
|
139
|
+
raise ToolError(f"Address {func_ea:#x} is not within a recognized function.")
|
|
140
|
+
start_ea = pfn.start_ea
|
|
141
|
+
|
|
142
|
+
# Verify the variable exists first.
|
|
143
|
+
cfunc = _decompile_func(func_ea)
|
|
144
|
+
_find_lvar(cfunc, name)
|
|
145
|
+
|
|
146
|
+
if new_name is not None:
|
|
147
|
+
ok = ida_hexrays.rename_lvar(start_ea, name, new_name)
|
|
148
|
+
if not ok:
|
|
149
|
+
raise ToolError(f"Failed to rename local variable '{name}' to '{new_name}'.")
|
|
150
|
+
log.info("Renamed local variable '%s' -> '%s' in func at %#x", name, new_name, start_ea)
|
|
151
|
+
|
|
152
|
+
if new_type is not None:
|
|
153
|
+
tif = _parse_type(new_type)
|
|
154
|
+
lsi = ida_hexrays.lvar_saved_info_t()
|
|
155
|
+
current_name = new_name if new_name is not None else name
|
|
156
|
+
lsi.name = current_name
|
|
157
|
+
lsi.type = tif
|
|
158
|
+
lsi.size = tif.get_size()
|
|
159
|
+
lsi.flags = ida_hexrays.LVINF_TYPE
|
|
160
|
+
|
|
161
|
+
# Find the lvar to get its location info.
|
|
162
|
+
cfunc2 = _decompile_func(func_ea)
|
|
163
|
+
lv = _find_lvar(cfunc2, current_name)
|
|
164
|
+
lsi.ll = lv.location
|
|
165
|
+
|
|
166
|
+
ok = ida_hexrays.modify_user_lvar_info(start_ea, ida_hexrays.MLI_TYPE, lsi)
|
|
167
|
+
if not ok:
|
|
168
|
+
raise ToolError(f"Failed to retype local variable '{current_name}' to '{new_type}'.")
|
|
169
|
+
log.info("Retyped local variable '%s' to '%s' in func at %#x", current_name, new_type, start_ea)
|
|
170
|
+
|
|
171
|
+
# Re-decompile and return updated info.
|
|
172
|
+
final_name = new_name if new_name is not None else name
|
|
173
|
+
cfunc_final = _decompile_func(func_ea)
|
|
174
|
+
lv_final = _find_lvar(cfunc_final, final_name)
|
|
175
|
+
result = _lvar_to_dict(lv_final, func_ea)
|
|
176
|
+
result["status"] = "modified"
|
|
177
|
+
return result
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def set_global_variable(
|
|
181
|
+
ea: int,
|
|
182
|
+
new_name: str | None = None,
|
|
183
|
+
new_type: str | None = None,
|
|
184
|
+
) -> dict:
|
|
185
|
+
"""Rename and/or retype a global variable/name."""
|
|
186
|
+
session.require_open()
|
|
187
|
+
|
|
188
|
+
import ida_name
|
|
189
|
+
import ida_typeinf
|
|
190
|
+
|
|
191
|
+
if new_name is not None:
|
|
192
|
+
ok = ida_name.set_name(ea, new_name, ida_name.SN_CHECK)
|
|
193
|
+
if not ok:
|
|
194
|
+
raise ToolError(f"Failed to rename global at {ea:#x} to '{new_name}'.")
|
|
195
|
+
log.info("Renamed global at %#x -> '%s'", ea, new_name)
|
|
196
|
+
|
|
197
|
+
if new_type is not None:
|
|
198
|
+
tif = _parse_type(new_type)
|
|
199
|
+
ok = ida_typeinf.apply_tinfo(ea, tif, ida_typeinf.TINFO_DEFINITE)
|
|
200
|
+
if not ok:
|
|
201
|
+
raise ToolError(f"Failed to retype global at {ea:#x} to '{new_type}'.")
|
|
202
|
+
log.info("Retyped global at %#x to '%s'", ea, new_type)
|
|
203
|
+
|
|
204
|
+
result = _global_to_dict(ea)
|
|
205
|
+
result["status"] = "modified"
|
|
206
|
+
return result
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ida-code
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: MCP server for AI-assisted IDAPython scripting via idalib
|
|
5
|
+
Project-URL: Homepage, https://github.com/Dil4rd/ida-code
|
|
6
|
+
Project-URL: Repository, https://github.com/Dil4rd/ida-code
|
|
7
|
+
Project-URL: Issues, https://github.com/Dil4rd/ida-code/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Dil4rd/ida-code/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Dil4rd
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ida,ida-pro,idalib,idapython,mcp,reverse-engineering
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Information Technology
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Classifier: Topic :: Software Development :: Disassemblers
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.12
|
|
24
|
+
Requires-Dist: fastmcp<4,>=3.0
|
|
25
|
+
Requires-Dist: lief>=0.15
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# ida-code
|
|
31
|
+
|
|
32
|
+
MCP server that lets AI coding agents interact with IDA Pro. Open binaries, decompile, run IDAPython, search the API docs — all through tool calls.
|
|
33
|
+
|
|
34
|
+
Built on [idalib](https://docs.hex-rays.com/developer-guide/idalib) for headless in-process operation and [fastmcp](https://github.com/jlowin/fastmcp) for the MCP transport.
|
|
35
|
+
|
|
36
|
+
> **Requires** a licensed IDA Pro 9.2+ with idalib support. `ida-code` does not install or replace IDA Pro — it loads `idapro` from your existing install at startup.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv tool install ida-code
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This puts the `ida-code` CLI on your `PATH` (via `uv`'s tool dir) so MCP clients can launch it directly. Don't have uv? `pip install ida-code` works too.
|
|
45
|
+
|
|
46
|
+
Then point `IDA_INSTALL_DIR` at your IDA Pro install (the directory that contains `idalib/python/`):
|
|
47
|
+
|
|
48
|
+
| OS | Typical path |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Linux | `/opt/ida-pro-9.3` |
|
|
51
|
+
| macOS | `/Applications/IDA Professional 9.3.app/Contents/MacOS` |
|
|
52
|
+
| Windows | `C:\Program Files\IDA Professional 9.3` |
|
|
53
|
+
|
|
54
|
+
## Use with Claude Code
|
|
55
|
+
|
|
56
|
+
Add `ida-code` to your project's `.mcp.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"ida-code": {
|
|
62
|
+
"command": "ida-code",
|
|
63
|
+
"env": {
|
|
64
|
+
"IDA_INSTALL_DIR": "/opt/ida-pro-9.3"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Restart Claude Code; the server is picked up automatically. You can confirm it's wired up by asking Claude to open a binary — it should call `open_database` and report architecture, entry point, and load address.
|
|
72
|
+
|
|
73
|
+
For other MCP clients, run the server directly:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
IDA_INSTALL_DIR=/opt/ida-pro-9.3 ida-code # stdio transport
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Tools (35)
|
|
80
|
+
|
|
81
|
+
Full parameter docs live in each tool's docstring — surfaced automatically to MCP clients via `tools/list`.
|
|
82
|
+
|
|
83
|
+
| Domain | Tools |
|
|
84
|
+
|---|---|
|
|
85
|
+
| Database | `open_database`, `close_database`, `get_database_info`, `list_architectures` |
|
|
86
|
+
| Code execution | `execute`, `execute_file` |
|
|
87
|
+
| Navigation | `list_functions`, `decompile`, `get_disassembly`, `get_xrefs_to`, `get_xrefs_from` |
|
|
88
|
+
| Annotation | `rename_function`, `retype_function`, `get_comment`, `set_comment`, `delete_comment`, `get_variable`, `set_variable` |
|
|
89
|
+
| Structures | `list_structures`, `get_structure`, `create_structure`, `edit_structure`, `delete_structure` |
|
|
90
|
+
| Snapshots | `list_snapshots`, `create_snapshot`, `restore_snapshot`, `delete_snapshot` |
|
|
91
|
+
| Undo/redo | `get_undo_status`, `perform_undo`, `perform_redo` |
|
|
92
|
+
| Inventory | `get_strings`, `get_imports`, `get_exports` |
|
|
93
|
+
| Search | `search_docs`, `search_examples` |
|
|
94
|
+
|
|
95
|
+
## Resources & prompts
|
|
96
|
+
|
|
97
|
+
| Type | URI / name | Purpose |
|
|
98
|
+
|---|---|---|
|
|
99
|
+
| Resource | `guidelines://standalone_script` | Boilerplate for standalone idalib scripts |
|
|
100
|
+
| Resource | `guidelines://plugin` | Boilerplate for IDA plugins (`plugin_t`) |
|
|
101
|
+
| Resource | `guidelines://idapython_script` | Boilerplate for IDAPython scripts run inside IDA GUI |
|
|
102
|
+
| Prompt | `reverse_engineer` | Five-phase RE workflow (recon, triage, analysis, annotation, iteration) |
|
|
103
|
+
| Prompt | `create_script` | Coding guidelines for a chosen target script type |
|
|
104
|
+
|
|
105
|
+
## Transport modes
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
ida-code # stdio (default)
|
|
109
|
+
ida-code --http # streamable-http on 127.0.0.1:8080
|
|
110
|
+
ida-code --http 0.0.0.0:9090 # custom host:port
|
|
111
|
+
ida-code --sse # SSE on 127.0.0.1:8080
|
|
112
|
+
ida-code --sse :9090 # SSE on 127.0.0.1:9090
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
HTTP/SSE require bearer token auth. Set `MCP_AUTH_TOKEN` or let the server generate one (printed to stderr on startup).
|
|
116
|
+
|
|
117
|
+
## Environment variables
|
|
118
|
+
|
|
119
|
+
| Variable | Default | Description |
|
|
120
|
+
|---|---|---|
|
|
121
|
+
| `IDA_INSTALL_DIR` | `/opt/ida-pro-9.3` | IDA Pro installation directory (must contain `idalib/python/`) |
|
|
122
|
+
| `LOG_LEVEL` | `WARNING` | Logging verbosity (`DEBUG`, `INFO`, `WARNING`, `ERROR`) |
|
|
123
|
+
| `MCP_AUTH_TOKEN` | (auto-generated) | Bearer token for HTTP/SSE transports |
|
|
124
|
+
|
|
125
|
+
Doc and example paths are derived from `IDA_INSTALL_DIR` (`docs/`, `python/`, `python/examples/`).
|
|
126
|
+
|
|
127
|
+
## Install from source
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/Dil4rd/ida-code
|
|
131
|
+
cd ida-code
|
|
132
|
+
uv sync
|
|
133
|
+
uv run ida-code
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
When wiring a source checkout into `.mcp.json`, use `uv` as the command:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"mcpServers": {
|
|
141
|
+
"ida-code": {
|
|
142
|
+
"command": "uv",
|
|
143
|
+
"args": ["run", "--directory", "/path/to/ida-code", "ida-code"],
|
|
144
|
+
"env": { "IDA_INSTALL_DIR": "/opt/ida-pro-9.3" }
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
> **Note:** the `fastmcp` dependency is the [community fastmcp](https://github.com/jlowin/fastmcp) package, not the official `mcp` SDK. Don't install `mcp` by mistake.
|
|
151
|
+
|
|
152
|
+
## Development
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
uv sync --extra dev
|
|
156
|
+
uv run pytest
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
The test suite covers the executor, doc/example search, comments, snapshots, structures, undo, variables, and Mach-O parsing. Tests that need idalib are skipped if it's not available.
|
|
160
|
+
|
|
161
|
+
## Credits
|
|
162
|
+
|
|
163
|
+
Thanks to [@p41l](https://github.com/p41l) for ideas and for testing the tool across different LLMs.
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT — see `LICENSE`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
ida_code/__init__.py,sha256=zWUY4fR0EJQx_22ZBPqyIDLdAMmZTbMzLR8PdkHzLZE,125
|
|
2
|
+
ida_code/_search_utils.py,sha256=fCrEc-exNzcRMms-cZMFGezxHUkgXmndWg2MVSk__84,1103
|
|
3
|
+
ida_code/comments.py,sha256=EvNuRIagaLpksg2nJETw4GXclXSAALU-pz-rIgUVKY4,5604
|
|
4
|
+
ida_code/config.py,sha256=lJwGy8F9j7yAS6gQYLsWS39B-KExYGPungzN8AOxbwo,358
|
|
5
|
+
ida_code/doc_search.py,sha256=ZQijMTInq93JvcF9CZv16n3jNI-qpc7iAgH6-qiZFqY,7461
|
|
6
|
+
ida_code/example_search.py,sha256=DCDuVrU2suhfn3hL6GhXTwTvdtXihCNaZJsQ6a87Tt4,17375
|
|
7
|
+
ida_code/executor.py,sha256=Ed_j0tpg4nTENQhT2LOrNRClfgh22MUfTHGbMXQvytM,4327
|
|
8
|
+
ida_code/guidelines.py,sha256=lqWgoPRgMEjamO4ORXaN3oKFo4STKFiBgIog6HtyOSM,10008
|
|
9
|
+
ida_code/macho.py,sha256=n8Ju7kWqTVKwFZAleLOaZupcDsvEHorzJchjSvpiKX8,2077
|
|
10
|
+
ida_code/prompts.py,sha256=UI9Wpy5tXzW9pTl3-UQyZrd0Ry2EhuT9agknqva82v4,8226
|
|
11
|
+
ida_code/server.py,sha256=k_NDC-_bYHk9lR7egiAKOdN-JiqiashnU7xinx1gask,33048
|
|
12
|
+
ida_code/session.py,sha256=7abx4BlX-LF8rYDDorc8Dohy0NfMjkyxuAog8EdN-Zc,8592
|
|
13
|
+
ida_code/snapshots.py,sha256=OxiIXIjuGVXQI1gGLH1s2HDPNW4Qm7CREkZTWJWiXoE,2967
|
|
14
|
+
ida_code/structures.py,sha256=JM3QXUGfLMcEZxhYnL6_RD2E2PG4-3JX_eVI3BpswA4,6370
|
|
15
|
+
ida_code/undo.py,sha256=2wYFpfDqPpqfXjyh1zzYAbULnTPgvdt_FNk4SwFcKGE,2705
|
|
16
|
+
ida_code/variables.py,sha256=orj-3qx5eYhsKV2S4oNoUwclWar0YFKWV5lKS1oj8GY,5980
|
|
17
|
+
ida_code-0.2.1.dist-info/METADATA,sha256=YgFfhCRL2oY8xsn48g4dJvn_r8XA_sdXenF2gnnIUDY,6066
|
|
18
|
+
ida_code-0.2.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
19
|
+
ida_code-0.2.1.dist-info/entry_points.txt,sha256=PxudvHpujlv2Ww0De7UN78LLjsN5VFHqC13znUTdIqg,50
|
|
20
|
+
ida_code-0.2.1.dist-info/licenses/LICENSE,sha256=6q3O_h5USiWV6W1EycYFMGr1IOeirQ8n37KS022-s6c,1063
|
|
21
|
+
ida_code-0.2.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dil4rd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|