chore(mcp): remove duplicate adapters, scripts, archives, example config, PR body and log

This commit is contained in:
giveen
2026-01-21 12:08:11 -07:00
parent e1b6a6af07
commit eccc8471e8
8 changed files with 0 additions and 440 deletions

View File

@@ -1,33 +0,0 @@
Summary:
Fixes runtime and UX bugs that prevented tool execution and caused inconsistent target selection in the TUI. Improves robustness across Textual versions and makes the target visible and authoritative to the LLM.
What was broken:
- TypeError when scheduling Textual workers: asyncio.create_task was given a Textual Worker (not a coroutine).
- LLM-generated flags-only terminal commands (e.g. -p 1-1024 ...) were passed to /bin/sh and caused '/bin/sh: Illegal option -'.
- Active workspace scope checks blocked scans when the target was not in the workspace, while stale/manual targets could persist in conversation and be used by the LLM.
- UI errors on some Textual versions from calling unsupported APIs (e.g. ScrollableContainer.mount_before), and duplicated in-chat target messages cluttered the chat.
What I changed (key files):
- pentestagent/interface/tui.py
- Stop wrapping @work-decorated methods with asyncio.create_task; use the returned Worker correctly.
- Ensure workspace activation/deactivation behavior: clear TUI/agent target on /workspace clear; restore last_target on activation.
- When operator sets a manual target (/target), append a short system AgentMessage so the LLM sees the change; track manual target and remove/supersede it when a workspace restores its saved target.
- Add a persistent header widget to display runtime/mode/target and remove duplicate in-chat target lines.
- Guard mount_before calls with a try/fallback to mount to support Textual versions without mount_before.
- pentestagent/agents/base_agent.py
- If a requested tool name is not found, fall back to the terminal tool and construct a best-effort command string from function-call arguments so semantic tool names (e.g., nmap) execute.
- Preserve workspace-scope validation but return explicit errors that instruct the operator how to proceed.
- pentestagent/tools/terminal/__init__.py
- Detect flags-only command strings and prefix them with a likely binary (nmap, gobuster, rustscan, masscan, curl, wget, etc.) using runtime-detected tools, preventing shell option errors.
- Make terminal execution tolerant to malformed inputs and avoid uncaught exceptions.
Rationale:
Textual workers are Worker objects; scheduling them as coroutines caused runtime errors. Handling Worker objects correctly preserves Textual semantics. LLMs sometimes emit partial or semantic commands; best-effort normalization reduces shell failures and improves task success. Explicit system messages and deterministic workspace restores ensure the LLM uses the intended target. A persistent header provides immediate operator context and avoids losing the active target when the chat scrolls.
Testing performed:
- Reproduced and fixed the Worker scheduling TypeError.
- Verified flags-only commands are now prefixed (nmap test) and no longer produce '/bin/sh: Illegal option -'.
- Walked through workspace/target flows to confirm authoritative target behavior.
- Confirmed mount_before fallback avoids AttributeError on older Textual versions.
Branch: changes pushed to giveen/bug-fix.

Binary file not shown.

Binary file not shown.

View File

@@ -1,10 +0,0 @@
{
"mcpServers": {
"hexstrike-local": {
"command": "python3",
"args": ["-u", "pentestagent/mcp/stdio_adapter.py"],
"description": "Generic StdIO adapter bridge to HTTP API (set STDIO_TARGET)",
"enabled": true
}
}
}

View File

