Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ The following MCP tools are available:
- `rename_method()` : Renames the method
- `rename_field()` : Renames the field
- `rename_package()` : Renames whole package
- `rename_variable()` : Renames the variable within a method
- `debug_get_stack_frames()` : Get the stack frames from jadx debugger
- `debug_get_threads()` : Get the insights of threads from jadx debugger
- `debug_get_variables()` : Get the variables from jadx debugger
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/zin/jadxaimcp/server/PluginServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ private void registerRoutes() {
app.get("/rename-method", refactoringRoutes::handleRenameMethod);
app.get("/rename-field", refactoringRoutes::handleRenameField);
app.get("/rename-package", refactoringRoutes::handleRenamePackage);
app.get("/rename-variable", refactoringRoutes::handleRenameVariable);

// --- Debugging ---
app.get("/debug/stack-frames", debugRoutes::handleGetStackFrames);
Expand Down
112 changes: 112 additions & 0 deletions src/main/java/com/zin/jadxaimcp/server/routes/RefactoringRoutes.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@
import jadx.gui.JadxWrapper;
import jadx.gui.ui.MainWindow;

import jadx.core.dex.nodes.MethodNode;
import jadx.core.dex.instructions.args.SSAVar;
import jadx.api.metadata.annotations.VarNode;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -154,6 +158,99 @@ public void handleRenameField(Context ctx) {
}
}

/**
* @return void
* @param Context
*
* This routing method handle the /rename-variable mcp tool call's http request.
* It validates required params: class_name, method_name, variable_name, new_name.
* It tries to find the method and then iterates over its SSA variables to find the matching variable.
* If found, it renames it using NodeRenamedByUser event.
*/
public void handleRenameVariable(Context ctx) {
String className = ctx.queryParam("class_name");
String methodName = ctx.queryParam("method_name");
String variableName = ctx.queryParam("variable_name");
String newName = ctx.queryParam("new_name");

// Optional params for more specific targeting
String regStr = ctx.queryParam("reg");
String ssaStr = ctx.queryParam("ssa");

if (validateParams(ctx, className, methodName, variableName, newName)) return;

// Strip method signature if present
if (methodName.contains("(")) {
methodName = methodName.substring(0, methodName.indexOf('('));
}

try {
JadxWrapper wrapper = mainWindow.getWrapper();

for (JavaClass cls : wrapper.getIncludedClassesWithInners()) {
if (cls.getFullName().equals(className)) {
for (JavaMethod method : cls.getMethods()) {
String fullMethodName = cls.getFullName() + "." + method.getName();
if (method.getName().equals(methodName) || fullMethodName.equalsIgnoreCase(methodName)) {
MethodNode methodNode = method.getMethodNode();
if (methodNode == null) continue;

List<SSAVar> sVars = methodNode.getSVars();

// Ensure class is processed to populate SSA variables
if (sVars.isEmpty()) {
logger.info("SSA variables empty for method {}, forcing class reload and processing...", method.getName());
try {
// defined class need to be unloaded to reset state and allow full processing
cls.getClassNode().unload();
cls.getClassNode().root().getProcessClasses().forceProcess(cls.getClassNode());

// Re-fetch method node and sVars after processing because unload/load recreates MethodNode objects
MethodNode newMethodNode = cls.getClassNode().searchMethodByShortName(method.getName());
if (newMethodNode != null) {
methodNode = newMethodNode;
sVars = methodNode.getSVars();
logger.info("Class reloaded. New SSA variables count: {}", sVars != null ? sVars.size() : "null");
} else {
logger.error("Failed to find method {} after reload", method.getName());
}

} catch (Exception e) {
logger.error("Failed to force process class {}", cls.getName(), e);
}
}

if (sVars == null || sVars.isEmpty()) continue;

for (SSAVar sVar : sVars) {
boolean nameMatch = variableName.equals(sVar.getName());
boolean regMatch = regStr == null || regStr.isEmpty() || String.valueOf(sVar.getRegNum()).equals(regStr);
boolean ssaMatch = ssaStr == null || ssaStr.isEmpty() || String.valueOf(sVar.getVersion()).equals(ssaStr);
if (nameMatch && regMatch && ssaMatch) {
VarNode varNode = VarNode.get(methodNode, sVar);
if (varNode != null) {
NodeRenamedByUser event = new NodeRenamedByUser(varNode, variableName, newName);
event.setRenameNode(varNode);
event.setResetName(newName.isEmpty());
mainWindow.events().send(event);

logger.info("Renamed variable {} to {} in method {}", variableName, newName, method.getName());
ctx.json(Map.of("result", "Rename variable " + variableName + " to " + newName));
return;
}
}
}
}
}
}
}

JadxAIMCPPluginError.handleError(ctx, 404, "Variable " + variableName + " not found in method " + methodName, logger);
} catch (Exception e) {
JadxAIMCPPluginError.handleError(ctx, "Internal error while trying to rename the variable: " + e.getMessage(), e, logger);
}
}

/**
* @return void
* @param Context
Expand Down Expand Up @@ -238,4 +335,19 @@ private boolean validateParams(Context ctx, String p1, String p2, String p3) {
return false;
}

/**
* @param Context, String, String, String, String
* @return boolean
*
* This method is used to validate the availability of required http params in RefactoringRoutes
* MCP tool's HTTP requests. If params are ok return true else return false.
*/
private boolean validateParams(Context ctx, String p1, String p2, String p3, String p4) {
if (p1 == null || p1.isEmpty() || p2 == null || p2.isEmpty() || p3 == null || p3.isEmpty() || p4 == null || p4.isEmpty()) {
JadxAIMCPPluginError.handleError(ctx, 400, "Missing required parameters", logger);
return true;
}
return false;
}

}