ida-pro-mcp-xjoker 1.0.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.
Files changed (45) hide show
  1. ida_pro_mcp/__init__.py +0 -0
  2. ida_pro_mcp/__main__.py +6 -0
  3. ida_pro_mcp/ida_mcp/__init__.py +68 -0
  4. ida_pro_mcp/ida_mcp/api_analysis.py +1296 -0
  5. ida_pro_mcp/ida_mcp/api_core.py +337 -0
  6. ida_pro_mcp/ida_mcp/api_debug.py +617 -0
  7. ida_pro_mcp/ida_mcp/api_memory.py +304 -0
  8. ida_pro_mcp/ida_mcp/api_modify.py +406 -0
  9. ida_pro_mcp/ida_mcp/api_python.py +179 -0
  10. ida_pro_mcp/ida_mcp/api_resources.py +295 -0
  11. ida_pro_mcp/ida_mcp/api_stack.py +167 -0
  12. ida_pro_mcp/ida_mcp/api_types.py +480 -0
  13. ida_pro_mcp/ida_mcp/auth.py +166 -0
  14. ida_pro_mcp/ida_mcp/cache.py +232 -0
  15. ida_pro_mcp/ida_mcp/config.py +228 -0
  16. ida_pro_mcp/ida_mcp/framework.py +547 -0
  17. ida_pro_mcp/ida_mcp/http.py +859 -0
  18. ida_pro_mcp/ida_mcp/port_utils.py +104 -0
  19. ida_pro_mcp/ida_mcp/rpc.py +187 -0
  20. ida_pro_mcp/ida_mcp/server_manager.py +339 -0
  21. ida_pro_mcp/ida_mcp/sync.py +233 -0
  22. ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
  23. ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +336 -0
  24. ida_pro_mcp/ida_mcp/tests/test_api_core.py +237 -0
  25. ida_pro_mcp/ida_mcp/tests/test_api_memory.py +207 -0
  26. ida_pro_mcp/ida_mcp/tests/test_api_modify.py +123 -0
  27. ida_pro_mcp/ida_mcp/tests/test_api_resources.py +199 -0
  28. ida_pro_mcp/ida_mcp/tests/test_api_stack.py +77 -0
  29. ida_pro_mcp/ida_mcp/tests/test_api_types.py +249 -0
  30. ida_pro_mcp/ida_mcp/ui.py +357 -0
  31. ida_pro_mcp/ida_mcp/utils.py +1186 -0
  32. ida_pro_mcp/ida_mcp/zeromcp/__init__.py +5 -0
  33. ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +384 -0
  34. ida_pro_mcp/ida_mcp/zeromcp/mcp.py +883 -0
  35. ida_pro_mcp/ida_mcp.py +186 -0
  36. ida_pro_mcp/idalib_server.py +354 -0
  37. ida_pro_mcp/idalib_session_manager.py +259 -0
  38. ida_pro_mcp/server.py +1060 -0
  39. ida_pro_mcp/test.py +170 -0
  40. ida_pro_mcp_xjoker-1.0.1.dist-info/METADATA +405 -0
  41. ida_pro_mcp_xjoker-1.0.1.dist-info/RECORD +45 -0
  42. ida_pro_mcp_xjoker-1.0.1.dist-info/WHEEL +5 -0
  43. ida_pro_mcp_xjoker-1.0.1.dist-info/entry_points.txt +4 -0
  44. ida_pro_mcp_xjoker-1.0.1.dist-info/licenses/LICENSE +21 -0
  45. ida_pro_mcp_xjoker-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,259 @@
