# Directory Structure ``` ├── ida-mcp-server.py ├── LICENSE ├── README.md └── requirements.txt ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | [](https://mseep.ai/app/taida957789-ida-mcp-server-plugin) 2 | 3 | # IDA Pro MCP Server 4 | 5 | IDA Pro MCP Server is a plugin that allows remote querying and control of IDA Pro through the Model Context Protocol (MCP) interface. This plugin enables AI assistants (such as Claude) to interact directly with IDA Pro for binary analysis tasks. 6 | 7 | ## Overview 8 | 9 | This server provides a series of tools that allow AI assistants to perform the following operations: 10 | - Get byte data from specific addresses 11 | - Get disassembly code 12 | - Get decompiled pseudocode 13 | - Query function names 14 | - Get segment information 15 | - List all functions 16 | - Find cross-references 17 | - Get import/export tables 18 | - Get entry points 19 | - Define/undefine functions 20 | - Get various data types (dword, word, byte, qword, float, double, string) 21 | - Get all strings in the binary file 22 | - Get the length of the instruction at the specified address 23 | 24 | ## Installation 25 | 26 | > **Note:** This plugin is designed for and tested with IDA Pro version 9.0+. 27 | 28 | 1. Ensure Python and related dependencies are installed: 29 | 30 | ```bash 31 | pip install -r requirements.txt 32 | ``` 33 | 34 | 2. Copy the `ida-mcp-server.py` file to the IDA Pro plugins directory: 35 | - Windows: `%Programfiles%\IDA Pro 9.0\plugins\` 36 | - Linux: `~/.idapro/plugins/` 37 | - macOS: `~/Library/Application Support/IDA Pro/plugins/` 38 | 39 | ## Configure Claude / VSCode 40 | 41 | Add the following configuration to the `mcp.json` file in Claude or VSCode: 42 | 43 | ```json 44 | { 45 | "mcpServers": { 46 | "IDAPro": { 47 | "url": "http://127.0.0.1:3000/sse", 48 | "type": "sse" 49 | } 50 | } 51 | } 52 | ``` 53 | 54 | ## Usage 55 | 56 | 1. Open a binary file in IDA Pro 57 | 2. The plugin will automatically load and start the MCP server locally (port 3000) 58 | 3. Connect your AI assistant (e.g., Claude) to this server 59 | 4. Use the AI assistant to perform binary analysis tasks 60 | 61 | ## Available Analysis Tools 62 | 63 | IDA Pro MCP Server provides the following tools: 64 | 65 | - `get_bytes`: Get bytes at a specified address 66 | - `get_disasm`: Get disassembly at a specified address 67 | - `get_decompiled_func`: Get pseudocode of the function containing the specified address 68 | - `get_function_name`: Get function name at a specified address 69 | - `get_segments`: Get all segment information 70 | - `get_functions`: Get all functions in the binary 71 | - `get_xrefs_to`: Get all cross-references to a specified address 72 | - `get_imports`: Get all imported functions 73 | - `get_exports`: Get all exported functions 74 | - `get_entry_point`: Get the entry point of the binary 75 | - `make_function`: Create a function at a specified address 76 | - `undefine_function`: Undefine a function at a specified address 77 | - `get_dword_at`: Get the dword at a specified address 78 | - `get_word_at`: Get the word at a specified address 79 | - `get_byte_at`: Get the byte at a specified address 80 | - `get_qword_at`: Get the qword at a specified address 81 | - `get_float_at`: Get the float at a specified address 82 | - `get_double_at`: Get the double at a specified address 83 | - `get_string_at`: Get the string at a specified address 84 | - `get_string_list`: Get all strings in the binary 85 | - `get_strings`: Get all strings in the binary (with addresses) 86 | 87 | ## Best Practices 88 | 89 | When analyzing binary files, it's recommended to follow these steps: 90 | 91 | 1. Examine the entry point 92 | 2. Analyze the import table 93 | 3. Review strings 94 | 4. Track key API calls 95 | 5. Identify main functional blocks 96 | 6. Analyze control flow 97 | 7. Identify malicious behaviors 98 | 8. Analyze algorithms and encryption routines 99 | 9. Document analysis results 100 | 10. Use advanced techniques 101 | 102 | ## License 103 | 104 | MIT License 105 | 106 | Copyright (c) 2023 107 | 108 | Permission is hereby granted, free of charge, to any person obtaining a copy 109 | of this software and associated documentation files (the "Software"), to deal 110 | in the Software without restriction, including without limitation the rights 111 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 112 | copies of the Software, and to permit persons to whom the Software is 113 | furnished to do so, subject to the following conditions: 114 | 115 | The above copyright notice and this permission notice shall be included in all 116 | copies or substantial portions of the Software. 117 | 118 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 119 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 120 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 121 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 122 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 123 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 124 | SOFTWARE. 125 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` 1 | mcp==1.5.0 2 | starlette==0.46.1 3 | sse-starlette==2.2.1 4 | uvicorn==0.34.0 5 | typing_extensions==4.12.2 6 | pydantic==2.10.6 7 | pydantic_core==2.27.2 8 | anyio==4.9.0 9 | idna==3.10 10 | sniffio==1.3.1 11 | httpcore==1.0.7 12 | httpx==0.28.1 13 | httpx-sse==0.4.0 14 | h11==0.14.0 15 | click==8.1.8 16 | ``` -------------------------------------------------------------------------------- /ida-mcp-server.py: -------------------------------------------------------------------------------- ```python 1 | import glob 2 | import json 3 | import os 4 | import ida_bytes 5 | import ida_ua 6 | import ida_funcs 7 | import ida_hexrays 8 | import ida_name 9 | import ida_segment 10 | import idautils 11 | import idc 12 | import ida_idaapi 13 | import ida_kernwin 14 | import idaapi 15 | import threading 16 | 17 | from typing import Dict, List, Optional, Any, Tuple 18 | from datetime import datetime 19 | from functools import wraps 20 | from starlette.middleware import Middleware 21 | from starlette.middleware.cors import CORSMiddleware 22 | from starlette.applications import Starlette 23 | from starlette.requests import Request 24 | from starlette.routing import Mount, Route 25 | from starlette.responses import Response 26 | from mcp.server import Server 27 | from mcp.server.sse import SseServerTransport 28 | from mcp.server import FastMCP 29 | import uvicorn 30 | 31 | 32 | # Initialize FastMCP server for IDA tools 33 | mcp = FastMCP("IDA MCP Server", port=3000) 34 | 35 | # 封裝函數執行在主線程的裝飾器 36 | def execute_on_main_thread(f): 37 | @wraps(f) 38 | def wrapper(*args, **kwargs): 39 | result = [] 40 | exception = [] 41 | 42 | def run_function(): 43 | try: 44 | result.append(f(*args, **kwargs)) 45 | except Exception as e: 46 | exception.append(e) 47 | return 0 48 | 49 | ida_kernwin.execute_sync(run_function, ida_kernwin.MFF_FAST) 50 | 51 | if exception: 52 | raise exception[0] 53 | return result[0] 54 | return wrapper 55 | 56 | 57 | @mcp.tool() 58 | @execute_on_main_thread 59 | def get_bytes(ea: int, size: int) -> List[int]: 60 | """Get bytes at specified address. 61 | 62 | Args: 63 | ea: Effective address to read from 64 | size: Number of bytes to read 65 | """ 66 | try: 67 | result = [ida_bytes.get_byte(ea + i) for i in range(size)] 68 | return result 69 | except Exception as e: 70 | print(f"Error in get_bytes: {str(e)}") 71 | return {"error": str(e)} 72 | 73 | 74 | @mcp.tool() 75 | @execute_on_main_thread 76 | def get_disasm(ea: int) -> str: 77 | """Get disassembly at specified address. 78 | 79 | Args: 80 | ea: Effective address to disassemble 81 | """ 82 | return idc.generate_disasm_line(ea, 0) 83 | 84 | 85 | @mcp.tool() 86 | @execute_on_main_thread 87 | def get_decompiled_func(ea: int) -> Dict[str, Any]: 88 | """Get decompiled pseudocode of function containing address. 89 | 90 | Args: 91 | ea: Effective address within the function 92 | """ 93 | try: 94 | func = ida_funcs.get_func(ea) 95 | if not func: 96 | return {"error": "No function found at address"} 97 | 98 | decompiler = ida_hexrays.decompile(func.start_ea) 99 | if not decompiler: 100 | return {"error": "Failed to decompile function"} 101 | 102 | return {"code": str(decompiler)} 103 | except Exception as e: 104 | return {"error": str(e)} 105 | 106 | 107 | @mcp.tool() 108 | @execute_on_main_thread 109 | def get_function_name(ea: int) -> str: 110 | """Get function name at specified address. 111 | 112 | Args: 113 | ea: Effective address of the function 114 | """ 115 | return ida_name.get_name(ea) 116 | 117 | 118 | @mcp.tool() 119 | @execute_on_main_thread 120 | def get_segments() -> List[Dict[str, Any]]: 121 | """Get all segments information. 122 | 123 | @return: List of segments (start, end, name, class, perm, bitness, align, comb, type, sel, flags) 124 | """ 125 | segments = [] 126 | n = 0 127 | seg = ida_segment.getnseg(n) 128 | while seg: 129 | segments.append( 130 | { 131 | "start": seg.start_ea, 132 | "end": seg.end_ea, 133 | "name": ida_segment.get_segm_name(seg), 134 | "class": ida_segment.get_segm_class(seg), 135 | "perm": seg.perm, 136 | "bitness": seg.bitness, 137 | "align": seg.align, 138 | "comb": seg.comb, 139 | "type": seg.type, 140 | "sel": seg.sel, 141 | "flags": seg.flags, 142 | } 143 | ) 144 | n += 1 145 | seg = ida_segment.getnseg(n) 146 | return segments 147 | 148 | 149 | @mcp.tool() 150 | @execute_on_main_thread 151 | def get_functions() -> List[Dict[str, Any]]: 152 | """Get all functions in the binary.""" 153 | functions = [] 154 | for func_ea in idautils.Functions(): 155 | func_name = ida_name.get_name(func_ea) 156 | functions.append({"address": func_ea, "name": func_name}) 157 | return functions 158 | 159 | 160 | @mcp.tool() 161 | @execute_on_main_thread 162 | def get_xrefs_to(ea: int) -> List[Dict[str, Any]]: 163 | """Get all cross references to specified address. 164 | 165 | Args: 166 | ea: Effective address to find references to 167 | """ 168 | xrefs = [] 169 | for xref in idautils.XrefsTo(ea, 0): 170 | xrefs.append({"from": xref.frm, "type": xref.type}) 171 | return xrefs 172 | 173 | 174 | @mcp.tool() 175 | @execute_on_main_thread 176 | def get_imports() -> dict[str, list[tuple[int, str, int]]]: 177 | """Get all imports in the binary. 178 | 179 | Args: 180 | None 181 | 182 | Returns: 183 | A dictionary where the keys are module names and the values are lists of tuples. 184 | Each tuple contains the address of the imported function, the name of the function, 185 | and the ordinal value of the function. 186 | """ 187 | tree = {} 188 | nimps = idaapi.get_import_module_qty() 189 | 190 | for i in range(0, nimps): 191 | name = idaapi.get_import_module_name(i) 192 | if not name: 193 | continue 194 | # Create a list for imported names 195 | items = [] 196 | 197 | def imports_names_cb(ea, name, ord): 198 | items.append((ea, "" if not name else name, ord)) 199 | # True -> Continue enumeration 200 | return True 201 | 202 | # Enum imported entries in this module 203 | idaapi.enum_import_names(i, imports_names_cb) 204 | 205 | if name not in tree: 206 | tree[name] = [] 207 | tree[name].extend(items) 208 | 209 | return tree 210 | 211 | 212 | @mcp.tool() 213 | @execute_on_main_thread 214 | def get_exports() -> List[Tuple[int, int, int, str]]: 215 | """Get all exports in the binary. 216 | 217 | @return: List of tuples (index, ordinal, ea, name) 218 | """ 219 | return list(idautils.Entries()) 220 | 221 | 222 | @mcp.tool() 223 | @execute_on_main_thread 224 | def get_entry_point() -> int: 225 | """Get the entry point of the binary.""" 226 | try: 227 | import ida_ida 228 | return ida_ida.inf_get_start_ea() 229 | except (ImportError, AttributeError): 230 | try: 231 | # Alternative method: idc.get_inf_attr to get 232 | import idc 233 | return idc.get_inf_attr(idc.INF_START_EA) 234 | except (ImportError, AttributeError): 235 | # Last alternative method: use cvar.inf 236 | return idaapi.cvar.inf.start_ea 237 | 238 | 239 | @mcp.tool() 240 | @execute_on_main_thread 241 | def make_function(ea: int) -> None: 242 | """Make a function at specified address.""" 243 | ida_funcs.add_func(ea) 244 | 245 | 246 | @mcp.tool() 247 | @execute_on_main_thread 248 | def undefine_function(ea: int) -> None: 249 | """Undefine a function at specified address.""" 250 | ida_funcs.del_func(ea) 251 | 252 | 253 | @mcp.tool() 254 | @execute_on_main_thread 255 | def get_dword_at(ea: int) -> int: 256 | """Get the dword at specified address.""" 257 | return idc.get_dword(ea) 258 | 259 | 260 | @mcp.tool() 261 | @execute_on_main_thread 262 | def get_word_at(ea: int) -> int: 263 | """Get the word at specified address.""" 264 | return idc.get_word(ea) 265 | 266 | 267 | @mcp.tool() 268 | @execute_on_main_thread 269 | def get_byte_at(ea: int) -> int: 270 | """Get the byte at specified address.""" 271 | return idc.get_byte(ea) 272 | 273 | 274 | @mcp.tool() 275 | @execute_on_main_thread 276 | def get_qword_at(ea: int) -> int: 277 | """Get the qword at specified address.""" 278 | return idc.get_qword(ea) 279 | 280 | 281 | @mcp.tool() 282 | @execute_on_main_thread 283 | def get_float_at(ea: int) -> float: 284 | """Get the float at specified address.""" 285 | return idc.get_float(ea) 286 | 287 | 288 | @mcp.tool() 289 | @execute_on_main_thread 290 | def get_double_at(ea: int) -> float: 291 | """Get the double at specified address.""" 292 | return idc.get_double(ea) 293 | 294 | 295 | @mcp.tool() 296 | @execute_on_main_thread 297 | def get_string_at(ea: int) -> str: 298 | """Get the string at specified address.""" 299 | return idc.get_strlit_contents(ea) 300 | 301 | 302 | @mcp.tool() 303 | @execute_on_main_thread 304 | def get_strings(): 305 | strings = [] 306 | for s in idautils.Strings(): 307 | strings.append({"address": s.ea, "string": str(s)}) 308 | return strings 309 | 310 | @mcp.tool() 311 | @execute_on_main_thread 312 | def get_current_file_path(): 313 | return idc.get_input_file_path() 314 | 315 | @mcp.tool() 316 | @execute_on_main_thread 317 | def list_files_with_relative_path(relative_path: str = ""): 318 | base_dir = os.path.dirname(idc.get_input_file_path()) 319 | if ':' in relative_path or '..' in relative_path or '//' in relative_path: 320 | return json.dumps({"error": "Invalid relative path"}) 321 | if relative_path is None or relative_path == "": 322 | return glob.glob(os.path.join(base_dir, "*")) 323 | else: 324 | return glob.glob(os.path.join(base_dir, relative_path, "*")) 325 | 326 | @mcp.tool() 327 | @execute_on_main_thread 328 | def read_file(relative_path: str): 329 | base_dir = os.path.dirname(idc.get_input_file_path()) 330 | if ':' in relative_path or '..' in relative_path or '//' in relative_path: 331 | return json.dumps({"error": "Invalid relative path"}) 332 | if relative_path is "": 333 | return json.dumps({"error": "Relative path is required"}) 334 | with open(os.path.join(base_dir, relative_path), "r") as f: 335 | return f.read() 336 | 337 | @mcp.tool() 338 | @execute_on_main_thread 339 | def write_file(relative_path: str, content: str): 340 | base_dir = os.path.dirname(idc.get_input_file_path()) 341 | if ':' in relative_path or '..' in relative_path or '//' in relative_path: 342 | return json.dumps({"error": "Invalid relative path"}) 343 | if relative_path is "": 344 | return json.dumps({"error": "Relative path is required"}) 345 | with open(os.path.join(base_dir, relative_path), "w") as f: 346 | f.write(content) 347 | 348 | @mcp.tool() 349 | @execute_on_main_thread 350 | def read_binary(relative_path: str): 351 | base_dir = os.path.dirname(idc.get_input_file_path()) 352 | if ':' in relative_path or '..' in relative_path or '//' in relative_path: 353 | return json.dumps({"error": "Invalid relative path"}) 354 | if relative_path is "": 355 | return json.dumps({"error": "Relative path is required"}) 356 | with open(os.path.join(base_dir, relative_path), "rb") as f: 357 | return f.read() 358 | 359 | @mcp.tool() 360 | @execute_on_main_thread 361 | def write_binary(relative_path: str , content: bytes): 362 | base_dir = os.path.dirname(idc.get_input_file_path()) 363 | if ':' in relative_path or '..' in relative_path or '//' in relative_path: 364 | return json.dumps({"error": "Invalid relative path"}) 365 | if relative_path is "": 366 | return json.dumps({"error": "Relative path is required"}) 367 | with open(os.path.join(base_dir, relative_path), "wb") as f: 368 | f.write(content) 369 | 370 | @mcp.tool() 371 | @execute_on_main_thread 372 | def eval_pythoni(script: str): 373 | return eval(script) 374 | 375 | @mcp.tool() 376 | @execute_on_main_thread 377 | def get_instruction_length(address: int) -> int: 378 | """ 379 | Retrieves the length (in bytes) of the instruction at the specified address. 380 | 381 | Args: 382 | address: The address of the instruction. 383 | 384 | Returns: 385 | The length (in bytes) of the instruction. Returns 0 if the instruction cannot be decoded. 386 | """ 387 | try: 388 | # Create an insn_t object to store instruction information. 389 | insn = ida_ua.insn_t() 390 | 391 | # Decode the instruction. 392 | length = ida_ua.decode_insn(insn, address) 393 | if length == 0: 394 | print(f"Failed to decode instruction at address {hex(address)}") 395 | return 0 396 | 397 | return length 398 | except Exception as e: 399 | print(f"Error getting instruction length: {str(e)}") 400 | return 0 401 | 402 | @mcp.prompt() 403 | def binary_analysis_strategy() -> str: 404 | """ 405 | Guild for analyzing the binary 406 | """ 407 | return ( 408 | "IDA Pro MCP Server Tools and Best Practices:\n\n." 409 | "Tools: \n" 410 | "- get_bytes: Get bytes at specified address.\n" 411 | "- get_disasm: Get disassembly at specified address.\n" 412 | "- get_decompiled_func: Get decompiled pseudocode of function containing address.\n" 413 | "- get_function_name: Get function name at specified address.\n" 414 | "- get_segments: Get all segments information.\n" 415 | "- get_functions: Get all functions in the binary.\n" 416 | "- get_xrefs_to: Get all cross references to a specified address.\n" 417 | "- get_imports: Get all imports in the binary.\n" 418 | "- get_exports: Get all exports in the binary.\n" 419 | "- get_entry_point: Get the entry point of the binary.\n" 420 | "- make_function: Make a function at specified address.\n" 421 | "- undefine_function: Undefine a function at specified address.\n" 422 | "- get_dword_at: Get the dword at specified address.\n" 423 | "- get_word_at: Get the word at specified address.\n" 424 | "- get_byte_at: Get the byte at specified address.\n" 425 | "- get_qword_at: Get the qword at specified address.\n" 426 | "- get_float_at: Get the float at specified address.\n" 427 | "- get_double_at: Get the double at specified address.\n" 428 | "- get_string_at: Get the string at specified address.\n" 429 | "- get_strings: Get all strings in the binary.\n" 430 | "- get_current_file_path: Get the current path of the binary.\n" 431 | "- list_files_with_relative_path: List all files in the specified relative path in the current directory.\n" 432 | "- read_file: Read the content of a file.\n" 433 | "- write_file: Write content to a file.\n" 434 | "- read_binary: Read the content of a binary file.\n" 435 | "- write_binary: Write content to a binary file.\n" 436 | "- eval_python: Evaluate a Python script in IDA Pro.\n" 437 | "- get_instruction_length: Get the length of the instruction at the specified address.\n" 438 | "Best Practices: \n" 439 | "- Initial Analysis Phase\n" 440 | " 1. Examine the Entry Point\n" 441 | " - Use the get_entry_point() tool to locate the program's entry point\n" 442 | " - Analyze the code at the entry point to understand the program's startup flow\n" 443 | " - Look for unusual instructions or jumps\n" 444 | " 2. Analyze Import Table\n" 445 | " - Use the get_imports() tool to view all imported functions\n" 446 | " - Look for suspicious API functions, such as:\n" 447 | " - File operations: CreateFile, WriteFile\n" 448 | " - Network communication: socket, connect, InternetOpen\n" 449 | " - Process manipulation: CreateProcess, VirtualAlloc\n" 450 | " - Registry operations: RegOpenKey, RegSetValue\n" 451 | " - Cryptography related: CryptEncrypt, CryptDecrypt\n" 452 | " 3. Review Strings\n" 453 | " - Use the get_strings() tool to obtain all strings\n" 454 | " - Pay attention to IP addresses, URLs, domain names, file paths\n" 455 | " - Look for encrypted or obfuscated string patterns\n" 456 | " - Analyze command line parameters and error messages\n" 457 | " 4. In-Depth Analysis Phase\n" 458 | " - Track Key API Calls\n" 459 | " - Use get_xrefs_to() to find cross-references to suspicious imported functions\n" 460 | " - Use get_decompiled_func() to analyze functions that call these APIs\n" 461 | " - Analyze how parameters and return values are handled\n" 462 | " 5. Identify Main Functional Blocks\n" 463 | " - Use get_functions() to get a list of all functions\n" 464 | " - Sort functions by size and complexity\n" 465 | " - Decompile and analyze large, complex functions\n" 466 | " - Look for suspicious function names or unnamed functions\n" 467 | " 6. Analyze Control Flow\n" 468 | " - Observe conditional branches and loop structures\n" 469 | " - Analyze function call graphs and execution paths\n" 470 | " - Look for anti-debugging and anti-VM detection techniques\n" 471 | " - Pay attention to unusual jumps and callback mechanisms\n" 472 | " 7. Identifying Malicious Behaviors\n" 473 | " - Identify Common Malicious Functionality\n" 474 | " - Persistence mechanisms: Registry modifications, startup items, service creation\n" 475 | " - Data theft: File searching, keylogging, screen capturing\n" 476 | " - Communication features: C&C communication, data exfiltration channels\n" 477 | " - Evasion techniques: Obfuscation, packing, anti-analysis checks\n" 478 | " - Destructive behaviors: File encryption, system damage\n" 479 | " 8. Analyze Algorithms and Encryption Routines\n" 480 | " - Identify encryption and decryption functions\n" 481 | " - Look for hardcoded keys and cryptographic constants\n" 482 | " - Analyze how data is processed in memory\n" 483 | " 9. Analyze Network Communication\n" 484 | " - Identify network communication functions\n" 485 | " - Look for IP addresses, URLs, and domain names\n" 486 | " - Analyze how data is sent and received over the network\n" 487 | " 10. Dump payloads if there is any decryption or encoding\n" 488 | " - Generate decryption or decodeing script in python and ida python\n" 489 | " - Use eval_python to execute the script in IDA Pro\n" 490 | " - Dump payloads in the current directory\n" 491 | " 11. Document Analysis Results\n" 492 | " - Add comments to key functions\n" 493 | " - Rename functions to reflect their actual functionality\n" 494 | " - Create a logical structure diagram of the code\n" 495 | " 12. Using Advanced Techniques\n" 496 | " - Use IDA Pro's advanced features like IDAPython scripting, IDA SDK, and IDA API\n" 497 | " - Implement custom analysis scripts to automate repetitive tasks\n" 498 | " - Explore IDA Pro's plugin ecosystem for additional analysis capabilities\n" 499 | ) 500 | 501 | 502 | def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: 503 | """Create a Starlette application that can serve the provided mcp server with SSE.""" 504 | 505 | middleware = [ 506 | Middleware( 507 | CORSMiddleware, 508 | allow_origins=["*"], 509 | allow_methods=["*"], 510 | allow_headers=["*"], 511 | ) 512 | ] 513 | return Starlette( 514 | debug=debug, 515 | middleware=middleware, 516 | routes=[ 517 | Mount("/", app=mcp.sse_app()), 518 | ], 519 | ) 520 | 521 | 522 | class ModelContextProtocolPlugin(ida_idaapi.plugin_t): 523 | flags = ida_idaapi.PLUGIN_FIX | ida_idaapi.PLUGIN_HIDE 524 | comment = "IDA Model Context Protocol Server" 525 | help = "Provides REST API and SSE for IDA Pro analysis" 526 | wanted_name = "IDA MCP Server" 527 | wanted_hotkey = "" 528 | 529 | def init(self): 530 | try: 531 | print("Initializing IDA Model Context Protocol Server...") 532 | # app = create_starlette_app(mcp, debug=True) 533 | 534 | def run_server(): 535 | try: 536 | # 設置將異常轉換為 JSON 響應 537 | mcp.run(transport="sse") 538 | # uvicorn.run(app, host="localhost", port=3000, log_level="debug") 539 | except Exception as e: 540 | print(f"Server error: {str(e)}") 541 | 542 | server_thread = threading.Thread(target=run_server) 543 | server_thread.daemon = True 544 | server_thread.start() 545 | print("Server started successfully!") 546 | return ida_idaapi.PLUGIN_KEEP 547 | except Exception as e: 548 | print(f"Failed to start server: {str(e)}") 549 | return ida_idaapi.PLUGIN_SKIP 550 | 551 | def run(self, arg): 552 | pass 553 | 554 | def term(self): 555 | print("Terminating IDA Model Context Protocol Server...") 556 | 557 | 558 | def PLUGIN_ENTRY(): 559 | return ModelContextProtocolPlugin() 560 | 561 | 562 | if __name__ == "__main__": 563 | PLUGIN_ENTRY() 564 | ```