Skip to content

MCP server support for "dangerous" actions & new tools#21632

Open
cdelafuente-r7 wants to merge 10 commits into
rapid7:masterfrom
cdelafuente-r7:mcp-server-enhancement
Open

MCP server support for "dangerous" actions & new tools#21632
cdelafuente-r7 wants to merge 10 commits into
rapid7:masterfrom
cdelafuente-r7:mcp-server-enhancement

Conversation

@cdelafuente-r7

@cdelafuente-r7 cdelafuente-r7 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Description

This adds three related features to the MCP server:

  1. Complete job tracking across every module type for module execution as job via RPC: Exploit, Auxiliary, Post and Evasion modules now return a (run_uuid, job_id) pair from module.execute, which can be polled via module.results. This is invisible to msfconsole users but is a prerequisite for the new MCP tools.
  2. Eight new MCP tools: that wrap the existing RPC endpoints for module and session control, backed by strict per-tool input validation.
  3. A "dangerous actions" opt-in gate: Four of the eight tools can perform destructive operations. They now fail closed unless the operator explicitly enables them through one of three well-known paths (CLI flag, env var, or config file), with clearly documented precedence.

The eight new tools

Tool RPC backing Class Annotations
msf_module_execute module.execute dangerous read_only_hint: false, destructive_hint: true, idempotent_hint: false
msf_module_check module.check dangerous read_only_hint: false, destructive_hint: true, idempotent_hint: false
msf_module_results module.results read-only read_only_hint: true, destructive_hint: false, idempotent_hint: true
msf_running_stats module.running_stats read-only read_only_hint: true, destructive_hint: false, idempotent_hint: true
msf_session_list session.list read-only read_only_hint: true, destructive_hint: false, idempotent_hint: true
msf_session_stop session.stop dangerous read_only_hint: false, destructive_hint: true, idempotent_hint: false
msf_session_read session.interactive_read read-only read_only_hint: true, destructive_hint: false, idempotent_hint: true
msf_session_write session.interactive_write dangerous read_only_hint: false, destructive_hint: true, idempotent_hint: false

Dangerous-mode gate

Dangerous tools fail closed. The gate is opened by any one of:

  1. --enable-dangerous-actions on the msfmcpd CLI.
  2. MSF_MCP_DANGEROUS_ACTIONS=true (or 1, yes, on) in the environment.
  3. mcp.dangerous_actions: true in config/mcp_config.yaml (or the JSON-RPC variant).

Related Issue:
This resolves #21270.

Breaking Changes

Minor changes:

  1. module.execute RPC for exploit, post, and evasion modules: uuid field semantics changed (now 24-character)
  2. module.results RPC: HTTP status code changed for unknown UUIDs (404 -> 500)
  3. RPC top-level exception handler broadened: rescue ::StandardError -> After: rescue ::StandardError, ::ScriptError

Verification Steps

Follow the steps in the documentation to setup and start the RPC and MCP servers.

The verification can be done manually using the MCP Inspector and test the MCP tools individually:

npx @modelcontextprotocol/inspector

Or you can use your favorite LLM to test live. (see the documentation)

I also included an integration test script that simulates a MCP client and performs standard operations with the MCP tools. The script can be found here: tools/dev/msfmcp_integration_test.rb

