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

```
├── .github
│   └── workflows
│       └── build.yml
├── .gitignore
├── bridge_mcp_ghidra.py
├── images
│   ├── enable_plugin.png
│   └── new_plugins_found.png
├── lib
│   └── .gitignore
├── LICENSE
├── pom.xml
├── README.md
├── requirements.txt
└── src
    ├── assembly
    │   └── ghidra-extension.xml
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── lauriewired
    │   │           └── GhidraMCPPlugin.java
    │   └── resources
    │       ├── extension.properties
    │       ├── META-INF
    │       │   └── MANIFEST.MF
    │       └── Module.manifest
    └── test
        └── java
            └── com
                └── lauriewired
                    └── AppTest.java
```

# Files

--------------------------------------------------------------------------------
/lib/.gitignore:
--------------------------------------------------------------------------------

```
1 | *
2 | !.gitignore
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Maven target directory
 2 | /target/
 3 | 
 4 | # Compiled class files
 5 | *.class
 6 | 
 7 | # Logs
 8 | *.log
 9 | 
10 | # IDE files
11 | # IntelliJ
12 | .idea/
13 | *.iml
14 | *.iws
15 | out/
16 | 
17 | # Eclipse
18 | .project
19 | .classpath
20 | .settings/
21 | bin/
22 | 
23 | # VS Code
24 | .vscode/
25 | 
26 | # macOS
27 | .DS_Store
28 | .AppleDouble
29 | .LSOverride
30 | 
31 | # Thumbnails
32 | ._*
33 | 
34 | # macOS metadata files
35 | .Spotlight-V100
36 | .Trashes
37 | 
38 | # Maven Wrapper
39 | .mvn/
40 | !/.mvn/wrapper/maven-wrapper.jar
41 | mvnw
42 | mvnw.cmd
43 | 
44 | # Environment files
45 | .env
46 | .env.*
47 | 
48 | # Java crash logs (if any)
49 | hs_err_pid*
50 | replay_pid*
51 | 
52 | # Third party JAR files from Ghidra
53 | lib/*.jar
54 | 
55 | 
```