@@ -1,84 +0,0 @@
#!/usr/bin/env bash
# Helper script to vendor MetasploitMCP into this repo using git subtree.
# Run from repository root.
set -euo pipefail
REPO_URL="${METASPLOIT_SUBTREE_REPO:-https://github.com/GH05TCREW/MetasploitMCP.git}"
PREFIX="third_party/MetasploitMCP"
BRANCH="main"
echo "This will add MetasploitMCP as a git subtree under ${PREFIX}."
echo "You can override the upstream repo with: METASPLOIT_SUBTREE_REPO=...\n"
echo "If the subtree already exists, the script will pull and rebase the subtree instead.\n"
if [ -d "${PREFIX}" ]; then
# If directory exists but is empty (left by manual mkdir or previous failed import),
# treat it as if the subtree is not yet added so we can perform the add operation.
if [ -z "$(ls -A "${PREFIX}" 2>/dev/null)" ]; then
echo "Detected empty directory at ${PREFIX}; adding subtree into it..."
mkdir -p "$(dirname "${PREFIX}")"
if git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash; then
echo "MetasploitMCP subtree added under ${PREFIX}."
else
echo "Failed to add subtree from ${REPO_URL}." >&2
echo "Check that the URL is correct or override with METASPLOIT_SUBTREE_REPO." >&2
exit 1
fi
exit 0
fi
# Directory exists; check whether the path is tracked in git.
if git ls-files --error-unmatch "${PREFIX}" >/dev/null 2>&1; then
echo "Detected existing subtree at ${PREFIX}."
if [ "${FORCE_SUBTREE_PULL:-false}" = "true" ]; then
echo "FORCE_SUBTREE_PULL=true: pulling latest changes into existing subtree..."
git subtree pull --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash || {
echo "git subtree pull failed; attempting without --squash..."
git subtree pull --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" || exit 1
}
echo "Subtree at ${PREFIX} updated."
else
echo "To update the existing subtree run:"
echo " FORCE_SUBTREE_PULL=true bash scripts/add_metasploit_subtree.sh"
echo "Or run manually: git subtree pull --prefix=\"${PREFIX}\" ${REPO_URL} ${BRANCH} --squash"
fi
else
# Directory exists but not tracked by git.
echo "Directory ${PREFIX} exists but is not tracked in git."
if [ "${FORCE_SUBTREE_PULL:-false}" = "true" ]; then
echo "FORCE_SUBTREE_PULL=true: backing up existing directory and attempting to add subtree..."
BACKUP="${PREFIX}.backup.$(date +%s)"
mv "${PREFIX}" "${BACKUP}" || { echo "Failed to move ${PREFIX} to ${BACKUP}" >&2; exit 1; }
# Ensure parent exists after move
mkdir -p "$(dirname "${PREFIX}")"
if git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash; then
echo "MetasploitMCP subtree added under ${PREFIX}."
echo "Removing backup ${BACKUP}."
rm -rf "${BACKUP}"
else
echo "Failed to add subtree from ${REPO_URL}. Restoring backup." >&2
rm -rf "${PREFIX}" || true
mv "${BACKUP}" "${PREFIX}" || { echo "Failed to restore ${BACKUP} to ${PREFIX}" >&2; exit 1; }
exit 1
fi
else
echo "To add the subtree into the existing directory, either remove/rename ${PREFIX} and retry,"
echo "or run with FORCE_SUBTREE_PULL=true to back up and add:"
echo " FORCE_SUBTREE_PULL=true bash scripts/add_metasploit_subtree.sh"
echo "Or override the repo with METASPLOIT_SUBTREE_REPO to use a different source."
exit 1
fi
fi
else
echo "Adding subtree for the first time..."
# Ensure parent dir exists for clearer errors
mkdir -p "$(dirname "${PREFIX}")"
if git subtree add --prefix="${PREFIX}" "${REPO_URL}" "${BRANCH}" --squash; then
echo "MetasploitMCP subtree added under ${PREFIX}."
else
echo "Failed to add subtree from ${REPO_URL}." >&2
echo "Check that the URL is correct or override with METASPLOIT_SUBTREE_REPO." >&2
exit 1
fi
fi

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Install vendored MetasploitMCP Python dependencies.
# This script will source a local .env if present so any environment
# variables (proxies/indices/LLM keys) are respected during installation.
HERE=$(dirname "${BASH_SOURCE[0]}")
ROOT=$(cd "$HERE/.." && pwd)
cd "$ROOT"
if [ -f ".env" ]; then
echo "Sourcing .env"
set -a
# shellcheck disable=SC1091
source .env
set +a
fi
REQ=third_party/MetasploitMCP/requirements.txt
if [ ! -f "$REQ" ]; then
echo "Cannot find $REQ. Is the MetasploitMCP subtree present?"
exit 1
fi
echo "Installing MetasploitMCP requirements from $REQ"
PY=$(which python || true)
if [ -n "${VIRTUAL_ENV:-}" ]; then
PY="$VIRTUAL_ENV/bin/python"
fi
"$PY" -m pip install --upgrade pip
"$PY" -m pip install -r "$REQ"
echo "MetasploitMCP dependencies installed. Note: external components may still be required."
exit 0

