MCP Server, specs and documentation#21315
Conversation
2879f3e to
4745002
Compare
4745002 to
04ffe3c
Compare
There was a problem hiding this comment.
Pull request overview
Adds an initial read-only Metasploit MCP Server (msfmcpd) implementation that exposes Metasploit data via MCP tools, plus supporting config/loading, RPC auto-start, logging, and a broad test suite (unit + integration).
Changes:
- Introduces MCP server wrapper + 8 MCP tools (module search/info, DB hosts/services/vulns/notes/creds/loot).
- Adds Metasploit RPC clients (MessagePack + JSON-RPC), response transformation, rate limiting, config loading/validation, and optional sanitized file logging.
- Adds extensive RSpec coverage, WebMock-based integration tests, config fixtures/examples, and wires new dependencies (
mcp,webmock).
Reviewed changes
Copilot reviewed 60 out of 61 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/lib/msfmcp/security/rate_limiter_spec.rb | Unit tests for token-bucket rate limiter + concurrency behavior. |
| spec/lib/msfmcp/metasploit/client_spec.rb | Tests for protocol selection and delegation in client facade. |
| spec/lib/msfmcp/mcp/tools/vulnerability_info_spec.rb | Tests MCP tool contract/behavior for vulnerability queries. |
| spec/lib/msfmcp/mcp/tools/service_info_spec.rb | Tests MCP tool contract/behavior for service queries. |
| spec/lib/msfmcp/mcp/tools/search_modules_spec.rb | Tests MCP tool contract/behavior for module searching + pagination. |
| spec/lib/msfmcp/mcp/tools/note_info_spec.rb | Tests MCP tool contract/behavior for note queries. |
| spec/lib/msfmcp/mcp/tools/module_info_spec.rb | Tests MCP tool contract/behavior for module details. |
| spec/lib/msfmcp/mcp/tools/loot_info_spec.rb | Tests MCP tool contract/behavior for loot queries. |
| spec/lib/msfmcp/mcp/tools/host_info_spec.rb | Tests MCP tool contract/behavior for host queries. |
| spec/lib/msfmcp/mcp/tools/credential_info_spec.rb | Tests MCP tool contract/behavior for credential queries. |
| spec/lib/msfmcp/logging/logger_spec.rb | Tests redaction + log-level filtering for file logger. |
| spec/lib/msfmcp/errors_spec.rb | Tests inheritance/structure of new error classes. |
| spec/integration/msfmcpd/tool_execution_search_spec.rb | End-to-end tool execution through HTTP stubs (module search). |
| spec/integration/msfmcpd/tool_execution_db_spec.rb | End-to-end tool execution through HTTP stubs (DB tools). |
| spec/integration/msfmcpd/rate_limiting_spec.rb | Integration coverage for rate limiting across tool calls. |
| spec/integration/msfmcpd/messagepack_auth_flow_spec.rb | Integration coverage for MessagePack auth + token reuse. |
| spec/integration/msfmcpd/jsonrpc_auth_flow_spec.rb | Integration coverage for JSON-RPC bearer auth behavior. |
| spec/integration/msfmcpd/error_handling_spec.rb | Integration coverage for network/auth/API error conversions. |
| spec/integration/msfmcpd/config_loading_spec.rb | Integration coverage for config defaults + env/CLI overrides. |
| spec/file_fixtures/config_files/msfmcpd/valid_messagepack.yaml | Fixture config for MessagePack-mode integration tests. |
| spec/file_fixtures/config_files/msfmcpd/valid_jsonrpc.yaml | Fixture config for JSON-RPC-mode integration tests. |
| msfrpcd | Allows RPC credentials to be provided via environment variables. |
| msfmcpd | Adds executable wrapper to run the MCP server application. |
| metasploit-framework.gemspec | Adds runtime dependency on the MCP Ruby SDK gem. |
| lib/msfmcp/security/rate_limiter.rb | Implements token-bucket rate limiter with mutex for concurrency. |
| lib/msfmcp/security/input_validator.rb | Adds input validation helpers (query/ports/protocol/pagination/etc.). |
| lib/msfmcp/rpc_manager.rb | Adds auto-start management for local msfrpcd lifecycle. |
| lib/msfmcp/metasploit/response_transformer.rb | Converts RPC responses into MCP-friendly structured output. |
| lib/msfmcp/metasploit/messagepack_client.rb | Implements MessagePack RPC client with retry + logging hooks. |
| lib/msfmcp/metasploit/jsonrpc_client.rb | Implements JSON-RPC client using bearer token auth. |
| lib/msfmcp/metasploit/client.rb | Facade that selects MessagePack vs JSON-RPC implementation. |
| lib/msfmcp/mcp/tools/vulnerability_info.rb | MCP tool implementation for vulnerabilities query. |
| lib/msfmcp/mcp/tools/service_info.rb | MCP tool implementation for services query. |
| lib/msfmcp/mcp/tools/search_modules.rb | MCP tool implementation for module searching. |
| lib/msfmcp/mcp/tools/note_info.rb | MCP tool implementation for notes query. |
| lib/msfmcp/mcp/tools/module_info.rb | MCP tool implementation for module details. |
| lib/msfmcp/mcp/tools/loot_info.rb | MCP tool implementation for loot query. |
| lib/msfmcp/mcp/tools/host_info.rb | MCP tool implementation for hosts query. |
| lib/msfmcp/mcp/tools/credential_info.rb | MCP tool implementation for credentials query. |
| lib/msfmcp/mcp/server.rb | Wraps MCP server startup and stdio/http transports with logging hooks. |
| lib/msfmcp/logging/logger.rb | Adds opt-in JSONL file logger with recursive sanitization. |
| lib/msfmcp/errors.rb | Defines error taxonomy across config/security/metasploit/mcp layers. |
| lib/msfmcp/config/validator.rb | Adds config validation for core msf_api + mcp settings. |
| lib/msfmcp/config/loader.rb | Loads YAML config, applies defaults, supports env overrides. |
| lib/msfmcp.rb | Main library entrypoint requiring all MCP/server components. |
| docs/navigation.rb | Adds MCP server doc page to documentation navigation. |
| config/mcp_config_jsonrpc.yaml.example | Example JSON-RPC config for MCP server. |
| config/mcp_config.yaml.example | Example MessagePack config for MCP server. |
| Gemfile.lock | Adds mcp and test deps (webmock and transitive gems). |
| Gemfile | Adds webmock to test group for HTTP stubbing in specs. |
| class Logger | ||
| SENSITIVE_PATTERNS = { | ||
| password: /password[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i, | ||
| token_keyval: /token[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i, | ||
| token_header: /token\s+[a-zA-Z0-9_\-\.]+/i, | ||
| api_key: /api[_-]?key[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i, | ||
| secret: /secret[_-]?key[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i, | ||
| credential: /credential[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i, | ||
| auth: /auth[\"']?\s*[:=]\s*[\"']?[^\"',\s}]+/i, | ||
| bearer: /bearer\s+[a-zA-Z0-9_\-\.]+/i | ||
| }.freeze | ||
|
|
||
| REDACTED = '[REDACTED]' |
There was a problem hiding this comment.
This probably needs to stay here for now though I could definitely see it being helpful in the future. The rest of this module though should use Rex::Logging now that it's available. That'll let us swap sinks and use the same logging subsystem as the rest of MSF.
There was a problem hiding this comment.
Framework is not loaded in the MCP server at this point. This is not necessary since there is no dependency; the MCP server communicated via RPC only. I tried to load all the individual files related to logging, but it is really messy and complex. Using Rex::Logging would require a big refactoring of the MCP server, with probably many design changes. Do you think it is worth the effort? What would be the benefit of using Rex::Logging in this context?
If you still think it's a real blocker, I will spend time trying to redesign the whole logging system.
There was a problem hiding this comment.
Well I have two concerns about leaving it as it is:
- It might be copied into new new code either by a human or an LLM thinking it's the correct pattern moving forward
- While the MCP server is not loading Framework, once Support starting the MCP server from within msfconsole #21297 is implemented, it's likely that Framework will need to load the MCP server at which point it will be helpful to have a unified log interface.
There was a problem hiding this comment.
Would something like this work? cdelafuente-r7#5 I still need to test it but I think it's working pretty well. It's mapping the original log levels to the rex names/numbers but we could change that so it's Rex::Logging first and foremost if we wanted.
There was a problem hiding this comment.
Thank you for your PR @smcintyre-r7, I redesigned the logging capability using Rex::Logging and Rack middleware. I wanted to keep the JSON-based logs and the way the MCP server filters the logging level. Rex::Logging use a log level mechanism for this. Therefore, I mapped the the debug type to the log level (e.g. DEBUG -> LVL3, INFO -> LVL2, etc.). This way, it is still possible to filter the kind of log we want to be logged (e.g. DEBUG) and abstracting the Rex::Logging log level concept to the user. Please, let me know what you think.
|
Thank you @smcintyre-r7 for your review and testing. The hang you observed seems to be related to the MSF RPC daemon itself. The connection times out and I could reproduce the same issue using the I didn't know there were a Also, I'm looking into implementing your suggestions right now. |
- add the `--mcp-transport` option - prefix the MCP env. variable with `MSF_` - move the code under `lib/msf/core/mcp/` - move specs under `spec/lib/msf/core/mcp/` - change the namespace from `MsfMcp` to `Msf::RPC` - update the `lib/msf_autoload.rb` to exclude the mcp-related files - add missing validation for the `mcp`, `rate_limit and `logging` sections in the config file - remove duplicate error exception classes - fix an error in the transformers related to the `created_at` field - fix a small issue in the input validator when regex are used - update the way error is reported for MCP Tools to be compatible with the changes in the new `mcp` gem - update and add specs
|
I implemented all the suggested changes, except the one related to |
- remove the original Logger - use Rex::Logging with helper methods (dlog, ilog, etc.) - add `sanitize` configuration option - create Sanitizing, JsonFlatfile and JsonStream sinks for JSON logging format - minor updates in apply_default (Loader) - update the re-authentication logic (fix a specific usecase) - add a Rack middleware that logs MCP HTTP request/response - use Rex::Socket::Tcp instead of TcpSocket - update the ensure_rpc_available for better validation - use around_request instead of the deprecated SDK instrumentation for logging - update and add specs
smcintyre-r7
left a comment
There was a problem hiding this comment.
New logging looks good. I setup my config like this so I could watch it while testing:
# Logging (optional - defaults shown)
logging:
enabled: true
level: DEBUG # DEBUG, INFO, WARN, ERROR
log_file: /dev/stdout
sanitize: falseAfter loading it up and testing all 8 different methods everything looks pretty good. I noticed a single issue in msf_host_info for which I'll open a dedicated ticket as to not postpone this further.
Testing Output
claude
╭─── Claude Code v2.1.123 ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ │ Tips for getting started │
│ Welcome back Spencer! │ Run /init to create a CLAUDE.md file with instructions for Claude │
│ │ Note: You have launched claude in your home directory. For the best experience, launch it in a project directory instead. │
│ ▐▛███▜▌ │ ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── │
│ ▝▜█████▛▘ │ What's new │
│ ▘▘ ▝▝ │ Fixed OAuth authentication failing with a 401 retry loop when `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1` is set │
│ Opus 4.7 (1M context) with xh… · Claude Max · │ Added `ANTHROPIC_BEDROCK_SERVICE_TIER` environment variable to select a Bedrock service tier (`default`, `flex`, or `priority`), sent as the `X-Amzn-Bedrock-Service-Tier` header │
│ zerosteiner@gmail.com's Organization │ Pasting a PR URL into the `/resume` search box now finds the session that created that PR (GitHub, GitHub Enterprise, GitLab, and Bitbucket) │
│ /home/smcintyre │ /release-notes for more │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
❯ I need to test my Metasploit MCP server. Can you please help me with that? You are connected to it.
● I'll load the Metasploit MCP tool schemas and run some tests against them.
● I'll exercise each tool in parallel — searching modules, fetching module details, and querying the database for hosts/services/vulns/creds/loot/notes.
Called metasploit 8 times (ctrl+o to expand)
● All 8 Metasploit MCP tools responded successfully. Quick summary of what came back:
Module tools
- msf_search_modules — "eternalblue" returned 5 modules (auxiliary + exploits) with rank/disclosure date
- msf_module_info — full details for exploit/windows/smb/ms17_010_eternalblue (description, options, targets, references, CVEs)
Database tools (workspace: default)
- msf_host_info — 3 hosts: 192.168.159.10, 192.168.159.132 (ubuntu2404), 192.168.159.136
- msf_service_info — 9 services (smb/dcerpc/ldap/http on ports 139/389/445/8080)
- msf_vulnerability_info — 3 vulns (SSH user code exec, LDAP login, cve_2026_31431_copy_fail)
- msf_credential_info — 5 creds (LDAP password + NTLM/NetNTLM hashes for smcintyre)
- msf_loot_info — 0 entries (empty, returned cleanly)
- msf_note_info — 2 notes (SMB domain lookupsid, host OS fingerprint)
Each response includes consistent metadata (query_time, total_items, returned_items, limit, offset) and a data array — pagination contract looks solid, and the empty-result case (loot) returns an empty array rather than erroring.
Want me to also exercise the filter parameters (e.g. host/ports/protocol/names/only_up/addresses CIDR), pagination via offset, and error paths (invalid workspace, bad module name)?
✻ Cooked for 26s
❯ Yes please
● Running filter, pagination, and error-path tests in parallel.
Called metasploit 11 times (ctrl+o to expand)
● Most paths work cleanly, but I spotted something suspicious: the CIDR 192.168.159.0/24 with only_up=true returned 0 hosts even though all 3 known hosts are in that range and marked state: "alive". Let me isolate whether it's the CIDR filter or only_up.
Called metasploit 3 times (ctrl+o to expand)
● Here are the consolidated results from the second batch:
Filters — working
- msf_service_info host + ports range + protocol: 4 services on 192.168.159.10:389-445/tcp ✅
- msf_service_info names=smb,ldap: 4 services, http/dcerpc/tcp correctly excluded ✅
- msf_vulnerability_info host + names array: 1 result for "SSH User Code Execution" on 192.168.159.132 ✅
- msf_note_info host + type: 1 result for smb.domain.lookupsid on 192.168.159.10 ✅
- msf_host_info single IP: 1 result ✅
- msf_host_info only_up=true alone: 3 results ✅
Pagination
- msf_search_modules query=smb: total_items=240, offset=0 and offset=3 returned 3 distinct results each — page boundary clean ✅
Error paths
- Invalid workspace → Metasploit API error: Invalid workspace ✅
- Invalid module name → Metasploit API error: Invalid Module ✅
- Empty result set (zzzzznoresults...) → total_items=0 with empty data array, no error ✅
🐛 Bug — CIDR filtering in msf_host_info
The addresses parameter does not expand CIDR ranges:
- addresses=192.168.159.10 (single IP) → 1 result ✅
- addresses=192.168.159.0/24 (CIDR) → 0 results ❌ — should return all 3 hosts (192.168.159.10, .132, .136)
- addresses=192.168.159.0/24 + only_up=true → 0 results (same root cause)
The CIDR is documented in the parameter description ("e.g., \"192.168.1.100\" or \"192.168.1.0/24\""), so this is a contract violation rather than a missing feature. Likely the server is doing an exact-string address match instead of subnet containment. Worth checking the host
query handler for IPAddr/Subnet parsing on the addresses argument.
Everything else looks solid — 8/8 tools functional, consistent metadata envelope, sane error messages.
✻ Cooked for 49s
Release NotesThis adds a read-only MCP server for Metasploit capable of retrieving information from the loaded modules and database. |

The Metasploit MCP Server (
msfmcpd) provides AI applications with secure, structured access to Metasploit Framework data through the Model Context Protocol (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 implementation relies on the official Ruby SDK for MCP (here).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.
All the details can be found in the documentation here.