diff --git a/docs/metasploit-framework.wiki/How-to-use-Metasploit-MCP-Server.md b/docs/metasploit-framework.wiki/How-to-use-Metasploit-MCP-Server.md index c46d7b671014c..a581da29b05ca 100644 --- a/docs/metasploit-framework.wiki/How-to-use-Metasploit-MCP-Server.md +++ b/docs/metasploit-framework.wiki/How-to-use-Metasploit-MCP-Server.md @@ -330,6 +330,9 @@ The [MCP Inspector](https://github.com/modelcontextprotocol/inspector) is an int ``` npx @modelcontextprotocol/inspector + +# stdio transport type usage: +npx @modelcontextprotocol/inspector -- bundle exec ./msfmcpd ``` ## Troubleshooting diff --git a/lib/msf/core/mcp/application.rb b/lib/msf/core/mcp/application.rb index 8d1ba4efe5a31..29542026b1d1c 100644 --- a/lib/msf/core/mcp/application.rb +++ b/lib/msf/core/mcp/application.rb @@ -45,6 +45,8 @@ def run authenticate_metasploit initialize_mcp_server start_mcp_server + rescue Interrupt + shutdown('INT') rescue Msf::MCP::Config::ValidationError, Msf::MCP::Config::ConfigurationError => e handle_configuration_error(e) rescue Msf::MCP::Metasploit::ConnectionError => e @@ -164,8 +166,9 @@ def initialize_logger # # @return [void] def install_signal_handlers - Signal.trap('INT') { shutdown('INT'); exit 0 } - Signal.trap('TERM') { shutdown('TERM'); exit 0 } + @main_thread = Thread.current + Signal.trap('INT') { @main_thread.raise(Interrupt) } + Signal.trap('TERM') { @main_thread.raise(Interrupt) } end # Load configuration from file or use defaults diff --git a/lib/msf/core/mcp/config/loader.rb b/lib/msf/core/mcp/config/loader.rb index 75f9cf39e3b7e..642f2abcd48a0 100644 --- a/lib/msf/core/mcp/config/loader.rb +++ b/lib/msf/core/mcp/config/loader.rb @@ -54,7 +54,7 @@ def self.apply_defaults(config) config[:logging] ||= {} config[:msf_api][:type] ||= 'messagepack' - config[:msf_api][:host] ||= 'localhost' + config[:msf_api][:host] ||= '127.0.0.1' config[:msf_api][:port] ||= (config[:msf_api][:type] == 'json-rpc') ? 8081 : 55553 config[:msf_api][:ssl] = config[:msf_api].fetch(:ssl, true) diff --git a/lib/msf/core/mcp/metasploit/response_transformer.rb b/lib/msf/core/mcp/metasploit/response_transformer.rb index 4a6999aadbc14..0b39f7dc4a1c7 100644 --- a/lib/msf/core/mcp/metasploit/response_transformer.rb +++ b/lib/msf/core/mcp/metasploit/response_transformer.rb @@ -52,6 +52,7 @@ def self.transform_module_info(info) stance: info['stance'], actions: info['actions'], default_action: info['default_action'], + notes: info['notes'], # TODO: write transformer for options options: info['options'] }.compact @@ -76,7 +77,8 @@ def self.transform_hosts(response) os_language: host['os_lang'], updated_at: format_timestamp(host['updated_at']), purpose: host['purpose'], - info: host['info'] + info: host['info'], + comments: host['comments'] }.compact end end diff --git a/lib/msf/core/mcp/rpc_manager.rb b/lib/msf/core/mcp/rpc_manager.rb index ee37460412f50..1cf884284ebd0 100644 --- a/lib/msf/core/mcp/rpc_manager.rb +++ b/lib/msf/core/mcp/rpc_manager.rb @@ -125,7 +125,7 @@ def wait_for_rpc(timeout: DEFAULT_WAIT_TIMEOUT, interval: DEFAULT_WAIT_INTERVAL) "Timed out waiting for RPC server after #{timeout} seconds" end - @output.puts 'Waiting for RPC server to become available...' + @output.puts "Waiting for RPC server to become available at #{Rex::Socket.to_authority(@config[:msf_api][:host], @config[:msf_api][:port])}..." sleep(interval) end end diff --git a/lib/msf/core/mcp/tools/host_info.rb b/lib/msf/core/mcp/tools/host_info.rb index cead5a9af0739..38b1237123aed 100644 --- a/lib/msf/core/mcp/tools/host_info.rb +++ b/lib/msf/core/mcp/tools/host_info.rb @@ -72,7 +72,8 @@ class HostInfo < ::MCP::Tool os_language: { type: 'string' }, updated_at: { type: 'string' }, purpose: { type: 'string' }, - info: { type: 'string' } + info: { type: 'string' }, + comments: { type: 'string' } } } } diff --git a/lib/msf/core/mcp/tools/module_info.rb b/lib/msf/core/mcp/tools/module_info.rb index de2bcacf7be6c..cdf93e248f34b 100644 --- a/lib/msf/core/mcp/tools/module_info.rb +++ b/lib/msf/core/mcp/tools/module_info.rb @@ -59,6 +59,7 @@ class ModuleInfo < ::MCP::Tool privileged: { type: 'boolean' }, has_check_method: { type: 'boolean' }, default_options: { type: 'object' }, + notes: { type: 'object' }, references: { type: 'array', items: { type: ['string', 'object'] } }, targets: { type: 'object' }, default_target: { type: 'integer' }, diff --git a/spec/lib/msf/core/mcp/config/loader_spec.rb b/spec/lib/msf/core/mcp/config/loader_spec.rb index 8f78a03819300..6e9554822bd3a 100644 --- a/spec/lib/msf/core/mcp/config/loader_spec.rb +++ b/spec/lib/msf/core/mcp/config/loader_spec.rb @@ -207,9 +207,9 @@ expect(config[:msf_api][:type]).to eq('messagepack') end - it 'sets default host to localhost' do + it 'sets default host to 127.0.0.1' do config = described_class.load_from_hash(config_hash) - expect(config[:msf_api][:host]).to eq('localhost') + expect(config[:msf_api][:host]).to eq('127.0.0.1') end it 'sets default port to 55553 for messagepack' do diff --git a/spec/lib/msf/core/mcp/metasploit/response_transformer_spec.rb b/spec/lib/msf/core/mcp/metasploit/response_transformer_spec.rb index 79b2ebf70bb49..679f042a04ce3 100644 --- a/spec/lib/msf/core/mcp/metasploit/response_transformer_spec.rb +++ b/spec/lib/msf/core/mcp/metasploit/response_transformer_spec.rb @@ -71,6 +71,7 @@ 'privileged' => true, 'check' => true, 'default_options' => { 'Option1' => 'Value1' }, + 'notes' => { 'SideEffects' => ['IOC_IN_LOGS'], 'Stability' => ['CRASH_SAFE'] }, 'references' => [{'CVE' => '2017-0144'}, {'URL' => 'https://example.com'}], 'targets' => { 0 => 'Windows 7', 1 => 'Windows 8' }, 'default_target' => 0, @@ -99,6 +100,7 @@ privileged: true, has_check_method: true, default_options: { 'Option1' => 'Value1' }, + notes: { 'SideEffects' => ['IOC_IN_LOGS'], 'Stability' => ['CRASH_SAFE'] }, references: [{'CVE' => '2017-0144'}, {'URL' => 'https://example.com'}], targets: { 0 => 'Windows 7', 1 => 'Windows 8' }, default_target: 0, @@ -173,6 +175,7 @@ 'os_lang' => 'English', 'purpose' => 'server', 'info' => 'Web server', + 'comments' => 'Primary web server', 'state' => 'alive', 'created_at' => 1609459200, 'updated_at' => 1640995200 @@ -192,7 +195,8 @@ hostname: 'testhost', os_name: 'Linux', os_flavor: 'Ubuntu', - state: 'alive' + state: 'alive', + comments: 'Primary web server' ) expect(result[0][:created_at]).to eq('2021-01-01T00:00:00Z') expect(result[0][:updated_at]).to eq('2022-01-01T00:00:00Z')