View File

@@ -1,136 +0,0 @@
#!/usr/bin/env python3
"""Small stdio JSON-RPC bridge for HexStrike HTTP API.
This adapter implements the minimal MCP stdio JSON-RPC surface
expected by `pentestagent`'s `StdioTransport`:
- Responds to `initialize` with a simple result
- Accepts `notifications/initialized` (notification)
- Implements `tools/list` returning one proxy tool: `http_api`
- Implements `tools/call` for the `http_api` tool and forwards
requests to the HexStrike HTTP API (configured via env var
`HEXSTRIKE_SERVER`, default `http://127.0.0.1:8888`).
Usage (mcp_servers.json):
{
"mcpServers": {
"hexstrike-local": {
"command": "python3",
"args": ["-u", "third_party/hexstrike/hexstrike_stdio_adapter.py"],
"description": "StdIO adapter bridge to HexStrike HTTP API"
}
}
}
"""
from __future__ import annotations
import json
import os
import sys
from typing import Any, Dict
try:
import requests
except Exception:
requests = None
BASE = os.environ.get("HEXSTRIKE_SERVER", "http://127.0.0.1:8888").rstrip("/")
def send_response(req_id: Any, result: Any = None, error: Any = None) -> None:
resp: Dict[str, Any] = {"jsonrpc": "2.0", "id": req_id}
if error is not None:
resp["error"] = {"code": -32000, "message": str(error)}
else:
resp["result"] = result if result is not None else {}
print(json.dumps(resp, separators=(",", ":")), flush=True)
def handle_tools_list(req_id: Any) -> None:
tools = [{"name": "http_api", "description": "Proxy to HexStrike HTTP API"}]
send_response(req_id, {"tools": tools})
def forward_http(path: str, method: str = "POST", params: Dict[str, Any] | None = None, body: Any | None = None) -> Any:
if requests is None:
raise RuntimeError("requests library is not available in adapter process")
url = path if path.startswith("http") else BASE + (path if path.startswith("/") else "/" + path)
method = (method or "POST").upper()
if method == "GET":
r = requests.get(url, params=params or {}, timeout=60)
else:
r = requests.post(url, json=body or {}, params=params or {}, timeout=300)
try:
return r.json()
except Exception:
return r.text
def handle_tools_call(req: Dict[str, Any]) -> None:
req_id = req.get("id")
params = req.get("params", {}) or {}
name = params.get("name")
arguments = params.get("arguments") or {}
if name != "http_api":
send_response(req_id, error=f"unknown tool '{name}'")
return
path = arguments.get("path")
if not path:
send_response(req_id, error="missing 'path' in arguments")
return
method = arguments.get("method", "POST")
body = arguments.get("body")
qparams = arguments.get("params")
try:
content = forward_http(path, method=method, params=qparams, body=body)
# Manager expects: result -> {"content": ...}
send_response(req_id, {"content": content})
except Exception as e:
send_response(req_id, error=str(e))
def main() -> None:
# Read newline-delimited JSON-RPC messages from stdin.
# Keep process alive until stdin is closed.
while True:
line = sys.stdin.readline()
if not line:
break
line = line.strip()
if not line:
continue
try:
req = json.loads(line)
except Exception:
# ignore invalid json
continue
method = req.get("method")
# Notifications have no id
req_id = req.get("id")
if method == "initialize":
send_response(req_id, {"capabilities": {}})
elif method == "notifications/initialized":
# notification — no response
continue
elif method == "tools/list":
handle_tools_list(req_id)
elif method == "tools/call":
handle_tools_call(req)
else:
# unknown method
if req_id is not None:
send_response(req_id, error=f"unsupported method '{method}'")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass

