ida-pro-mcp-xjoker 1.0.1__tar.gz → 1.0.2__tar.gz

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.
Files changed (54) hide show
  1. ida_pro_mcp_xjoker-1.0.2/PKG-INFO +117 -0
  2. ida_pro_mcp_xjoker-1.0.2/README.md +95 -0
  3. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/pyproject.toml +5 -2
  4. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_analysis.py +3 -1
  5. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_core.py +1 -1
  6. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/auth.py +40 -3
  7. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/framework.py +3 -1
  8. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/server_manager.py +1 -1
  9. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/sync.py +10 -14
  10. ida_pro_mcp_xjoker-1.0.2/src/ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
  11. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_resources.py +1 -1
  12. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/ui.py +2 -3
  13. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/utils.py +5 -6
  14. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp.py +86 -38
  15. ida_pro_mcp_xjoker-1.0.2/src/ida_pro_mcp_xjoker.egg-info/PKG-INFO +117 -0
  16. ida_pro_mcp_xjoker-1.0.1/PKG-INFO +0 -405
  17. ida_pro_mcp_xjoker-1.0.1/README.md +0 -384
  18. ida_pro_mcp_xjoker-1.0.1/src/ida_pro_mcp/ida_mcp/tests/__init__.py +0 -14
  19. ida_pro_mcp_xjoker-1.0.1/src/ida_pro_mcp_xjoker.egg-info/PKG-INFO +0 -405
  20. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/LICENSE +0 -0
  21. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/setup.cfg +0 -0
  22. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/__init__.py +0 -0
  23. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/__main__.py +0 -0
  24. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/__init__.py +0 -0
  25. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_debug.py +0 -0
  26. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_memory.py +0 -0
  27. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_modify.py +0 -0
  28. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_python.py +0 -0
  29. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_resources.py +0 -0
  30. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_stack.py +0 -0
  31. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/api_types.py +0 -0
  32. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/cache.py +0 -0
  33. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/config.py +0 -0
  34. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/http.py +0 -0
  35. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/port_utils.py +0 -0
  36. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/rpc.py +0 -0
  37. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +0 -0
  38. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_core.py +0 -0
  39. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_memory.py +0 -0
  40. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_modify.py +0 -0
  41. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_stack.py +0 -0
  42. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/tests/test_api_types.py +0 -0
  43. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/zeromcp/__init__.py +0 -0
  44. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +0 -0
  45. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/ida_mcp/zeromcp/mcp.py +0 -0
  46. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/idalib_server.py +0 -0
  47. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/idalib_session_manager.py +0 -0
  48. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/server.py +0 -0
  49. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp/test.py +0 -0
  50. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp_xjoker.egg-info/SOURCES.txt +0 -0
  51. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp_xjoker.egg-info/dependency_links.txt +0 -0
  52. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp_xjoker.egg-info/entry_points.txt +0 -0
  53. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp_xjoker.egg-info/requires.txt +0 -0
  54. {ida_pro_mcp_xjoker-1.0.1 → ida_pro_mcp_xjoker-1.0.2}/src/ida_pro_mcp_xjoker.egg-info/top_level.txt +0 -0
