# Directory Structure ``` ├── bridge_mcp_cutter.py ├── images │ └── cutterMCP.png ├── LICENSE ├── README.md ├── requirements.txt └── src └── CutterMCPPlugin.py ``` # Files -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown 1 | [](https://www.apache.org/licenses/LICENSE-2.0) 2 | [](https://www.linkedin.com/in/amey-pathak/) 3 | 4 |  5 | 6 | 7 | # cutterMCP 8 | cutterMCP is an Model Context Protocol server for allowing LLMs to autonomously reverse engineer applications. It exposes numerous tools from core Cutter functionality to MCP clients. 9 | 10 | # Features 11 | MCP Server + Cutter Plugin 12 | 13 | - Decompile and analyze binaries in Cutter 14 | - Automatically rename methods and data 15 | - List methods, imports, and exports 16 | 17 | # Installation 18 | 19 | ## Prerequisites 20 | - Install [Cutter](https://github.com/rizinorg/cutter) 21 | - Python3 22 | - MCP [SDK](https://github.com/modelcontextprotocol/python-sdk) 23 | 24 | ## Cutter 25 | First, download the latest release from this repository. This contains the Cutter plugin and Python MCP client. Then, you can directly import the plugin into Cutter. 26 | 27 | 1. Run Cutter 28 | 2. Go to **Edit -> Preferences -> Plugins** 29 | 3. Find the plugin directory location 30 | 4. Copy `CutterMCPPlugin.py` from the downloaded release and paste it inside the **python** folder 31 | 5. Restart Cutter 32 | 6. If successful, you’ll see the plugin under **Windows -> Plugins** and a new widget in the bottom panel 33 | 34 | 35 | ## MCP Clients 36 | 37 | Theoretically, any MCP client should work with cutterMCP. one example is given below. 38 | 39 | ## Example 1: Claude Desktop 40 | To set up Claude Desktop as a Cutter MCP client, go to `Claude` -> `Settings` -> `Developer` -> `Edit Config` -> `claude_desktop_config.json` and add the following: 41 | 42 | MacOS/Linux : 43 | ```json 44 | { 45 | "mcpServers": { 46 | "cutter": { 47 | "command": "python", 48 | "args": [ 49 | "/ABSOLUTE_PATH_TO/bridge_mcp_cutter.py" 50 | ] 51 | } 52 | } 53 | } 54 | ``` 55 | 56 | Windows : 57 | ```json 58 | { 59 | "mcpServers": { 60 | "cutter": { 61 | "command": "python", 62 | "args": [ 63 | "C:\\ABSOLUTE_PATH_TO\\bridge_mcp_cutter.py" 64 | ] 65 | } 66 | } 67 | } 68 | ``` 69 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- ``` 1 | mcp==1.5.0 2 | requests==2.32.3 ``` -------------------------------------------------------------------------------- /bridge_mcp_cutter.py: -------------------------------------------------------------------------------- ```python 1 | # Cutter MCP Server using FastMCP 2 | 3 | import sys 4 | import requests 5 | import argparse 6 | import logging 7 | from mcp.server.fastmcp import FastMCP 8 | 9 | DEFAULT_CUTTER_SERVER = "http://127.0.0.1:8000/" 10 | cutter_server_url = DEFAULT_CUTTER_SERVER 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | mcp = FastMCP("cutter-mcp") 15 | 16 | def safe_get(endpoint: str, params: dict = None) -> list: 17 | if params is None: 18 | params = {} 19 | url = f"{cutter_server_url}/{endpoint}" 20 | try: 21 | response = requests.get(url, params=params, timeout=5) 22 | response.encoding = 'utf-8' 23 | if response.ok: 24 | return response.text.splitlines() 25 | else: 26 | return [f"Error {response.status_code}: {response.text.strip()}"] 27 | except Exception as e: 28 | return [f"Request failed: {str(e)}"] 29 | 30 | def safe_post(endpoint: str, data: dict | str) -> str: 31 | try: 32 | if isinstance(data, dict): 33 | response = requests.post(f"{cutter_server_url}/{endpoint}", data=data, timeout=5) 34 | else: 35 | response = requests.post(f"{cutter_server_url}/{endpoint}", data=data.encode("utf-8"), timeout=5) 36 | response.encoding = 'utf-8' 37 | if response.ok: 38 | return response.text.strip() 39 | else: 40 | return f"Error {response.status_code}: {response.text.strip()}" 41 | except Exception as e: 42 | return f"Request failed: {str(e)}" 43 | 44 | @mcp.tool() 45 | def list_functions(offset: int = 0, limit: int = 100) -> list: 46 | """ 47 | List all function names in the binary with their addresses and pagination. 48 | """ 49 | return safe_get("functions", {"offset": offset, "limit": limit}) 50 | 51 | @mcp.tool() 52 | def decompile_function_by_address(address: str) -> str: 53 | """ 54 | Decompile a function at the given address. 55 | """ 56 | return "\n".join(safe_get("decompile", {"addr": address})) 57 | 58 | @mcp.tool() 59 | def list_segments(offset: int = 0, limit: int = 100) -> list: 60 | """ 61 | List all memory segments in the program with pagination. 62 | """ 63 | return safe_get("segments", {"offset": offset, "limit": limit}) 64 | 65 | @mcp.tool() 66 | def list_imports(offset: int = 0, limit: int = 100) -> list: 67 | """ 68 | List imported symbols in the binary. 69 | """ 70 | return safe_get("imports", {"offset": offset, "limit": limit}) 71 | 72 | @mcp.tool() 73 | def list_exports(offset: int = 0, limit: int = 100) -> list: 74 | """ 75 | List exported symbols in the binary. 76 | """ 77 | return safe_get("exports", {"offset": offset, "limit": limit}) 78 | 79 | def list_data(offset: int = 0, limit: int = 1000) -> list: 80 | """ 81 | List defined data for each function with pagination. 82 | """ 83 | return safe_get("data", {"offset": offset, "limit": limit}) 84 | 85 | @mcp.tool() 86 | def search_functions_by_name(query: str, offset: int = 0, limit: int = 100) -> list: 87 | """ 88 | Search for functions whose name contains the given substring. 89 | """ 90 | if not query: 91 | return ["Error: query string is required"] 92 | return safe_get("searchFunctions", {"query": query, "offset": offset, "limit": limit}) 93 | 94 | @mcp.tool() 95 | def rename_function_by_address(function_address: str, new_name: str) -> str: 96 | """ 97 | Rename a function by its address. 98 | """ 99 | return safe_post("renameFunction", {"address": function_address, "newName": new_name}) 100 | 101 | @mcp.tool() 102 | def set_decompiler_comment(address: str, comment: str) -> str: 103 | """ 104 | Set a comment for a given address in the function pseudocode. 105 | """ 106 | return safe_post("set_decompiler_comment", {"address": address, "comment": comment}) 107 | 108 | @mcp.tool() 109 | def list_libraries(offset: int = 0, limit: int = 100) -> list: 110 | """ 111 | List shared libraries used in the binary. 112 | """ 113 | return safe_get("libraries", {"offset": offset, "limit": limit}) 114 | 115 | @mcp.tool() 116 | def show_headers(offset: int = 0, limit: int = 100) -> list: 117 | """ 118 | Show header information in the binary. 119 | """ 120 | return safe_get("headers", {"offset": offset, "limit": limit}) 121 | 122 | @mcp.tool() 123 | def show_function_detail(address: str) -> str: 124 | """ 125 | Show details about function at the given address. 126 | """ 127 | return "\n".join(safe_get("showFunctionDetails", {"addr": address})) 128 | 129 | @mcp.tool() 130 | def get_function_prototype(address: str) -> str: 131 | """ 132 | Get function signature at the given address. 133 | """ 134 | return "\n".join(safe_get("getFunctionPrototype", {"addr": address})) 135 | 136 | @mcp.tool() 137 | def xrefs_to(address: str) -> str: 138 | """ 139 | List code references to the given address. 140 | """ 141 | return "\n".join(safe_get("xrefsTo", {"addr": address})) 142 | 143 | @mcp.tool() 144 | def disassemble_function(address: str) -> str: 145 | """ 146 | Disassemble a function at the given address. 147 | """ 148 | return "\n".join(safe_get("disassembleFunction", {"addr": address})) 149 | 150 | @mcp.tool() 151 | def set_function_prototype(address: str, description: str) -> str: 152 | """ 153 | Rename a function by its address. 154 | """ 155 | return safe_post("setFunctionPrototype", {"address": address, "description": description}) 156 | 157 | if __name__ == "__main__": 158 | mcp.run() 159 | ``` -------------------------------------------------------------------------------- /src/CutterMCPPlugin.py: -------------------------------------------------------------------------------- ```python 1 | import cutter 2 | from http.server import BaseHTTPRequestHandler, HTTPServer 3 | import threading 4 | from datetime import datetime 5 | from PySide6.QtWidgets import ( 6 | QWidget, QVBoxLayout, QLabel, 7 | QPlainTextEdit, QPushButton 8 | ) 9 | from PySide6.QtCore import Qt, QObject, Signal 10 | from urllib.parse import urlparse, parse_qs 11 | 12 | class ServerSignals(QObject): 13 | log_signal = Signal(str) 14 | status_signal = Signal(str) 15 | 16 | class MCPDockWidget(cutter.CutterDockWidget): 17 | def __init__(self, parent, signals): 18 | super().__init__(parent) 19 | self.setObjectName("MCPDockWidget") 20 | self.setWindowTitle("HTTP Server") 21 | self.signals = signals 22 | 23 | container = QWidget() 24 | layout = QVBoxLayout(container) 25 | 26 | self.status_label = QLabel("🟢 HTTP Server: Running (Port 8000)") 27 | self.status_label.setAlignment(Qt.AlignCenter) 28 | layout.addWidget(self.status_label) 29 | 30 | self.log_view = QPlainTextEdit() 31 | self.log_view.setReadOnly(True) 32 | self.log_view.setPlaceholderText("HTTP Server logs will appear here...") 33 | layout.addWidget(self.log_view) 34 | 35 | self.setWidget(container) 36 | 37 | self.signals.log_signal.connect(self.log) 38 | 39 | def log(self, message): 40 | timestamp = datetime.now().strftime("%H:%M:%S") 41 | self.log_view.appendPlainText(f"[{timestamp}] {message}") 42 | 43 | class MCPPlugin(cutter.CutterPlugin): 44 | def __init__(self): 45 | super().__init__() 46 | self.server = None 47 | self.server_thread = None 48 | self.signals = ServerSignals() 49 | self.dock_widget = None 50 | 51 | class MCPRequestHandler(BaseHTTPRequestHandler): 52 | def log_message(self, format, *args): 53 | message = format % args 54 | self.server.parent.signals.log_signal.emit(message) 55 | 56 | def do_GET(self): 57 | parsed = urlparse(self.path) 58 | path = parsed.path 59 | query = parse_qs(parsed.query) 60 | 61 | if path == '/functions': 62 | self.handle_functions(query) 63 | elif path == '/decompile': 64 | self.handle_decompile(query) 65 | elif path == '/segments': 66 | self.handle_segments(query) 67 | elif path == '/imports': 68 | self.handle_imports(query) 69 | elif path == '/exports': 70 | self.handle_exports(query) 71 | elif path == '/data': 72 | self.handle_data(query) 73 | elif path == '/searchFunctions': 74 | self.handle_search_functions(query) 75 | elif path == '/libraries': 76 | self.handle_libraries(query) 77 | elif path == '/headers': 78 | self.handle_headers(query) 79 | elif path == '/showFunctionDetails': 80 | self.handle_show_function_details(query) 81 | elif path == '/getFunctionPrototype': 82 | self.handle_get_function_prototype(query) 83 | elif path == '/xrefsTo': 84 | self.handle_xrefs_to(query) 85 | elif path == '/disassembleFunction': 86 | self.handle_disassemble_function(query) 87 | else: 88 | self.handle_root() 89 | 90 | def do_POST(self): 91 | content_length = int(self.headers['Content-Length']) 92 | post_data = self.rfile.read(content_length) 93 | 94 | parsed = urlparse(self.path) 95 | if parsed.path == '/renameFunction': 96 | self.handle_rename_function(post_data) 97 | elif parsed.path == '/setDecompilerComment': 98 | self.handle_set_decompiler_comment(post_data) 99 | elif parsed.path == '/setFunctionPrototype': 100 | self.handle_set_function_prototype(post_data) 101 | else: 102 | self.send_error(404, "Endpoint not found") 103 | 104 | def handle_root(self): 105 | self.send_response(200) 106 | self.send_header('Content-type', 'text/plain') 107 | self.end_headers() 108 | response = """HTTP Server Endpoints: 109 | GET /functions - List all functions 110 | GET /decompile?addr=ADDR - Decompile function 111 | GET /segments - List memory segments 112 | GET /imports - List imports 113 | GET /exports - List exports 114 | GET /data - List defined data 115 | GET /searchFunctions?query=NAME - Search functions 116 | GET /libraries - List shared libraries 117 | GET /headers - Show header information 118 | GET /showFunctionDetails?addr=ADDR - Show details about function 119 | GET /getFunctionPrototype?addr=ADDR - Get function signature 120 | GET /xrefsTo?addr=ADDR - List code references 121 | GET /disassembleFunction?addr=ADDR - Disassemble function 122 | 123 | POST /renameFunction - Rename a function 124 | POST /setDecompilerComment - Set decompiler comment 125 | POST /setFunctionPrototype - Set function signature""" 126 | self.wfile.write(response.encode('utf-8')) 127 | 128 | def handle_rename_function(self, post_data): 129 | try: 130 | params = parse_qs(post_data.decode('utf-8')) 131 | function_address = params.get('address', [''])[0] 132 | new_name = params.get('newName', [''])[0] 133 | 134 | if not function_address or not new_name: 135 | self.send_error(400, "Both address and newName parameters are required") 136 | return 137 | 138 | cutter.cmd(f"afn {new_name} @ {function_address}") 139 | self.server.parent.signals.log_signal.emit(f"Renamed function at {function_address} to {new_name}") 140 | 141 | self.send_response(200) 142 | self.send_header('Content-type', 'text/plain') 143 | self.end_headers() 144 | self.wfile.write(f"Successfully renamed function at {function_address} to {new_name}".encode('utf-8')) 145 | except Exception as e: 146 | self.send_error(500, f"Error renaming function: {str(e)}") 147 | 148 | def handle_set_decompiler_comment(self, post_data): 149 | try: 150 | params = parse_qs(post_data.decode('utf-8')) 151 | address = params.get('address', [''])[0] 152 | comment = params.get('comment', [''])[0] 153 | 154 | if not address or not comment: 155 | self.send_error(400, "Both address and comment parameters are required") 156 | return 157 | 158 | cutter.cmd(f"CCu {comment} @ {address}") 159 | self.server.parent.signals.log_signal.emit(f"Set decompiler comment at {address} to: {comment}") 160 | 161 | self.send_response(200) 162 | self.send_header('Content-type', 'text/plain') 163 | self.end_headers() 164 | self.wfile.write(f"Successfully set decompiler comment at {address}".encode('utf-8')) 165 | except Exception as e: 166 | self.send_error(500, f"Error setting decompiler comment: {str(e)}") 167 | 168 | def handle_functions(self, query): 169 | try: 170 | offset = int(query.get('offset', [0])[0]) 171 | limit = int(query.get('limit', [100])[0]) 172 | funcs = cutter.cmd("aflq").splitlines() 173 | paginated_funcs = funcs[offset:offset+limit] 174 | response = "\n".join(paginated_funcs) 175 | self.server.parent.signals.log_signal.emit(f"Served {len(paginated_funcs)} functions") 176 | self.send_response(200) 177 | self.send_header('Content-type', 'text/plain') 178 | self.end_headers() 179 | self.wfile.write(response.encode('utf-8')) 180 | except Exception as e: 181 | self.send_error(500, f"Error: {str(e)}") 182 | 183 | def handle_decompile(self, query): 184 | try: 185 | addr = query.get('addr', [''])[0] 186 | if not addr: 187 | self.send_error(400, "Address parameter is required") 188 | return 189 | decompiled = cutter.cmd(f"pdg @ {addr}") 190 | self.server.parent.signals.log_signal.emit(f"Decompiled function at {addr}") 191 | self.send_response(200) 192 | self.send_header('Content-type', 'text/plain') 193 | self.end_headers() 194 | self.wfile.write(decompiled.encode('utf-8')) 195 | except Exception as e: 196 | self.send_error(500, f"Error: {str(e)}") 197 | 198 | def handle_segments(self, query): 199 | try: 200 | offset = int(query.get('offset', [0])[0]) 201 | limit = int(query.get('limit', [100])[0]) 202 | segments = cutter.cmd("iS").splitlines() 203 | result = segments[offset:offset+limit] 204 | response = "\n".join(result) 205 | self.server.parent.signals.log_signal.emit(f"Served {len(result)} segments") 206 | self.send_response(200) 207 | self.send_header('Content-type', 'text/plain') 208 | self.end_headers() 209 | self.wfile.write(response.encode('utf-8')) 210 | except Exception as e: 211 | self.send_error(500, f"Error: {str(e)}") 212 | 213 | def handle_imports(self, query): 214 | try: 215 | offset = int(query.get('offset', [0])[0]) 216 | limit = int(query.get('limit', [100])[0]) 217 | imports = cutter.cmd("ii").splitlines() 218 | result = imports[offset:offset+limit] 219 | response = "\n".join(result) 220 | self.server.parent.signals.log_signal.emit(f"Served {len(result)} imports") 221 | self.send_response(200) 222 | self.send_header('Content-type', 'text/plain') 223 | self.end_headers() 224 | self.wfile.write(response.encode('utf-8')) 225 | except Exception as e: 226 | self.send_error(500, f"Error: {str(e)}") 227 | 228 | def handle_exports(self, query): 229 | try: 230 | offset = int(query.get('offset', [0])[0]) 231 | limit = int(query.get('limit', [100])[0]) 232 | exports = cutter.cmd("iE").splitlines() 233 | result = exports[offset:offset+limit] 234 | response = "\n".join(result) 235 | self.server.parent.signals.log_signal.emit(f"Served {len(result)} exports") 236 | self.send_response(200) 237 | self.send_header('Content-type', 'text/plain') 238 | self.end_headers() 239 | self.wfile.write(response.encode('utf-8')) 240 | except Exception as e: 241 | self.send_error(500, f"Error: {str(e)}") 242 | 243 | def handle_data(self, query): 244 | try: 245 | offset = int(query.get('offset', [0])[0]) 246 | limit = int(query.get('limit', [100])[0]) 247 | data = cutter.cmd("pd 1000").splitlines() 248 | result = data[offset:offset+limit] 249 | response = "\n".join(result) 250 | self.server.parent.signals.log_signal.emit(f"Served {len(result)} data items") 251 | self.send_response(200) 252 | self.send_header('Content-type', 'text/plain') 253 | self.end_headers() 254 | self.wfile.write(response.encode('utf-8')) 255 | except Exception as e: 256 | self.send_error(500, f"Error: {str(e)}") 257 | 258 | def handle_search_functions(self, query): 259 | try: 260 | search_term = query.get('query', [''])[0] 261 | offset = int(query.get('offset', [0])[0]) 262 | limit = int(query.get('limit', [100])[0]) 263 | if not search_term: 264 | self.send_error(400, "Search term is required") 265 | return 266 | search_results = cutter.cmd(f"afl~{search_term}").splitlines() 267 | paginated_results = search_results[offset:offset+limit] 268 | response = "\n".join(paginated_results) 269 | self.server.parent.signals.log_signal.emit( 270 | f"Found {len(search_results)} functions matching '{search_term}', " 271 | f"returning {len(paginated_results)}" 272 | ) 273 | self.send_response(200) 274 | self.send_header('Content-type', 'text/plain') 275 | self.end_headers() 276 | self.wfile.write(response.encode('utf-8')) 277 | except Exception as e: 278 | self.send_error(500, f"Error searching functions: {str(e)}") 279 | 280 | def handle_libraries(self, query): 281 | try: 282 | offset = int(query.get('offset', [0])[0]) 283 | limit = int(query.get('limit', [100])[0]) 284 | libraries = cutter.cmd("ilq").splitlines() 285 | result = libraries[offset:offset+limit] 286 | response = "\n".join(result) 287 | self.server.parent.signals.log_signal.emit(f"Served {len(result)} libraries") 288 | self.send_response(200) 289 | self.send_header('Content-type', 'text/plain') 290 | self.end_headers() 291 | self.wfile.write(response.encode('utf-8')) 292 | except Exception as e: 293 | self.send_error(500, f"Error: {str(e)}") 294 | 295 | def handle_headers(self, query): 296 | try: 297 | offset = int(query.get('offset', [0])[0]) 298 | limit = int(query.get('limit', [100])[0]) 299 | headers = cutter.cmd("i;iH").splitlines() 300 | result = headers[offset:offset+limit] 301 | response = "\n".join(result) 302 | self.server.parent.signals.log_signal.emit(f"Served {len(result)} headers") 303 | self.send_response(200) 304 | self.send_header('Content-type', 'text/plain') 305 | self.end_headers() 306 | self.wfile.write(response.encode('utf-8')) 307 | except Exception as e: 308 | self.send_error(500, f"Error: {str(e)}") 309 | 310 | def handle_show_function_details(self, query): 311 | try: 312 | addr = query.get('addr', [''])[0] 313 | if not addr: 314 | self.send_error(400, "Address parameter is required") 315 | return 316 | functionDetail = cutter.cmd(f"afi @ {addr}") 317 | self.server.parent.signals.log_signal.emit(f"Served details about function at {addr}") 318 | self.send_response(200) 319 | self.send_header('Content-type', 'text/plain') 320 | self.end_headers() 321 | self.wfile.write(functionDetail.encode('utf-8')) 322 | except Exception as e: 323 | self.send_error(500, f"Error: {str(e)}") 324 | 325 | def handle_get_function_prototype(self, query): 326 | try: 327 | addr = query.get('addr', [''])[0] 328 | if not addr: 329 | self.send_error(400, "Address parameter is required") 330 | return 331 | functionPrototype = cutter.cmd(f"afs @ {addr}") 332 | self.server.parent.signals.log_signal.emit(f"Served signature of function at {addr}") 333 | self.send_response(200) 334 | self.send_header('Content-type', 'text/plain') 335 | self.end_headers() 336 | self.wfile.write(functionPrototype.encode('utf-8')) 337 | except Exception as e: 338 | self.send_error(500, f"Error: {str(e)}") 339 | 340 | def handle_xrefs_to(self, query): 341 | try: 342 | addr = query.get('addr', [''])[0] 343 | if not addr: 344 | self.send_error(400, "Address parameter is required") 345 | return 346 | xrefsTo = cutter.cmd(f"axt @ {addr}") 347 | self.server.parent.signals.log_signal.emit(f"Served references of code at {addr}") 348 | self.send_response(200) 349 | self.send_header('Content-type', 'text/plain') 350 | self.end_headers() 351 | self.wfile.write(xrefsTo.encode('utf-8')) 352 | except Exception as e: 353 | self.send_error(500, f"Error: {str(e)}") 354 | 355 | def handle_disassemble_function(self, query): 356 | try: 357 | addr = query.get('addr', [''])[0] 358 | if not addr: 359 | self.send_error(400, "Address parameter is required") 360 | return 361 | disassembledFunction = cutter.cmd(f"pdf @ {addr}") 362 | self.server.parent.signals.log_signal.emit(f"Disassembled function at {addr}") 363 | self.send_response(200) 364 | self.send_header('Content-type', 'text/plain') 365 | self.end_headers() 366 | self.wfile.write(disassembledFunction.encode('utf-8')) 367 | except Exception as e: 368 | self.send_error(500, f"Error: {str(e)}") 369 | 370 | def handle_set_function_prototype(self, post_data): 371 | try: 372 | params = parse_qs(post_data.decode('utf-8')) 373 | address = params.get('address', [''])[0] 374 | description = params.get('description', [''])[0] 375 | 376 | if not address or not description: 377 | self.send_error(400, "Both address and description parameters are required") 378 | return 379 | 380 | cutter.cmd(f"afs {description} @ {address}") 381 | self.server.parent.signals.log_signal.emit(f"Set function signature at {address} to: {description}") 382 | 383 | self.send_response(200) 384 | self.send_header('Content-type', 'text/plain') 385 | self.end_headers() 386 | self.wfile.write(f"Successfully set function signature at {address}".encode('utf-8')) 387 | except Exception as e: 388 | self.send_error(500, f"Error setting function signature: {str(e)}") 389 | 390 | def setupPlugin(self): 391 | pass 392 | 393 | def setupInterface(self, main): 394 | self.dock_widget = MCPDockWidget(main, self.signals) 395 | main.addPluginDockWidget(self.dock_widget) 396 | self.start_server() 397 | 398 | def start_server(self): 399 | if self.server is not None: 400 | self.signals.log_signal.emit("Server is already running!") 401 | return 402 | 403 | def run_server(): 404 | server_address = ('', 8000) 405 | self.server = HTTPServer(server_address, self.MCPRequestHandler) 406 | self.server.parent = self 407 | self.signals.log_signal.emit("HTTP Server started at http://localhost:8000") 408 | self.signals.log_signal.emit("Available endpoints:") 409 | self.signals.log_signal.emit("GET /functions - List functions") 410 | self.signals.log_signal.emit("GET /decompile - Decompile function") 411 | self.signals.log_signal.emit("GET /segments - List memory segments") 412 | self.signals.log_signal.emit("GET /imports - List imports") 413 | self.signals.log_signal.emit("GET /exports - List exports") 414 | self.signals.log_signal.emit("GET /data - List defined data") 415 | self.signals.log_signal.emit("GET /searchFunctions - Search functions by name") 416 | self.signals.log_signal.emit("GET /libraries - List libraries") 417 | self.signals.log_signal.emit("GET /headers - Show headers") 418 | self.signals.log_signal.emit("GET /showFunctionDetails - Show details about function") 419 | self.signals.log_signal.emit("GET /getFunctionPrototype - Show signature of function") 420 | self.signals.log_signal.emit("GET /xrefsTo - List code references") 421 | self.signals.log_signal.emit("GET /disassembleFunction - Disassemble function") 422 | self.signals.log_signal.emit("POST /renameFunction - Rename a function") 423 | self.signals.log_signal.emit("POST /setDecompilerComment - Set decompiler comment") 424 | self.signals.log_signal.emit("POST /setFunctionPrototype - Set signature of function") 425 | self.server.serve_forever() 426 | 427 | self.server_thread = threading.Thread(target=run_server) 428 | self.server_thread.daemon = True 429 | self.server_thread.start() 430 | 431 | def terminate(self): 432 | if self.server: 433 | self.signals.log_signal.emit("Shutting down HTTP Server...") 434 | self.server.shutdown() 435 | self.server.server_close() 436 | self.server = None 437 | if self.server_thread and self.server_thread.is_alive(): 438 | self.server_thread.join() 439 | 440 | def create_cutter_plugin(): 441 | return MCPPlugin() 442 | ```