#
tokens: 7443/50000 4/4 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── bridge_mcp_cutter.py
├── images
│   └── cutterMCP.png
├── LICENSE
├── README.md
├── requirements.txt
└── src
    └── CutterMCPPlugin.py
```

# Files

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
 2 | [![LinkedIn](https://img.shields.io/badge/LinkedIn-Connect-blue)](https://www.linkedin.com/in/amey-pathak/)
 3 | 
 4 | ![cutter_MCP_logo](images/cutterMCP.png)
 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 | 
```