--------------------------------------------------------------------------------
/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 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/LaurieWired/GhidraMCP)](https://github.com/LaurieWired/GhidraMCP/releases)
  3 | [![GitHub stars](https://img.shields.io/github/stars/LaurieWired/GhidraMCP)](https://github.com/LaurieWired/GhidraMCP/stargazers)
  4 | [![GitHub forks](https://img.shields.io/github/forks/LaurieWired/GhidraMCP)](https://github.com/LaurieWired/GhidraMCP/network/members)
  5 | [![GitHub contributors](https://img.shields.io/github/contributors/LaurieWired/GhidraMCP)](https://github.com/LaurieWired/GhidraMCP/graphs/contributors)
  6 | [![Follow @lauriewired](https://img.shields.io/twitter/follow/lauriewired?style=social)](https://twitter.com/lauriewired)
  7 | 
  8 | ![ghidra_MCP_logo](https://github.com/user-attachments/assets/4986d702-be3f-4697-acce-aea55cd79ad3)
  9 | 
 10 | 
 11 | # ghidraMCP
 12 | ghidraMCP is an Model Context Protocol server for allowing LLMs to autonomously reverse engineer applications. It exposes numerous tools from core Ghidra functionality to MCP clients.
 13 | 
 14 | https://github.com/user-attachments/assets/36080514-f227-44bd-af84-78e29ee1d7f9
 15 | 
 16 | 
 17 | # Features
 18 | MCP Server + Ghidra Plugin
 19 | 
 20 | - Decompile and analyze binaries in Ghidra
 21 | - Automatically rename methods and data
 22 | - List methods, classes, imports, and exports
 23 | 
 24 | # Installation
 25 | 
 26 | ## Prerequisites
 27 | - Install [Ghidra](https://ghidra-sre.org)
 28 | - Python3
 29 | - MCP [SDK](https://github.com/modelcontextprotocol/python-sdk)
 30 | 
 31 | ## Ghidra
 32 | First, download the latest [release](https://github.com/LaurieWired/GhidraMCP/releases) from this repository. This contains the Ghidra plugin and Python MCP client. Then, you can directly import the plugin into Ghidra.
 33 | 
 34 | 1. Run Ghidra
 35 | 2. Select `File` -> `Install Extensions`
 36 | 3. Click the `+` button
 37 | 4. Select the `GhidraMCP-1-2.zip` (or your chosen version) from the downloaded release
 38 | 5. Restart Ghidra
 39 | 6. Make sure the GhidraMCPPlugin is enabled in `File` -> `Configure` -> `Developer`
 40 | 7. *Optional*: Configure the port in Ghidra with `Edit` -> `Tool Options` -> `GhidraMCP HTTP Server`
 41 | 
 42 | Video Installation Guide:
 43 | 
 44 | 
 45 | https://github.com/user-attachments/assets/75f0c176-6da1-48dc-ad96-c182eb4648c3
 46 | 
 47 | 
 48 | 
 49 | ## MCP Clients
 50 | 
 51 | Theoretically, any MCP client should work with ghidraMCP.  Three examples are given below.
 52 | 
 53 | ## Example 1: Claude Desktop
 54 | To set up Claude Desktop as a Ghidra MCP client, go to `Claude` -> `Settings` -> `Developer` -> `Edit Config` -> `claude_desktop_config.json` and add the following:
 55 | 
 56 | ```json
 57 | {
 58 |   "mcpServers": {
 59 |     "ghidra": {
 60 |       "command": "python",
 61 |       "args": [
 62 |         "/ABSOLUTE_PATH_TO/bridge_mcp_ghidra.py",
 63 |         "--ghidra-server",
 64 |         "http://127.0.0.1:8080/"
 65 |       ]
 66 |     }
 67 |   }
 68 | }
 69 | ```
 70 | 
 71 | Alternatively, edit this file directly:
 72 | ```
 73 | /Users/YOUR_USER/Library/Application Support/Claude/claude_desktop_config.json
 74 | ```
 75 | 
 76 | The server IP and port are configurable and should be set to point to the target Ghidra instance. If not set, both will default to localhost:8080.
 77 | 
 78 | ## Example 2: Cline
 79 | To use GhidraMCP with [Cline](https://cline.bot), this requires manually running the MCP server as well. First run the following command:
 80 | 
 81 | ```
 82 | python bridge_mcp_ghidra.py --transport sse --mcp-host 127.0.0.1 --mcp-port 8081 --ghidra-server http://127.0.0.1:8080/
 83 | ```
 84 | 
 85 | The only *required* argument is the transport. If all other arguments are unspecified, they will default to the above. Once the MCP server is running, open up Cline and select `MCP Servers` at the top.
 86 | 
 87 | ![Cline select](https://github.com/user-attachments/assets/88e1f336-4729-46ee-9b81-53271e9c0ce0)
 88 | 
 89 | Then select `Remote Servers` and add the following, ensuring that the url matches the MCP host and port:
 90 | 
 91 | 1. Server Name: GhidraMCP
 92 | 2. Server URL: `http://127.0.0.1:8081/sse`
 93 | 
 94 | ## Example 3: 5ire
 95 | Another MCP client that supports multiple models on the backend is [5ire](https://github.com/nanbingxyz/5ire). To set up GhidraMCP, open 5ire and go to `Tools` -> `New` and set the following configurations:
 96 | 
 97 | 1. Tool Key: ghidra
 98 | 2. Name: GhidraMCP
 99 | 3. Command: `python /ABSOLUTE_PATH_TO/bridge_mcp_ghidra.py`
100 | 
101 | # Building from Source
102 | 1. Copy the following files from your Ghidra directory to this project's `lib/` directory:
103 | - `Ghidra/Features/Base/lib/Base.jar`
104 | - `Ghidra/Features/Decompiler/lib/Decompiler.jar`
105 | - `Ghidra/Framework/Docking/lib/Docking.jar`
106 | - `Ghidra/Framework/Generic/lib/Generic.jar`
107 | - `Ghidra/Framework/Project/lib/Project.jar`
108 | - `Ghidra/Framework/SoftwareModeling/lib/SoftwareModeling.jar`
109 | - `Ghidra/Framework/Utility/lib/Utility.jar`
110 | - `Ghidra/Framework/Gui/lib/Gui.jar`
111 | 2. Build with Maven by running:
112 | 
113 | `mvn clean package assembly:single`
114 | 
115 | The generated zip file includes the built Ghidra plugin and its resources. These files are required for Ghidra to recognize the new extension.
116 | 
117 | - lib/GhidraMCP.jar
118 | - extensions.properties
119 | - Module.manifest
120 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
1 | mcp==1.5.0
2 | requests==2.32.3
3 | 
```

--------------------------------------------------------------------------------
/src/main/resources/extension.properties:
--------------------------------------------------------------------------------

```
1 | name=GhidraMCP
2 | description=A plugin that runs an embedded HTTP server to expose program data.
3 | author=LaurieWired
4 | createdOn=2025-03-22
5 | version=11.3.2
6 | ghidraVersion=11.3.2
```

--------------------------------------------------------------------------------
/src/test/java/com/lauriewired/AppTest.java:
--------------------------------------------------------------------------------

```java
 1 | package com.lauriewired;
 2 | 
 3 | import junit.framework.Test;
 4 | import junit.framework.TestCase;
 5 | import junit.framework.TestSuite;
 6 | 
 7 | /**
 8 |  * Unit test for simple App.
 9 |  */
10 | public class AppTest 
11 |     extends TestCase
12 | {
13 |     /**
14 |      * Create the test case
15 |      *
16 |      * @param testName name of the test case
17 |      */
18 |     public AppTest( String testName )
19 |     {
20 |         super( testName );
21 |     }
22 | 
23 |     /**
24 |      * @return the suite of tests being tested
25 |      */
26 |     public static Test suite()
27 |     {
28 |         return new TestSuite( AppTest.class );
29 |     }
30 | 
31 |     /**
32 |      * Rigourous Test :-)
33 |      */
34 |     public void testApp()
35 |     {
36 |         assertTrue( true );
37 |     }
38 | }
39 | 
```

--------------------------------------------------------------------------------
/src/assembly/ghidra-extension.xml:
--------------------------------------------------------------------------------

```
 1 | <assembly
 2 |     xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
 3 |     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 4 |     xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 
 5 |         http://maven.apache.org/xsd/assembly-1.1.3.xsd">
 6 |     
 7 |     <!-- Just a name for reference -->
 8 |     <id>ghidra-extension</id>
 9 |     
10 |     <!-- We want a .zip file -->
11 |     <formats>
12 |         <format>zip</format>
13 |     </formats>
14 |     
15 |     <!-- Don't put everything in an extra top-level directory named after the assembly ID -->
16 |     <includeBaseDirectory>false</includeBaseDirectory>
17 |     
18 |     <fileSets>
19 |         <!-- 1) Copy extension.properties and Module.manifest into the top level 
20 |                of a folder named GhidraMCP/ (the actual extension folder). -->
21 |         <fileSet>
22 |             <directory>src/main/resources</directory>
23 |             <includes>
24 |                 <include>extension.properties</include>
25 |                 <include>Module.manifest</include>
26 |             </includes>
27 |             <outputDirectory>GhidraMCP</outputDirectory>
28 |         </fileSet>
29 |         
30 |         <!-- 2) Copy your built plugin JAR into GhidraMCP/lib -->
31 |         <fileSet>
32 |             <directory>${project.build.directory}</directory>
33 |             <includes>
34 |                 <!-- Use the finalized JAR name from the maven-jar-plugin -->
35 |                 <include>GhidraMCP.jar</include>
36 |             </includes>
37 |             <outputDirectory>GhidraMCP/lib</outputDirectory>
38 |         </fileSet>
39 |     </fileSets>
40 | </assembly>
41 | 
```

--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Build with Maven
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ "main" ]
 6 |   pull_request:
 7 |     branches: [ "main" ]
 8 | 
 9 | jobs:
10 |   build:
11 |     runs-on: ubuntu-latest
12 |     env:
13 |       GHIDRA_VERSION: 11.3.2
14 |       GHIDRA_DATE: 20250415
15 |       GHIDRA_LIBS: >-
16 |         Features/Base/lib/Base.jar
17 |         Features/Decompiler/lib/Decompiler.jar
18 |         Framework/Docking/lib/Docking.jar
19 |         Framework/Generic/lib/Generic.jar
20 |         Framework/Project/lib/Project.jar
21 |         Framework/SoftwareModeling/lib/SoftwareModeling.jar
22 |         Framework/Utility/lib/Utility.jar
23 |         Framework/Gui/lib/Gui.jar
24 | 
25 |     steps:
26 |     - uses: actions/checkout@v4
27 |     - name: Set up JDK 21
28 |       uses: actions/setup-java@v4
29 |       with:
30 |         java-version: '21'
31 |         distribution: 'temurin'
32 |         cache: maven
33 | 
34 |     - name: Download Ghidra
35 |       run: |
36 |         wget --no-verbose -O ghidra.zip https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_${{ env.GHIDRA_VERSION }}_build/ghidra_${{ env.GHIDRA_VERSION }}_PUBLIC_${{ env.GHIDRA_DATE }}.zip
37 |         7z x -bd ghidra.zip
38 | 
39 |     - name: Copy Ghidra libs
40 |       run: |
41 |         mkdir -p ./lib
42 |         for libfile in ${{ env.GHIDRA_LIBS }}
43 |           do echo "Copying ${libfile} to lib/"
44 |           cp ghidra_${{ env.GHIDRA_VERSION }}_PUBLIC/Ghidra/${libfile} ./lib/
45 |         done
46 | 
47 |     - name: Build with Maven
48 |       run: mvn clean package assembly:single
49 | 
50 |     - name: Assemble release directory
51 |       run: |
52 |         mkdir release
53 |         cp target/GhidraMCP-*-SNAPSHOT.zip release/
54 |         cp bridge_mcp_ghidra.py release/
55 | 
56 |     - name: Upload artifact
57 |       uses: actions/upload-artifact@v4
58 |       with:
59 |         name: GhidraMCP-artifact
60 |         path: |
61 |           release/*
62 | 
```

--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------

```
  1 | <project xmlns="http://maven.apache.org/POM/4.0.0"
  2 |          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3 |          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  4 | 
  5 |   <modelVersion>4.0.0</modelVersion>
  6 |   <groupId>com.lauriewired</groupId>
  7 |   <artifactId>GhidraMCP</artifactId>
  8 |   <packaging>jar</packaging>
  9 |   <version>1.0-SNAPSHOT</version>
 10 |   <name>GhidraMCP</name>
 11 |   <url>http://maven.apache.org</url>
 12 | 
 13 |   <dependencies>
 14 |     <!-- Ghidra JARs as system-scoped dependencies -->
 15 |     <dependency>
 16 |       <groupId>ghidra</groupId>
 17 |       <artifactId>Generic</artifactId>
 18 |       <version>11.3.2</version>
 19 |       <scope>system</scope>
 20 |       <systemPath>${project.basedir}/lib/Generic.jar</systemPath>
 21 |     </dependency>
 22 |     <dependency>
 23 |       <groupId>ghidra</groupId>
 24 |       <artifactId>SoftwareModeling</artifactId>
 25 |       <version>11.3.2</version>
 26 |       <scope>system</scope>
 27 |       <systemPath>${project.basedir}/lib/SoftwareModeling.jar</systemPath>
 28 |     </dependency>
 29 |     <dependency>
 30 |       <groupId>ghidra</groupId>
 31 |       <artifactId>Project</artifactId>
 32 |       <version>11.3.2</version>
 33 |       <scope>system</scope>
 34 |       <systemPath>${project.basedir}/lib/Project.jar</systemPath>
 35 |     </dependency>
 36 |     <dependency>
 37 |       <groupId>ghidra</groupId>
 38 |       <artifactId>Docking</artifactId>
 39 |       <version>11.3.2</version>
 40 |       <scope>system</scope>
 41 |       <systemPath>${project.basedir}/lib/Docking.jar</systemPath>
 42 |     </dependency>
 43 |     <dependency>
 44 |       <groupId>ghidra</groupId>
 45 |       <artifactId>Decompiler</artifactId>
 46 |       <version>11.3.2</version>
 47 |       <scope>system</scope>
 48 |       <systemPath>${project.basedir}/lib/Decompiler.jar</systemPath>
 49 |     </dependency>
 50 |     <dependency>
 51 |       <groupId>ghidra</groupId>
 52 |       <artifactId>Utility</artifactId>
 53 |       <version>11.3.2</version>
 54 |       <scope>system</scope>
 55 |       <systemPath>${project.basedir}/lib/Utility.jar</systemPath>
 56 |     </dependency>
 57 |     <dependency>
 58 |       <groupId>ghidra</groupId>
 59 |       <artifactId>Base</artifactId>
 60 |       <version>11.3.2</version>
 61 |       <scope>system</scope>
 62 |       <systemPath>${project.basedir}/lib/Base.jar</systemPath>
 63 |     </dependency>
 64 |     <dependency>
 65 |       <groupId>ghidra</groupId>
 66 |       <artifactId>Gui</artifactId>
 67 |       <version>11.3.2</version>
 68 |       <scope>system</scope>
 69 |       <systemPath>${project.basedir}/lib/Gui.jar</systemPath>
 70 |     </dependency>
 71 | 
 72 |     <!-- JUnit (test only) -->
 73 |     <dependency>
 74 |       <groupId>junit</groupId>
 75 |       <artifactId>junit</artifactId>
 76 |       <version>3.8.1</version>
 77 |       <scope>test</scope>
 78 |     </dependency>
 79 |   </dependencies>
 80 | 
 81 |   <build>
 82 |     <plugins>
 83 |       <!-- Use custom MANIFEST.MF -->
 84 |       <plugin>
 85 |         <artifactId>maven-jar-plugin</artifactId>
 86 |         <version>3.2.2</version>
 87 |         <configuration>
 88 |           <archive>
 89 |             <manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile>
 90 |           </archive>
 91 |           <!-- Set a fixed name for the JAR without version -->
 92 |           <finalName>GhidraMCP</finalName>
 93 |           <!-- Exclude the App class -->
 94 |           <excludes>
 95 |             <exclude>**/App.class</exclude>
 96 |           </excludes>
 97 |           <!-- Make sure output directory is target for consistency -->
 98 |           <outputDirectory>${project.build.directory}</outputDirectory>
 99 |         </configuration>
100 |       </plugin>
101 |       
102 |       <!-- The Assembly Plugin for creating the Ghidra extension ZIP -->
103 |       <plugin>
104 |         <groupId>org.apache.maven.plugins</groupId>
105 |         <artifactId>maven-assembly-plugin</artifactId>
106 |         <version>3.3.0</version>
107 |         <configuration>
108 |           <!-- Using the custom assembly descriptor -->
109 |           <descriptors>
110 |             <descriptor>src/assembly/ghidra-extension.xml</descriptor>
111 |           </descriptors>
112 |           
113 |           <!-- The name of the final zip -->
114 |           <finalName>GhidraMCP-${project.version}</finalName>
115 |           
116 |           <!-- Don't append the assembly ID -->
117 |           <appendAssemblyId>false</appendAssemblyId>
118 |         </configuration>
119 |         
120 |         <executions>
121 |           <execution>
122 |             <id>make-assembly</id>
123 |             <phase>package</phase>
124 |             <goals>
125 |               <goal>single</goal>
126 |             </goals>
127 |           </execution>
128 |         </executions>
129 |       </plugin>
130 |       
131 |       <!-- Copy dependencies to target/lib for the assembly -->
132 |       <plugin>
133 |         <groupId>org.apache.maven.plugins</groupId>
134 |         <artifactId>maven-dependency-plugin</artifactId>
135 |         <version>3.1.2</version>
136 |         <executions>
137 |           <execution>
138 |             <id>copy-dependencies</id>
139 |             <phase>prepare-package</phase>
140 |             <goals>
141 |               <goal>copy-dependencies</goal>
142 |             </goals>
143 |             <configuration>
144 |               <outputDirectory>${project.build.directory}/lib</outputDirectory>
145 |               <includeScope>runtime</includeScope>
146 |             </configuration>
147 |           </execution>
148 |         </executions>
149 |       </plugin>
150 |     </plugins>
151 |   </build>
152 | </project>
153 | 
```

--------------------------------------------------------------------------------
/bridge_mcp_ghidra.py:
--------------------------------------------------------------------------------

```python
  1 | # /// script
  2 | # requires-python = ">=3.10"
  3 | # dependencies = [
  4 | #     "requests>=2,<3",
  5 | #     "mcp>=1.2.0,<2",
  6 | # ]
  7 | # ///
  8 | 
  9 | import sys
 10 | import requests
 11 | import argparse
 12 | import logging
 13 | from urllib.parse import urljoin
 14 | 
 15 | from mcp.server.fastmcp import FastMCP
 16 | 
 17 | DEFAULT_GHIDRA_SERVER = "http://127.0.0.1:8080/"
 18 | 
 19 | logger = logging.getLogger(__name__)
 20 | 
 21 | mcp = FastMCP("ghidra-mcp")
 22 | 
 23 | # Initialize ghidra_server_url with default value
 24 | ghidra_server_url = DEFAULT_GHIDRA_SERVER
 25 | 
 26 | def safe_get(endpoint: str, params: dict = None) -> list:
 27 |     """
 28 |     Perform a GET request with optional query parameters.
 29 |     """
 30 |     if params is None:
 31 |         params = {}
 32 | 
 33 |     url = urljoin(ghidra_server_url, endpoint)
 34 | 
 35 |     try:
 36 |         response = requests.get(url, params=params, timeout=5)
 37 |         response.encoding = 'utf-8'
 38 |         if response.ok:
 39 |             return response.text.splitlines()
 40 |         else:
 41 |             return [f"Error {response.status_code}: {response.text.strip()}"]
 42 |     except Exception as e:
 43 |         return [f"Request failed: {str(e)}"]
 44 | 
 45 | def safe_post(endpoint: str, data: dict | str) -> str:
 46 |     try:
 47 |         url = urljoin(ghidra_server_url, endpoint)
 48 |         if isinstance(data, dict):
 49 |             response = requests.post(url, data=data, timeout=5)
 50 |         else:
 51 |             response = requests.post(url, data=data.encode("utf-8"), timeout=5)
 52 |         response.encoding = 'utf-8'
 53 |         if response.ok:
 54 |             return response.text.strip()
 55 |         else:
 56 |             return f"Error {response.status_code}: {response.text.strip()}"
 57 |     except Exception as e:
 58 |         return f"Request failed: {str(e)}"
 59 | 
 60 | @mcp.tool()
 61 | def list_methods(offset: int = 0, limit: int = 100) -> list:
 62 |     """
 63 |     List all function names in the program with pagination.
 64 |     """
 65 |     return safe_get("methods", {"offset": offset, "limit": limit})
 66 | 
 67 | @mcp.tool()
 68 | def list_classes(offset: int = 0, limit: int = 100) -> list:
 69 |     """
 70 |     List all namespace/class names in the program with pagination.
 71 |     """
 72 |     return safe_get("classes", {"offset": offset, "limit": limit})
 73 | 
 74 | @mcp.tool()
 75 | def decompile_function(name: str) -> str:
 76 |     """
 77 |     Decompile a specific function by name and return the decompiled C code.
 78 |     """
 79 |     return safe_post("decompile", name)
 80 | 
 81 | @mcp.tool()
 82 | def rename_function(old_name: str, new_name: str) -> str:
 83 |     """
 84 |     Rename a function by its current name to a new user-defined name.
 85 |     """
 86 |     return safe_post("renameFunction", {"oldName": old_name, "newName": new_name})
 87 | 
 88 | @mcp.tool()
 89 | def rename_data(address: str, new_name: str) -> str:
 90 |     """
 91 |     Rename a data label at the specified address.
 92 |     """
 93 |     return safe_post("renameData", {"address": address, "newName": new_name})
 94 | 
 95 | @mcp.tool()
 96 | def list_segments(offset: int = 0, limit: int = 100) -> list:
 97 |     """
 98 |     List all memory segments in the program with pagination.
 99 |     """
100 |     return safe_get("segments", {"offset": offset, "limit": limit})
101 | 
102 | @mcp.tool()
103 | def list_imports(offset: int = 0, limit: int = 100) -> list:
104 |     """
105 |     List imported symbols in the program with pagination.
106 |     """
107 |     return safe_get("imports", {"offset": offset, "limit": limit})
108 | 
109 | @mcp.tool()
110 | def list_exports(offset: int = 0, limit: int = 100) -> list:
111 |     """
112 |     List exported functions/symbols with pagination.
113 |     """
114 |     return safe_get("exports", {"offset": offset, "limit": limit})
115 | 
116 | @mcp.tool()
117 | def list_namespaces(offset: int = 0, limit: int = 100) -> list:
118 |     """
119 |     List all non-global namespaces in the program with pagination.
120 |     """
121 |     return safe_get("namespaces", {"offset": offset, "limit": limit})
122 | 
123 | @mcp.tool()
124 | def list_data_items(offset: int = 0, limit: int = 100) -> list:
125 |     """
126 |     List defined data labels and their values with pagination.
127 |     """
128 |     return safe_get("data", {"offset": offset, "limit": limit})
129 | 
130 | @mcp.tool()
131 | def search_functions_by_name(query: str, offset: int = 0, limit: int = 100) -> list:
132 |     """
133 |     Search for functions whose name contains the given substring.
134 |     """
135 |     if not query:
136 |         return ["Error: query string is required"]
137 |     return safe_get("searchFunctions", {"query": query, "offset": offset, "limit": limit})
138 | 
139 | @mcp.tool()
140 | def rename_variable(function_name: str, old_name: str, new_name: str) -> str:
141 |     """
142 |     Rename a local variable within a function.
143 |     """
144 |     return safe_post("renameVariable", {
145 |         "functionName": function_name,
146 |         "oldName": old_name,
147 |         "newName": new_name
148 |     })
149 | 
150 | @mcp.tool()
151 | def get_function_by_address(address: str) -> str:
152 |     """
153 |     Get a function by its address.
154 |     """
155 |     return "\n".join(safe_get("get_function_by_address", {"address": address}))
156 | 
157 | @mcp.tool()
158 | def get_current_address() -> str:
159 |     """
160 |     Get the address currently selected by the user.
161 |     """
162 |     return "\n".join(safe_get("get_current_address"))
163 | 
164 | @mcp.tool()
165 | def get_current_function() -> str:
166 |     """
167 |     Get the function currently selected by the user.
168 |     """
169 |     return "\n".join(safe_get("get_current_function"))
170 | 
171 | @mcp.tool()
172 | def list_functions() -> list:
173 |     """
174 |     List all functions in the database.
175 |     """
176 |     return safe_get("list_functions")
177 | 
178 | @mcp.tool()
179 | def decompile_function_by_address(address: str) -> str:
180 |     """
181 |     Decompile a function at the given address.
182 |     """
183 |     return "\n".join(safe_get("decompile_function", {"address": address}))
184 | 
185 | @mcp.tool()
186 | def disassemble_function(address: str) -> list:
187 |     """
188 |     Get assembly code (address: instruction; comment) for a function.
189 |     """
190 |     return safe_get("disassemble_function", {"address": address})
191 | 
192 | @mcp.tool()
193 | def set_decompiler_comment(address: str, comment: str) -> str:
194 |     """
195 |     Set a comment for a given address in the function pseudocode.
196 |     """
197 |     return safe_post("set_decompiler_comment", {"address": address, "comment": comment})
198 | 
199 | @mcp.tool()
200 | def set_disassembly_comment(address: str, comment: str) -> str:
201 |     """
202 |     Set a comment for a given address in the function disassembly.
203 |     """
204 |     return safe_post("set_disassembly_comment", {"address": address, "comment": comment})
205 | 
206 | @mcp.tool()
207 | def rename_function_by_address(function_address: str, new_name: str) -> str:
208 |     """
209 |     Rename a function by its address.
210 |     """
211 |     return safe_post("rename_function_by_address", {"function_address": function_address, "new_name": new_name})
212 | 
213 | @mcp.tool()
214 | def set_function_prototype(function_address: str, prototype: str) -> str:
215 |     """
216 |     Set a function's prototype.
217 |     """
218 |     return safe_post("set_function_prototype", {"function_address": function_address, "prototype": prototype})
219 | 
220 | @mcp.tool()
221 | def set_local_variable_type(function_address: str, variable_name: str, new_type: str) -> str:
222 |     """
223 |     Set a local variable's type.
224 |     """
225 |     return safe_post("set_local_variable_type", {"function_address": function_address, "variable_name": variable_name, "new_type": new_type})
226 | 
227 | @mcp.tool()
228 | def get_xrefs_to(address: str, offset: int = 0, limit: int = 100) -> list:
229 |     """
230 |     Get all references to the specified address (xref to).
231 |     
232 |     Args:
233 |         address: Target address in hex format (e.g. "0x1400010a0")
234 |         offset: Pagination offset (default: 0)
235 |         limit: Maximum number of references to return (default: 100)
236 |         
237 |     Returns:
238 |         List of references to the specified address
239 |     """
240 |     return safe_get("xrefs_to", {"address": address, "offset": offset, "limit": limit})
241 | 
242 | @mcp.tool()
243 | def get_xrefs_from(address: str, offset: int = 0, limit: int = 100) -> list:
244 |     """
245 |     Get all references from the specified address (xref from).
246 |     
247 |     Args:
248 |         address: Source address in hex format (e.g. "0x1400010a0")
249 |         offset: Pagination offset (default: 0)
250 |         limit: Maximum number of references to return (default: 100)
251 |         
252 |     Returns:
253 |         List of references from the specified address
254 |     """
255 |     return safe_get("xrefs_from", {"address": address, "offset": offset, "limit": limit})
256 | 
257 | @mcp.tool()
258 | def get_function_xrefs(name: str, offset: int = 0, limit: int = 100) -> list:
259 |     """
260 |     Get all references to the specified function by name.
261 |     
262 |     Args:
263 |         name: Function name to search for
264 |         offset: Pagination offset (default: 0)
265 |         limit: Maximum number of references to return (default: 100)
266 |         
267 |     Returns:
268 |         List of references to the specified function
269 |     """
270 |     return safe_get("function_xrefs", {"name": name, "offset": offset, "limit": limit})
271 | 
272 | @mcp.tool()
273 | def list_strings(offset: int = 0, limit: int = 2000, filter: str = None) -> list:
274 |     """
275 |     List all defined strings in the program with their addresses.
276 |     
277 |     Args:
278 |         offset: Pagination offset (default: 0)
279 |         limit: Maximum number of strings to return (default: 2000)
280 |         filter: Optional filter to match within string content
281 |         
282 |     Returns:
283 |         List of strings with their addresses
284 |     """
285 |     params = {"offset": offset, "limit": limit}
286 |     if filter:
287 |         params["filter"] = filter
288 |     return safe_get("strings", params)
289 | 
290 | def main():
291 |     parser = argparse.ArgumentParser(description="MCP server for Ghidra")
292 |     parser.add_argument("--ghidra-server", type=str, default=DEFAULT_GHIDRA_SERVER,
293 |                         help=f"Ghidra server URL, default: {DEFAULT_GHIDRA_SERVER}")
294 |     parser.add_argument("--mcp-host", type=str, default="127.0.0.1",
295 |                         help="Host to run MCP server on (only used for sse), default: 127.0.0.1")
296 |     parser.add_argument("--mcp-port", type=int,
297 |                         help="Port to run MCP server on (only used for sse), default: 8081")
298 |     parser.add_argument("--transport", type=str, default="stdio", choices=["stdio", "sse"],
299 |                         help="Transport protocol for MCP, default: stdio")
300 |     args = parser.parse_args()
301 |     
302 |     # Use the global variable to ensure it's properly updated
303 |     global ghidra_server_url
304 |     if args.ghidra_server:
305 |         ghidra_server_url = args.ghidra_server
306 |     
307 |     if args.transport == "sse":
308 |         try:
309 |             # Set up logging
310 |             log_level = logging.INFO
311 |             logging.basicConfig(level=log_level)
312 |             logging.getLogger().setLevel(log_level)
313 | 
314 |             # Configure MCP settings
315 |             mcp.settings.log_level = "INFO"
316 |             if args.mcp_host:
317 |                 mcp.settings.host = args.mcp_host
318 |             else:
319 |                 mcp.settings.host = "127.0.0.1"
320 | 
321 |             if args.mcp_port:
322 |                 mcp.settings.port = args.mcp_port
323 |             else:
324 |                 mcp.settings.port = 8081
325 | 
326 |             logger.info(f"Connecting to Ghidra server at {ghidra_server_url}")
327 |             logger.info(f"Starting MCP server on http://{mcp.settings.host}:{mcp.settings.port}/sse")
328 |             logger.info(f"Using transport: {args.transport}")
329 | 
330 |             mcp.run(transport="sse")
331 |         except KeyboardInterrupt:
332 |             logger.info("Server stopped by user")
333 |     else:
334 |         mcp.run()
335 |         
336 | if __name__ == "__main__":
337 |     main()
338 | 
339 | 
```

--------------------------------------------------------------------------------
/src/main/java/com/lauriewired/GhidraMCPPlugin.java:
--------------------------------------------------------------------------------

```java
   1 | package com.lauriewired;
   2 | 
   3 | import ghidra.framework.plugintool.Plugin;
   4 | import ghidra.framework.plugintool.PluginTool;
   5 | import ghidra.program.model.address.Address;
   6 | import ghidra.program.model.address.GlobalNamespace;
   7 | import ghidra.program.model.listing.*;
   8 | import ghidra.program.model.mem.MemoryBlock;
   9 | import ghidra.program.model.symbol.*;
  10 | import ghidra.program.model.symbol.ReferenceManager;
  11 | import ghidra.program.model.symbol.Reference;
  12 | import ghidra.program.model.symbol.ReferenceIterator;
  13 | import ghidra.program.model.symbol.RefType;
  14 | import ghidra.program.model.pcode.HighFunction;
  15 | import ghidra.program.model.pcode.HighSymbol;
  16 | import ghidra.program.model.pcode.LocalSymbolMap;
  17 | import ghidra.program.model.pcode.HighFunctionDBUtil;
  18 | import ghidra.program.model.pcode.HighFunctionDBUtil.ReturnCommitOption;
  19 | import ghidra.app.decompiler.DecompInterface;
  20 | import ghidra.app.decompiler.DecompileResults;
  21 | import ghidra.app.plugin.PluginCategoryNames;
  22 | import ghidra.app.services.CodeViewerService;
  23 | import ghidra.app.services.ProgramManager;
  24 | import ghidra.app.util.PseudoDisassembler;
  25 | import ghidra.app.cmd.function.SetVariableNameCmd;
  26 | import ghidra.program.model.symbol.SourceType;
  27 | import ghidra.program.model.listing.LocalVariableImpl;
  28 | import ghidra.program.model.listing.ParameterImpl;
  29 | import ghidra.util.exception.DuplicateNameException;
  30 | import ghidra.util.exception.InvalidInputException;
  31 | import ghidra.framework.plugintool.PluginInfo;
  32 | import ghidra.framework.plugintool.util.PluginStatus;
  33 | import ghidra.program.util.ProgramLocation;
  34 | import ghidra.util.Msg;
  35 | import ghidra.util.task.ConsoleTaskMonitor;
  36 | import ghidra.util.task.TaskMonitor;
  37 | import ghidra.program.model.pcode.HighVariable;
  38 | import ghidra.program.model.pcode.Varnode;
  39 | import ghidra.program.model.data.DataType;
  40 | import ghidra.program.model.data.DataTypeManager;
  41 | import ghidra.program.model.data.PointerDataType;
  42 | import ghidra.program.model.data.Undefined1DataType;
  43 | import ghidra.program.model.listing.Variable;
  44 | import ghidra.app.decompiler.component.DecompilerUtils;
  45 | import ghidra.app.decompiler.ClangToken;
  46 | import ghidra.framework.options.Options;
  47 | 
  48 | import com.sun.net.httpserver.HttpExchange;
  49 | import com.sun.net.httpserver.HttpServer;
  50 | 
  51 | import javax.swing.SwingUtilities;
  52 | import java.io.IOException;
  53 | import java.io.OutputStream;
  54 | import java.lang.reflect.InvocationTargetException;
  55 | import java.net.InetSocketAddress;
  56 | import java.net.URLDecoder;
  57 | import java.nio.charset.StandardCharsets;
  58 | import java.util.*;
  59 | import java.util.concurrent.atomic.AtomicBoolean;
  60 | 
  61 | @PluginInfo(
  62 |     status = PluginStatus.RELEASED,
  63 |     packageName = ghidra.app.DeveloperPluginPackage.NAME,
  64 |     category = PluginCategoryNames.ANALYSIS,
  65 |     shortDescription = "HTTP server plugin",
  66 |     description = "Starts an embedded HTTP server to expose program data. Port configurable via Tool Options."
  67 | )
  68 | public class GhidraMCPPlugin extends Plugin {
  69 | 
  70 |     private HttpServer server;
  71 |     private static final String OPTION_CATEGORY_NAME = "GhidraMCP HTTP Server";
  72 |     private static final String PORT_OPTION_NAME = "Server Port";
  73 |     private static final int DEFAULT_PORT = 8080;
  74 | 
  75 |     public GhidraMCPPlugin(PluginTool tool) {
  76 |         super(tool);
  77 |         Msg.info(this, "GhidraMCPPlugin loading...");
  78 | 
  79 |         // Register the configuration option
  80 |         Options options = tool.getOptions(OPTION_CATEGORY_NAME);
  81 |         options.registerOption(PORT_OPTION_NAME, DEFAULT_PORT,
  82 |             null, // No help location for now
  83 |             "The network port number the embedded HTTP server will listen on. " +
  84 |             "Requires Ghidra restart or plugin reload to take effect after changing.");
  85 | 
  86 |         try {
  87 |             startServer();
  88 |         }
  89 |         catch (IOException e) {
  90 |             Msg.error(this, "Failed to start HTTP server", e);
  91 |         }
  92 |         Msg.info(this, "GhidraMCPPlugin loaded!");
  93 |     }
  94 | 
  95 |     private void startServer() throws IOException {
  96 |         // Read the configured port
  97 |         Options options = tool.getOptions(OPTION_CATEGORY_NAME);
  98 |         int port = options.getInt(PORT_OPTION_NAME, DEFAULT_PORT);
  99 | 
 100 |         // Stop existing server if running (e.g., if plugin is reloaded)
 101 |         if (server != null) {
 102 |             Msg.info(this, "Stopping existing HTTP server before starting new one.");
 103 |             server.stop(0);
 104 |             server = null;
 105 |         }
 106 | 
 107 |         server = HttpServer.create(new InetSocketAddress(port), 0);
 108 | 
 109 |         // Each listing endpoint uses offset & limit from query params:
 110 |         server.createContext("/methods", exchange -> {
 111 |             Map<String, String> qparams = parseQueryParams(exchange);
 112 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 113 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 114 |             sendResponse(exchange, getAllFunctionNames(offset, limit));
 115 |         });
 116 | 
 117 |         server.createContext("/classes", exchange -> {
 118 |             Map<String, String> qparams = parseQueryParams(exchange);
 119 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 120 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 121 |             sendResponse(exchange, getAllClassNames(offset, limit));
 122 |         });
 123 | 
 124 |         server.createContext("/decompile", exchange -> {
 125 |             String name = new String(exchange.getRequestBody().readAllBytes(), StandardCharsets.UTF_8);
 126 |             sendResponse(exchange, decompileFunctionByName(name));
 127 |         });
 128 | 
 129 |         server.createContext("/renameFunction", exchange -> {
 130 |             Map<String, String> params = parsePostParams(exchange);
 131 |             String response = renameFunction(params.get("oldName"), params.get("newName"))
 132 |                     ? "Renamed successfully" : "Rename failed";
 133 |             sendResponse(exchange, response);
 134 |         });
 135 | 
 136 |         server.createContext("/renameData", exchange -> {
 137 |             Map<String, String> params = parsePostParams(exchange);
 138 |             renameDataAtAddress(params.get("address"), params.get("newName"));
 139 |             sendResponse(exchange, "Rename data attempted");
 140 |         });
 141 | 
 142 |         server.createContext("/renameVariable", exchange -> {
 143 |             Map<String, String> params = parsePostParams(exchange);
 144 |             String functionName = params.get("functionName");
 145 |             String oldName = params.get("oldName");
 146 |             String newName = params.get("newName");
 147 |             String result = renameVariableInFunction(functionName, oldName, newName);
 148 |             sendResponse(exchange, result);
 149 |         });
 150 | 
 151 |         server.createContext("/segments", exchange -> {
 152 |             Map<String, String> qparams = parseQueryParams(exchange);
 153 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 154 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 155 |             sendResponse(exchange, listSegments(offset, limit));
 156 |         });
 157 | 
 158 |         server.createContext("/imports", exchange -> {
 159 |             Map<String, String> qparams = parseQueryParams(exchange);
 160 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 161 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 162 |             sendResponse(exchange, listImports(offset, limit));
 163 |         });
 164 | 
 165 |         server.createContext("/exports", exchange -> {
 166 |             Map<String, String> qparams = parseQueryParams(exchange);
 167 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 168 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 169 |             sendResponse(exchange, listExports(offset, limit));
 170 |         });
 171 | 
 172 |         server.createContext("/namespaces", exchange -> {
 173 |             Map<String, String> qparams = parseQueryParams(exchange);
 174 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 175 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 176 |             sendResponse(exchange, listNamespaces(offset, limit));
 177 |         });
 178 | 
 179 |         server.createContext("/data", exchange -> {
 180 |             Map<String, String> qparams = parseQueryParams(exchange);
 181 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 182 |             int limit  = parseIntOrDefault(qparams.get("limit"),  100);
 183 |             sendResponse(exchange, listDefinedData(offset, limit));
 184 |         });
 185 | 
 186 |         server.createContext("/searchFunctions", exchange -> {
 187 |             Map<String, String> qparams = parseQueryParams(exchange);
 188 |             String searchTerm = qparams.get("query");
 189 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 190 |             int limit = parseIntOrDefault(qparams.get("limit"), 100);
 191 |             sendResponse(exchange, searchFunctionsByName(searchTerm, offset, limit));
 192 |         });
 193 | 
 194 |         // New API endpoints based on requirements
 195 |         
 196 |         server.createContext("/get_function_by_address", exchange -> {
 197 |             Map<String, String> qparams = parseQueryParams(exchange);
 198 |             String address = qparams.get("address");
 199 |             sendResponse(exchange, getFunctionByAddress(address));
 200 |         });
 201 | 
 202 |         server.createContext("/get_current_address", exchange -> {
 203 |             sendResponse(exchange, getCurrentAddress());
 204 |         });
 205 | 
 206 |         server.createContext("/get_current_function", exchange -> {
 207 |             sendResponse(exchange, getCurrentFunction());
 208 |         });
 209 | 
 210 |         server.createContext("/list_functions", exchange -> {
 211 |             sendResponse(exchange, listFunctions());
 212 |         });
 213 | 
 214 |         server.createContext("/decompile_function", exchange -> {
 215 |             Map<String, String> qparams = parseQueryParams(exchange);
 216 |             String address = qparams.get("address");
 217 |             sendResponse(exchange, decompileFunctionByAddress(address));
 218 |         });
 219 | 
 220 |         server.createContext("/disassemble_function", exchange -> {
 221 |             Map<String, String> qparams = parseQueryParams(exchange);
 222 |             String address = qparams.get("address");
 223 |             sendResponse(exchange, disassembleFunction(address));
 224 |         });
 225 | 
 226 |         server.createContext("/set_decompiler_comment", exchange -> {
 227 |             Map<String, String> params = parsePostParams(exchange);
 228 |             String address = params.get("address");
 229 |             String comment = params.get("comment");
 230 |             boolean success = setDecompilerComment(address, comment);
 231 |             sendResponse(exchange, success ? "Comment set successfully" : "Failed to set comment");
 232 |         });
 233 | 
 234 |         server.createContext("/set_disassembly_comment", exchange -> {
 235 |             Map<String, String> params = parsePostParams(exchange);
 236 |             String address = params.get("address");
 237 |             String comment = params.get("comment");
 238 |             boolean success = setDisassemblyComment(address, comment);
 239 |             sendResponse(exchange, success ? "Comment set successfully" : "Failed to set comment");
 240 |         });
 241 | 
 242 |         server.createContext("/rename_function_by_address", exchange -> {
 243 |             Map<String, String> params = parsePostParams(exchange);
 244 |             String functionAddress = params.get("function_address");
 245 |             String newName = params.get("new_name");
 246 |             boolean success = renameFunctionByAddress(functionAddress, newName);
 247 |             sendResponse(exchange, success ? "Function renamed successfully" : "Failed to rename function");
 248 |         });
 249 | 
 250 |         server.createContext("/set_function_prototype", exchange -> {
 251 |             Map<String, String> params = parsePostParams(exchange);
 252 |             String functionAddress = params.get("function_address");
 253 |             String prototype = params.get("prototype");
 254 | 
 255 |             // Call the set prototype function and get detailed result
 256 |             PrototypeResult result = setFunctionPrototype(functionAddress, prototype);
 257 | 
 258 |             if (result.isSuccess()) {
 259 |                 // Even with successful operations, include any warning messages for debugging
 260 |                 String successMsg = "Function prototype set successfully";
 261 |                 if (!result.getErrorMessage().isEmpty()) {
 262 |                     successMsg += "\n\nWarnings/Debug Info:\n" + result.getErrorMessage();
 263 |                 }
 264 |                 sendResponse(exchange, successMsg);
 265 |             } else {
 266 |                 // Return the detailed error message to the client
 267 |                 sendResponse(exchange, "Failed to set function prototype: " + result.getErrorMessage());
 268 |             }
 269 |         });
 270 | 
 271 |         server.createContext("/set_local_variable_type", exchange -> {
 272 |             Map<String, String> params = parsePostParams(exchange);
 273 |             String functionAddress = params.get("function_address");
 274 |             String variableName = params.get("variable_name");
 275 |             String newType = params.get("new_type");
 276 | 
 277 |             // Capture detailed information about setting the type
 278 |             StringBuilder responseMsg = new StringBuilder();
 279 |             responseMsg.append("Setting variable type: ").append(variableName)
 280 |                       .append(" to ").append(newType)
 281 |                       .append(" in function at ").append(functionAddress).append("\n\n");
 282 | 
 283 |             // Attempt to find the data type in various categories
 284 |             Program program = getCurrentProgram();
 285 |             if (program != null) {
 286 |                 DataTypeManager dtm = program.getDataTypeManager();
 287 |                 DataType directType = findDataTypeByNameInAllCategories(dtm, newType);
 288 |                 if (directType != null) {
 289 |                     responseMsg.append("Found type: ").append(directType.getPathName()).append("\n");
 290 |                 } else if (newType.startsWith("P") && newType.length() > 1) {
 291 |                     String baseTypeName = newType.substring(1);
 292 |                     DataType baseType = findDataTypeByNameInAllCategories(dtm, baseTypeName);
 293 |                     if (baseType != null) {
 294 |                         responseMsg.append("Found base type for pointer: ").append(baseType.getPathName()).append("\n");
 295 |                     } else {
 296 |                         responseMsg.append("Base type not found for pointer: ").append(baseTypeName).append("\n");
 297 |                     }
 298 |                 } else {
 299 |                     responseMsg.append("Type not found directly: ").append(newType).append("\n");
 300 |                 }
 301 |             }
 302 | 
 303 |             // Try to set the type
 304 |             boolean success = setLocalVariableType(functionAddress, variableName, newType);
 305 | 
 306 |             String successMsg = success ? "Variable type set successfully" : "Failed to set variable type";
 307 |             responseMsg.append("\nResult: ").append(successMsg);
 308 | 
 309 |             sendResponse(exchange, responseMsg.toString());
 310 |         });
 311 | 
 312 |         server.createContext("/xrefs_to", exchange -> {
 313 |             Map<String, String> qparams = parseQueryParams(exchange);
 314 |             String address = qparams.get("address");
 315 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 316 |             int limit = parseIntOrDefault(qparams.get("limit"), 100);
 317 |             sendResponse(exchange, getXrefsTo(address, offset, limit));
 318 |         });
 319 | 
 320 |         server.createContext("/xrefs_from", exchange -> {
 321 |             Map<String, String> qparams = parseQueryParams(exchange);
 322 |             String address = qparams.get("address");
 323 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 324 |             int limit = parseIntOrDefault(qparams.get("limit"), 100);
 325 |             sendResponse(exchange, getXrefsFrom(address, offset, limit));
 326 |         });
 327 | 
 328 |         server.createContext("/function_xrefs", exchange -> {
 329 |             Map<String, String> qparams = parseQueryParams(exchange);
 330 |             String name = qparams.get("name");
 331 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 332 |             int limit = parseIntOrDefault(qparams.get("limit"), 100);
 333 |             sendResponse(exchange, getFunctionXrefs(name, offset, limit));
 334 |         });
 335 | 
 336 |         server.createContext("/strings", exchange -> {
 337 |             Map<String, String> qparams = parseQueryParams(exchange);
 338 |             int offset = parseIntOrDefault(qparams.get("offset"), 0);
 339 |             int limit = parseIntOrDefault(qparams.get("limit"), 100);
 340 |             String filter = qparams.get("filter");
 341 |             sendResponse(exchange, listDefinedStrings(offset, limit, filter));
 342 |         });
 343 | 
 344 |         server.setExecutor(null);
 345 |         new Thread(() -> {
 346 |             try {
 347 |                 server.start();
 348 |                 Msg.info(this, "GhidraMCP HTTP server started on port " + port);
 349 |             } catch (Exception e) {
 350 |                 Msg.error(this, "Failed to start HTTP server on port " + port + ". Port might be in use.", e);
 351 |                 server = null; // Ensure server isn't considered running
 352 |             }
 353 |         }, "GhidraMCP-HTTP-Server").start();
 354 |     }
 355 | 
 356 |     // ----------------------------------------------------------------------------------
 357 |     // Pagination-aware listing methods
 358 |     // ----------------------------------------------------------------------------------
 359 | 
 360 |     private String getAllFunctionNames(int offset, int limit) {
 361 |         Program program = getCurrentProgram();
 362 |         if (program == null) return "No program loaded";
 363 | 
 364 |         List<String> names = new ArrayList<>();
 365 |         for (Function f : program.getFunctionManager().getFunctions(true)) {
 366 |             names.add(f.getName());
 367 |         }
 368 |         return paginateList(names, offset, limit);
 369 |     }
 370 | 
 371 |     private String getAllClassNames(int offset, int limit) {
 372 |         Program program = getCurrentProgram();
 373 |         if (program == null) return "No program loaded";
 374 | 
 375 |         Set<String> classNames = new HashSet<>();
 376 |         for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
 377 |             Namespace ns = symbol.getParentNamespace();
 378 |             if (ns != null && !ns.isGlobal()) {
 379 |                 classNames.add(ns.getName());
 380 |             }
 381 |         }
 382 |         // Convert set to list for pagination
 383 |         List<String> sorted = new ArrayList<>(classNames);
 384 |         Collections.sort(sorted);
 385 |         return paginateList(sorted, offset, limit);
 386 |     }
 387 | 
 388 |     private String listSegments(int offset, int limit) {
 389 |         Program program = getCurrentProgram();
 390 |         if (program == null) return "No program loaded";
 391 | 
 392 |         List<String> lines = new ArrayList<>();
 393 |         for (MemoryBlock block : program.getMemory().getBlocks()) {
 394 |             lines.add(String.format("%s: %s - %s", block.getName(), block.getStart(), block.getEnd()));
 395 |         }
 396 |         return paginateList(lines, offset, limit);
 397 |     }
 398 | 
 399 |     private String listImports(int offset, int limit) {
 400 |         Program program = getCurrentProgram();
 401 |         if (program == null) return "No program loaded";
 402 | 
 403 |         List<String> lines = new ArrayList<>();
 404 |         for (Symbol symbol : program.getSymbolTable().getExternalSymbols()) {
 405 |             lines.add(symbol.getName() + " -> " + symbol.getAddress());
 406 |         }
 407 |         return paginateList(lines, offset, limit);
 408 |     }
 409 | 
 410 |     private String listExports(int offset, int limit) {
 411 |         Program program = getCurrentProgram();
 412 |         if (program == null) return "No program loaded";
 413 | 
 414 |         SymbolTable table = program.getSymbolTable();
 415 |         SymbolIterator it = table.getAllSymbols(true);
 416 | 
 417 |         List<String> lines = new ArrayList<>();
 418 |         while (it.hasNext()) {
 419 |             Symbol s = it.next();
 420 |             // On older Ghidra, "export" is recognized via isExternalEntryPoint()
 421 |             if (s.isExternalEntryPoint()) {
 422 |                 lines.add(s.getName() + " -> " + s.getAddress());
 423 |             }
 424 |         }
 425 |         return paginateList(lines, offset, limit);
 426 |     }
 427 | 
 428 |     private String listNamespaces(int offset, int limit) {
 429 |         Program program = getCurrentProgram();
 430 |         if (program == null) return "No program loaded";
 431 | 
 432 |         Set<String> namespaces = new HashSet<>();
 433 |         for (Symbol symbol : program.getSymbolTable().getAllSymbols(true)) {
 434 |             Namespace ns = symbol.getParentNamespace();
 435 |             if (ns != null && !(ns instanceof GlobalNamespace)) {
 436 |                 namespaces.add(ns.getName());
 437 |             }
 438 |         }
 439 |         List<String> sorted = new ArrayList<>(namespaces);
 440 |         Collections.sort(sorted);
 441 |         return paginateList(sorted, offset, limit);
 442 |     }
 443 | 
 444 |     private String listDefinedData(int offset, int limit) {
 445 |         Program program = getCurrentProgram();
 446 |         if (program == null) return "No program loaded";
 447 | 
 448 |         List<String> lines = new ArrayList<>();
 449 |         for (MemoryBlock block : program.getMemory().getBlocks()) {
 450 |             DataIterator it = program.getListing().getDefinedData(block.getStart(), true);
 451 |             while (it.hasNext()) {
 452 |                 Data data = it.next();
 453 |                 if (block.contains(data.getAddress())) {
 454 |                     String label   = data.getLabel() != null ? data.getLabel() : "(unnamed)";
 455 |                     String valRepr = data.getDefaultValueRepresentation();
 456 |                     lines.add(String.format("%s: %s = %s",
 457 |                         data.getAddress(),
 458 |                         escapeNonAscii(label),
 459 |                         escapeNonAscii(valRepr)
 460 |                     ));
 461 |                 }
 462 |             }
 463 |         }
 464 |         return paginateList(lines, offset, limit);
 465 |     }
 466 | 
 467 |     private String searchFunctionsByName(String searchTerm, int offset, int limit) {
 468 |         Program program = getCurrentProgram();
 469 |         if (program == null) return "No program loaded";
 470 |         if (searchTerm == null || searchTerm.isEmpty()) return "Search term is required";
 471 |     
 472 |         List<String> matches = new ArrayList<>();
 473 |         for (Function func : program.getFunctionManager().getFunctions(true)) {
 474 |             String name = func.getName();
 475 |             // simple substring match
 476 |             if (name.toLowerCase().contains(searchTerm.toLowerCase())) {
 477 |                 matches.add(String.format("%s @ %s", name, func.getEntryPoint()));
 478 |             }
 479 |         }
 480 |     
 481 |         Collections.sort(matches);
 482 |     
 483 |         if (matches.isEmpty()) {
 484 |             return "No functions matching '" + searchTerm + "'";
 485 |         }
 486 |         return paginateList(matches, offset, limit);
 487 |     }    
 488 | 
 489 |     // ----------------------------------------------------------------------------------
 490 |     // Logic for rename, decompile, etc.
 491 |     // ----------------------------------------------------------------------------------
 492 | 
 493 |     private String decompileFunctionByName(String name) {
 494 |         Program program = getCurrentProgram();
 495 |         if (program == null) return "No program loaded";
 496 |         DecompInterface decomp = new DecompInterface();
 497 |         decomp.openProgram(program);
 498 |         for (Function func : program.getFunctionManager().getFunctions(true)) {
 499 |             if (func.getName().equals(name)) {
 500 |                 DecompileResults result =
 501 |                     decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
 502 |                 if (result != null && result.decompileCompleted()) {
 503 |                     return result.getDecompiledFunction().getC();
 504 |                 } else {
 505 |                     return "Decompilation failed";
 506 |                 }
 507 |             }
 508 |         }
 509 |         return "Function not found";
 510 |     }
 511 | 
 512 |     private boolean renameFunction(String oldName, String newName) {
 513 |         Program program = getCurrentProgram();
 514 |         if (program == null) return false;
 515 | 
 516 |         AtomicBoolean successFlag = new AtomicBoolean(false);
 517 |         try {
 518 |             SwingUtilities.invokeAndWait(() -> {
 519 |                 int tx = program.startTransaction("Rename function via HTTP");
 520 |                 try {
 521 |                     for (Function func : program.getFunctionManager().getFunctions(true)) {
 522 |                         if (func.getName().equals(oldName)) {
 523 |                             func.setName(newName, SourceType.USER_DEFINED);
 524 |                             successFlag.set(true);
 525 |                             break;
 526 |                         }
 527 |                     }
 528 |                 }
 529 |                 catch (Exception e) {
 530 |                     Msg.error(this, "Error renaming function", e);
 531 |                 }
 532 |                 finally {
 533 |                     successFlag.set(program.endTransaction(tx, successFlag.get()));
 534 |                 }
 535 |             });
 536 |         }
 537 |         catch (InterruptedException | InvocationTargetException e) {
 538 |             Msg.error(this, "Failed to execute rename on Swing thread", e);
 539 |         }
 540 |         return successFlag.get();
 541 |     }
 542 | 
 543 |     private void renameDataAtAddress(String addressStr, String newName) {
 544 |         Program program = getCurrentProgram();
 545 |         if (program == null) return;
 546 | 
 547 |         try {
 548 |             SwingUtilities.invokeAndWait(() -> {
 549 |                 int tx = program.startTransaction("Rename data");
 550 |                 try {
 551 |                     Address addr = program.getAddressFactory().getAddress(addressStr);
 552 |                     Listing listing = program.getListing();
 553 |                     Data data = listing.getDefinedDataAt(addr);
 554 |                     if (data != null) {
 555 |                         SymbolTable symTable = program.getSymbolTable();
 556 |                         Symbol symbol = symTable.getPrimarySymbol(addr);
 557 |                         if (symbol != null) {
 558 |                             symbol.setName(newName, SourceType.USER_DEFINED);
 559 |                         } else {
 560 |                             symTable.createLabel(addr, newName, SourceType.USER_DEFINED);
 561 |                         }
 562 |                     }
 563 |                 }
 564 |                 catch (Exception e) {
 565 |                     Msg.error(this, "Rename data error", e);
 566 |                 }
 567 |                 finally {
 568 |                     program.endTransaction(tx, true);
 569 |                 }
 570 |             });
 571 |         }
 572 |         catch (InterruptedException | InvocationTargetException e) {
 573 |             Msg.error(this, "Failed to execute rename data on Swing thread", e);
 574 |         }
 575 |     }
 576 | 
 577 |     private String renameVariableInFunction(String functionName, String oldVarName, String newVarName) {
 578 |         Program program = getCurrentProgram();
 579 |         if (program == null) return "No program loaded";
 580 | 
 581 |         DecompInterface decomp = new DecompInterface();
 582 |         decomp.openProgram(program);
 583 | 
 584 |         Function func = null;
 585 |         for (Function f : program.getFunctionManager().getFunctions(true)) {
 586 |             if (f.getName().equals(functionName)) {
 587 |                 func = f;
 588 |                 break;
 589 |             }
 590 |         }
 591 | 
 592 |         if (func == null) {
 593 |             return "Function not found";
 594 |         }
 595 | 
 596 |         DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
 597 |         if (result == null || !result.decompileCompleted()) {
 598 |             return "Decompilation failed";
 599 |         }
 600 | 
 601 |         HighFunction highFunction = result.getHighFunction();
 602 |         if (highFunction == null) {
 603 |             return "Decompilation failed (no high function)";
 604 |         }
 605 | 
 606 |         LocalSymbolMap localSymbolMap = highFunction.getLocalSymbolMap();
 607 |         if (localSymbolMap == null) {
 608 |             return "Decompilation failed (no local symbol map)";
 609 |         }
 610 | 
 611 |         HighSymbol highSymbol = null;
 612 |         Iterator<HighSymbol> symbols = localSymbolMap.getSymbols();
 613 |         while (symbols.hasNext()) {
 614 |             HighSymbol symbol = symbols.next();
 615 |             String symbolName = symbol.getName();
 616 |             
 617 |             if (symbolName.equals(oldVarName)) {
 618 |                 highSymbol = symbol;
 619 |             }
 620 |             if (symbolName.equals(newVarName)) {
 621 |                 return "Error: A variable with name '" + newVarName + "' already exists in this function";
 622 |             }
 623 |         }
 624 | 
 625 |         if (highSymbol == null) {
 626 |             return "Variable not found";
 627 |         }
 628 | 
 629 |         boolean commitRequired = checkFullCommit(highSymbol, highFunction);
 630 | 
 631 |         final HighSymbol finalHighSymbol = highSymbol;
 632 |         final Function finalFunction = func;
 633 |         AtomicBoolean successFlag = new AtomicBoolean(false);
 634 | 
 635 |         try {
 636 |             SwingUtilities.invokeAndWait(() -> {           
 637 |                 int tx = program.startTransaction("Rename variable");
 638 |                 try {
 639 |                     if (commitRequired) {
 640 |                         HighFunctionDBUtil.commitParamsToDatabase(highFunction, false,
 641 |                             ReturnCommitOption.NO_COMMIT, finalFunction.getSignatureSource());
 642 |                     }
 643 |                     HighFunctionDBUtil.updateDBVariable(
 644 |                         finalHighSymbol,
 645 |                         newVarName,
 646 |                         null,
 647 |                         SourceType.USER_DEFINED
 648 |                     );
 649 |                     successFlag.set(true);
 650 |                 }
 651 |                 catch (Exception e) {
 652 |                     Msg.error(this, "Failed to rename variable", e);
 653 |                 }
 654 |                 finally {
 655 |                     successFlag.set(program.endTransaction(tx, true));
 656 |                 }
 657 |             });
 658 |         } catch (InterruptedException | InvocationTargetException e) {
 659 |             String errorMsg = "Failed to execute rename on Swing thread: " + e.getMessage();
 660 |             Msg.error(this, errorMsg, e);
 661 |             return errorMsg;
 662 |         }
 663 |         return successFlag.get() ? "Variable renamed" : "Failed to rename variable";
 664 |     }
 665 | 
 666 |     /**
 667 |      * Copied from AbstractDecompilerAction.checkFullCommit, it's protected.
 668 | 	 * Compare the given HighFunction's idea of the prototype with the Function's idea.
 669 | 	 * Return true if there is a difference. If a specific symbol is being changed,
 670 | 	 * it can be passed in to check whether or not the prototype is being affected.
 671 | 	 * @param highSymbol (if not null) is the symbol being modified
 672 | 	 * @param hfunction is the given HighFunction
 673 | 	 * @return true if there is a difference (and a full commit is required)
 674 | 	 */
 675 | 	protected static boolean checkFullCommit(HighSymbol highSymbol, HighFunction hfunction) {
 676 | 		if (highSymbol != null && !highSymbol.isParameter()) {
 677 | 			return false;
 678 | 		}
 679 | 		Function function = hfunction.getFunction();
 680 | 		Parameter[] parameters = function.getParameters();
 681 | 		LocalSymbolMap localSymbolMap = hfunction.getLocalSymbolMap();
 682 | 		int numParams = localSymbolMap.getNumParams();
 683 | 		if (numParams != parameters.length) {
 684 | 			return true;
 685 | 		}
 686 | 
 687 | 		for (int i = 0; i < numParams; i++) {
 688 | 			HighSymbol param = localSymbolMap.getParamSymbol(i);
 689 | 			if (param.getCategoryIndex() != i) {
 690 | 				return true;
 691 | 			}
 692 | 			VariableStorage storage = param.getStorage();
 693 | 			// Don't compare using the equals method so that DynamicVariableStorage can match
 694 | 			if (0 != storage.compareTo(parameters[i].getVariableStorage())) {
 695 | 				return true;
 696 | 			}
 697 | 		}
 698 | 
 699 | 		return false;
 700 | 	}
 701 | 
 702 |     // ----------------------------------------------------------------------------------
 703 |     // New methods to implement the new functionalities
 704 |     // ----------------------------------------------------------------------------------
 705 | 
 706 |     /**
 707 |      * Get function by address
 708 |      */
 709 |     private String getFunctionByAddress(String addressStr) {
 710 |         Program program = getCurrentProgram();
 711 |         if (program == null) return "No program loaded";
 712 |         if (addressStr == null || addressStr.isEmpty()) return "Address is required";
 713 | 
 714 |         try {
 715 |             Address addr = program.getAddressFactory().getAddress(addressStr);
 716 |             Function func = program.getFunctionManager().getFunctionAt(addr);
 717 | 
 718 |             if (func == null) return "No function found at address " + addressStr;
 719 | 
 720 |             return String.format("Function: %s at %s\nSignature: %s\nEntry: %s\nBody: %s - %s",
 721 |                 func.getName(),
 722 |                 func.getEntryPoint(),
 723 |                 func.getSignature(),
 724 |                 func.getEntryPoint(),
 725 |                 func.getBody().getMinAddress(),
 726 |                 func.getBody().getMaxAddress());
 727 |         } catch (Exception e) {
 728 |             return "Error getting function: " + e.getMessage();
 729 |         }
 730 |     }
 731 | 
 732 |     /**
 733 |      * Get current address selected in Ghidra GUI
 734 |      */
 735 |     private String getCurrentAddress() {
 736 |         CodeViewerService service = tool.getService(CodeViewerService.class);
 737 |         if (service == null) return "Code viewer service not available";
 738 | 
 739 |         ProgramLocation location = service.getCurrentLocation();
 740 |         return (location != null) ? location.getAddress().toString() : "No current location";
 741 |     }
 742 | 
 743 |     /**
 744 |      * Get current function selected in Ghidra GUI
 745 |      */
 746 |     private String getCurrentFunction() {
 747 |         CodeViewerService service = tool.getService(CodeViewerService.class);
 748 |         if (service == null) return "Code viewer service not available";
 749 | 
 750 |         ProgramLocation location = service.getCurrentLocation();
 751 |         if (location == null) return "No current location";
 752 | 
 753 |         Program program = getCurrentProgram();
 754 |         if (program == null) return "No program loaded";
 755 | 
 756 |         Function func = program.getFunctionManager().getFunctionContaining(location.getAddress());
 757 |         if (func == null) return "No function at current location: " + location.getAddress();
 758 | 
 759 |         return String.format("Function: %s at %s\nSignature: %s",
 760 |             func.getName(),
 761 |             func.getEntryPoint(),
 762 |             func.getSignature());
 763 |     }
 764 | 
 765 |     /**
 766 |      * List all functions in the database
 767 |      */
 768 |     private String listFunctions() {
 769 |         Program program = getCurrentProgram();
 770 |         if (program == null) return "No program loaded";
 771 | 
 772 |         StringBuilder result = new StringBuilder();
 773 |         for (Function func : program.getFunctionManager().getFunctions(true)) {
 774 |             result.append(String.format("%s at %s\n", 
 775 |                 func.getName(), 
 776 |                 func.getEntryPoint()));
 777 |         }
 778 | 
 779 |         return result.toString();
 780 |     }
 781 | 
 782 |     /**
 783 |      * Gets a function at the given address or containing the address
 784 |      * @return the function or null if not found
 785 |      */
 786 |     private Function getFunctionForAddress(Program program, Address addr) {
 787 |         Function func = program.getFunctionManager().getFunctionAt(addr);
 788 |         if (func == null) {
 789 |             func = program.getFunctionManager().getFunctionContaining(addr);
 790 |         }
 791 |         return func;
 792 |     }
 793 | 
 794 |     /**
 795 |      * Decompile a function at the given address
 796 |      */
 797 |     private String decompileFunctionByAddress(String addressStr) {
 798 |         Program program = getCurrentProgram();
 799 |         if (program == null) return "No program loaded";
 800 |         if (addressStr == null || addressStr.isEmpty()) return "Address is required";
 801 | 
 802 |         try {
 803 |             Address addr = program.getAddressFactory().getAddress(addressStr);
 804 |             Function func = getFunctionForAddress(program, addr);
 805 |             if (func == null) return "No function found at or containing address " + addressStr;
 806 | 
 807 |             DecompInterface decomp = new DecompInterface();
 808 |             decomp.openProgram(program);
 809 |             DecompileResults result = decomp.decompileFunction(func, 30, new ConsoleTaskMonitor());
 810 | 
 811 |             return (result != null && result.decompileCompleted()) 
 812 |                 ? result.getDecompiledFunction().getC() 
 813 |                 : "Decompilation failed";
 814 |         } catch (Exception e) {
 815 |             return "Error decompiling function: " + e.getMessage();
 816 |         }
 817 |     }
 818 | 
 819 |     /**
 820 |      * Get assembly code for a function
 821 |      */
 822 |     private String disassembleFunction(String addressStr) {
 823 |         Program program = getCurrentProgram();
 824 |         if (program == null) return "No program loaded";
 825 |         if (addressStr == null || addressStr.isEmpty()) return "Address is required";
 826 | 
 827 |         try {
 828 |             Address addr = program.getAddressFactory().getAddress(addressStr);
 829 |             Function func = getFunctionForAddress(program, addr);
 830 |             if (func == null) return "No function found at or containing address " + addressStr;
 831 | 
 832 |             StringBuilder result = new StringBuilder();
 833 |             Listing listing = program.getListing();
 834 |             Address start = func.getEntryPoint();
 835 |             Address end = func.getBody().getMaxAddress();
 836 | 
 837 |             InstructionIterator instructions = listing.getInstructions(start, true);
 838 |             while (instructions.hasNext()) {
 839 |                 Instruction instr = instructions.next();
 840 |                 if (instr.getAddress().compareTo(end) > 0) {
 841 |                     break; // Stop if we've gone past the end of the function
 842 |                 }
 843 |                 String comment = listing.getComment(CodeUnit.EOL_COMMENT, instr.getAddress());
 844 |                 comment = (comment != null) ? "; " + comment : "";
 845 | 
 846 |                 result.append(String.format("%s: %s %s\n", 
 847 |                     instr.getAddress(), 
 848 |                     instr.toString(),
 849 |                     comment));
 850 |             }
 851 | 
 852 |             return result.toString();
 853 |         } catch (Exception e) {
 854 |             return "Error disassembling function: " + e.getMessage();
 855 |         }
 856 |     }    
 857 | 
 858 |     /**
 859 |      * Set a comment using the specified comment type (PRE_COMMENT or EOL_COMMENT)
 860 |      */
 861 |     private boolean setCommentAtAddress(String addressStr, String comment, int commentType, String transactionName) {
 862 |         Program program = getCurrentProgram();
 863 |         if (program == null) return false;
 864 |         if (addressStr == null || addressStr.isEmpty() || comment == null) return false;
 865 | 
 866 |         AtomicBoolean success = new AtomicBoolean(false);
 867 | 
 868 |         try {
 869 |             SwingUtilities.invokeAndWait(() -> {
 870 |                 int tx = program.startTransaction(transactionName);
 871 |                 try {
 872 |                     Address addr = program.getAddressFactory().getAddress(addressStr);
 873 |                     program.getListing().setComment(addr, commentType, comment);
 874 |                     success.set(true);
 875 |                 } catch (Exception e) {
 876 |                     Msg.error(this, "Error setting " + transactionName.toLowerCase(), e);
 877 |                 } finally {
 878 |                     success.set(program.endTransaction(tx, success.get()));
 879 |                 }
 880 |             });
 881 |         } catch (InterruptedException | InvocationTargetException e) {
 882 |             Msg.error(this, "Failed to execute " + transactionName.toLowerCase() + " on Swing thread", e);
 883 |         }
 884 | 
 885 |         return success.get();
 886 |     }
 887 | 
 888 |     /**
 889 |      * Set a comment for a given address in the function pseudocode
 890 |      */
 891 |     private boolean setDecompilerComment(String addressStr, String comment) {
 892 |         return setCommentAtAddress(addressStr, comment, CodeUnit.PRE_COMMENT, "Set decompiler comment");
 893 |     }
 894 | 
 895 |     /**
 896 |      * Set a comment for a given address in the function disassembly
 897 |      */
 898 |     private boolean setDisassemblyComment(String addressStr, String comment) {
 899 |         return setCommentAtAddress(addressStr, comment, CodeUnit.EOL_COMMENT, "Set disassembly comment");
 900 |     }
 901 | 
 902 |     /**
 903 |      * Class to hold the result of a prototype setting operation
 904 |      */
 905 |     private static class PrototypeResult {
 906 |         private final boolean success;
 907 |         private final String errorMessage;
 908 | 
 909 |         public PrototypeResult(boolean success, String errorMessage) {
 910 |             this.success = success;
 911 |             this.errorMessage = errorMessage;
 912 |         }
 913 | 
 914 |         public boolean isSuccess() {
 915 |             return success;
 916 |         }
 917 | 
 918 |         public String getErrorMessage() {
 919 |             return errorMessage;
 920 |         }
 921 |     }
 922 | 
 923 |     /**
 924 |      * Rename a function by its address
 925 |      */
 926 |     private boolean renameFunctionByAddress(String functionAddrStr, String newName) {
 927 |         Program program = getCurrentProgram();
 928 |         if (program == null) return false;
 929 |         if (functionAddrStr == null || functionAddrStr.isEmpty() || 
 930 |             newName == null || newName.isEmpty()) {
 931 |             return false;
 932 |         }
 933 | 
 934 |         AtomicBoolean success = new AtomicBoolean(false);
 935 | 
 936 |         try {
 937 |             SwingUtilities.invokeAndWait(() -> {
 938 |                 performFunctionRename(program, functionAddrStr, newName, success);
 939 |             });
 940 |         } catch (InterruptedException | InvocationTargetException e) {
 941 |             Msg.error(this, "Failed to execute rename function on Swing thread", e);
 942 |         }
 943 | 
 944 |         return success.get();
 945 |     }
 946 | 
 947 |     /**
 948 |      * Helper method to perform the actual function rename within a transaction
 949 |      */
 950 |     private void performFunctionRename(Program program, String functionAddrStr, String newName, AtomicBoolean success) {
 951 |         int tx = program.startTransaction("Rename function by address");
 952 |         try {
 953 |             Address addr = program.getAddressFactory().getAddress(functionAddrStr);
 954 |             Function func = getFunctionForAddress(program, addr);
 955 | 
 956 |             if (func == null) {
 957 |                 Msg.error(this, "Could not find function at address: " + functionAddrStr);
 958 |                 return;
 959 |             }
 960 | 
 961 |             func.setName(newName, SourceType.USER_DEFINED);
 962 |             success.set(true);
 963 |         } catch (Exception e) {
 964 |             Msg.error(this, "Error renaming function by address", e);
 965 |         } finally {
 966 |             program.endTransaction(tx, success.get());
 967 |         }
 968 |     }
 969 | 
 970 |     /**
 971 |      * Set a function's prototype with proper error handling using ApplyFunctionSignatureCmd
 972 |      */
 973 |     private PrototypeResult setFunctionPrototype(String functionAddrStr, String prototype) {
 974 |         // Input validation
 975 |         Program program = getCurrentProgram();
 976 |         if (program == null) return new PrototypeResult(false, "No program loaded");
 977 |         if (functionAddrStr == null || functionAddrStr.isEmpty()) {
 978 |             return new PrototypeResult(false, "Function address is required");
 979 |         }
 980 |         if (prototype == null || prototype.isEmpty()) {
 981 |             return new PrototypeResult(false, "Function prototype is required");
 982 |         }
 983 | 
 984 |         final StringBuilder errorMessage = new StringBuilder();
 985 |         final AtomicBoolean success = new AtomicBoolean(false);
 986 | 
 987 |         try {
 988 |             SwingUtilities.invokeAndWait(() -> 
 989 |                 applyFunctionPrototype(program, functionAddrStr, prototype, success, errorMessage));
 990 |         } catch (InterruptedException | InvocationTargetException e) {
 991 |             String msg = "Failed to set function prototype on Swing thread: " + e.getMessage();
 992 |             errorMessage.append(msg);
 993 |             Msg.error(this, msg, e);
 994 |         }
 995 | 
 996 |         return new PrototypeResult(success.get(), errorMessage.toString());
 997 |     }
 998 | 
 999 |     /**
1000 |      * Helper method that applies the function prototype within a transaction
1001 |      */
1002 |     private void applyFunctionPrototype(Program program, String functionAddrStr, String prototype, 
1003 |                                        AtomicBoolean success, StringBuilder errorMessage) {
1004 |         try {
1005 |             // Get the address and function
1006 |             Address addr = program.getAddressFactory().getAddress(functionAddrStr);
1007 |             Function func = getFunctionForAddress(program, addr);
1008 | 
1009 |             if (func == null) {
1010 |                 String msg = "Could not find function at address: " + functionAddrStr;
1011 |                 errorMessage.append(msg);
1012 |                 Msg.error(this, msg);
1013 |                 return;
1014 |             }
1015 | 
1016 |             Msg.info(this, "Setting prototype for function " + func.getName() + ": " + prototype);
1017 | 
1018 |             // Store original prototype as a comment for reference
1019 |             addPrototypeComment(program, func, prototype);
1020 | 
1021 |             // Use ApplyFunctionSignatureCmd to parse and apply the signature
1022 |             parseFunctionSignatureAndApply(program, addr, prototype, success, errorMessage);
1023 | 
1024 |         } catch (Exception e) {
1025 |             String msg = "Error setting function prototype: " + e.getMessage();
1026 |             errorMessage.append(msg);
1027 |             Msg.error(this, msg, e);
1028 |         }
1029 |     }
1030 | 
1031 |     /**
1032 |      * Add a comment showing the prototype being set
1033 |      */
1034 |     private void addPrototypeComment(Program program, Function func, String prototype) {
1035 |         int txComment = program.startTransaction("Add prototype comment");
1036 |         try {
1037 |             program.getListing().setComment(
1038 |                 func.getEntryPoint(), 
1039 |                 CodeUnit.PLATE_COMMENT, 
1040 |                 "Setting prototype: " + prototype
1041 |             );
1042 |         } finally {
1043 |             program.endTransaction(txComment, true);
1044 |         }
1045 |     }
1046 | 
1047 |     /**
1048 |      * Parse and apply the function signature with error handling
1049 |      */
1050 |     private void parseFunctionSignatureAndApply(Program program, Address addr, String prototype,
1051 |                                               AtomicBoolean success, StringBuilder errorMessage) {
1052 |         // Use ApplyFunctionSignatureCmd to parse and apply the signature
1053 |         int txProto = program.startTransaction("Set function prototype");
1054 |         try {
1055 |             // Get data type manager
1056 |             DataTypeManager dtm = program.getDataTypeManager();
1057 | 
1058 |             // Get data type manager service
1059 |             ghidra.app.services.DataTypeManagerService dtms = 
1060 |                 tool.getService(ghidra.app.services.DataTypeManagerService.class);
1061 | 
1062 |             // Create function signature parser
1063 |             ghidra.app.util.parser.FunctionSignatureParser parser = 
1064 |                 new ghidra.app.util.parser.FunctionSignatureParser(dtm, dtms);
1065 | 
1066 |             // Parse the prototype into a function signature
1067 |             ghidra.program.model.data.FunctionDefinitionDataType sig = parser.parse(null, prototype);
1068 | 
1069 |             if (sig == null) {
1070 |                 String msg = "Failed to parse function prototype";
1071 |                 errorMessage.append(msg);
1072 |                 Msg.error(this, msg);
1073 |                 return;
1074 |             }
1075 | 
1076 |             // Create and apply the command
1077 |             ghidra.app.cmd.function.ApplyFunctionSignatureCmd cmd = 
1078 |                 new ghidra.app.cmd.function.ApplyFunctionSignatureCmd(
1079 |                     addr, sig, SourceType.USER_DEFINED);
1080 | 
1081 |             // Apply the command to the program
1082 |             boolean cmdResult = cmd.applyTo(program, new ConsoleTaskMonitor());
1083 | 
1084 |             if (cmdResult) {
1085 |                 success.set(true);
1086 |                 Msg.info(this, "Successfully applied function signature");
1087 |             } else {
1088 |                 String msg = "Command failed: " + cmd.getStatusMsg();
1089 |                 errorMessage.append(msg);
1090 |                 Msg.error(this, msg);
1091 |             }
1092 |         } catch (Exception e) {
1093 |             String msg = "Error applying function signature: " + e.getMessage();
1094 |             errorMessage.append(msg);
1095 |             Msg.error(this, msg, e);
1096 |         } finally {
1097 |             program.endTransaction(txProto, success.get());
1098 |         }
1099 |     }
1100 | 
1101 |     /**
1102 |      * Set a local variable's type using HighFunctionDBUtil.updateDBVariable
1103 |      */
1104 |     private boolean setLocalVariableType(String functionAddrStr, String variableName, String newType) {
1105 |         // Input validation
1106 |         Program program = getCurrentProgram();
1107 |         if (program == null) return false;
1108 |         if (functionAddrStr == null || functionAddrStr.isEmpty() || 
1109 |             variableName == null || variableName.isEmpty() ||
1110 |             newType == null || newType.isEmpty()) {
1111 |             return false;
1112 |         }
1113 | 
1114 |         AtomicBoolean success = new AtomicBoolean(false);
1115 | 
1116 |         try {
1117 |             SwingUtilities.invokeAndWait(() -> 
1118 |                 applyVariableType(program, functionAddrStr, variableName, newType, success));
1119 |         } catch (InterruptedException | InvocationTargetException e) {
1120 |             Msg.error(this, "Failed to execute set variable type on Swing thread", e);
1121 |         }
1122 | 
1123 |         return success.get();
1124 |     }
1125 | 
1126 |     /**
1127 |      * Helper method that performs the actual variable type change
1128 |      */
1129 |     private void applyVariableType(Program program, String functionAddrStr, 
1130 |                                   String variableName, String newType, AtomicBoolean success) {
1131 |         try {
1132 |             // Find the function
1133 |             Address addr = program.getAddressFactory().getAddress(functionAddrStr);
1134 |             Function func = getFunctionForAddress(program, addr);
1135 | 
1136 |             if (func == null) {
1137 |                 Msg.error(this, "Could not find function at address: " + functionAddrStr);
1138 |                 return;
1139 |             }
1140 | 
1141 |             DecompileResults results = decompileFunction(func, program);
1142 |             if (results == null || !results.decompileCompleted()) {
1143 |                 return;
1144 |             }
1145 | 
1146 |             ghidra.program.model.pcode.HighFunction highFunction = results.getHighFunction();
1147 |             if (highFunction == null) {
1148 |                 Msg.error(this, "No high function available");
1149 |                 return;
1150 |             }
1151 | 
1152 |             // Find the symbol by name
1153 |             HighSymbol symbol = findSymbolByName(highFunction, variableName);
1154 |             if (symbol == null) {
1155 |                 Msg.error(this, "Could not find variable '" + variableName + "' in decompiled function");
1156 |                 return;
1157 |             }
1158 | 
1159 |             // Get high variable
1160 |             HighVariable highVar = symbol.getHighVariable();
1161 |             if (highVar == null) {
1162 |                 Msg.error(this, "No HighVariable found for symbol: " + variableName);
1163 |                 return;
1164 |             }
1165 | 
1166 |             Msg.info(this, "Found high variable for: " + variableName + 
1167 |                      " with current type " + highVar.getDataType().getName());
1168 | 
1169 |             // Find the data type
1170 |             DataTypeManager dtm = program.getDataTypeManager();
1171 |             DataType dataType = resolveDataType(dtm, newType);
1172 | 
1173 |             if (dataType == null) {
1174 |                 Msg.error(this, "Could not resolve data type: " + newType);
1175 |                 return;
1176 |             }
1177 | 
1178 |             Msg.info(this, "Using data type: " + dataType.getName() + " for variable " + variableName);
1179 | 
1180 |             // Apply the type change in a transaction
1181 |             updateVariableType(program, symbol, dataType, success);
1182 | 
1183 |         } catch (Exception e) {
1184 |             Msg.error(this, "Error setting variable type: " + e.getMessage());
1185 |         }
1186 |     }
1187 | 
1188 |     /**
1189 |      * Find a high symbol by name in the given high function
1190 |      */
1191 |     private HighSymbol findSymbolByName(ghidra.program.model.pcode.HighFunction highFunction, String variableName) {
1192 |         Iterator<HighSymbol> symbols = highFunction.getLocalSymbolMap().getSymbols();
1193 |         while (symbols.hasNext()) {
1194 |             HighSymbol s = symbols.next();
1195 |             if (s.getName().equals(variableName)) {
1196 |                 return s;
1197 |             }
1198 |         }
1199 |         return null;
1200 |     }
1201 | 
1202 |     /**
1203 |      * Decompile a function and return the results
1204 |      */
1205 |     private DecompileResults decompileFunction(Function func, Program program) {
1206 |         // Set up decompiler for accessing the decompiled function
1207 |         DecompInterface decomp = new DecompInterface();
1208 |         decomp.openProgram(program);
1209 |         decomp.setSimplificationStyle("decompile"); // Full decompilation
1210 | 
1211 |         // Decompile the function
1212 |         DecompileResults results = decomp.decompileFunction(func, 60, new ConsoleTaskMonitor());
1213 | 
1214 |         if (!results.decompileCompleted()) {
1215 |             Msg.error(this, "Could not decompile function: " + results.getErrorMessage());
1216 |             return null;
1217 |         }
1218 | 
1219 |         return results;
1220 |     }
1221 | 
1222 |     /**
1223 |      * Apply the type update in a transaction
1224 |      */
1225 |     private void updateVariableType(Program program, HighSymbol symbol, DataType dataType, AtomicBoolean success) {
1226 |         int tx = program.startTransaction("Set variable type");
1227 |         try {
1228 |             // Use HighFunctionDBUtil to update the variable with the new type
1229 |             HighFunctionDBUtil.updateDBVariable(
1230 |                 symbol,                // The high symbol to modify
1231 |                 symbol.getName(),      // Keep original name
1232 |                 dataType,              // The new data type
1233 |                 SourceType.USER_DEFINED // Mark as user-defined
1234 |             );
1235 | 
1236 |             success.set(true);
1237 |             Msg.info(this, "Successfully set variable type using HighFunctionDBUtil");
1238 |         } catch (Exception e) {
1239 |             Msg.error(this, "Error setting variable type: " + e.getMessage());
1240 |         } finally {
1241 |             program.endTransaction(tx, success.get());
1242 |         }
1243 |     }
1244 | 
1245 |     /**
1246 |      * Get all references to a specific address (xref to)
1247 |      */
1248 |     private String getXrefsTo(String addressStr, int offset, int limit) {
1249 |         Program program = getCurrentProgram();
1250 |         if (program == null) return "No program loaded";
1251 |         if (addressStr == null || addressStr.isEmpty()) return "Address is required";
1252 | 
1253 |         try {
1254 |             Address addr = program.getAddressFactory().getAddress(addressStr);
1255 |             ReferenceManager refManager = program.getReferenceManager();
1256 |             
1257 |             ReferenceIterator refIter = refManager.getReferencesTo(addr);
1258 |             
1259 |             List<String> refs = new ArrayList<>();
1260 |             while (refIter.hasNext()) {
1261 |                 Reference ref = refIter.next();
1262 |                 Address fromAddr = ref.getFromAddress();
1263 |                 RefType refType = ref.getReferenceType();
1264 |                 
1265 |                 Function fromFunc = program.getFunctionManager().getFunctionContaining(fromAddr);
1266 |                 String funcInfo = (fromFunc != null) ? " in " + fromFunc.getName() : "";
1267 |                 
1268 |                 refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName()));
1269 |             }
1270 |             
1271 |             return paginateList(refs, offset, limit);
1272 |         } catch (Exception e) {
1273 |             return "Error getting references to address: " + e.getMessage();
1274 |         }
1275 |     }
1276 | 
1277 |     /**
1278 |      * Get all references from a specific address (xref from)
1279 |      */
1280 |     private String getXrefsFrom(String addressStr, int offset, int limit) {
1281 |         Program program = getCurrentProgram();
1282 |         if (program == null) return "No program loaded";
1283 |         if (addressStr == null || addressStr.isEmpty()) return "Address is required";
1284 | 
1285 |         try {
1286 |             Address addr = program.getAddressFactory().getAddress(addressStr);
1287 |             ReferenceManager refManager = program.getReferenceManager();
1288 |             
1289 |             Reference[] references = refManager.getReferencesFrom(addr);
1290 |             
1291 |             List<String> refs = new ArrayList<>();
1292 |             for (Reference ref : references) {
1293 |                 Address toAddr = ref.getToAddress();
1294 |                 RefType refType = ref.getReferenceType();
1295 |                 
1296 |                 String targetInfo = "";
1297 |                 Function toFunc = program.getFunctionManager().getFunctionAt(toAddr);
1298 |                 if (toFunc != null) {
1299 |                     targetInfo = " to function " + toFunc.getName();
1300 |                 } else {
1301 |                     Data data = program.getListing().getDataAt(toAddr);
1302 |                     if (data != null) {
1303 |                         targetInfo = " to data " + (data.getLabel() != null ? data.getLabel() : data.getPathName());
1304 |                     }
1305 |                 }
1306 |                 
1307 |                 refs.add(String.format("To %s%s [%s]", toAddr, targetInfo, refType.getName()));
1308 |             }
1309 |             
1310 |             return paginateList(refs, offset, limit);
1311 |         } catch (Exception e) {
1312 |             return "Error getting references from address: " + e.getMessage();
1313 |         }
1314 |     }
1315 | 
1316 |     /**
1317 |      * Get all references to a specific function by name
1318 |      */
1319 |     private String getFunctionXrefs(String functionName, int offset, int limit) {
1320 |         Program program = getCurrentProgram();
1321 |         if (program == null) return "No program loaded";
1322 |         if (functionName == null || functionName.isEmpty()) return "Function name is required";
1323 | 
1324 |         try {
1325 |             List<String> refs = new ArrayList<>();
1326 |             FunctionManager funcManager = program.getFunctionManager();
1327 |             for (Function function : funcManager.getFunctions(true)) {
1328 |                 if (function.getName().equals(functionName)) {
1329 |                     Address entryPoint = function.getEntryPoint();
1330 |                     ReferenceIterator refIter = program.getReferenceManager().getReferencesTo(entryPoint);
1331 |                     
1332 |                     while (refIter.hasNext()) {
1333 |                         Reference ref = refIter.next();
1334 |                         Address fromAddr = ref.getFromAddress();
1335 |                         RefType refType = ref.getReferenceType();
1336 |                         
1337 |                         Function fromFunc = funcManager.getFunctionContaining(fromAddr);
1338 |                         String funcInfo = (fromFunc != null) ? " in " + fromFunc.getName() : "";
1339 |                         
1340 |                         refs.add(String.format("From %s%s [%s]", fromAddr, funcInfo, refType.getName()));
1341 |                     }
1342 |                 }
1343 |             }
1344 |             
1345 |             if (refs.isEmpty()) {
1346 |                 return "No references found to function: " + functionName;
1347 |             }
1348 |             
1349 |             return paginateList(refs, offset, limit);
1350 |         } catch (Exception e) {
1351 |             return "Error getting function references: " + e.getMessage();
1352 |         }
1353 |     }
1354 | 
1355 | /**
1356 |  * List all defined strings in the program with their addresses
1357 |  */
1358 |     private String listDefinedStrings(int offset, int limit, String filter) {
1359 |         Program program = getCurrentProgram();
1360 |         if (program == null) return "No program loaded";
1361 | 
1362 |         List<String> lines = new ArrayList<>();
1363 |         DataIterator dataIt = program.getListing().getDefinedData(true);
1364 |         
1365 |         while (dataIt.hasNext()) {
1366 |             Data data = dataIt.next();
1367 |             
1368 |             if (data != null && isStringData(data)) {
1369 |                 String value = data.getValue() != null ? data.getValue().toString() : "";
1370 |                 
1371 |                 if (filter == null || value.toLowerCase().contains(filter.toLowerCase())) {
1372 |                     String escapedValue = escapeString(value);
1373 |                     lines.add(String.format("%s: \"%s\"", data.getAddress(), escapedValue));
1374 |                 }
1375 |             }
1376 |         }
1377 |         
1378 |         return paginateList(lines, offset, limit);
1379 |     }
1380 | 
1381 |     /**
1382 |      * Check if the given data is a string type
1383 |      */
1384 |     private boolean isStringData(Data data) {
1385 |         if (data == null) return false;
1386 |         
1387 |         DataType dt = data.getDataType();
1388 |         String typeName = dt.getName().toLowerCase();
1389 |         return typeName.contains("string") || typeName.contains("char") || typeName.equals("unicode");
1390 |     }
1391 | 
1392 |     /**
1393 |      * Escape special characters in a string for display
1394 |      */
1395 |     private String escapeString(String input) {
1396 |         if (input == null) return "";
1397 |         
1398 |         StringBuilder sb = new StringBuilder();
1399 |         for (int i = 0; i < input.length(); i++) {
1400 |             char c = input.charAt(i);
1401 |             if (c >= 32 && c < 127) {
1402 |                 sb.append(c);
1403 |             } else if (c == '\n') {
1404 |                 sb.append("\\n");
1405 |             } else if (c == '\r') {
1406 |                 sb.append("\\r");
1407 |             } else if (c == '\t') {
1408 |                 sb.append("\\t");
1409 |             } else {
1410 |                 sb.append(String.format("\\x%02x", (int)c & 0xFF));
1411 |             }
1412 |         }
1413 |         return sb.toString();
1414 |     }
1415 | 
1416 |     /**
1417 |      * Resolves a data type by name, handling common types and pointer types
1418 |      * @param dtm The data type manager
1419 |      * @param typeName The type name to resolve
1420 |      * @return The resolved DataType, or null if not found
1421 |      */
1422 |     private DataType resolveDataType(DataTypeManager dtm, String typeName) {
1423 |         // First try to find exact match in all categories
1424 |         DataType dataType = findDataTypeByNameInAllCategories(dtm, typeName);
1425 |         if (dataType != null) {
1426 |             Msg.info(this, "Found exact data type match: " + dataType.getPathName());
1427 |             return dataType;
1428 |         }
1429 | 
1430 |         // Check for Windows-style pointer types (PXXX)
1431 |         if (typeName.startsWith("P") && typeName.length() > 1) {
1432 |             String baseTypeName = typeName.substring(1);
1433 | 
1434 |             // Special case for PVOID
1435 |             if (baseTypeName.equals("VOID")) {
1436 |                 return new PointerDataType(dtm.getDataType("/void"));
1437 |             }
1438 | 
1439 |             // Try to find the base type
1440 |             DataType baseType = findDataTypeByNameInAllCategories(dtm, baseTypeName);
1441 |             if (baseType != null) {
1442 |                 return new PointerDataType(baseType);
1443 |             }
1444 | 
1445 |             Msg.warn(this, "Base type not found for " + typeName + ", defaulting to void*");
1446 |             return new PointerDataType(dtm.getDataType("/void"));
1447 |         }
1448 | 
1449 |         // Handle common built-in types
1450 |         switch (typeName.toLowerCase()) {
1451 |             case "int":
1452 |             case "long":
1453 |                 return dtm.getDataType("/int");
1454 |             case "uint":
1455 |             case "unsigned int":
1456 |             case "unsigned long":
1457 |             case "dword":
1458 |                 return dtm.getDataType("/uint");
1459 |             case "short":
1460 |                 return dtm.getDataType("/short");
1461 |             case "ushort":
1462 |             case "unsigned short":
1463 |             case "word":
1464 |                 return dtm.getDataType("/ushort");
1465 |             case "char":
1466 |             case "byte":
1467 |                 return dtm.getDataType("/char");
1468 |             case "uchar":
1469 |             case "unsigned char":
1470 |                 return dtm.getDataType("/uchar");
1471 |             case "longlong":
1472 |             case "__int64":
1473 |                 return dtm.getDataType("/longlong");
1474 |             case "ulonglong":
1475 |             case "unsigned __int64":
1476 |                 return dtm.getDataType("/ulonglong");
1477 |             case "bool":
1478 |             case "boolean":
1479 |                 return dtm.getDataType("/bool");
1480 |             case "void":
1481 |                 return dtm.getDataType("/void");
1482 |             default:
1483 |                 // Try as a direct path
1484 |                 DataType directType = dtm.getDataType("/" + typeName);
1485 |                 if (directType != null) {
1486 |                     return directType;
1487 |                 }
1488 | 
1489 |                 // Fallback to int if we couldn't find it
1490 |                 Msg.warn(this, "Unknown type: " + typeName + ", defaulting to int");
1491 |                 return dtm.getDataType("/int");
1492 |         }
1493 |     }
1494 |     
1495 |     /**
1496 |      * Find a data type by name in all categories/folders of the data type manager
1497 |      * This searches through all categories rather than just the root
1498 |      */
1499 |     private DataType findDataTypeByNameInAllCategories(DataTypeManager dtm, String typeName) {
1500 |         // Try exact match first
1501 |         DataType result = searchByNameInAllCategories(dtm, typeName);
1502 |         if (result != null) {
1503 |             return result;
1504 |         }
1505 | 
1506 |         // Try lowercase
1507 |         return searchByNameInAllCategories(dtm, typeName.toLowerCase());
1508 |     }
1509 | 
1510 |     /**
1511 |      * Helper method to search for a data type by name in all categories
1512 |      */
1513 |     private DataType searchByNameInAllCategories(DataTypeManager dtm, String name) {
1514 |         // Get all data types from the manager
1515 |         Iterator<DataType> allTypes = dtm.getAllDataTypes();
1516 |         while (allTypes.hasNext()) {
1517 |             DataType dt = allTypes.next();
1518 |             // Check if the name matches exactly (case-sensitive) 
1519 |             if (dt.getName().equals(name)) {
1520 |                 return dt;
1521 |             }
1522 |             // For case-insensitive, we want an exact match except for case
1523 |             if (dt.getName().equalsIgnoreCase(name)) {
1524 |                 return dt;
1525 |             }
1526 |         }
1527 |         return null;
1528 |     }
1529 | 
1530 |     // ----------------------------------------------------------------------------------
1531 |     // Utility: parse query params, parse post params, pagination, etc.
1532 |     // ----------------------------------------------------------------------------------
1533 | 
1534 |     /**
1535 |      * Parse query parameters from the URL, e.g. ?offset=10&limit=100
1536 |      */
1537 |     private Map<String, String> parseQueryParams(HttpExchange exchange) {
1538 |         Map<String, String> result = new HashMap<>();
1539 |         String query = exchange.getRequestURI().getQuery(); // e.g. offset=10&limit=100
1540 |         if (query != null) {
1541 |             String[] pairs = query.split("&");
1542 |             for (String p : pairs) {
1543 |                 String[] kv = p.split("=");
1544 |                 if (kv.length == 2) {
1545 |                     // URL decode parameter values
1546 |                     try {
1547 |                         String key = URLDecoder.decode(kv[0], StandardCharsets.UTF_8);
1548 |                         String value = URLDecoder.decode(kv[1], StandardCharsets.UTF_8);
1549 |                         result.put(key, value);
1550 |                     } catch (Exception e) {
1551 |                         Msg.error(this, "Error decoding URL parameter", e);
1552 |                     }
1553 |                 }
1554 |             }
1555 |         }
1556 |         return result;
1557 |     }
1558 | 
1559 |     /**
1560 |      * Parse post body form params, e.g. oldName=foo&newName=bar
1561 |      */
1562 |     private Map<String, String> parsePostParams(HttpExchange exchange) throws IOException {
1563 |         byte[] body = exchange.getRequestBody().readAllBytes();
1564 |         String bodyStr = new String(body, StandardCharsets.UTF_8);
1565 |         Map<String, String> params = new HashMap<>();
1566 |         for (String pair : bodyStr.split("&")) {
1567 |             String[] kv = pair.split("=");
1568 |             if (kv.length == 2) {
1569 |                 // URL decode parameter values
1570 |                 try {
1571 |                     String key = URLDecoder.decode(kv[0], StandardCharsets.UTF_8);
1572 |                     String value = URLDecoder.decode(kv[1], StandardCharsets.UTF_8);
1573 |                     params.put(key, value);
1574 |                 } catch (Exception e) {
1575 |                     Msg.error(this, "Error decoding URL parameter", e);
1576 |                 }
1577 |             }
1578 |         }
1579 |         return params;
1580 |     }
1581 | 
1582 |     /**
1583 |      * Convert a list of strings into one big newline-delimited string, applying offset & limit.
1584 |      */
1585 |     private String paginateList(List<String> items, int offset, int limit) {
1586 |         int start = Math.max(0, offset);
1587 |         int end   = Math.min(items.size(), offset + limit);
1588 | 
1589 |         if (start >= items.size()) {
1590 |             return ""; // no items in range
1591 |         }
1592 |         List<String> sub = items.subList(start, end);
1593 |         return String.join("\n", sub);
1594 |     }
1595 | 
1596 |     /**
1597 |      * Parse an integer from a string, or return defaultValue if null/invalid.
1598 |      */
1599 |     private int parseIntOrDefault(String val, int defaultValue) {
1600 |         if (val == null) return defaultValue;
1601 |         try {
1602 |             return Integer.parseInt(val);
1603 |         }
1604 |         catch (NumberFormatException e) {
1605 |             return defaultValue;
1606 |         }
1607 |     }
1608 | 
1609 |     /**
1610 |      * Escape non-ASCII chars to avoid potential decode issues.
1611 |      */
1612 |     private String escapeNonAscii(String input) {
1613 |         if (input == null) return "";
1614 |         StringBuilder sb = new StringBuilder();
1615 |         for (char c : input.toCharArray()) {
1616 |             if (c >= 32 && c < 127) {
1617 |                 sb.append(c);
1618 |             }
1619 |             else {
1620 |                 sb.append("\\x");
1621 |                 sb.append(Integer.toHexString(c & 0xFF));
1622 |             }
1623 |         }
1624 |         return sb.toString();
1625 |     }
1626 | 
1627 |     public Program getCurrentProgram() {
1628 |         ProgramManager pm = tool.getService(ProgramManager.class);
1629 |         return pm != null ? pm.getCurrentProgram() : null;
1630 |     }
1631 | 
1632 |     private void sendResponse(HttpExchange exchange, String response) throws IOException {
1633 |         byte[] bytes = response.getBytes(StandardCharsets.UTF_8);
1634 |         exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
1635 |         exchange.sendResponseHeaders(200, bytes.length);
1636 |         try (OutputStream os = exchange.getResponseBody()) {
1637 |             os.write(bytes);
1638 |         }
1639 |     }
1640 | 
1641 |     @Override
1642 |     public void dispose() {
1643 |         if (server != null) {
1644 |             Msg.info(this, "Stopping GhidraMCP HTTP server...");
1645 |             server.stop(1); // Stop with a small delay (e.g., 1 second) for connections to finish
1646 |             server = null; // Nullify the reference
1647 |             Msg.info(this, "GhidraMCP HTTP server stopped.");
1648 |         }
1649 |         super.dispose();
1650 |     }
1651 | }
1652 | 
```