From e4b2d42a397734b459b00625c3e27268120259f1 Mon Sep 17 00:00:00 2001 From: karan Date: Wed, 8 Jul 2026 17:42:12 +0530 Subject: [PATCH 1/2] Use Puma::Server instead of Puma::Launcher to fix Ctrl+C handling Puma::Launcher installs process-wide SIGINT/SIGTERM signal traps as part of its startup, intended for when Puma owns the entire process. Since the MCP server runs on a background thread inside msfconsole, these traps are process-global and silently override msfconsole's own Ctrl+C handling. Puma::Server is the lower-level, embeddable API and does not install any signal traps, making it safe to run inside a host process like msfconsole. Fixes #21650 --- lib/msf/core/mcp/server.rb | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/msf/core/mcp/server.rb b/lib/msf/core/mcp/server.rb index 864a3c45c9d9d..3cf53bf0b0b04 100644 --- a/lib/msf/core/mcp/server.rb +++ b/lib/msf/core/mcp/server.rb @@ -87,12 +87,12 @@ def start(transport: :stdio, host: 'localhost', port: 3000, auth_token: nil, min # Shutdown the MCP server and cleanup resources # def shutdown - @puma_launcher&.stop + @puma_server&.stop(true) rescue StandardError => e elog({ message: 'Error stopping Puma', exception: e }, LOG_SOURCE, LOG_ERROR) ensure @puma_log_io&.close - @puma_launcher = nil + @puma_server = nil @puma_log_io = nil @msf_client&.shutdown @mcp_server = nil @@ -130,8 +130,7 @@ def start_stdio def start_http(host, port, auth_token, min_threads: PUMA_MIN_THREADS, max_threads: PUMA_MAX_THREADS, workers: PUMA_WORKERS) require 'rack' require 'puma' - require 'puma/configuration' - require 'puma/launcher' + require 'puma/server' require 'puma/log_writer' transport = ::MCP::Server::Transports::StreamableHTTPTransport.new(@mcp_server) @@ -147,26 +146,25 @@ def start_http(host, port, auth_token, min_threads: PUMA_MIN_THREADS, max_thread # Use Puma's server API directly so we can stop it gracefully on shutdown. bind_host = host.include?(':') ? "[#{host}]" : host @puma_log_io = File.open(File::NULL, 'w') - begin - puma_config = Puma::Configuration.new do |config| - config.bind "tcp://#{bind_host}:#{port}" - config.threads min_threads, max_threads - config.workers workers - config.log_requests false - config.app rack_app - end + begin # Suppress Puma's startup banner by providing a silent log writer log_writer = Puma::LogWriter.new(@puma_log_io, @puma_log_io) - @puma_launcher = Puma::Launcher.new(puma_config, log_writer: log_writer) - @puma_launcher.run + + @puma_server = Puma::Server.new(rack_app, nil, { + log_writer: log_writer, + min_threads: min_threads, + max_threads: max_threads + }) + @puma_server.add_tcp_listener(bind_host, port) + @puma_server.run.join rescue StandardError begin - @puma_launcher&.stop + @puma_server&.stop(true) rescue StandardError nil end - @puma_launcher = nil + @puma_server = nil @puma_log_io&.close @puma_log_io = nil raise From 85f3312adceb9f5ad680df025004cdf49a4f74e5 Mon Sep 17 00:00:00 2001 From: karan Date: Fri, 10 Jul 2026 11:49:40 +0530 Subject: [PATCH 2/2] Preserve Puma::Launcher clustering when workers > 0 Puma::Server does not support clustering, so falling back to Puma::Launcher when workers > 0 preserves the existing MSF_MCP_WORKERS behavior for the standalone msfmcpd binary, while still using Puma::Server (no signal trapping) for the plugin's embedded case where workers defaults to 0. --- lib/msf/core/mcp/server.rb | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/mcp/server.rb b/lib/msf/core/mcp/server.rb index 3cf53bf0b0b04..efebb875385c4 100644 --- a/lib/msf/core/mcp/server.rb +++ b/lib/msf/core/mcp/server.rb @@ -88,11 +88,13 @@ def start(transport: :stdio, host: 'localhost', port: 3000, auth_token: nil, min # def shutdown @puma_server&.stop(true) + @puma_launcher&.stop rescue StandardError => e elog({ message: 'Error stopping Puma', exception: e }, LOG_SOURCE, LOG_ERROR) ensure @puma_log_io&.close @puma_server = nil + @puma_launcher = nil @puma_log_io = nil @msf_client&.shutdown @mcp_server = nil @@ -146,25 +148,41 @@ def start_http(host, port, auth_token, min_threads: PUMA_MIN_THREADS, max_thread # Use Puma's server API directly so we can stop it gracefully on shutdown. bind_host = host.include?(':') ? "[#{host}]" : host @puma_log_io = File.open(File::NULL, 'w') - begin - # Suppress Puma's startup banner by providing a silent log writer log_writer = Puma::LogWriter.new(@puma_log_io, @puma_log_io) - @puma_server = Puma::Server.new(rack_app, nil, { - log_writer: log_writer, - min_threads: min_threads, - max_threads: max_threads - }) - @puma_server.add_tcp_listener(bind_host, port) - @puma_server.run.join + if workers.to_i > 0 + require 'puma/configuration' + require 'puma/launcher' + + puma_config = Puma::Configuration.new do |config| + config.bind "tcp://#{bind_host}:#{port}" + config.threads min_threads, max_threads + config.workers workers + config.log_requests false + config.app rack_app + end + + @puma_launcher = Puma::Launcher.new(puma_config, log_writer: log_writer) + @puma_launcher.run + else + @puma_server = Puma::Server.new(rack_app, nil, { + log_writer: log_writer, + min_threads: min_threads, + max_threads: max_threads + }) + @puma_server.add_tcp_listener(bind_host, port) + @puma_server.run.join + end rescue StandardError begin @puma_server&.stop(true) + @puma_launcher&.stop rescue StandardError nil end @puma_server = nil + @puma_launcher = nil @puma_log_io&.close @puma_log_io = nil raise