1
+ """IDALib Session Manager - Multi-binary management for headless MCP server
2
+
3
+ This module provides session management for multiple IDA databases in idalib mode.
4
+ Each session represents an opened binary with its own IDA database instance.
5
+ """
6
+
7
+ import uuid
8
+ import threading
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Dict, Optional, Any
12
+ from dataclasses import dataclass, field
13
+ from datetime import datetime
14
+
15
+ import idapro
16
+ import ida_auto
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+
21
+ @dataclass
22
+ class IDASession:
23
+ """Represents a single IDA database session"""
24
+
25
+ session_id: str
26
+ input_path: Path
27
+ created_at: datetime = field(default_factory=datetime.now)
28
+ last_accessed: datetime = field(default_factory=datetime.now)
29
+ is_analyzing: bool = False
30
+ metadata: Dict[str, Any] = field(default_factory=dict)
31
+
32
+ def to_dict(self) -> dict:
33
+ """Convert session to dictionary format"""
34
+ return {
35
+ "session_id": self.session_id,
36
+ "input_path": str(self.input_path),
37
+ "filename": self.input_path.name,
38
+ "created_at": self.created_at.isoformat(),
39
+ "last_accessed": self.last_accessed.isoformat(),
40
+ "is_analyzing": self.is_analyzing,
41
+ "metadata": self.metadata,
42
+ }
43
+
44
+
45
+ class IDASessionManager:
46
+ """Manages multiple IDA database sessions for idalib mode"""
47
+
48
+ def __init__(self):
49
+ self._sessions: Dict[str, IDASession] = {}
50
+ self._current_session_id: Optional[str] = None
51
+ self._lock = threading.RLock()
52
+ logger.info("IDASessionManager initialized")
53
+
54
+ def open_binary(
55
+ self,
56
+ input_path: Path | str,
57
+ run_auto_analysis: bool = True,
58
+ session_id: Optional[str] = None,
59
+ ) -> str:
60
+ """Open a binary file and create a new session
61
+
62
+ Args:
63
+ input_path: Path to the binary file
64
+ run_auto_analysis: Whether to run auto-analysis
65
+ session_id: Optional custom session ID (auto-generated if not provided)
66
+
67
+ Returns:
68
+ Session ID for the opened binary
69
+
70
+ Raises:
71
+ FileNotFoundError: If the input file doesn't exist
72
+ RuntimeError: If failed to open the database
73
+ """
74
+ input_path = Path(input_path)
75
+
76
+ if not input_path.exists():
77
+ raise FileNotFoundError(f"Input file not found: {input_path}")
78
+
79
+ with self._lock:
80
+ # Check if this file is already open
81
+ for sid, session in self._sessions.items():
82
+ if session.input_path.resolve() == input_path.resolve():
83
+ logger.info(f"Binary already open in session: {sid}")
84
+ self._current_session_id = sid
85
+ session.last_accessed = datetime.now()
86
+ return sid
87
+
88
+ # Close current database if any (Do we need to close the database first?)
89
+ if self._current_session_id is not None:
90
+ logger.debug("Closing current database before opening new one")
91
+ idapro.close_database()
92
+
93
+ # Generate session ID
94
+ if session_id is None:
95
+ session_id = str(uuid.uuid4())[:8]
96
+
97
+ # Open the database
98
+ logger.info(f"Opening database: {input_path} (session: {session_id})")
99
+
100
+ if idapro.open_database(
101
+ str(input_path), run_auto_analysis=run_auto_analysis
102
+ ):
103
+ raise RuntimeError(f"Failed to open database: {input_path}")
104
+
105
+ # Create session object
106
+ session = IDASession(
107
+ session_id=session_id,
108
+ input_path=input_path,
109
+ is_analyzing=run_auto_analysis,
110
+ )
111
+
112
+ self._sessions[session_id] = session
113
+ self._current_session_id = session_id
114
+
115
+ # Wait for analysis if requested
116
+ if run_auto_analysis:
117
+ logger.debug(
118
+ f"Waiting for auto-analysis to complete (session: {session_id})"
119
+ )
120
+ ida_auto.auto_wait()
121
+ session.is_analyzing = False
122
+ logger.info(f"Auto-analysis completed (session: {session_id})")
123
+
124
+ logger.info(f"Session created: {session_id} for {input_path.name}")
125
+ return session_id
126
+
127
+ def close_session(self, session_id: str) -> bool:
128
+ """Close a specific session and its database
129
+
130
+ Args:
131
+ session_id: Session ID to close
132
+
133
+ Returns:
134
+ True if closed successfully, False if session not found
135
+ """
136
+ with self._lock:
137
+ if session_id not in self._sessions:
138
+ logger.warning(f"Session not found: {session_id}")
139
+ return False
140
+
141
+ session = self._sessions[session_id]
142
+ logger.info(f"Closing session: {session_id} ({session.input_path.name})")
143
+
144
+ # If this is the current session, close the database
145
+ if self._current_session_id == session_id:
146
+ idapro.close_database()
147
+ self._current_session_id = None
148
+
149
+ # Remove session
150
+ del self._sessions[session_id]
151
+ logger.info(f"Session closed: {session_id}")
152
+ return True
153
+
154
+ def switch_session(self, session_id: str) -> bool:
155
+ """Switch to a different session
156
+
157
+ Args:
158
+ session_id: Session ID to switch to
159
+
160
+ Returns:
161
+ True if switched successfully
162
+
163
+ Raises:
164
+ ValueError: If session not found
165
+ """
166
+ with self._lock:
167
+ if session_id not in self._sessions:
168
+ raise ValueError(f"Session not found: {session_id}")
169
+
170
+ if self._current_session_id == session_id:
171
+ logger.debug(f"Already on session: {session_id}")
172
+ return True
173
+
174
+ session = self._sessions[session_id]
175
+
176
+ # Close current database
177
+ if self._current_session_id is not None:
178
+ logger.debug(f"Closing current session: {self._current_session_id}")
179
+ idapro.close_database()
180
+
181
+ # Open the target session's database
182
+ logger.info(
183
+ f"Switching to session: {session_id} ({session.input_path.name})"
184
+ )
185
+
186
+ if idapro.open_database(str(session.input_path), run_auto_analysis=False):
187
+ raise RuntimeError(f"Failed to switch to session: {session_id}")
188
+
189
+ self._current_session_id = session_id
190
+ session.last_accessed = datetime.now()
191
+
192
+ logger.info(f"Switched to session: {session_id}")
193
+ return True
194
+
195
+ def get_current_session(self) -> Optional[IDASession]:
196
+ """Get the current active session
197
+
198
+ Returns:
199
+ Current session or None if no active session
200
+ """
201
+ with self._lock:
202
+ if self._current_session_id is None:
203
+ return None
204
+ return self._sessions.get(self._current_session_id)
205
+
206
+ def list_sessions(self) -> list[dict]:
207
+ """List all open sessions
208
+
209
+ Returns:
210
+ List of session dictionaries with metadata
211
+ """
212
+ with self._lock:
213
+ return [
214
+ {
215
+ **session.to_dict(),
216
+ "is_current": session.session_id == self._current_session_id,
217
+ }
218
+ for session in self._sessions.values()
219
+ ]
220
+
221
+ def get_session(self, session_id: str) -> Optional[IDASession]:
222
+ """Get a specific session by ID
223
+
224
+ Args:
225
+ session_id: Session ID to retrieve
226
+
227
+ Returns:
228
+ Session object or None if not found
229
+ """
230
+ with self._lock:
231
+ return self._sessions.get(session_id)
232
+
233
+ def close_all_sessions(self):
234
+ """Close all sessions and databases"""
235
+ with self._lock:
236
+ logger.info(f"Closing all {len(self._sessions)} sessions")
237
+
238
+ if self._current_session_id is not None:
239
+ idapro.close_database()
240
+ self._current_session_id = None
241
+
242
+ self._sessions.clear()
243
+ logger.info("All sessions closed")
244
+
245
+
246
+ # Global session manager instance
247
+ _session_manager: Optional[IDASessionManager] = None
248
+
249
+
250
+ def get_session_manager() -> IDASessionManager:
251
+ """Get the global session manager instance
252
+
253
+ Returns:
254
+ Global IDASessionManager instance
255
+ """
256
+ global _session_manager
257
+ if _session_manager is None:
258
+ _session_manager = IDASessionManager()
259
+ return _session_manager