1717
1818from typing import Dict , List , Any , Optional , Union , Callable
1919from fastmcp import FastMCP
20+ from fastmcp .server .middleware .logging import StructuredLoggingMiddleware
21+
2022
2123# Set up logging configuration
2224logger = logging .getLogger ()
3133# Initialize the MCP server
3234mcp = FastMCP ("JADX-AI-MCP Plugin Reverse Engineering Server" )
3335
36+ # for development only
37+ mcp .add_middleware (StructuredLoggingMiddleware (include_payloads = True ))
38+
3439# Parse the arguments
3540parser = argparse .ArgumentParser ("MCP Server for Jadx" )
3641parser .add_argument ("--http" , help = "Serve MCP Server over HTTP stream." , action = "store_true" , default = False )
@@ -200,7 +205,10 @@ async def get_from_jadx(endpoint: str, params: dict = {}) -> Union[str, dict]:
200205 async with httpx .AsyncClient () as client :
201206 resp = await client .get (f"{ JADX_HTTP_BASE } /{ endpoint } " , params = params , timeout = 60 )
202207 resp .raise_for_status ()
203- return resp .text
208+ response = resp .text
209+ if isinstance (response , str ):
210+ return json .loads (response )
211+ return response
204212 except httpx .HTTPStatusError as e :
205213 error_message = f"HTTP error { e .response .status_code } : { e .response .text } "
206214 logger .error (error_message )
@@ -226,16 +234,19 @@ async def fetch_current_class() -> dict:
226234 return await get_from_jadx ("current-class" )
227235
228236@mcp .tool ()
229- async def get_selected_text () -> str :
237+ async def get_selected_text () -> dict :
230238 """Returns the currently selected text in the decompiled code view.
231239
232240 Returns:
233241 String containing currently highlighted/selected text in jadx-gui.
234242 """
235- return await get_from_jadx ("selected-text" )
243+ response = await get_from_jadx ("selected-text" )
244+ if isinstance (response , str ):
245+ return json .loads (response )
246+ return response
236247
237248@mcp .tool ()
238- async def get_method_by_name (class_name : str , method_name : str ) -> str :
249+ async def get_method_by_name (class_name : str , method_name : str ) -> dict :
239250 """Fetch the source code of a method from a specific class.
240251
241252 Args:
@@ -245,7 +256,10 @@ async def get_method_by_name(class_name: str, method_name: str) -> str:
245256 Returns:
246257 Code of requested method as String.
247258 """
248- return await get_from_jadx ("method-by-name" , {"class" : class_name , "method" : method_name })
259+ response = await get_from_jadx ("method-by-name" , {"class" : class_name , "method" : method_name })
260+ if isinstance (response , str ):
261+ return json .loads (response )
262+ return response
249263
250264@mcp .tool ()
251265async def get_all_classes (offset : int = 0 , count : int = 0 ) -> dict :
@@ -266,7 +280,7 @@ async def get_all_classes(offset: int = 0, count: int = 0) -> dict:
266280 )
267281
268282@mcp .tool ()
269- async def get_class_source (class_name : str ) -> str :
283+ async def get_class_source (class_name : str ) -> dict :
270284 """Fetch the Java source of a specific class.
271285
272286 Args:
@@ -275,10 +289,13 @@ async def get_class_source(class_name: str) -> str:
275289 Returns:
276290 Code of requested class as String.
277291 """
278- return await get_from_jadx ("class-source" , {"class" : class_name })
292+ response = await get_from_jadx ("class-source" , {"class" : class_name })
293+ if isinstance (response , str ):
294+ return json .loads (response )
295+ return response
279296
280297@mcp .tool ()
281- async def search_method_by_name (method_name : str ) -> List [ str ] :
298+ async def search_method_by_name (method_name : str ) -> dict :
282299 """Search for a method name across all classes.
283300
284301 Args:
@@ -287,14 +304,13 @@ async def search_method_by_name(method_name: str) -> List[str]:
287304 Returns:
288305 A list of all classes containing the method.
289306 """
290-
291307 response = await get_from_jadx ("search-method" , {"method" : method_name })
292- all_matches = response . splitlines () if response else []
293-
294- return all_matches
308+ if isinstance ( response , str ):
309+ return json . loads ( response )
310+ return response
295311
296312@mcp .tool ()
297- async def get_methods_of_class (class_name : str ) -> List [ str ] :
313+ async def get_methods_of_class (class_name : str ) -> dict :
298314 """List all method names in a class.
299315
300316 Args:
@@ -304,13 +320,13 @@ async def get_methods_of_class(class_name: str) -> List[str]:
304320 A list of all methods in the class.
305321 """
306322
307- response = await get_from_jadx ("methods-of-class" , {"class " : class_name })
308- all_methods = response . splitlines () if response else []
309-
310- return all_methods
323+ response = await get_from_jadx ("methods-of-class" , {"method " : class_name })
324+ if isinstance ( response , str ):
325+ return json . loads ( response )
326+ return response
311327
312328@mcp .tool ()
313- async def get_fields_of_class (class_name : str ) -> List [ str ] :
329+ async def get_fields_of_class (class_name : str ) -> dict :
314330 """List all field names in a class.
315331
316332 Args:
@@ -320,13 +336,13 @@ async def get_fields_of_class(class_name: str) -> List[str]:
320336 A list of all fields in the class.
321337 """
322338
323- response = await get_from_jadx ("fields-of-class" , {"class " : class_name })
324- all_fields = response . splitlines () if response else []
325-
326- return all_fields
339+ response = await get_from_jadx ("fields-of-class" , {"method " : class_name })
340+ if isinstance ( response , str ):
341+ return json . loads ( response )
342+ return response
327343
328344@mcp .tool ()
329- async def get_smali_of_class (class_name : str ) -> str :
345+ async def get_smali_of_class (class_name : str ) -> dict :
330346 """Fetch the smali representation of a class.
331347
332348 Args:
@@ -335,7 +351,10 @@ async def get_smali_of_class(class_name: str) -> str:
335351 Returns:
336352 Smali code of the requested class as String.
337353 """
338- return await get_from_jadx ("smali-of-class" , {"class" : class_name })
354+ response = await get_from_jadx ("smali-of-class" , {"class" : class_name })
355+ if isinstance (response , str ):
356+ return json .loads (response )
357+ return response
339358
340359@mcp .tool ()
341360async def get_android_manifest () -> dict :
@@ -389,7 +408,7 @@ async def get_resource_file(resource_name: str) -> dict:
389408 return await get_from_jadx ("get-resource-file" , {"name" : resource_name })
390409
391410@mcp .tool ()
392- async def get_main_application_classes_names () -> List [ str ] :
411+ async def get_main_application_classes_names () -> dict :
393412 """Fetch all the main application classes' names based on the package name defined in the AndroidManifest.xml.
394413
395414 Args:
@@ -400,18 +419,9 @@ async def get_main_application_classes_names() -> List[str]:
400419 """
401420
402421 response = await get_from_jadx ("main-application-classes-names" )
403- if isinstance (response , dict ):
404- class_names = response .get ("classes" , [])
405- else :
406- import json
407- try :
408- parsed = json .loads (response )
409- class_info_list = parsed .get ("classes" , [])
410- class_names = [cls_info .get ("name" ) for cls_info in class_info_list if "name" in cls_info ]
411- except (json .JSONDecodeError , AttributeError ):
412- class_names = []
413-
414- return class_names
422+ if isinstance (response , str ):
423+ return json .loads (response )
424+ return response
415425
416426@mcp .tool ()
417427async def get_main_application_classes_code (offset : int = 0 , count : int = 0 ) -> dict :
0 commit comments