Skip to content

Add Bearer Authentication to the MCP server plugin#21622

Open
zeroSteiner wants to merge 3 commits into
rapid7:masterfrom
zeroSteiner:feat/mcp/plugin-auth
Open

Add Bearer Authentication to the MCP server plugin#21622
zeroSteiner wants to merge 3 commits into
rapid7:masterfrom
zeroSteiner:feat/mcp/plugin-auth

Conversation

@zeroSteiner

Copy link
Copy Markdown
Contributor

Description

This adds bearer authentication to the MCP server started by the plugin, bringing it inline with the same behavior added in #21527. The value meanings are the same, by default a random token is generated, if the user explicitly sets a blank value, authentication is disabled otherwise the token is used as-is.

Related Issue:

Breaking Changes

None

Reviewer Notes

Verification Steps

Use the same steps from #21527

    • Load the plugin
    • Start the server by default, see a randomly generated API token displayed
    • Stop the server and restart it, adding AuthToken= to the end, see that now authentication is disabled
    • Stop the server and restart it, adding AuthToken=whatever to the end, see that now authentication is enabled, and the token is not echoed back to the user. They gave it to us, they know it, we don't need to be logging it anywhere.

Test Evidence

msf exploit(windows/smb/psexec) > load mcp
[*] MCP plugin loaded. Use mcp start to start the server.
[*] Successfully loaded plugin: mcp
msf exploit(windows/smb/psexec) > mcp start
[*] Auto-started msgrpc - User: msf, Pass: 2MXwknnMf7zy
[*] MCP server listening on http://localhost:3000/
[*] Authentication: Bearer token (auto-generated)
[*]   Configure your MCP client with: Authorization: Bearer 43b93f53b68816f30dff19058fb4f676234e1943dc67ffa232b53b7a3e96733d
[*] Starting MCP server on HTTP transport...
msf exploit(windows/smb/psexec) > mcp stop
[*] MCP server stopped
msf exploit(windows/smb/psexec) > mcp start AuthToken=
key: "AuthToken" value: ""
[*] MCP server listening on http://localhost:3000/
[*] Authentication: disabled
[*] Starting MCP server on HTTP transport...
msf exploit(windows/smb/psexec) > mcp restart AuthToken=imhungryisitlunchtimeyet
[*] MCP server listening on http://localhost:3000/
[*] Authentication: enabled
[*] Starting MCP server on HTTP transport...
msf exploit(windows/smb/psexec) >

Environment

Field Details
Operating System Fedora
Target Software/Hardware N/A
Docker Image / Vagrant Setup

AI Usage Disclosure

Did everything by hand.

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.

Comment thread plugins/mcp.rb
Comment on lines 300 to +312
@server_thread = framework.threads.spawn('MCPServer', false) do
@mcp_server.start(transport: :http, host: host, port: port)
print_status("Starting MCP server on HTTP transport...")
@mcp_server.start(transport: :http, host: host, port: port, auth_token: auth_token)
end

@started_at = Time.now
print_server_status(mcp_config)
print_status("MCP server listening on http://#{Rex::Socket.to_authority(mcp_config[:host], mcp_config[:port])}/")
if auth_token_generated
print_status("Authentication: Bearer token (auto-generated)")
print_status(" Configure your MCP client with: Authorization: Bearer #{auth_token}")
else
print_status("Authentication: #{auth_token ? 'enabled' : 'disabled'}")
end

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.

While this code is duplicated, I took care so that what is printed is consistent between the plugin and the msfmcpd stand-alone utility, so it's not visually obvious that they're different.

@sjanusz-r7 sjanusz-r7 self-assigned this Jul 2, 2026
@sjanusz-r7

Copy link
Copy Markdown
Contributor

Two things came up during testing:

  • Loading mcp plugin, calling mcp start results in Ctrl + C interrupt not working:
msf auxiliary(scanner/discovery/udp_sweep) > Interrupt: use the 'exit' command to quit
msf auxiliary(scanner/discovery/udp_sweep) > mcp start
[*] Auto-started msgrpc - User: msf, Pass: x
[*] MCP server listening on http://localhost:3000/
[*] Authentication: Bearer token (auto-generated)
[*]   Configure your MCP client with: Authorization: Bearer x
[*] Starting MCP server on HTTP transport...
msf auxiliary(scanner/discovery/udp_sweep) > // Ctrl + C does nothing here, even after calling `mcp stop`
  • I can start up the mcp daemon and the mcp plugin together, both report as listening:
## Tab 1
bundle exec ./msfmcpd --mcp-transport http
...
MCP server listening on http://localhost:3000/

## Tab 2
msf auxiliary(scanner/discovery/udp_sweep) > mcp start
[*] MCP server listening on http://localhost:3000/
[*] Authentication: Bearer token (auto-generated)
[*]   Configure your MCP client with: Authorization: Bearer x
[*] Starting MCP server on HTTP transport...

I expected the second tab to fail, as the address is already in use.

Otherwise, the bearer auth tests:

Default auto-generated token

// No token
curl http://localhost:3000/
{"error":"Unauthorized"}

// Correct token
curl -H "Authorization: Bearer foo" http://localhost:3000/
{"error":"Missing session ID"}

// Incorrect token
curl -H "Authorization: Bearer wrong_foo" http://localhost:3000/
{"error":"Unauthorized"}

Custom Token

Starting:

mcp start AuthToken='foo'
[*] MCP server listening on http://localhost:3000/
[*] Authentication: enabled
[*] Starting MCP server on HTTP transport...

Then:

// No token
curl http://localhost:3000/
{"error":"Unauthorized"}

// Correct token
curl -H "Authorization: Bearer foo" http://localhost:3000/
{"error":"Missing session ID"}

// Wrong token
curl -H "Authorization: Bearer wrong_foo" http://localhost:3000/
{"error":"Unauthorized"}

No auth token

Starting:

mcp start AuthToken=''
[*] MCP server listening on http://localhost:3000/
[*] Authentication: disabled
[*] Starting MCP server on HTTP transport...

Then:

// No token
curl http://localhost:3000/                               
{"error":"Missing session ID"}

// Any token provided
curl -H "Authorization: Bearer foo" http://localhost:3000/
{"error":"Missing session ID"}

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

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants