mirror of
https://github.com/GH05TCREW/pentestagent.git
synced 2026-03-06 22:04:08 +00:00
chore(mcp): remove duplicate adapters, scripts, archives, example config, PR body and log
This commit is contained in:
33
PR_BODY.txt
33
PR_BODY.txt
@@ -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.
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
136
third_party/hexstrike/hexstrike_stdio_adapter.py
vendored
136
third_party/hexstrike/hexstrike_stdio_adapter.py
vendored
@@ -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
|
||||
137
third_party/mcp/stdio_adapter.py
vendored
137
third_party/mcp/stdio_adapter.py
vendored
@@ -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
|
||||
Reference in New Issue
Block a user