@@ -0,0 +1,117 @@
1
+ Metadata-Version: 2.4
2
+ Name: ida-pro-mcp-xjoker
3
+ Version: 1.0.2
4
+ Summary: Vibe reversing with IDA Pro (enhanced fork)
5
+ Author: mrexodia, can1357, IDA Pro MCP Contributors
6
+ Maintainer: xjoker
7
+ Project-URL: Repository, https://github.com/xjoker/ida-pro-mcp
8
+ Project-URL: Issues, https://github.com/xjoker/ida-pro-mcp/issues
9
+ Keywords: ida,mcp,llm,plugin
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: idapro>=0.0.7
20
+ Requires-Dist: tomli-w>=1.0.0
21
+ Dynamic: license-file
22
+
23
+ # IDA Pro MCP (Enhanced Fork)
24
+
25
+ [中文文档](https://github.com/xjoker/ida-pro-mcp/blob/main/README_zh.md) | English
26
+
27
+ [![PyPI](https://img.shields.io/pypi/v/ida-pro-mcp-xjoker)](https://pypi.org/project/ida-pro-mcp-xjoker/)
28
+ [![Python](https://img.shields.io/pypi/pyversions/ida-pro-mcp-xjoker)](https://pypi.org/project/ida-pro-mcp-xjoker/)
29
+
30
+ An enhanced fork of [mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp) - MCP Server for LLM-assisted reverse engineering in IDA Pro.
31
+
32
+ ## What's Different from Original
33
+
34
+ | Feature | Original | This Fork |
35
+ |---------|----------|-----------|
36
+ | **Multi-instance Support** | ❌ Port conflict crashes | ✅ Auto port increment (13337→13346) |
37
+ | **Web Configuration** | ❌ None | ✅ Bilingual UI at `/config.html` |
38
+ | **API Key Auth** | ❌ None | ✅ Bearer token + env var support |
39
+ | **Server Startup** | Manual hotkey | ✅ Auto-start on IDA launch |
40
+ | **Hotkey Conflicts** | Occupies Ctrl+Alt+M | ✅ No hotkey, menu-only |
41
+ | **Config Persistence** | None | ✅ Saved per IDB database |
42
+
43
+ ### Key Enhancements
44
+
45
+ - **Port Conflict Auto-Retry**: Multiple IDA instances automatically use different ports
46
+ - **Web Config UI**: `http://localhost:13337/config.html` with English/中文 interface
47
+ - **API Key Authentication**: Secure remote access with Bearer token
48
+ - **Bug Fixes**: Thread safety, regex handling, type parsing errors fixed
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install ida-pro-mcp-xjoker
54
+ ida-pro-mcp --install
55
+ ```
56
+
57
+ Restart IDA Pro completely after installation.
58
+
59
+ ## Quick Start
60
+
61
+ 1. Open a binary in IDA Pro
62
+ 2. MCP server starts automatically on `http://127.0.0.1:13337`
63
+ 3. Configure your MCP client:
64
+
65
+ ```bash
66
+ # Claude Code
67
+ claude mcp add ida-pro-mcp http://127.0.0.1:13337/mcp
68
+
69
+ # With API Key authentication
70
+ claude mcp add --transport http ida-pro-mcp http://127.0.0.1:13337/mcp \
71
+ --header "Authorization: Bearer your-api-key"
72
+ ```
73
+
74
+ 4. Open web config at `http://127.0.0.1:13337/config.html` to customize settings
75
+
76
+ ## Requirements
77
+
78
+ - Python 3.11+
79
+ - IDA Pro 8.3+ (9.0 recommended), **IDA Free not supported**
80
+ - Any [MCP-compatible client](https://modelcontextprotocol.io/clients)
81
+
82
+ ## API Overview
83
+
84
+ **71 MCP Tools** including:
85
+
86
+ | Category | Tools |
87
+ |----------|-------|
88
+ | Analysis | `decompile`, `disasm`, `xrefs_to`, `callees`, `callers`, `basic_blocks` |
89
+ | Memory | `get_bytes`, `get_string`, `get_int`, `patch` |
90
+ | Types | `declare_type`, `set_type`, `infer_types`, `read_struct` |
91
+ | Modify | `set_comments`, `rename`, `patch_asm` |
92
+ | Search | `find_bytes`, `find_insns`, `find_regex` |
93
+ | Debug | `dbg_*` (20+ debugger tools, enable with `?ext=dbg`) |
94
+ | Python | `py_eval` - execute Python in IDA context |
95
+
96
+ **24 MCP Resources** for read-only access:
97
+ - `ida://idb/metadata`, `ida://cursor`, `ida://structs`, `ida://xrefs/from/{addr}`, etc.
98
+
99
+ ## Headless Mode
100
+
101
+ ```bash
102
+ # SSE transport
103
+ ida-pro-mcp --transport http://127.0.0.1:8744/sse
104
+
105
+ # With idalib (no GUI)
106
+ idalib-mcp --host 127.0.0.1 --port 8745 /path/to/binary
107
+ ```
108
+
109
+ ## Links
110
+
111
+ - [Original Project](https://github.com/mrexodia/ida-pro-mcp) by mrexodia
112
+ - [Changelog](https://github.com/xjoker/ida-pro-mcp/blob/main/CHANGELOG.md)
113
+ - [Issues](https://github.com/xjoker/ida-pro-mcp/issues)
114
+
115
+ ## License
116
+
117
+ MIT - Same as original project
@@ -0,0 +1,95 @@
1
+ # IDA Pro MCP (Enhanced Fork)
2
+
3
+ [中文文档](https://github.com/xjoker/ida-pro-mcp/blob/main/README_zh.md) | English
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/ida-pro-mcp-xjoker)](https://pypi.org/project/ida-pro-mcp-xjoker/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/ida-pro-mcp-xjoker)](https://pypi.org/project/ida-pro-mcp-xjoker/)
7
+
8
+ An enhanced fork of [mrexodia/ida-pro-mcp](https://github.com/mrexodia/ida-pro-mcp) - MCP Server for LLM-assisted reverse engineering in IDA Pro.
9
+
10
+ ## What's Different from Original
11
+
12
+ | Feature | Original | This Fork |
13
+ |---------|----------|-----------|
14
+ | **Multi-instance Support** | ❌ Port conflict crashes | ✅ Auto port increment (13337→13346) |
15
+ | **Web Configuration** | ❌ None | ✅ Bilingual UI at `/config.html` |
16
+ | **API Key Auth** | ❌ None | ✅ Bearer token + env var support |
17
+ | **Server Startup** | Manual hotkey | ✅ Auto-start on IDA launch |
18
+ | **Hotkey Conflicts** | Occupies Ctrl+Alt+M | ✅ No hotkey, menu-only |
19
+ | **Config Persistence** | None | ✅ Saved per IDB database |
20
+
21
+ ### Key Enhancements
22
+
23
+ - **Port Conflict Auto-Retry**: Multiple IDA instances automatically use different ports
24
+ - **Web Config UI**: `http://localhost:13337/config.html` with English/中文 interface
25
+ - **API Key Authentication**: Secure remote access with Bearer token
26
+ - **Bug Fixes**: Thread safety, regex handling, type parsing errors fixed
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ pip install ida-pro-mcp-xjoker
32
+ ida-pro-mcp --install
33
+ ```
34
+
35
+ Restart IDA Pro completely after installation.
36
+
37
+ ## Quick Start
38
+
39
+ 1. Open a binary in IDA Pro
40
+ 2. MCP server starts automatically on `http://127.0.0.1:13337`
41
+ 3. Configure your MCP client:
42
+
43
+ ```bash
44
+ # Claude Code
45
+ claude mcp add ida-pro-mcp http://127.0.0.1:13337/mcp
46
+
47
+ # With API Key authentication
48
+ claude mcp add --transport http ida-pro-mcp http://127.0.0.1:13337/mcp \
49
+ --header "Authorization: Bearer your-api-key"
50
+ ```
51
+
52
+ 4. Open web config at `http://127.0.0.1:13337/config.html` to customize settings
53
+
54
+ ## Requirements
55
+
56
+ - Python 3.11+
57
+ - IDA Pro 8.3+ (9.0 recommended), **IDA Free not supported**
58
+ - Any [MCP-compatible client](https://modelcontextprotocol.io/clients)
59
+
60
+ ## API Overview
61
+
62
+ **71 MCP Tools** including:
63
+
64
+ | Category | Tools |
65
+ |----------|-------|
66
+ | Analysis | `decompile`, `disasm`, `xrefs_to`, `callees`, `callers`, `basic_blocks` |
67
+ | Memory | `get_bytes`, `get_string`, `get_int`, `patch` |
68
+ | Types | `declare_type`, `set_type`, `infer_types`, `read_struct` |
69
+ | Modify | `set_comments`, `rename`, `patch_asm` |
70
+ | Search | `find_bytes`, `find_insns`, `find_regex` |
71
+ | Debug | `dbg_*` (20+ debugger tools, enable with `?ext=dbg`) |
72
+ | Python | `py_eval` - execute Python in IDA context |
73
+
74
+ **24 MCP Resources** for read-only access:
75
+ - `ida://idb/metadata`, `ida://cursor`, `ida://structs`, `ida://xrefs/from/{addr}`, etc.
76
+
77
+ ## Headless Mode
78
+
79
+ ```bash
80
+ # SSE transport
81
+ ida-pro-mcp --transport http://127.0.0.1:8744/sse
82
+
83
+ # With idalib (no GUI)
84
+ idalib-mcp --host 127.0.0.1 --port 8745 /path/to/binary
85
+ ```
86
+
87
+ ## Links
88
+
89
+ - [Original Project](https://github.com/mrexodia/ida-pro-mcp) by mrexodia
90
+ - [Changelog](https://github.com/xjoker/ida-pro-mcp/blob/main/CHANGELOG.md)
91
+ - [Issues](https://github.com/xjoker/ida-pro-mcp/issues)
92
+
93
+ ## License
94
+
95
+ MIT - Same as original project
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ida-pro-mcp-xjoker"
3
- version = "1.0.1"
3
+ version = "1.0.2"
4
4
  description = "Vibe reversing with IDA Pro (enhanced fork)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -9,6 +9,9 @@ authors = [
9
9
  { name = "can1357" },
10
10
  { name = "IDA Pro MCP Contributors" },
11
11
  ]
12
+ maintainers = [
13
+ { name = "xjoker" },
14
+ ]
12
15
  keywords = ["ida", "mcp", "llm", "plugin"]
13
16
  classifiers = [
14
17
  "Development Status :: 5 - Production/Stable",
@@ -50,7 +53,7 @@ dev = [
50
53
 
51
54
  [tool.commitizen]
52
55
  name = "cz_conventional_commits"
53
- version = "1.0.1"
56
+ version = "1.0.2"
54
57
  version_files = [
55
58
  "pyproject.toml:^version"
56
59
  ]
@@ -13,6 +13,7 @@ import ida_idaapi
13
13
  import ida_xref
14
14
  import ida_ua
15
15
  import ida_name
16
+ import ida_idp
16
17
  from .rpc import tool
17
18
  from .sync import idasync, tool_timeout
18
19
  from .cache import decompile_cache, xrefs_cache
@@ -503,7 +504,8 @@ def callees(
503
504
  break
504
505
  current_ea = next_ea
505
506
  continue
506
- if insn.itype in [idaapi.NN_call, idaapi.NN_callfi, idaapi.NN_callni]:
507
+ # Use architecture-independent call instruction detection
508
+ if ida_idp.is_call_insn(insn):
507
509
  op0 = insn.ops[0]
508
510
  if op0.type in (ida_ua.o_mem, ida_ua.o_near, ida_ua.o_far):
509
511
  target = op0.addr
@@ -10,7 +10,7 @@ import ida_nalt
10
10
 
11
11
  from .rpc import tool
12
12
  from .sync import idasync
13
- from .cache import function_cache, string_cache
13
+ from .cache import function_cache
14
14
 
15
15
  # Cached strings list: [(ea, text), ...]
16
16
  _strings_cache: list[tuple[int, str]] | None = None
@@ -3,11 +3,14 @@
3
3
  Provides authentication middleware for MCP server with support for:
4
4
  - Bearer token authentication (Authorization: Bearer <key>)
5
5
  - X-API-Key header authentication
6
+ - Environment variable expansion (${ENV_VAR} syntax)
6
7
  - Timing-attack resistant comparison
7
8
  """
8
9
 
9
10
  import hmac
10
11
  import logging
12
+ import os
13
+ import re
11
14
  from typing import Optional, Callable
12
15
 
13
16
  logger = logging.getLogger(__name__)
@@ -18,6 +21,33 @@ AUTH_EXEMPT_PATHS = frozenset({
18
21
  "/config.html",
19
22
  })
20
23
 
24
+ # Pattern to match ${ENV_VAR} syntax
25
+ _ENV_VAR_PATTERN = re.compile(r"^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$")
26
+
27
+
28
+ def resolve_env_var(value: Optional[str]) -> Optional[str]:
29
+ """Resolve environment variable reference in ${VAR} format.
30
+
31
+ Args:
32
+ value: The value to resolve, may be a literal or ${ENV_VAR} reference
33
+
34
+ Returns:
35
+ The resolved value (from environment) or the original value if not a reference
36
+ """
37
+ if not value:
38
+ return value
39
+
40
+ match = _ENV_VAR_PATTERN.match(value.strip())
41
+ if match:
42
+ env_name = match.group(1)
43
+ env_value = os.environ.get(env_name)
44
+ if env_value:
45
+ return env_value
46
+ else:
47
+ logger.warning(f"Environment variable '{env_name}' not found, using literal value")
48
+ return None # Env var not set, disable auth
49
+ return value
50
+
21
51
 
22
52
  def check_api_key(provided_key: Optional[str], expected_key: Optional[str]) -> bool:
23
53
  """Compare API keys using constant-time comparison to prevent timing attacks.
@@ -100,23 +130,29 @@ class AuthMiddleware:
100
130
 
101
131
  Args:
102
132
  api_key: The expected API key (None = no authentication)
133
+ Supports ${ENV_VAR} syntax to reference environment variables
103
134
  enabled: Whether authentication is enabled
104
135
  """
105
- self._api_key = api_key
136
+ self._api_key_raw = api_key # Store original value (may be ${ENV_VAR})
106
137
  self._enabled = enabled and api_key is not None
107
138
 
108
139
  @property
109
140
  def enabled(self) -> bool:
110
141
  return self._enabled
111
142
 
143
+ @property
144
+ def _api_key(self) -> Optional[str]:
145
+ """Get the resolved API key (expands ${ENV_VAR} references)."""
146
+ return resolve_env_var(self._api_key_raw)
147
+
112
148
  def update_key(self, api_key: Optional[str], enabled: bool = True) -> None:
113
149
  """Update the API key configuration.
114
150
 
115
151
  Args:
116
- api_key: New API key
152
+ api_key: New API key (supports ${ENV_VAR} syntax)
117
153
  enabled: Whether to enable authentication
118
154
  """
119
- self._api_key = api_key
155
+ self._api_key_raw = api_key
120
156
  self._enabled = enabled and api_key is not None
121
157
 
122
158
  def authenticate(self, path: str, headers: dict) -> bool:
@@ -160,6 +196,7 @@ __all__ = [
160
196
  "check_api_key",
161
197
  "extract_api_key_from_headers",
162
198
  "is_path_exempt",
199
+ "resolve_env_var",
163
200
  "AuthMiddleware",
164
201
  "create_auth_check",
165
202
  "AUTH_EXEMPT_PATHS",
@@ -340,6 +340,7 @@ def get_functions_with_calls() -> list[str]:
340
340
  """
341
341
  import idaapi
342
342
  import idautils
343
+ import ida_idp
343
344
 
344
345
  result = []
345
346
  for func_ea in idautils.Functions():
@@ -352,7 +353,8 @@ def get_functions_with_calls() -> list[str]:
352
353
  for head in idautils.Heads(func.start_ea, func.end_ea):
353
354
  insn = idaapi.insn_t()
354
355
  if idaapi.decode_insn(insn, head) > 0:
355
- if insn.itype in [idaapi.NN_call, idaapi.NN_callfi, idaapi.NN_callni]:
356
+ # Use architecture-independent call instruction detection
357
+ if ida_idp.is_call_insn(insn):
356
358
  has_call = True
357
359
  break
358
360
 
@@ -11,7 +11,7 @@ import threading
11
11
  from dataclasses import dataclass, field
12
12
  from typing import Optional, Callable, TYPE_CHECKING
13
13
 
14
- from .config import ServerInstanceConfig, McpConfig, get_config, save_config
14
+ from .config import ServerInstanceConfig, McpConfig, get_config
15
15
  from .auth import AuthMiddleware
16
16
  from .port_utils import try_serve_with_port_retry
17
17
 
@@ -107,11 +107,14 @@ def _sync_wrapper(ff):
107
107
  raise IDASyncError(error_str)
108
108
 
109
109
  call_stack.put((ff.__name__))
110
+ # Enable batch mode for all synchronized operations
111
+ old_batch = idc.batch(1)
110
112
  try:
111
113
  res_container.put(ff())
112
114
  except Exception as x:
113
115
  res_container.put(x)
114
116
  finally:
117
+ idc.batch(old_batch)
115
118
  call_stack.get()
116
119
 
117
120
  idaapi.execute_sync(runned, idaapi.MFF_WRITE)
@@ -132,21 +135,14 @@ def _normalize_timeout(value: object) -> float | None:
132
135
 
133
136
 
134
137
  def sync_wrapper(ff, timeout_override: float | None = None):
135
- """Wrapper to enable batch mode during IDA synchronization."""
138
+ """Wrapper to enable timeout and cancellation during IDA synchronization.
139
+
140
+ Note: Batch mode is handled in _sync_wrapper to ensure it's always
141
+ applied consistently for all synchronized operations.
142
+ """
136
143
  # Capture cancel event from thread-local before execute_sync
137
144
  cancel_event = get_current_cancel_event()
138
145
 
139
- def _run_with_batch(inner_ff):
140
- def _wrapped():
141
- old_batch = idc.batch(1)
142
- try:
143
- return inner_ff()
144
- finally:
145
- idc.batch(old_batch)
146
-
147
- _wrapped.__name__ = inner_ff.__name__
148
- return _wrapped
149
-
150
146
  timeout = timeout_override
151
147
  if timeout is None:
152
148
  timeout = _get_tool_timeout_seconds()
@@ -172,8 +168,8 @@ def sync_wrapper(ff, timeout_override: float | None = None):
172
168
  sys.setprofile(old_profile)
173
169
 
174
170
  timed_ff.__name__ = ff.__name__
175
- return _sync_wrapper(_run_with_batch(timed_ff))
176
- return _sync_wrapper(_run_with_batch(ff))
171
+ return _sync_wrapper(timed_ff)
172
+ return _sync_wrapper(ff)
177
173
 
178
174
 
179
175
  def idasync(f):
@@ -0,0 +1,14 @@
1
+ """IDA Pro MCP Test Package.
2
+
3
+ This package contains test modules for each API module.
4
+ Tests are registered via the @test decorator from the framework module.
5
+ """
6
+
7
+ # Import all test modules to register tests when the package is imported
8
+ from . import test_api_core as test_api_core
9
+ from . import test_api_analysis as test_api_analysis
10
+ from . import test_api_memory as test_api_memory
11
+ from . import test_api_modify as test_api_modify
12
+ from . import test_api_types as test_api_types
13
+ from . import test_api_stack as test_api_stack
14
+ from . import test_api_resources as test_api_resources
@@ -145,7 +145,7 @@ def test_resource_struct_name():
145
145
  def test_resource_struct_name_not_found():
146
146
  """struct_name_resource handles non-existent structure"""
147
147
  try:
148
- result = struct_name_resource("NonExistentStruct12345")
148
+ struct_name_resource("NonExistentStruct12345")
149
149
  # Should return error or empty
150
150
  except IDAError:
151
151
  pass # Expected for non-existent struct
@@ -10,7 +10,6 @@ from typing import Optional, TYPE_CHECKING
10
10
 
11
11
  from .config import (
12
12
  ServerInstanceConfig,
13
- McpConfig,
14
13
  get_config,
15
14
  save_config,
16
15
  reload_config,
@@ -34,9 +33,9 @@ class ServerConfigForm(idaapi.Form):
34
33
  instance_id = "" if is_new else config.instance_id
35
34
  host = "127.0.0.1" if is_new else config.host
36
35
  port = 13337 if is_new else config.port
37
- auth_enabled = False if is_new else config.auth_enabled
36
+ _ = False if is_new else config.auth_enabled # Reserved for future use
38
37
  api_key = "" if is_new else (config.api_key or "")
39
- auto_start = False if is_new else config.auto_start
38
+ _ = False if is_new else config.auto_start # Reserved for future use
40
39
 
41
40
  form_template = r"""STARTITEM 0
42
41
  BUTTON YES* Save
@@ -22,6 +22,7 @@ from typing import (
22
22
 
23
23
  import ida_funcs
24
24
  import ida_hexrays
25
+ import ida_idp
25
26
  import ida_kernwin
26
27
  import ida_nalt
27
28
  import ida_typeinf
@@ -1022,7 +1023,8 @@ def get_callees(addr: str) -> list[dict]:
1022
1023
  while current_ea < func_end:
1023
1024
  insn = idaapi.insn_t()
1024
1025
  idaapi.decode_insn(insn, current_ea)
1025
- if insn.itype in [idaapi.NN_call, idaapi.NN_callfi, idaapi.NN_callni]:
1026
+ # Use architecture-independent call instruction detection
1027
+ if ida_idp.is_call_insn(insn):
1026
1028
  target = idc.get_operand_value(current_ea, 0)
1027
1029
  target_type = idc.get_operand_type(current_ea, 0)
1028
1030
  if target_type in [idaapi.o_mem, idaapi.o_near, idaapi.o_far]:
@@ -1064,11 +1066,8 @@ def get_callers(addr: str, limit: int = 50) -> list[Function]:
1064
1066
  continue
1065
1067
  insn = idaapi.insn_t()
1066
1068
  idaapi.decode_insn(insn, caller_addr)
1067
- if insn.itype not in [
1068
- idaapi.NN_call,
1069
- idaapi.NN_callfi,
1070
- idaapi.NN_callni,
1071
- ]:
1069
+ # Use architecture-independent call instruction detection
1070
+ if not ida_idp.is_call_insn(insn):
1072
1071
  continue
1073
1072
  callers[func["addr"]] = func
1074
1073