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,357 @@
1
+ """IDA MCP Configuration UI
2
+
3
+ Provides native IDA Pro dialogs for configuring MCP server instances.
4
+ Uses idaapi.Form for cross-platform compatibility.
5
+ """
6
+
7
+ import idaapi
8
+ import logging
9
+ from typing import Optional, TYPE_CHECKING
10
+
11
+ from .config import (
12
+ ServerInstanceConfig,
13
+ McpConfig,
14
+ get_config,
15
+ save_config,
16
+ reload_config,
17
+ )
18
+ from .server_manager import get_server_manager, ServerManager
19
+
20
+ if TYPE_CHECKING:
21
+ pass
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+
26
+ class ServerConfigForm(idaapi.Form):
27
+ """Form for adding/editing a server instance configuration."""
28
+
29
+ def __init__(self, config: Optional[ServerInstanceConfig] = None):
30
+ self.config = config
31
+ is_new = config is None
32
+
33
+ # Default values
34
+ instance_id = "" if is_new else config.instance_id
35
+ host = "127.0.0.1" if is_new else config.host
36
+ port = 13337 if is_new else config.port
37
+ auth_enabled = False if is_new else config.auth_enabled
38
+ api_key = "" if is_new else (config.api_key or "")
39
+ auto_start = False if is_new else config.auto_start
40
+
41
+ form_template = r"""STARTITEM 0
42
+ BUTTON YES* Save
43
+ BUTTON CANCEL Cancel
44
+ {title}
45
+
46
+ <Instance ID:{iInstanceId}>
47
+ <Host:{iHost}>
48
+ <Port:{iPort}>
49
+
50
+ <Enable Authentication:{cAuthEnabled}>{cAuthGroup}>
51
+ <API Key:{iApiKey}>
52
+
53
+ <Auto-start on plugin load:{cAutoStart}>{cAutoGroup}>
54
+ """
55
+ title = "Add Server" if is_new else f"Edit Server: {instance_id}"
56
+
57
+ idaapi.Form.__init__(
58
+ self,
59
+ form_template.format(title=title),
60
+ {
61
+ "iInstanceId": idaapi.Form.StringInput(value=instance_id),
62
+ "iHost": idaapi.Form.StringInput(value=host),
63
+ "iPort": idaapi.Form.NumericInput(value=port, tp=idaapi.Form.FT_DEC),
64
+ "cAuthGroup": idaapi.Form.ChkGroupControl(("cAuthEnabled",)),
65
+ "iApiKey": idaapi.Form.StringInput(value=api_key),
66
+ "cAutoGroup": idaapi.Form.ChkGroupControl(("cAutoStart",)),
67
+ },
68
+ )
69
+
70
+ self._is_new = is_new
71
+
72
+ def get_config(self) -> Optional[ServerInstanceConfig]:
73
+ """Get the configured ServerInstanceConfig after form execution."""
74
+ instance_id = self.iInstanceId.value.strip()
75
+ if not instance_id:
76
+ return None
77
+
78
+ return ServerInstanceConfig(
79
+ instance_id=instance_id,
80
+ host=self.iHost.value.strip() or "127.0.0.1",
81
+ port=self.iPort.value or 13337,
82
+ enabled=True,
83
+ auth_enabled=bool(self.cAuthGroup.value & 1),
84
+ api_key=self.iApiKey.value.strip() or None,
85
+ auto_start=bool(self.cAutoGroup.value & 1),
86
+ )
87
+
88
+
89
+ class ServerListChooser(idaapi.Choose):
90
+ """Chooser widget for displaying and selecting server instances."""
91
+
92
+ def __init__(self, manager: ServerManager, title: str = "MCP Servers"):
93
+ columns = [
94
+ ["ID", 15],
95
+ ["Address", 25],
96
+ ["Status", 12],
97
+ ["Auth", 8],
98
+ ]
99
+ idaapi.Choose.__init__(
100
+ self,
101
+ title,
102
+ columns,
103
+ flags=idaapi.Choose.CH_MODAL | idaapi.Choose.CH_CAN_DEL,
104
+ )
105
+ self.manager = manager
106
+ self._items: list[tuple[str, str, str, str]] = []
107
+ self._refresh()
108
+
109
+ def _refresh(self) -> None:
110
+ """Refresh the list of servers."""
111
+ self._items = []
112
+ for instance_id, status in self.manager.get_status().items():
113
+ self._items.append((
114
+ instance_id,
115
+ status["address"],
116
+ status["status"],
117
+ "Yes" if status["auth_enabled"] else "No",
118
+ ))
119
+
120
+ def OnGetSize(self) -> int:
121
+ return len(self._items)
122
+
123
+ def OnGetLine(self, n: int) -> list[str]:
124
+ if 0 <= n < len(self._items):
125
+ return list(self._items[n])
126
+ return ["", "", "", ""]
127
+
128
+ def OnDeleteLine(self, n: int) -> int:
129
+ """Handle delete action."""
130
+ if 0 <= n < len(self._items):
131
+ instance_id = self._items[n][0]
132
+ if self.manager.remove_server(instance_id):
133
+ self._refresh()
134
+ return n
135
+ return -1
136
+
137
+ def OnRefresh(self, n: int) -> int:
138
+ self._refresh()
139
+ return n
140
+
141
+ def get_selected_id(self, n: int) -> Optional[str]:
142
+ """Get the instance ID at the given index."""
143
+ if 0 <= n < len(self._items):
144
+ return self._items[n][0]
145
+ return None
146
+
147
+
148
+ class McpConfigDialog(idaapi.Form):
149
+ """Main configuration dialog for MCP servers."""
150
+
151
+ def __init__(self):
152
+ form_template = r"""STARTITEM 0
153
+ BUTTON YES* Close
154
+ MCP Server Configuration
155
+
156
+ {FormChangeCb}
157
+ <Server List:{cServerList}>
158
+
159
+ <Add Server:{iAddBtn}> <Start:{iStartBtn}> <Stop:{iStopBtn}> <Edit:{iEditBtn}>
160
+
161
+ <Tool Timeout (sec):{iTimeout}>
162
+ <Debug Mode:{cDebug}>{cDebugGroup}>
163
+
164
+ <Save Config:{iSaveBtn}> <Reload Config:{iReloadBtn}>
165
+ """
166
+
167
+ self.manager = get_server_manager()
168
+ self.config = get_config()
169
+
170
+ # Create embedded chooser
171
+ self.server_chooser = ServerListChooser(self.manager)
172
+
173
+ idaapi.Form.__init__(
174
+ self,
175
+ form_template,
176
+ {
177
+ "FormChangeCb": idaapi.Form.FormChangeCb(self._on_form_change),
178
+ "cServerList": idaapi.Form.EmbeddedChooserControl(self.server_chooser),
179
+ "iAddBtn": idaapi.Form.ButtonInput(self._on_add_click),
180
+ "iStartBtn": idaapi.Form.ButtonInput(self._on_start_click),
181
+ "iStopBtn": idaapi.Form.ButtonInput(self._on_stop_click),
182
+ "iEditBtn": idaapi.Form.ButtonInput(self._on_edit_click),
183
+ "iTimeout": idaapi.Form.NumericInput(
184
+ value=int(self.config.tool_timeout_sec),
185
+ tp=idaapi.Form.FT_DEC,
186
+ ),
187
+ "cDebugGroup": idaapi.Form.ChkGroupControl(("cDebug",)),
188
+ "iSaveBtn": idaapi.Form.ButtonInput(self._on_save_click),
189
+ "iReloadBtn": idaapi.Form.ButtonInput(self._on_reload_click),
190
+ },
191
+ )
192
+
193
+ def _on_form_change(self, fid: int) -> int:
194
+ return 1
195
+
196
+ def _get_selected_instance_id(self) -> Optional[str]:
197
+ """Get the currently selected server instance ID."""
198
+ sel = self.GetControlValue(self.cServerList)
199
+ if sel is None or sel < 0:
200
+ return None
201
+ return self.server_chooser.get_selected_id(sel)
202
+
203
+ def _on_add_click(self, code: int) -> int:
204
+ """Handle Add Server button click."""
205
+ form = ServerConfigForm()
206
+ form.Compile()
207
+ if form.Execute() == 1:
208
+ new_config = form.get_config()
209
+ if new_config:
210
+ try:
211
+ self.manager.add_server(new_config)
212
+ self.config.add_server(new_config)
213
+ self.RefreshField(self.cServerList)
214
+ print(f"[MCP] Added server: {new_config.instance_id}")
215
+ except ValueError as e:
216
+ print(f"[MCP] Error: {e}")
217
+ form.Free()
218
+ return 1
219
+
220
+ def _on_start_click(self, code: int) -> int:
221
+ """Handle Start button click."""
222
+ instance_id = self._get_selected_instance_id()
223
+ if instance_id:
224
+ if self.manager.start_server(instance_id):
225
+ self.RefreshField(self.cServerList)
226
+ else:
227
+ print("[MCP] No server selected")
228
+ return 1
229
+
230
+ def _on_stop_click(self, code: int) -> int:
231
+ """Handle Stop button click."""
232
+ instance_id = self._get_selected_instance_id()
233
+ if instance_id:
234
+ if self.manager.stop_server(instance_id):
235
+ self.RefreshField(self.cServerList)
236
+ else:
237
+ print("[MCP] No server selected")
238
+ return 1
239
+
240
+ def _on_edit_click(self, code: int) -> int:
241
+ """Handle Edit button click."""
242
+ instance_id = self._get_selected_instance_id()
243
+ if not instance_id:
244
+ print("[MCP] No server selected")
245
+ return 1
246
+
247
+ instance = self.manager.get_instance(instance_id)
248
+ if not instance:
249
+ return 1
250
+
251
+ form = ServerConfigForm(instance.config)
252
+ form.Compile()
253
+ if form.Execute() == 1:
254
+ new_config = form.get_config()
255
+ if new_config:
256
+ # Update config
257
+ was_running = instance.is_running
258
+ if was_running:
259
+ self.manager.stop_server(instance_id)
260
+
261
+ # Update instance config
262
+ instance.config = new_config
263
+
264
+ # Restart if was running
265
+ if was_running:
266
+ self.manager.start_server(new_config.instance_id)
267
+
268
+ self.RefreshField(self.cServerList)
269
+ print(f"[MCP] Updated server: {new_config.instance_id}")
270
+ form.Free()
271
+ return 1
272
+
273
+ def _on_save_click(self, code: int) -> int:
274
+ """Handle Save Config button click."""
275
+ # Update config from form values
276
+ self.config.tool_timeout_sec = float(self.iTimeout.value or 15)
277
+ self.config.debug = bool(self.cDebugGroup.value & 1)
278
+
279
+ # Sync server list
280
+ self.config.servers = [
281
+ inst.config for inst in self.manager._instances.values()
282
+ ]
283
+
284
+ if save_config(self.config):
285
+ print("[MCP] Configuration saved")
286
+ else:
287
+ print("[MCP] Failed to save configuration")
288
+ return 1
289
+
290
+ def _on_reload_click(self, code: int) -> int:
291
+ """Handle Reload Config button click."""
292
+ self.config = reload_config()
293
+ self.manager.load_from_config(self.config)
294
+ self.RefreshField(self.cServerList)
295
+ self.SetControlValue(self.iTimeout, int(self.config.tool_timeout_sec))
296
+ print("[MCP] Configuration reloaded")
297
+ return 1
298
+
299
+
300
+ def show_config_dialog() -> None:
301
+ """Show the MCP configuration dialog."""
302
+ dialog = McpConfigDialog()
303
+ dialog.Compile()
304
+ dialog.Execute()
305
+ dialog.Free()
306
+
307
+
308
+ class McpConfigAction(idaapi.action_handler_t):
309
+ """Action handler for showing the config dialog."""
310
+
311
+ ACTION_ID = "ida_mcp:config"
312
+ ACTION_NAME = "MCP Configuration"
313
+ ACTION_HOTKEY = "Ctrl+Shift+M"
314
+
315
+ def __init__(self):
316
+ idaapi.action_handler_t.__init__(self)
317
+
318
+ def activate(self, ctx) -> int:
319
+ show_config_dialog()
320
+ return 1
321
+
322
+ def update(self, ctx) -> int:
323
+ return idaapi.AST_ENABLE_ALWAYS
324
+
325
+
326
+ def register_actions() -> bool:
327
+ """Register UI actions with IDA."""
328
+ action_desc = idaapi.action_desc_t(
329
+ McpConfigAction.ACTION_ID,
330
+ McpConfigAction.ACTION_NAME,
331
+ McpConfigAction(),
332
+ McpConfigAction.ACTION_HOTKEY,
333
+ "Configure MCP server instances",
334
+ -1,
335
+ )
336
+
337
+ if not idaapi.register_action(action_desc):
338
+ logger.warning("Failed to register MCP config action")
339
+ return False
340
+
341
+ return True
342
+
343
+
344
+ def unregister_actions() -> None:
345
+ """Unregister UI actions from IDA."""
346
+ idaapi.unregister_action(McpConfigAction.ACTION_ID)
347
+
348
+
349
+ __all__ = [
350
+ "ServerConfigForm",
351
+ "ServerListChooser",
352
+ "McpConfigDialog",
353
+ "show_config_dialog",
354
+ "McpConfigAction",
355
+ "register_actions",
356
+ "unregister_actions",
357
+ ]