diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 330d83c..9271e8d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,11 @@ jobs: - name: Get version from pom.xml id: get_version - run: echo "VERSION=$(grep -m1 '' pom.xml | sed 's/[[:space:]]*\(.*\)<\/version>.*/\1/')" >> $GITHUB_OUTPUT + run: | + VERSION=$(grep -m1 '' pom.xml | sed 's/[[:space:]]*\(.*\)<\/version>.*/\1/') + # Strip any suffix after version numbers (like -SNAPSHOT) + BASE_VERSION=$(echo "$VERSION" | sed 's/^\([0-9]\+\.[0-9]\+\)[^0-9].*/\1/') + echo "VERSION=$BASE_VERSION" >> $GITHUB_OUTPUT - name: Set build version id: set_version @@ -32,10 +36,20 @@ jobs: if [[ "${{ github.ref_type }}" == "tag" ]]; then VERSION="${{ github.ref_name }}" VERSION="${VERSION#v}" - echo "BUILD_VERSION=$VERSION" >> $GITHUB_OUTPUT + echo "BUILD_VERSION=${VERSION}" >> $GITHUB_OUTPUT + echo "::debug::Set BUILD_VERSION to ${VERSION}" else SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-8) - echo "BUILD_VERSION=${{ steps.get_version.outputs.VERSION }}-nightly-$SHORT_SHA" >> $GITHUB_OUTPUT + TIMESTAMP=$(date +"%Y%m%d-%H%M%S") + LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + if [ -z "$LATEST_TAG" ]; then + BASE_VERSION="${{ steps.get_version.outputs.VERSION }}" + else + BASE_VERSION="${LATEST_TAG#v}" + fi + FULL_VERSION="${BASE_VERSION}-${SHORT_SHA}-${TIMESTAMP}" + echo "BUILD_VERSION=${FULL_VERSION}" >> $GITHUB_OUTPUT + echo "::debug::Set BUILD_VERSION to ${FULL_VERSION}" fi - name: Update version in files @@ -61,7 +75,6 @@ jobs: path: | target/GhydraMCP-*.zip bridge_mcp_hydra.py - target/GhydraMCP-Complete-*.zip - name: Generate Release Notes if: github.ref_type == 'tag' @@ -103,6 +116,5 @@ jobs: files: | target/GhydraMCP-*.zip bridge_mcp_hydra.py - target/GhydraMCP-Complete-*.zip draft: false - prerelease: false \ No newline at end of file + prerelease: false diff --git a/bridge_mcp_hydra.py b/bridge_mcp_hydra.py index 9c5a278..91b3fe5 100644 --- a/bridge_mcp_hydra.py +++ b/bridge_mcp_hydra.py @@ -368,85 +368,162 @@ def search_functions_by_name(port: int = DEFAULT_GHIDRA_PORT, query: str = "", o @mcp.tool() def get_function_by_address(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> str: - """ - Get a function by its address. + """Get function details by its memory address + + Args: + port: Ghidra instance port (default: 8192) + address: Memory address of the function (hex string) + + Returns: + Multiline string with function details including name, address, and signature """ return "\n".join(safe_get(port, "get_function_by_address", {"address": address})) @mcp.tool() def get_current_address(port: int = DEFAULT_GHIDRA_PORT) -> str: - """ - Get the address currently selected by the user. + """Get the address currently selected in Ghidra's UI + + Args: + port: Ghidra instance port (default: 8192) + + Returns: + String containing the current memory address (hex format) """ return "\n".join(safe_get(port, "get_current_address")) @mcp.tool() def get_current_function(port: int = DEFAULT_GHIDRA_PORT) -> str: - """ - Get the function currently selected by the user. + """Get the function currently selected in Ghidra's UI + + Args: + port: Ghidra instance port (default: 8192) + + Returns: + Multiline string with function details including name, address, and signature """ return "\n".join(safe_get(port, "get_current_function")) @mcp.tool() def list_functions(port: int = DEFAULT_GHIDRA_PORT) -> list: - """ - List all functions in the database. + """List all functions in the current program + + Args: + port: Ghidra instance port (default: 8192) + + Returns: + List of strings with function names and addresses """ return safe_get(port, "list_functions") @mcp.tool() def decompile_function_by_address(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> str: - """ - Decompile a function at the given address. + """Decompile a function at a specific memory address + + Args: + port: Ghidra instance port (default: 8192) + address: Memory address of the function (hex string) + + Returns: + Multiline string containing the decompiled pseudocode """ return "\n".join(safe_get(port, "decompile_function", {"address": address})) @mcp.tool() def disassemble_function(port: int = DEFAULT_GHIDRA_PORT, address: str = "") -> list: - """ - Get assembly code (address: instruction; comment) for a function. + """Get disassembly for a function at a specific address + + Args: + port: Ghidra instance port (default: 8192) + address: Memory address of the function (hex string) + + Returns: + List of strings showing assembly instructions with addresses and comments """ return safe_get(port, "disassemble_function", {"address": address}) @mcp.tool() def set_decompiler_comment(port: int = DEFAULT_GHIDRA_PORT, address: str = "", comment: str = "") -> str: - """ - Set a comment for a given address in the function pseudocode. + """Add/edit a comment in the decompiler view at a specific address + + Args: + port: Ghidra instance port (default: 8192) + address: Memory address to place comment (hex string) + comment: Text of the comment to add + + Returns: + Confirmation message or error if failed """ return safe_post(port, "set_decompiler_comment", {"address": address, "comment": comment}) @mcp.tool() def set_disassembly_comment(port: int = DEFAULT_GHIDRA_PORT, address: str = "", comment: str = "") -> str: - """ - Set a comment for a given address in the function disassembly. + """Add/edit a comment in the disassembly view at a specific address + + Args: + port: Ghidra instance port (default: 8192) + address: Memory address to place comment (hex string) + comment: Text of the comment to add + + Returns: + Confirmation message or error if failed """ return safe_post(port, "set_disassembly_comment", {"address": address, "comment": comment}) @mcp.tool() def rename_local_variable(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", old_name: str = "", new_name: str = "") -> str: - """ - Rename a local variable in a function. + """Rename a local variable within a function + + Args: + port: Ghidra instance port (default: 8192) + function_address: Memory address of the function (hex string) + old_name: Current name of the variable + new_name: New name for the variable + + Returns: + Confirmation message or error if failed """ return safe_post(port, "rename_local_variable", {"function_address": function_address, "old_name": old_name, "new_name": new_name}) @mcp.tool() def rename_function_by_address(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", new_name: str = "") -> str: - """ - Rename a function by its address. + """Rename a function at a specific memory address + + Args: + port: Ghidra instance port (default: 8192) + function_address: Memory address of the function (hex string) + new_name: New name for the function + + Returns: + Confirmation message or error if failed """ return safe_post(port, "rename_function_by_address", {"function_address": function_address, "new_name": new_name}) @mcp.tool() def set_function_prototype(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", prototype: str = "") -> str: - """ - Set a function's prototype. + """Update a function's signature/prototype + + Args: + port: Ghidra instance port (default: 8192) + function_address: Memory address of the function (hex string) + prototype: New function prototype string (e.g. "int func(int param1)") + + Returns: + Confirmation message or error if failed """ return safe_post(port, "set_function_prototype", {"function_address": function_address, "prototype": prototype}) @mcp.tool() def set_local_variable_type(port: int = DEFAULT_GHIDRA_PORT, function_address: str = "", variable_name: str = "", new_type: str = "") -> str: - """ - Set a local variable's type. + """Change the data type of a local variable in a function + + Args: + port: Ghidra instance port (default: 8192) + function_address: Memory address of the function (hex string) + variable_name: Name of the variable to modify + new_type: New data type for the variable (e.g. "int", "char*") + + Returns: + Confirmation message or error if failed """ return safe_post(port, "set_local_variable_type", {"function_address": function_address, "variable_name": variable_name, "new_type": new_type}) diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF index bc8e073..840a5df 100644 --- a/src/main/resources/META-INF/MANIFEST.MF +++ b/src/main/resources/META-INF/MANIFEST.MF @@ -2,5 +2,6 @@ Manifest-Version: 1.0 Plugin-Class: eu.starsong.ghidra.GhydraMCP Plugin-Name: GhydraMCP Plugin-Version: 11.3.1 +Bundle-Version: dev-SNAPSHOT Plugin-Author: LaurieWired, Teal Bauer Plugin-Description: Expose multiple Ghidra tools to MCP servers with variable management