View File

@@ -1,137 +0,0 @@
#!/usr/bin/env python3
"""Generic stdio JSON-RPC adapter bridge to an HTTP API.
Configure via environment variables:
- `STDIO_TARGET` (default: "http://127.0.0.1:8888")
- `STDIO_TOOLS` (JSON list of tool descriptors, default: `[{"name":"http_api","description":"Generic HTTP proxy"}]`)
The adapter implements the minimal MCP/stdio surface required by
`pentestagent`'s `StdioTransport`:
- handle `initialize` and `notifications/initialized`
- respond to `tools/list`
- handle `tools/call` and forward to HTTP endpoints
`tools/call` arguments format (generic):
{"path": "/api/foo", "method": "POST", "params": {...}, "body": {...} }
This file is intentionally small and dependency-light; it uses `requests`
when available and returns response JSON or text.
"""
from __future__ import annotations
import json
import os
import sys
from typing import Any, Dict, List
try:
import requests
except Exception:
requests = None
TARGET = os.environ.get("STDIO_TARGET", "http://127.0.0.1:8888").rstrip("/")
_tools_env = os.environ.get("STDIO_TOOLS")
if _tools_env:
try:
TOOLS: List[Dict[str, str]] = json.loads(_tools_env)
except Exception:
TOOLS = [{"name": "http_api", "description": "Generic HTTP proxy"}]
else:
TOOLS = [{"name": "http_api", "description": "Generic HTTP proxy"}]
def _send(resp: Dict[str, Any]) -> None:
print(json.dumps(resp, separators=(",", ":")), flush=True)
def send_response(req_id: Any, result: Any = None, error: Any = None) -> None:
resp: Dict[str, Any] = {"jsonrpc": "2.0", "id": req_id}
if error is not None:
resp["error"] = {"code": -32000, "message": str(error)}
else:
resp["result"] = result if result is not None else {}
_send(resp)
def handle_tools_list(req_id: Any) -> None:
send_response(req_id, {"tools": TOOLS})
def _http_forward(path: str, method: str = "POST", params: Dict[str, Any] | None = None, body: Any | None = None) -> Any:
if requests is None:
raise RuntimeError("`requests` not installed in adapter process")
url = path if path.startswith("http") else TARGET + (path if path.startswith("/") else "/" + path)
method = (method or "POST").upper()
if method == "GET":
r = requests.get(url, params=params or {}, timeout=60)
else:
r = requests.request(method, url, json=body or {}, params=params or {}, timeout=300)
try:
return r.json()
except Exception:
return r.text
def handle_tools_call(req: Dict[str, Any]) -> None:
req_id = req.get("id")
params = req.get("params", {}) or {}
name = params.get("name")
arguments = params.get("arguments") or {}
# Validate tool
if not any(t.get("name") == name for t in TOOLS):
send_response(req_id, error=f"unknown tool '{name}'")
return
path = arguments.get("path")
if not path:
send_response(req_id, error="missing 'path' in arguments")
return
method = arguments.get("method", "POST")
body = arguments.get("body")
qparams = arguments.get("params")
try:
content = _http_forward(path, method=method, params=qparams, body=body)
send_response(req_id, {"content": content})
except Exception as e:
send_response(req_id, error=str(e))
def main() -> None:
while True:
line = sys.stdin.readline()
if not line:
break
line = line.strip()
if not line:
continue
try:
req = json.loads(line)
except Exception:
continue
method = req.get("method")
req_id = req.get("id")
if method == "initialize":
send_response(req_id, {"capabilities": {}})
elif method == "notifications/initialized":
# ignore notification
continue
elif method == "tools/list":
handle_tools_list(req_id)
elif method == "tools/call":
handle_tools_call(req)
else:
if req_id is not None:
send_response(req_id, error=f"unsupported method '{method}'")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
pass