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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions config/mcp_config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ mcp:
# max_threads: 5 # Maximum thread pool size (default: 5)
# workers: 0 # Number of worker processes (default: 0, single-process mode)

# Dangerous actions gate (default: false)
# When false, destructive tools (msf_module_execute, msf_module_check,
# msf_session_stop, msf_session_write) return an error. Override at runtime
# with the --enable-dangerous-actions CLI flag or by setting the
# MSF_MCP_DANGEROUS_ACTIONS=true environment variable.
dangerous_actions: false

# Rate limiting (optional - defaults shown)
rate_limit:
enabled: true
Expand Down
7 changes: 7 additions & 0 deletions config/mcp_config_jsonrpc.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ mcp:
# max_threads: 5 # Maximum thread pool size (default: 5)
# workers: 0 # Number of worker processes (default: 0, single-process mode)

# Dangerous actions gate (default: false)
# When false, destructive tools (msf_module_execute, msf_module_check,
# msf_session_stop, msf_session_write) return an error. Override at runtime
# with the --enable-dangerous-actions CLI flag or by setting the
# MSF_MCP_DANGEROUS_ACTIONS=true environment variable.
dangerous_actions: false

# Rate limiting (optional - defaults shown)
rate_limit:
enabled: true
Expand Down
157 changes: 143 additions & 14 deletions docs/metasploit-framework.wiki/How-to-use-Metasploit-MCP-Server.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
The Metasploit MCP Server (`msfmcpd`) provides AI applications with secure, structured access to Metasploit Framework data through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). It acts as a middleware layer between AI clients (such as Claude, Cursor, or custom agents) and Metasploit, exposing 8 standardized tools for querying reconnaissance data and searching modules.
The Metasploit MCP Server (`msfmcpd`) provides AI applications with secure, structured access to Metasploit Framework data and execution capabilities through the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP). It acts as a middleware layer between AI clients (such as Claude, Cursor, or custom agents) and Metasploit, exposing 16 standardized tools for searching modules, querying reconnaissance data, executing modules, and interacting with sessions.

This initial implementation is **read-only**. Only tools that query data (modules, hosts, services, vulnerabilities, etc.) are available. Tools for module execution, session interaction, and database modifications will be added in a future iteration.
Tools are split into two classes:

