
ssh into one of the Kali VMs that has been brought up on Compute Engine in the bsidespdx25 project.

In your home directory, clone the repository the exercises reside in.
git clone https://github.com/Craftzman7/pentest-mcp
Change into the exercise directory.
cd pentest-mcp
PentestMCP provides a collection of servers that agents can utilize to perform penetration testing. One of the most important aspects of MCP server design are tools that are highly descriptive and specific so that an agent can accurately determine when and how to use each one. Towards this end, we'll first walk-through the tool descriptions for the nmap, nuclei, and metasploit MCP servers which will be all that is needed to automatically compromise a vulnerable Struts server using an agent. The tool code provides us an intuition on agent behavior.
The description below describes the tool exported by the nmap server. For this tool, the agent must provide a target address as well as command-line options that will be passed directly through to the nmap command. Note that in this case, we are completely reliant on the LLM to perform accurate generation of the command-line options for nmap. If it is unable to do so accurately, then one might consider implementing individual tools with preset options.
@mcp.tool("nmap_scan")
async def nmap_scan(target: str, options: str, ctx: Context = None):
"""
Perform an NMap scan on the specified target with parameters.
Options are any valid NMap flag.
Full port scans like -p- are time-consuming and resource intensive, avoid usage if possible.
If you need to scan all ports, please make the scan as efficient as possible by using options like `-sS` for a SYN scan.
Returns the scan results as a string.
Example usage:
nmap_scan("192.168.1.1", "-sC -sV")
"""
The description below describes the tool exported by the nuclei server, a vulnerability scanner that is continually updated with new detections for new CVEs. For this tool, the agent must provide a target address that will be passed directly through to nuclei command.
@mcp.tool("nuclei_scan")
async def nuclei_scan(target: str):
"""
Perform an Nuclei vulnerability scan on the specified target.
Returns the scan results as a string.
Example usage: nuclei_scan("http://example.com")
The target can be a URL or an IP address.
"""
metasploit, via its RPC daemon (msfrpcd) that is running on the Kali VM provides many functions for identifying vulnerabilities, exploits and exploit payloads that can be utilized to compromise a target. There are a set of tools that the metasploit MCP server implements that an agent can leverage whose descriptions are shown below. These descriptions are what an LLM agent will utilize to craft tool calls so they must be precisely specified and accurate. With each description, an example invocation is given so the LLM is able to model the call it generates to invoke each tool.
As shown in their descriptions, metasploit_search searches for exploit modules to utilize based on a search term. metasploit_info obtains information about a particular module. metasploit_module_payloads lists payloads that can be delivered with a particular module, and metasploit_payload_info gets information about a specific payload.
@mcp.tool("metasploit_search")
async def metasploit_search(query: str):
"""
Search for a module in Metasploit using the provided query.
Returns the search results as a list of module names.
Example usage:
metasploit_search("wordpress 4.6")
"""
@mcp.tool("metasploit_info")
async def metasploit_info(module_type: str, module_name: str):
"""
Get information about a specific Metasploit module.
Returns a dictionary with the module's description, options, and references.
Example usage:
metasploit_info("exploit", "unix/webapp/wp_admin_shell_upload")
module_type is "exploit", "auxiliary", "post", "payload", or "encoder".
module_name is the name of the module excluding the type.
"""
@mcp.tool("metasploit_module_payloads")
async def metasploit_module_payloads(module: str):
"""
List all available payloads for a given Metasploit exploit module.
Returns a list of payload names.
Example usage:
metasploit_module_payloads("unix/webapp/wp_admin_shell_upload")
module is the name of the exploit module excluding the leading "exploit/".
"""
@mcp.tool("metasploit_payload_info")
async def metasploit_payload_info(payload: str):
"""
Get information about a specific Metasploit payload.
Returns the payload's description and options.
Example usage:
metasploit_payload_info("php/meterpreter/reverse_tcp")
payload is the name of the payload excluding the leading "payload/".
"""
The metasploit MCP server also supports tools for performing the exploitation (metasploit_exploit), listing any interactive sessions/shells that have been obtained (metasploit_sessions), and interacting with a particular session (metasploit_session_interact). Descriptions given to the LLM agent are shown below:
@mcp.tool("metasploit_search")
async def metasploit_search(query: str):
"""
Search for a module in Metasploit using the provided query.
Returns the search results as a list of module names.
Example usage:
metasploit_search("wordpress 4.6")
"""
@mcp.tool("metasploit_info")
async def metasploit_info(module_type: str, module_name: str):
"""
Get information about a specific Metasploit module.
Returns a dictionary with the module's description, options, and references.
Example usage:
metasploit_info("exploit", "unix/webapp/wp_admin_shell_upload")
module_type is "exploit", "auxiliary", "post", "payload", or "encoder".
module_name is the name of the module excluding the type.
"""
@mcp.tool("metasploit_module_payloads")
async def metasploit_module_payloads(module: str):
"""
List all available payloads for a given Metasploit exploit module.
Returns a list of payload names.
Example usage:
metasploit_module_payloads("unix/webapp/wp_admin_shell_upload")
module is the name of the exploit module excluding the leading "exploit/".
"""
@mcp.tool("metasploit_payload_info")
async def metasploit_payload_info(payload: str, ctx: Context = None):
"""
Get information about a specific Metasploit payload.
Returns the payload's description and options.
Example usage:
metasploit_payload_info("php/meterpreter/reverse_tcp")
payload is the name of the payload excluding the leading "payload/".
"""
To leverage this server, we'll need to instantiate a client agent.
The configuration for utilizing the MCP servers is shown below. A FastAgent client will automatically load this configuration to initialize its tools. For the agent we'll be using, all of the MCP servers included in PentestMCP are made available so that we can allow the user to select the specific ones it wants its agent to utilize at run-time.
mcp:
servers:
. . .
nmap:
command: "python"
args: ["nmap_mcp_server.py"]
nuclei:
command: "python"
args: ["nuclei_mcp_server.py"]
metasploit:
command: "python"
args: ["metasploit_mcp_server.py"]
. . .
Then, a FastAgent can be instantiated to utilize the selected MCP servers to answer queries.
@fast.agent(
instruction=f"You are a vulnerability discovery assistant. Your task is to discover vulnerabilities and verify them by exploiting them using the tools provided.",
model=model,
servers=tools,
use_history=True,
)
Note that the agent supplied also allows the user to select a model to run the agent with at run-time, allowing you to compare major models in their ability to drive penetration tests using PentestMCP. Models include Google's Gemini, Claude's Opus, and OpenAI's GPT models. You may select alternate models from the list provided by FastAgent here.
To run the agent and MCP servers, create a virtual environment and install the packages required using the uv package manager.
uv init uv add -r requirements.txt
Then, run the agent and interact with the interactive interface implemented by the FastAgent package.
uv run agent.py
Select the nmap, nuclei, and metasploit servers using the arrow keys to navigate and the space bar to select. Then, select a model to use. Options can be found here and include the model aliases below:
opus, sonnet, haikugemini25, gemini25progpt-4.1, gpt-4.1-mini, gpt-4o, gpt-4o-mini, o3-mini, gpt-5, gpt-5-miniVisit the Compute Engine console for the project to find the Internal IP address of a Struts server to target.

Then, attempt to initiate tests using the agent..
Type /exit to exit out of the agent.