From a417522f1a9e4667e3498c500d6b2122d0fa9dba Mon Sep 17 00:00:00 2001 From: giveen Date: Wed, 21 Jan 2026 11:31:03 -0700 Subject: [PATCH] chore(mcp): remove docs refs to vendored adapters; add example adapter scaffold and tests --- .env.example | 24 ++++----- pentestagent/interface/tui.py | 9 ++-- pentestagent/mcp/example_adapter.py | 83 +++++++++++++++++++++++++++++ pentestagent/mcp/manager.py | 54 +++---------------- pentestagent/workspaces/utils.py | 2 +- tests/test_mcp_scaffold.py | 22 ++++++++ 6 files changed, 127 insertions(+), 67 deletions(-) create mode 100644 pentestagent/mcp/example_adapter.py create mode 100644 tests/test_mcp_scaffold.py diff --git a/.env.example b/.env.example index 88b5dc0..f81de82 100644 --- a/.env.example +++ b/.env.example @@ -17,21 +17,15 @@ PENTESTAGENT_DEBUG=true # vendored MCP servers and helper daemons. Set to `true` to enable auto-start. # - Defaults are `false` to avoid automatically running networked services. -# Vendored HexStrike MCP adapter (legacy name support: LAUNCH_HEXSTRIKE) -LAUNCH_HEXTRIKE=false -#LAUNCH_HEXSTRIKE=false # alternate spelling (kept for compatibility) - -# Metasploit MCP (MetasploitMCP) -# When `LAUNCH_METASPLOIT_MCP=true` the setup script may attempt to start -# `msfrpcd` (Metasploit RPC daemon) and then start the vendored MetasploitMCP -# HTTP/SSE server. Provide `MSF_PASSWORD` if you want the setup script to -# auto-launch `msfrpcd` (it will never invoke sudo). -LAUNCH_METASPLOIT_MCP=false - -# When set to `true`, the subtree helper scripts (e.g. scripts/add_metasploit_subtree.sh) -# will force a pull/update of vendored subtrees. Useful when you want to refresh -# the third_party trees during setup. Set to `true` to enable. -FORCE_SUBTREE_PULL=true +# MCP adapters and vendored integrations +# The project no longer vendors external MCP adapters such as HexStrike +# or MetasploitMCP. Operators who need external adapters should install +# and run them manually (for example under `third_party/`) and then +# configure `mcp_servers.json` to reference the adapter. +# +# A minimal example adapter scaffold is provided at +# `pentestagent/mcp/example_adapter.py` to help implement adapters that +# match the expected adapter interface. # Metasploit RPC (msfrpcd) connection settings # - `MSF_USER`/`MSF_PASSWORD`: msfrpcd credentials (keep password secret) diff --git a/pentestagent/interface/tui.py b/pentestagent/interface/tui.py index 2a04ba7..df8941c 100644 --- a/pentestagent/interface/tui.py +++ b/pentestagent/interface/tui.py @@ -1314,11 +1314,10 @@ class PentestAgentTUI(App): self.rag_engine = None # MCP: automatic install/start has been removed. Operators should - # run the scripts in `third_party/` manually to install and start - # any vendored MCP servers (e.g., HexStrike, MetasploitMCP) and - # then configure `mcp_servers.json` accordingly. If a config is - # present, `MCPManager` will auto-connect and register tools so - # the TUI can display MCP-provided tools. + # install and run any external MCP adapters themselves (for + # example under `third_party/`) and then configure + # `mcp_servers.json` accordingly. A minimal example adapter is + # available at `pentestagent/mcp/example_adapter.py`. try: from ..mcp import MCPManager diff --git a/pentestagent/mcp/example_adapter.py b/pentestagent/mcp/example_adapter.py new file mode 100644 index 0000000..bed58a0 --- /dev/null +++ b/pentestagent/mcp/example_adapter.py @@ -0,0 +1,83 @@ +"""Minimal MCP adapter scaffold for PentestAgent. + +This module provides a small example adapter and a base interface that +adapter implementers can follow. Adapters are expected to provide a +lightweight set of methods so the `MCPManager` or external tools can +manage adapter lifecycle and issue tool calls. This scaffold intentionally +does not auto-start external processes; it's a development aid only. + +Implemented surface (example): + - `BaseAdapter` (abstract interface) + - `ExampleAdapter` (in-process mock adapter for testing) + +Usage: + - Use `ExampleAdapter` as a working reference when implementing real + adapters under `third_party/` or when wiring an adapter into + `mcp_servers.json`. +""" +from __future__ import annotations + +from typing import Any, Dict, List, Optional + + +class BaseAdapter: + """Minimal adapter interface. + + Implementers should provide at least these methods. Real adapters may + expose additional methods such as `stop_sync` or an underlying + `_process` attribute that the manager may inspect when cleaning up. + """ + + name: str = "base" + + async def start(self) -> None: # pragma: no cover - interface + raise NotImplementedError() + + async def stop(self) -> None: # pragma: no cover - interface + raise NotImplementedError() + + def stop_sync(self) -> None: # pragma: no cover - optional + raise NotImplementedError() + + async def list_tools(self) -> List[Dict[str, Any]]: # pragma: no cover - interface + raise NotImplementedError() + + async def call_tool(self, name: str, arguments: Dict[str, Any]) -> Any: # pragma: no cover - interface + raise NotImplementedError() + + +class ExampleAdapter(BaseAdapter): + """A trivial in-process adapter useful for tests and development. + + - `list_tools()` returns a single example tool definition. + - `call_tool()` returns a simple echo response. + """ + + name = "example" + + def __init__(self): + self._running = False + + async def start(self) -> None: + self._running = True + + async def stop(self) -> None: + self._running = False + + def stop_sync(self) -> None: + # Synchronous stop helper for manager cleanup code paths + self._running = False + + async def list_tools(self) -> List[Dict[str, Any]]: + return [ + { + "name": "ping", + "description": "Return a ping response", + "inputSchema": {"type": "object", "properties": {}}, + } + ] + + async def call_tool(self, name: str, arguments: Dict[str, Any]) -> Any: + if name == "ping": + return [{"type": "text", "text": "pong"}] + raise ValueError(f"Unknown tool: {name}") diff --git a/pentestagent/mcp/manager.py b/pentestagent/mcp/manager.py index ac65ba6..fed13d0 100644 --- a/pentestagent/mcp/manager.py +++ b/pentestagent/mcp/manager.py @@ -142,19 +142,11 @@ class MCPManager: start_on_launch=config.get("start_on_launch", False), description=config.get("description", ""), ) - # Allow override via environment variables for vendored MCP servers. - # Per-adapter overrides supported: - # - Hexstrike: LAUNCH_HEXTRIKE or LAUNCH_HEXSTRIKE - # - Metasploit: LAUNCH_METASPLOIT_MCP - # If set to a truthy value (1,true,y), force-enable auto-start for matching vendored server. - # If set to a falsy value (0,false,n), force-disable auto-start for matching vendored server. - # NOTE: Removed automatic LAUNCH_* overrides. MCP vendored adapters and - # auto-start behavior is intentionally disabled. MCP servers should be - # installed and configured manually via scripts in `third_party/` and - # `mcp_servers.json` should be prepared by the operator. Automatic - # start-on-launch logic previously controlled by environment variables - # is deprecated to simplify setup and avoid unexpected background - # processes. + # Environment-based auto-start overrides (previously supported via + # LAUNCH_* variables) have been removed. MCP adapters are no + # longer auto-started by the manager; operators should install and + # run adapters manually and add them to `mcp_servers.json` if they + # want `MCPManager` to connect to them. return servers except json.JSONDecodeError as e: @@ -330,39 +322,9 @@ class MCPManager: if name not in servers_config: return None config = servers_config[name] - # If this appears to be a vendored Metasploit MCP entry, attempt to auto-start - # the vendored adapter so `pentestagent mcp test metasploit-local` works - try: - args_joined = " ".join(config.args or []) - cmd_str = config.command or "" - is_msf = ( - (name and "metasploit" in name.lower()) - or ("third_party/MetasploitMCP" in cmd_str) - or ("third_party/MetasploitMCP" in args_joined) - ) - if is_msf: - launch_msf_env = os.environ.get("LAUNCH_METASPLOIT_MCP") - launch_disabled = False - if launch_msf_env is not None: - v = str(launch_msf_env).strip().lower() - if v in ("0", "false", "no", "n"): - launch_disabled = True - if not launch_disabled: - try: - from .metasploit_adapter import MetasploitAdapter - - adapter = MetasploitAdapter() - started = await adapter.start() - if started: - try: - self._started_adapters[name] = adapter - except Exception: - pass - print(f"[MCP] Auto-started vendored server for {name}") - except Exception: - pass - except Exception: - pass + # Auto-start of vendored adapters (HexStrike/MetasploitMCP) has been + # removed. Operators should install and run any third-party MCP adapters + # manually under `third_party/` and configure `mcp_servers.json`. server = await self._connect_server(config) if server: diff --git a/pentestagent/workspaces/utils.py b/pentestagent/workspaces/utils.py index 9c3539b..806316d 100644 --- a/pentestagent/workspaces/utils.py +++ b/pentestagent/workspaces/utils.py @@ -36,7 +36,7 @@ def get_loot_base(root: Optional[Path] = None) -> Path: def get_loot_file(relpath: str, root: Optional[Path] = None) -> Path: """Return a Path for a file under the loot base, creating parent dirs. - Example: get_loot_file('artifacts/hexstrike.log') + Example: get_loot_file('artifacts/example.log') """ base = get_loot_base(root=root) p = base / relpath diff --git a/tests/test_mcp_scaffold.py b/tests/test_mcp_scaffold.py new file mode 100644 index 0000000..d612ea0 --- /dev/null +++ b/tests/test_mcp_scaffold.py @@ -0,0 +1,22 @@ +import asyncio + + +from pentestagent.mcp.example_adapter import ExampleAdapter + + +def test_example_adapter_list_and_call(): + adapter = ExampleAdapter() + + async def run(): + await adapter.start() + tools = await adapter.list_tools() + assert isinstance(tools, list) + assert any(t.get("name") == "ping" for t in tools) + + result = await adapter.call_tool("ping", {}) + assert isinstance(result, list) + assert result[0].get("text") == "pong" + + await adapter.stop() + + asyncio.run(run())