mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-03-14 19:45:54 +00:00
tui: in-UI image building, hub registry auto-recovery, clean hub-config
This commit is contained in:
@@ -96,7 +96,8 @@ class FuzzForgeApp(App[None]):
|
||||
|
||||
/* Modal screens */
|
||||
AgentSetupScreen, AgentUnlinkScreen,
|
||||
HubManagerScreen, LinkHubScreen, CloneHubScreen {
|
||||
HubManagerScreen, LinkHubScreen, CloneHubScreen,
|
||||
BuildImageScreen {
|
||||
align: center middle;
|
||||
}
|
||||
|
||||
@@ -130,6 +131,30 @@ class FuzzForgeApp(App[None]):
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#build-dialog {
|
||||
width: 100;
|
||||
height: 80%;
|
||||
border: thick #4699fc;
|
||||
background: $surface;
|
||||
padding: 2 3;
|
||||
}
|
||||
|
||||
#build-log {
|
||||
height: 1fr;
|
||||
border: round $panel;
|
||||
margin: 1 0;
|
||||
}
|
||||
|
||||
#build-subtitle {
|
||||
color: $text-muted;
|
||||
margin-bottom: 1;
|
||||
}
|
||||
|
||||
#build-status {
|
||||
height: 1;
|
||||
margin-top: 1;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
text-style: bold;
|
||||
text-align: center;
|
||||
@@ -168,6 +193,7 @@ class FuzzForgeApp(App[None]):
|
||||
Binding("q", "quit", "Quit"),
|
||||
Binding("h", "manage_hubs", "Hub Manager"),
|
||||
Binding("r", "refresh", "Refresh"),
|
||||
Binding("enter", "select_row", "Select", show=False),
|
||||
]
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
@@ -194,7 +220,9 @@ class FuzzForgeApp(App[None]):
|
||||
def on_mount(self) -> None:
|
||||
"""Populate tables on startup."""
|
||||
self._agent_rows: list[_AgentRow] = []
|
||||
self.query_one("#hub-panel").border_title = "Hub Servers"
|
||||
# hub row data: (server_name, image, hub_name) | None for group headers
|
||||
self._hub_rows: list[tuple[str, str, str] | None] = []
|
||||
self.query_one("#hub-panel").border_title = "Hub Servers [dim](Enter to build)[/dim]"
|
||||
self.query_one("#agents-panel").border_title = "AI Agents"
|
||||
self._refresh_agents()
|
||||
self._refresh_hub()
|
||||
@@ -220,6 +248,7 @@ class FuzzForgeApp(App[None]):
|
||||
|
||||
def _refresh_hub(self) -> None:
|
||||
"""Refresh the hub servers table, grouped by source hub."""
|
||||
self._hub_rows = []
|
||||
table = self.query_one("#hub-table", DataTable)
|
||||
table.clear(columns=True)
|
||||
table.add_columns("Server", "Image", "Hub", "Status")
|
||||
@@ -275,6 +304,7 @@ class FuzzForgeApp(App[None]):
|
||||
style="bold",
|
||||
)
|
||||
table.add_row(header, "", "", "")
|
||||
self._hub_rows.append(None) # group header — not selectable
|
||||
|
||||
# Tool rows
|
||||
for server, is_ready, status_text in statuses:
|
||||
@@ -287,7 +317,7 @@ class FuzzForgeApp(App[None]):
|
||||
elif is_ready:
|
||||
status_cell = Text("✓ Ready", style="green")
|
||||
else:
|
||||
status_cell = Text(f"✗ {status_text}", style="red")
|
||||
status_cell = Text(f"✗ {status_text}", style="red dim")
|
||||
|
||||
table.add_row(
|
||||
f" {name}",
|
||||
@@ -295,13 +325,17 @@ class FuzzForgeApp(App[None]):
|
||||
hub_name,
|
||||
status_cell,
|
||||
)
|
||||
self._hub_rows.append((name, image, hub_name))
|
||||
|
||||
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
||||
"""Handle row selection on the agents table."""
|
||||
if event.data_table.id != "agents-table":
|
||||
return
|
||||
"""Handle row selection on agents and hub tables."""
|
||||
if event.data_table.id == "agents-table":
|
||||
self._handle_agent_row(event.cursor_row)
|
||||
elif event.data_table.id == "hub-table":
|
||||
self._handle_hub_row(event.cursor_row)
|
||||
|
||||
idx = event.cursor_row
|
||||
def _handle_agent_row(self, idx: int) -> None:
|
||||
"""Open agent setup/unlink for the selected agent row."""
|
||||
if idx < 0 or idx >= len(self._agent_rows):
|
||||
return
|
||||
|
||||
@@ -322,6 +356,32 @@ class FuzzForgeApp(App[None]):
|
||||
callback=self._on_agent_changed,
|
||||
)
|
||||
|
||||
def _handle_hub_row(self, idx: int) -> None:
|
||||
"""Open the build dialog for the selected hub tool row."""
|
||||
if idx < 0 or idx >= len(self._hub_rows):
|
||||
return
|
||||
row_data = self._hub_rows[idx]
|
||||
if row_data is None:
|
||||
return # group header row — ignore
|
||||
|
||||
server_name, image, hub_name = row_data
|
||||
if hub_name == "manual":
|
||||
self.notify("Manual servers must be built outside FuzzForge")
|
||||
return
|
||||
|
||||
from fuzzforge_cli.tui.screens.build_image import BuildImageScreen
|
||||
|
||||
self.push_screen(
|
||||
BuildImageScreen(server_name, image, hub_name),
|
||||
callback=self._on_image_built,
|
||||
)
|
||||
|
||||
def _on_image_built(self, success: bool) -> None:
|
||||
"""Refresh hub status after a build attempt."""
|
||||
self._refresh_hub()
|
||||
if success:
|
||||
self.notify("Image built successfully", severity="information")
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""Handle button presses."""
|
||||
if event.button.id == "btn-hub-manager":
|
||||
|
||||
@@ -287,20 +287,80 @@ def get_default_hubs_dir() -> Path:
|
||||
return get_fuzzforge_user_dir() / "hubs"
|
||||
|
||||
|
||||
def _discover_hub_dirs() -> list[Path]:
|
||||
"""Scan known hub directories for cloned repos.
|
||||
|
||||
Checks both the current global location (``~/.fuzzforge/hubs/``) and the
|
||||
legacy workspace-local location (``<cwd>/.fuzzforge/hubs/``) so that hubs
|
||||
cloned before the global-dir migration are still found.
|
||||
|
||||
:return: List of hub directory paths (each is a direct child with a ``.git``
|
||||
sub-directory).
|
||||
|
||||
"""
|
||||
candidates: list[Path] = []
|
||||
for base in (get_fuzzforge_user_dir() / "hubs", get_fuzzforge_dir() / "hubs"):
|
||||
if base.is_dir():
|
||||
for entry in base.iterdir():
|
||||
if entry.is_dir() and (entry / ".git").is_dir():
|
||||
candidates.append(entry)
|
||||
return candidates
|
||||
|
||||
|
||||
def load_hubs_registry() -> dict[str, Any]:
|
||||
"""Load the hubs registry from disk.
|
||||
|
||||
If the registry file does not exist, auto-recovers it by scanning known hub
|
||||
directories and rebuilding entries for any discovered hubs. This handles
|
||||
the migration from the old workspace-local ``<cwd>/.fuzzforge/hubs.json``
|
||||
path to the global ``~/.fuzzforge/hubs.json`` path, as well as any case
|
||||
where the registry was lost.
|
||||
|
||||
:return: Registry dict with ``hubs`` key containing a list of hub entries.
|
||||
|
||||
"""
|
||||
path = get_hubs_registry_path()
|
||||
if not path.exists():
|
||||
if path.exists():
|
||||
try:
|
||||
data: dict[str, Any] = json.loads(path.read_text())
|
||||
return data
|
||||
except (json.JSONDecodeError, OSError):
|
||||
pass
|
||||
|
||||
# Registry missing — attempt to rebuild from discovered hub directories.
|
||||
discovered = _discover_hub_dirs()
|
||||
if not discovered:
|
||||
return {"hubs": []}
|
||||
|
||||
hubs: list[dict[str, Any]] = []
|
||||
for hub_dir in discovered:
|
||||
name = hub_dir.name
|
||||
# Try to read the git remote URL
|
||||
git_url: str = ""
|
||||
try:
|
||||
import subprocess as _sp
|
||||
r = _sp.run(
|
||||
["git", "-C", str(hub_dir), "remote", "get-url", "origin"],
|
||||
check=False, capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
git_url = r.stdout.strip()
|
||||
except Exception:
|
||||
pass
|
||||
hubs.append({
|
||||
"name": name,
|
||||
"path": str(hub_dir),
|
||||
"git_url": git_url,
|
||||
"is_default": name == FUZZFORGE_DEFAULT_HUB_NAME,
|
||||
})
|
||||
|
||||
registry: dict[str, Any] = {"hubs": hubs}
|
||||
# Persist so we don't re-scan on every load
|
||||
try:
|
||||
data: dict[str, Any] = json.loads(path.read_text())
|
||||
return data
|
||||
except (json.JSONDecodeError, OSError):
|
||||
return {"hubs": []}
|
||||
save_hubs_registry(registry)
|
||||
except OSError:
|
||||
pass
|
||||
return registry
|
||||
|
||||
|
||||
def save_hubs_registry(registry: dict[str, Any]) -> None:
|
||||
@@ -566,3 +626,62 @@ def _remove_hub_servers_from_config(hub_name: str) -> int:
|
||||
|
||||
config_path.write_text(json.dumps(config, indent=2))
|
||||
return before - after
|
||||
|
||||
|
||||
def find_dockerfile_for_server(server_name: str, hub_name: str) -> Path | None:
|
||||
"""Find the Dockerfile for a hub server tool.
|
||||
|
||||
Looks up the hub path from the registry, then scans for
|
||||
``category/<server_name>/Dockerfile``.
|
||||
|
||||
:param server_name: Tool name (e.g. ``"nmap-mcp"``).
|
||||
:param hub_name: Hub name as stored in the registry.
|
||||
:return: Absolute path to the Dockerfile, or ``None`` if not found.
|
||||
|
||||
"""
|
||||
registry = load_hubs_registry()
|
||||
hub_entry = next(
|
||||
(h for h in registry.get("hubs", []) if h.get("name") == hub_name),
|
||||
None,
|
||||
)
|
||||
if not hub_entry:
|
||||
return None
|
||||
|
||||
hub_path = Path(hub_entry["path"])
|
||||
for dockerfile in hub_path.rglob("Dockerfile"):
|
||||
rel = dockerfile.relative_to(hub_path)
|
||||
parts = rel.parts
|
||||
if len(parts) == 3 and parts[1] == server_name:
|
||||
return dockerfile
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def build_image(
|
||||
image: str,
|
||||
dockerfile: Path,
|
||||
*,
|
||||
engine: str | None = None,
|
||||
) -> subprocess.Popen[str]:
|
||||
"""Start a non-blocking ``docker/podman build`` subprocess.
|
||||
|
||||
Returns the running :class:`subprocess.Popen` object so the caller
|
||||
can stream ``stdout`` / ``stderr`` lines incrementally.
|
||||
|
||||
:param image: Image tag (e.g. ``"nmap-mcp:latest"``).
|
||||
:param dockerfile: Path to the ``Dockerfile``.
|
||||
:param engine: ``"docker"`` or ``"podman"`` (auto-detected if ``None``).
|
||||
:return: Running subprocess with merged stdout+stderr.
|
||||
|
||||
"""
|
||||
if engine is None:
|
||||
engine = os.environ.get("FUZZFORGE_ENGINE__TYPE", "docker").lower()
|
||||
engine = "podman" if engine == "podman" else "docker"
|
||||
|
||||
context_dir = str(dockerfile.parent)
|
||||
return subprocess.Popen(
|
||||
[engine, "build", "-t", image, context_dir],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
)
|
||||
|
||||
103
fuzzforge-cli/src/fuzzforge_cli/tui/screens/build_image.py
Normal file
103
fuzzforge-cli/src/fuzzforge_cli/tui/screens/build_image.py
Normal file
@@ -0,0 +1,103 @@
|
||||
"""Build-image modal screen for FuzzForge TUI.
|
||||
|
||||
Provides a modal dialog that runs ``docker/podman build`` for a single
|
||||
hub tool and streams the build log into a scrollable log area.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from textual import work
|
||||
from textual.app import ComposeResult
|
||||
from textual.containers import Horizontal, Vertical
|
||||
from textual.screen import ModalScreen
|
||||
from textual.widgets import Button, Label, Log
|
||||
|
||||
from fuzzforge_cli.tui.helpers import build_image, find_dockerfile_for_server
|
||||
|
||||
|
||||
class BuildImageScreen(ModalScreen[bool]):
|
||||
"""Modal that builds a Docker/Podman image and streams the build log."""
|
||||
|
||||
BINDINGS = [("escape", "cancel", "Close")]
|
||||
|
||||
def __init__(self, server_name: str, image: str, hub_name: str) -> None:
|
||||
super().__init__()
|
||||
self._server_name = server_name
|
||||
self._image = image
|
||||
self._hub_name = hub_name
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
"""Compose the build dialog layout."""
|
||||
with Vertical(id="build-dialog"):
|
||||
yield Label(f"Build {self._image}", classes="dialog-title")
|
||||
yield Label(
|
||||
f"Hub: {self._hub_name} • Tool: {self._server_name}",
|
||||
id="build-subtitle",
|
||||
)
|
||||
yield Log(id="build-log", auto_scroll=True)
|
||||
yield Label("", id="build-status")
|
||||
with Horizontal(classes="dialog-buttons"):
|
||||
yield Button("Close", variant="default", id="btn-close", disabled=True)
|
||||
|
||||
def on_mount(self) -> None:
|
||||
"""Start the build as soon as the screen is shown."""
|
||||
self._start_build()
|
||||
|
||||
def action_cancel(self) -> None:
|
||||
"""Only dismiss when the build is not running (Close button enabled)."""
|
||||
close_btn = self.query_one("#btn-close", Button)
|
||||
if not close_btn.disabled:
|
||||
self.dismiss(False)
|
||||
|
||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||
"""Handle Close button."""
|
||||
if event.button.id == "btn-close":
|
||||
self.dismiss(self._succeeded)
|
||||
|
||||
@work(thread=True)
|
||||
def _start_build(self) -> None:
|
||||
"""Run the build in a background thread and stream output."""
|
||||
self._succeeded = False
|
||||
log = self.query_one("#build-log", Log)
|
||||
status = self.query_one("#build-status", Label)
|
||||
|
||||
dockerfile = find_dockerfile_for_server(self._server_name, self._hub_name)
|
||||
if dockerfile is None:
|
||||
log.write_line(f"ERROR: Dockerfile not found for '{self._server_name}' in hub '{self._hub_name}'")
|
||||
status.update("[red]Build failed — Dockerfile not found[/red]")
|
||||
self.query_one("#btn-close", Button).disabled = False
|
||||
return
|
||||
|
||||
log.write_line(f"$ {self._get_engine()} build -t {self._image} {dockerfile.parent}")
|
||||
log.write_line("")
|
||||
|
||||
try:
|
||||
proc = build_image(self._image, dockerfile)
|
||||
except FileNotFoundError as exc:
|
||||
log.write_line(f"ERROR: {exc}")
|
||||
status.update("[red]Build failed — engine not found[/red]")
|
||||
self.query_one("#btn-close", Button).disabled = False
|
||||
return
|
||||
|
||||
assert proc.stdout is not None
|
||||
for line in proc.stdout:
|
||||
log.write_line(line.rstrip())
|
||||
|
||||
proc.wait()
|
||||
|
||||
if proc.returncode == 0:
|
||||
self._succeeded = True
|
||||
status.update(f"[green]✓ Built {self._image} successfully[/green]")
|
||||
else:
|
||||
status.update(f"[red]✗ Build failed (exit {proc.returncode})[/red]")
|
||||
|
||||
self.query_one("#btn-close", Button).disabled = False
|
||||
|
||||
@staticmethod
|
||||
def _get_engine() -> str:
|
||||
import os
|
||||
engine = os.environ.get("FUZZFORGE_ENGINE__TYPE", "docker").lower()
|
||||
return "podman" if engine == "podman" else "docker"
|
||||
503
hub-config.json
503
hub-config.json
@@ -1,502 +1 @@
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"name": "nmap-mcp",
|
||||
"description": "Network reconnaissance using Nmap - port scanning, service detection, OS fingerprinting",
|
||||
"type": "docker",
|
||||
"image": "nmap-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "binwalk-mcp",
|
||||
"description": "Firmware extraction and analysis using Binwalk - file signatures, entropy analysis, embedded file extraction",
|
||||
"type": "docker",
|
||||
"image": "binwalk-mcp:latest",
|
||||
"category": "binary-analysis",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "yara-mcp",
|
||||
"description": "Pattern matching and malware classification using YARA rules",
|
||||
"type": "docker",
|
||||
"image": "yara-mcp:latest",
|
||||
"category": "binary-analysis",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "capa-mcp",
|
||||
"description": "Static capability detection using capa - identifies malware capabilities in binaries",
|
||||
"type": "docker",
|
||||
"image": "capa-mcp:latest",
|
||||
"category": "binary-analysis",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "radare2-mcp",
|
||||
"description": "Binary analysis and reverse engineering using radare2",
|
||||
"type": "docker",
|
||||
"image": "radare2-mcp:latest",
|
||||
"category": "binary-analysis",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "ghidra-mcp",
|
||||
"description": "Advanced binary decompilation and reverse engineering using Ghidra",
|
||||
"type": "docker",
|
||||
"image": "ghcr.io/clearbluejar/pyghidra-mcp:latest",
|
||||
"category": "binary-analysis",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "searchsploit-mcp",
|
||||
"description": "CVE and exploit search using SearchSploit / Exploit-DB",
|
||||
"type": "docker",
|
||||
"image": "searchsploit-mcp:latest",
|
||||
"category": "exploitation",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "nuclei-mcp",
|
||||
"description": "Vulnerability scanning using Nuclei templates",
|
||||
"type": "docker",
|
||||
"image": "nuclei-mcp:latest",
|
||||
"category": "web-security",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "trivy-mcp",
|
||||
"description": "Container and filesystem vulnerability scanning using Trivy",
|
||||
"type": "docker",
|
||||
"image": "trivy-mcp:latest",
|
||||
"category": "cloud-security",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "gitleaks-mcp",
|
||||
"description": "Secret and credential detection in code and firmware using Gitleaks",
|
||||
"type": "docker",
|
||||
"image": "gitleaks-mcp:latest",
|
||||
"category": "secrets",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"~/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"name": "bloodhound-mcp",
|
||||
"description": "bloodhound-mcp \u2014 active-directory",
|
||||
"type": "docker",
|
||||
"image": "bloodhound-mcp:latest",
|
||||
"category": "active-directory",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "ida-mcp",
|
||||
"description": "ida-mcp \u2014 binary-analysis",
|
||||
"type": "docker",
|
||||
"image": "ida-mcp:latest",
|
||||
"category": "binary-analysis",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "daml-viewer-mcp",
|
||||
"description": "daml-viewer-mcp \u2014 blockchain",
|
||||
"type": "docker",
|
||||
"image": "daml-viewer-mcp:latest",
|
||||
"category": "blockchain",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "medusa-mcp",
|
||||
"description": "medusa-mcp \u2014 blockchain",
|
||||
"type": "docker",
|
||||
"image": "medusa-mcp:latest",
|
||||
"category": "blockchain",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "solazy-mcp",
|
||||
"description": "solazy-mcp \u2014 blockchain",
|
||||
"type": "docker",
|
||||
"image": "solazy-mcp:latest",
|
||||
"category": "blockchain",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "prowler-mcp",
|
||||
"description": "prowler-mcp \u2014 cloud-security",
|
||||
"type": "docker",
|
||||
"image": "prowler-mcp:latest",
|
||||
"category": "cloud-security",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "roadrecon-mcp",
|
||||
"description": "roadrecon-mcp \u2014 cloud-security",
|
||||
"type": "docker",
|
||||
"image": "roadrecon-mcp:latest",
|
||||
"category": "cloud-security",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "semgrep-mcp",
|
||||
"description": "semgrep-mcp \u2014 code-security",
|
||||
"type": "docker",
|
||||
"image": "semgrep-mcp:latest",
|
||||
"category": "code-security",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "boofuzz-mcp",
|
||||
"description": "boofuzz-mcp \u2014 fuzzing",
|
||||
"type": "docker",
|
||||
"image": "boofuzz-mcp:latest",
|
||||
"category": "fuzzing",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "dharma-mcp",
|
||||
"description": "dharma-mcp \u2014 fuzzing",
|
||||
"type": "docker",
|
||||
"image": "dharma-mcp:latest",
|
||||
"category": "fuzzing",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "dnstwist-mcp",
|
||||
"description": "dnstwist-mcp \u2014 osint",
|
||||
"type": "docker",
|
||||
"image": "dnstwist-mcp:latest",
|
||||
"category": "osint",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "maigret-mcp",
|
||||
"description": "maigret-mcp \u2014 osint",
|
||||
"type": "docker",
|
||||
"image": "maigret-mcp:latest",
|
||||
"category": "osint",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "hashcat-mcp",
|
||||
"description": "hashcat-mcp \u2014 password-cracking",
|
||||
"type": "docker",
|
||||
"image": "hashcat-mcp:latest",
|
||||
"category": "password-cracking",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "externalattacker-mcp",
|
||||
"description": "externalattacker-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "externalattacker-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "masscan-mcp",
|
||||
"description": "masscan-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "masscan-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "networksdb-mcp",
|
||||
"description": "networksdb-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "networksdb-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "pd-tools-mcp",
|
||||
"description": "pd-tools-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "pd-tools-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "shodan-mcp",
|
||||
"description": "shodan-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "shodan-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "whatweb-mcp",
|
||||
"description": "whatweb-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "whatweb-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "zoomeye-mcp",
|
||||
"description": "zoomeye-mcp \u2014 reconnaissance",
|
||||
"type": "docker",
|
||||
"image": "zoomeye-mcp:latest",
|
||||
"category": "reconnaissance",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "otx-mcp",
|
||||
"description": "otx-mcp \u2014 threat-intel",
|
||||
"type": "docker",
|
||||
"image": "otx-mcp:latest",
|
||||
"category": "threat-intel",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "virustotal-mcp",
|
||||
"description": "virustotal-mcp \u2014 threat-intel",
|
||||
"type": "docker",
|
||||
"image": "virustotal-mcp:latest",
|
||||
"category": "threat-intel",
|
||||
"capabilities": [],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "burp-mcp",
|
||||
"description": "burp-mcp \u2014 web-security",
|
||||
"type": "docker",
|
||||
"image": "burp-mcp:latest",
|
||||
"category": "web-security",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "ffuf-mcp",
|
||||
"description": "ffuf-mcp \u2014 web-security",
|
||||
"type": "docker",
|
||||
"image": "ffuf-mcp:latest",
|
||||
"category": "web-security",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "nikto-mcp",
|
||||
"description": "nikto-mcp \u2014 web-security",
|
||||
"type": "docker",
|
||||
"image": "nikto-mcp:latest",
|
||||
"category": "web-security",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "sqlmap-mcp",
|
||||
"description": "sqlmap-mcp \u2014 web-security",
|
||||
"type": "docker",
|
||||
"image": "sqlmap-mcp:latest",
|
||||
"category": "web-security",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
},
|
||||
{
|
||||
"name": "waybackurls-mcp",
|
||||
"description": "waybackurls-mcp \u2014 web-security",
|
||||
"type": "docker",
|
||||
"image": "waybackurls-mcp:latest",
|
||||
"category": "web-security",
|
||||
"capabilities": [
|
||||
"NET_RAW"
|
||||
],
|
||||
"volumes": [
|
||||
"/home/afredefon/FuzzingLabs/FuzzForge/fuzzforge-oss/.fuzzforge/hub/workspace:/data"
|
||||
],
|
||||
"enabled": true,
|
||||
"source_hub": "mcp-security-hub"
|
||||
}
|
||||
],
|
||||
"default_timeout": 300,
|
||||
"cache_tools": true
|
||||
}
|
||||
{"servers": []}
|
||||
|
||||
Reference in New Issue
Block a user