- **Read-only tools** (always enabled): query modules, hosts, services, vulnerabilities, notes, credentials, loot, jobs, and sessions.
- **Dangerous tools** (gated, disabled by default): run module execution and check methods, stop sessions, and write to interactive sessions. These tools must be explicitly enabled by the operator via the `--enable-dangerous-actions` CLI flag, the `MSF_MCP_DANGEROUS_ACTIONS` environment variable, or the `mcp.dangerous_actions` configuration key. See [Dangerous Actions Mode](#dangerous-actions-mode) below.

## Architecture

Expand All @@ -9,7 +12,7 @@ flowchart TD
ai_app["AI Application<br>(Claude, Cursor, etc.)"]

subgraph msfmcp_server["MsfMcp Server"]
mcp_layer["MCP Layer (8 Tools)<br>Input Validation / Rate Limiting / Response Transformation"]
mcp_layer["MCP Layer (16 Tools)<br>Input Validation / Rate Limiting / Response Transformation<br>Dangerous Actions Gate"]
rpc_manager["RPC Manager<br>Auto-detect / Auto-start / Lifecycle Management"]
api_client["Metasploit API Client<br>MessagePack RPC (port 55553) / JSON-RPC (port 8081)<br>Session Management"]

Expand Down Expand Up @@ -95,6 +98,8 @@ Options:
--password PASS MSF API password (for MessagePack auth)
--no-auto-start-rpc Disable automatic RPC server startup
--mcp-transport TRANSPORT MCP server transport type ('stdio' or 'http')
--enable-dangerous-actions Enable dangerous tools (module execute/check,
session stop/write). Disabled by default.
-h, --help Show this help message
-v, --version Show version information
```
Expand All @@ -118,6 +123,7 @@ All configuration settings can be overridden by environment variables:
| `MSF_MCP_HOST` | MCP server host (for HTTP transport) |
| `MSF_MCP_PORT` | MCP server port (for HTTP transport) |
| `MSF_MCP_AUTH_TOKEN` | MCP server Bearer token for authentication (for HTTP transport) |
| `MSF_MCP_DANGEROUS_ACTIONS` | Enable dangerous tools (`true`/`1`/`yes`/`on` to enable, anything else to disable) |

Example using environment variables:

Expand Down Expand Up @@ -225,26 +231,43 @@ The same can be achieved through the environment variable, e.g. `MSF_MCP_AUTH_TO

## MCP Tools

The server exposes 8 tools to AI applications via the MCP protocol.
The server exposes 16 tools to AI applications via the MCP protocol. Tools marked **dangerous** are gated by [Dangerous Actions Mode](#dangerous-actions-mode) and return a tool error unless the operator has explicitly enabled it.

### Response envelope

Every successful tool response is wrapped in the same envelope:

```jsonc
{
"metadata": { "query_time": <seconds> /* plus tool-specific keys */ },
"data": <tool-specific payload>
}
```

`metadata.query_time` is the wall-clock time (in seconds) the MCP server spent handling the call. Individual tools may add fields to `metadata` (for example, database tools include `workspace`, `total_items`, `returned_items`, `limit`, and `offset`; `msf_session_list` includes `total_sessions`). The shapes documented per tool below describe `data` only.

### msf_search_modules
Errors surface as MCP tool errors (`isError: true` responses with a `text` content). Validation errors, rate-limit rejections, authentication failures, and Metasploit API errors all follow that path.

### Read-only Tools

#### msf_search_modules

Search for Metasploit modules by keywords, CVE IDs, or module names.

- `query` (string, required): Search terms (e.g., `windows smb`, `CVE-2017-0144`)
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

### msf_module_info
#### msf_module_info

Get detailed information about a specific Metasploit module.

- `type` (string, required): Module type (`exploit`, `auxiliary`, `post`, `payload`, `encoder`, `nop`)
- `type` (string, required): Module type (`exploit`, `auxiliary`, `post`, `payload`, `encoder`, `evasion`, `nop`)
- `name` (string, required): Module path (e.g., `windows/smb/ms17_010_eternalblue`)

Returns complete module details including options, targets, references, and authors.

### msf_host_info
#### msf_host_info

Query discovered hosts from the Metasploit database.

Expand All @@ -254,7 +277,7 @@ Query discovered hosts from the Metasploit database.
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

### msf_service_info
#### msf_service_info

Query discovered services on hosts.

Expand All @@ -267,7 +290,7 @@ Query discovered services on hosts.
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

### msf_vulnerability_info
#### msf_vulnerability_info

Query discovered vulnerabilities.

Expand All @@ -279,7 +302,7 @@ Query discovered vulnerabilities.
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

### msf_note_info
#### msf_note_info

Query notes stored in the database.

Expand All @@ -291,22 +314,124 @@ Query notes stored in the database.
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

### msf_credential_info
#### msf_credential_info

Query discovered credentials.

- `workspace` (string, optional): Workspace name
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

### msf_loot_info
#### msf_loot_info

Query collected loot (files, data dumps).

- `workspace` (string, optional): Workspace name
- `limit` (integer, optional): Max results (1-1000, default: 100)
- `offset` (integer, optional): Pagination offset (default: 0)

#### msf_module_results

Retrieve the result of a previously executed module run by its UUID.

- `uuid` (string, required): 24-character alphanumeric run UUID returned by `msf_module_execute` or `msf_module_check`

`data` is one of:

- `{ "status": "ready" }` — job is queued but has not started yet
- `{ "status": "running" }` — job is still executing
- `{ "status": "completed", "result": ... }` — job finished; payload depends on module type (a check returns a `CheckCode` payload, a scanner returns per-host results, etc.)
- `{ "status": "errored", "error": "..." }` — job finished with an error

#### msf_running_stats

Return a snapshot of running and waiting module runs known to the framework.

Takes no parameters. `data` is an object with:

- `waiting`: list of run UUIDs queued but not yet started
- `running`: list of run UUIDs currently executing
- `results`: list of run UUIDs whose results are available via `msf_module_results`

#### msf_session_list

List active Metasploit sessions.

Takes no parameters. `data` is a hash keyed by session id (serialised as a string when transported over JSON), where each value describes the session type (e.g. `meterpreter`, `shell`), tunnel endpoints, the target host, and the originating module. `metadata.total_sessions` reports the number of entries in `data`.

#### msf_session_read

Non-destructively read pending output from an interactive session (meterpreter, database, or SMB).

- `session_id` (integer, required): Session id from `msf_session_list`

Returns `data: { "data": "..." }`. The MCP server does not buffer; clients are responsible for reassembling reads. Returns a tool error if the session id is not active.

### Dangerous Tools

These four tools can change the state of the framework, remote targets, or running sessions. They are gated by [Dangerous Actions Mode](#dangerous-actions-mode) and return a tool error unless the operator has explicitly enabled it.

#### msf_module_execute

Run a Metasploit module as a background job.

- `type` (string, required): Module type (`exploit`, `auxiliary`, `post`, `payload`, `evasion`)
- `name` (string, required): Module path (e.g., `multi/handler`)
- `options` (object, required): Datastore options as a flat JSON object. Keys are Metasploit option names; namespaced (`HTTP::compression`) and hyphenated (`BEARER-TOKEN`) names are supported. Values must be scalars (string, integer, number, boolean, or null). No nested objects or arrays.

Returns `data: { "job_id": N, "uuid": "<24 chars>" }`. Poll `msf_module_results` with the returned UUID to retrieve the outcome.

This tool does not implement check-before-exploit. If a module supports `Msf::Exploit::Remote::AutoCheck`, the framework runs the check automatically; otherwise, run `msf_module_check` first.

#### msf_module_check

Run the `check` method of an exploit or auxiliary module to determine whether a target is vulnerable, without exploiting it.

- `type` (string, required): Module type (`exploit` or `auxiliary`)
- `name` (string, required): Module path
- `options` (object, required): Datastore options (same shape as `msf_module_execute`)

Returns `data: { "job_id": N, "uuid": "<24 chars>" }`. Poll `msf_module_results` for the `CheckCode` (e.g. `Vulnerable`, `Safe`, `Detected`, `Appears`, `Unknown`).

When the target module does not implement a `check` method, the tool returns a **successful** response with `data: { "status": "unsupported", "message": "Module does not implement a check method" }` rather than a tool error, so clients can distinguish "not supported" from a transport or runtime failure.

#### msf_session_stop

Terminate an active session.

- `session_id` (integer, required): Session id from `msf_session_list`

Returns `data: { "result": "success" }`. Returns a tool error if the session id is not active.

#### msf_session_write

Send data to an interactive session (meterpreter, database, or SMB). Use `msf_session_read` afterwards to retrieve any output produced.

- `session_id` (integer, required): Session id from `msf_session_list`
- `data` (string, required): Bytes to write to the session, capped at 10,000 characters per call

Returns `data: { "result": "<framework result string>" }`.

## Dangerous Actions Mode

The four tools listed under [Dangerous Tools](#dangerous-tools) are disabled by default and must be explicitly enabled. When the gate is closed, calls to any of those tools return an MCP tool error and the underlying RPC call is never issued.

### Enabling

Dangerous mode can be enabled through any of three mechanisms, in order of precedence (highest first):

1. **CLI flag**: pass `--enable-dangerous-actions` to `msfmcpd`.
2. **Environment variable**: set `MSF_MCP_DANGEROUS_ACTIONS=true` (also accepts `1`, `yes`, `on`).
3. **Configuration file**: set `mcp.dangerous_actions: true` in `mcp_config.yaml`.

When the gate is open, `msfmcpd` prints a warning banner on startup:

```
WARNING: dangerous actions mode is ENABLED. Destructive tools (module
execute/check, session stop/write) are now callable from connected MCP
clients.
```

## Integration with AI Applications

Add the MCP server to your AI application configuration. The exact format depends on the client.
Expand Down Expand Up @@ -352,9 +477,13 @@ If you use RVM to manage Ruby versions, specify the full path to RVM so the corr

## Security Considerations

### Dangerous Actions Gate

Tools that execute modules or interact with sessions are disabled by default. They must be explicitly enabled per session via CLI flag, environment variable, or configuration key — see [Dangerous Actions Mode](#dangerous-actions-mode). When the gate is closed, the RPC client is never invoked for a gated call.

### Input Validation

All tool parameters are validated against strict JSON schemas. IP addresses are validated using Ruby's `IPAddr` class with CIDR support, workspace names are restricted to alphanumeric characters plus underscore/hyphen, port ranges are validated (1-65535), and search queries are limited to 500 characters.
All tool parameters are validated against strict JSON schemas. IP addresses are validated using Ruby's `IPAddr` class with CIDR support, workspace names are restricted to alphanumeric characters plus underscore/hyphen, port ranges are validated (1-65535), and search queries are limited to 500 characters. Module datastore option keys must match Metasploit's option-name shape (identifier segments joined by `::` or `-`, up to 128 characters); option payloads are capped at 100 keys, 8 KB per string value, and 64 KB aggregate. Session data writes are capped at 64 KB per call.

### Credential Management

Expand Down
8 changes: 4 additions & 4 deletions lib/msf/base/sessions/scriptable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,15 +129,15 @@ def execute_script(script_name, *args)
opts[k.downcase] = v
end
if mod.type == "post"
mod.run_simple(
mod.run_simple({
# Run with whatever the default stance is for now. At some
# point in the future, we'll probably want a way to force a
# module to run in the background
#'RunAsJob' => true,
'LocalInput' => self.user_input,
'LocalOutput' => self.user_output,
'Options' => opts
)
})
elsif mod.type == "exploit"
# well it must be a local, we're not currently supporting anything else
if mod.exploit_type == "local"
Expand All @@ -160,13 +160,13 @@ def execute_script(script_name, *args)
# session
local_exploit_opts = local_exploit_opts.merge(opts)

mod.exploit_simple(
mod.exploit_simple({
'Payload' => local_exploit_opts.delete('payload'),
'Target' => local_exploit_opts.delete('target'),
'LocalInput' => self.user_input,
'LocalOutput' => self.user_output,
'Options' => local_exploit_opts
)
})

end # end if local
end # end if exploit
Expand Down
11 changes: 7 additions & 4 deletions lib/msf/base/simple/auxiliary.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ def self.run_simple(omod, opts = {}, job_listener: Msf::Simple::NoopJobListener.
end

run_uuid = Rex::Text.rand_text_alphanumeric(24)
mod.run_uuid = run_uuid
omod.run_uuid = run_uuid
job_listener.waiting run_uuid
ctx = [mod, run_uuid, job_listener]
run_as_job = opts['RunAsJob'].nil? ? mod.passive? : opts['RunAsJob']
Expand All @@ -94,8 +96,8 @@ def self.run_simple(omod, opts = {}, job_listener: Msf::Simple::NoopJobListener.
#
# Calls the class method.
#
def run_simple(opts = {}, &block)
Msf::Simple::Auxiliary.run_simple(self, opts, &block)
def run_simple(opts = {}, job_listener: Msf::Simple::NoopJobListener.instance, &block)
Msf::Simple::Auxiliary.run_simple(self, opts, job_listener: job_listener, &block)
end

#
Expand Down Expand Up @@ -128,6 +130,7 @@ def self.check_simple(mod, opts, job_listener: Msf::Simple::NoopJobListener.inst
mod.validate

run_uuid = Rex::Text.rand_text_alphanumeric(24)
mod.run_uuid = run_uuid
job_listener.waiting run_uuid
ctx = [mod, run_uuid, job_listener]

Expand Down Expand Up @@ -158,8 +161,8 @@ def self.check_simple(mod, opts, job_listener: Msf::Simple::NoopJobListener.inst
#
# Calls the class method.
#
def check_simple(opts = {})
Msf::Simple::Auxiliary.check_simple(self, opts)
def check_simple(opts = {}, job_listener: Msf::Simple::NoopJobListener.instance)
Msf::Simple::Auxiliary.check_simple(self, opts, job_listener: job_listener)
end


Expand Down
10 changes: 6 additions & 4 deletions lib/msf/base/simple/evasion.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ module Evasion

include Module

def self.run_simple(oevasion, opts, &block)
def self.run_simple(oevasion, opts, job_listener: Msf::Simple::NoopJobListener.instance, &block)
evasion = oevasion.replicant
# Trap and print errors here (makes them UI-independent)
begin
Expand All @@ -28,7 +28,7 @@ def self.run_simple(oevasion, opts, &block)
evasion.options.validate(evasion.datastore)

# Start it up
driver = EvasionDriver.new(evasion.framework)
driver = EvasionDriver.new(evasion.framework, job_listener: job_listener)

# Initialize the driver instance
driver.evasion = evasion
Expand Down Expand Up @@ -85,9 +85,11 @@ def self.run_simple(oevasion, opts, &block)

# Save the job identifier this evasion is running as
evasion.job_id = driver.job_id
evasion.run_uuid = driver.run_uuid

# Propagate this back to the caller for console mgmt
oevasion.job_id = evasion.job_id
oevasion.run_uuid = evasion.run_uuid
rescue ::Interrupt
evasion.error = $!
raise $!
Expand All @@ -103,8 +105,8 @@ def self.run_simple(oevasion, opts, &block)
nil
end

def run_simple(opts = {}, &block)
Msf::Simple::Evasion.run_simple(self, opts, &block)
def run_simple(opts = {}, job_listener: Msf::Simple::NoopJobListener.instance, &block)
Msf::Simple::Evasion.run_simple(self, opts, job_listener: job_listener, &block)
end

end
Expand Down
Loading
Loading