> ruby tools/dev/msfmcp_integration_test.rb --help
Usage: ruby tools/dev/msfmcp_integration_test.rb [options]
        --url URL                    MCP HTTP endpoint (default: http://127.0.0.1:3000)
        --token TOKEN                Bearer token (or set $MSF_MCP_TOKEN)
        --enable-dangerous           Run the dangerous-tool execution paths too
        --rhost IP                   RHOSTS target for dangerous tests (e.g. 192.0.2.10)
        --lhost IP                   LHOST for payload-bound dangerous tests
        --lport PORT                 LPORT for payload-bound dangerous tests
        --session-id N               Existing session id for session read test
        --check-module SPEC          Module spec for msf_module_check as TYPE:NAME
                                     (default: exploit:windows/smb/ms17_010_eternalblue)
        --check-option K=V           Datastore option for msf_module_check (repeatable, overrides defaults)
        --execute-module SPEC        Module spec for msf_module_execute as TYPE:NAME
                                     (default: auxiliary:scanner/portscan/tcp)
        --execute-option K=V         Datastore option for msf_module_execute (repeatable, overrides defaults)
        --exploit-timeout SEC        Seconds to wait for the exploit run to reach a terminal state (default: 120)
        --filter PAT                 Only run tests whose name matches PAT
    -v, --verbose                    Print full responses on failure
    -d, --debug                      Print the full HTTP request/response for every call (succeeds too)
    -h, --help                       Show this help

AI Usage Disclosure

AI was used to help designing and implementing these changes (Copilot).

Pre-Submission Checklist

  • Included a corresponding documentation markdown file in documentation/modules (new modules only)
  • No sensitive information (IP addresses, credentials, API keys, hashes) in code or documentation
  • Tested on the target environment specified in the Environment section above
  • Included RSpec tests for library changes (encouraged for lib/ changes)
  • Read the CONTRIBUTING.md and module acceptance guidelines
Hardware and Complex Software Module Guidance

If your module targets specialized hardware (routers, IoT, PLCs, etc.) or complex software (licensed, multi-service, or multi-version), provide a pcap, screen recording, or video showing successful execution.

Email sanitized pcaps/recordings to msfdev@metasploit.com — remove real IPs, credentials, and hostnames before sending. If hardware/software is unavailable, explain in the PR description.

Responsiveness and PR Takeover Policy

We want every contribution to make it into the project. If approximately 2 weeks pass after a review request without a comment or code update from you, the team may take over the PR and complete the work on your behalf.

If this happens, you will remain credited as a co-author on the final commit — your contribution is always recognized.

This policy exists to keep the project moving forward. It is not a reflection on the quality of your work or your involvement. Life happens, and we would rather finish the work together than let a good contribution go stale.

default: 0
}
},
required: [:workspace]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing workspace from required since it defaults to the default workspace if not provided. This also applies to the other DB tools.

@cdelafuente-r7 cdelafuente-r7 force-pushed the mcp-server-enhancement branch from 4125e03 to 8b0ebbb Compare July 1, 2026 16:32
{"status" => "ready"}
else
error(404, "Results not found for module instance #{uuid}")
error(500, "Results not found for module instance #{uuid}")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an application level error and not an HTTP error (cannot find the requested resource).

res.message = e.http_msg
rescue ::StandardError => e
elog('Unknown Exception', error: e)
rescue ::StandardError, ::ScriptError => e

@cdelafuente-r7 cdelafuente-r7 Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

::ScriptError is there to catch any ::NotImplementedError (e.g. check method not implemented)

@cdelafuente-r7 cdelafuente-r7 marked this pull request as draft July 1, 2026 17:24
@cdelafuente-r7 cdelafuente-r7 marked this pull request as ready for review July 2, 2026 13:24
@dwelch-r7 dwelch-r7 self-assigned this Jul 2, 2026
Comment thread docs/metasploit-framework.wiki/How-to-use-Metasploit-MCP-Server.md

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see if we can make the run_simple changes work here too

I think it may also need updated here

def run_simple(opts = {}, &block)
Msf::Simple::Exploit.exploit_simple(self, opts, &block)
end

mod.init_ui(nil, nil)
end

run_uuid = Rex::Text.rand_text_alphanumeric(24)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Investigate using a real UUID for this

'RunAsJob' => true,
'Options' => opts
'Options' => opts,
'JobListener' => self.job_status_tracker

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will also need updated when ou make exploit work like the rest

{
"job_id" => mod.job_id,
"uuid" => mod.uuid
"uuid" => mod.run_uuid || mod.uuid

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's verify if the old uuid actually makes sense to fall back to

Comment thread lib/msf/core/mcp/logging/sinks/sanitizing.rb
# all inherited mixins, so 100 is a comfortable ceiling. This is a
# defence-in-depth cap against DoS-style input abuse from an MCP client,
# not a transport limit (MCP itself does not restrict payload size).
MODULE_OPTIONS_MAX_KEYS = 100

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this kind of validation live on the RPC side of things rather than MCP?

'Values must be scalars (string, integer, float, boolean, or null). ' \
'Example: {"RHOSTS": "192.0.2.10", "RPORT": 445}. ' \
'No nested objects or arrays.',
additionalProperties: { type: %w[string integer number boolean null] }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verify this includes floats

Comment on lines +21 to +22
minLength: 1,
maxLength: 50

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this might need updated

results: raw_result['results'] || []
}

metadata = { query_time: (Time.now - start_time).round(3) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rex::StopWatch or MonotonicClock (check my spelling there)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs enhancement rn-enhancement release notes enhancement

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Enhance the MCP server capabilities

2 participants