About BugBounty MCP Server
BugBounty MCP Server is an MCP server published by Coding-with-Mayank in the Security category: π₯ AI-Powered Bug Bounty Hunting Platform - MCP Server integrated with Claude for intelligent vulnerability detection, reconnaissance, and exploitation.
The repository has 10 stars and 2 forks, with the last commit 7 days ago. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/Coding-with-Mayank/BugBounty-mcp-serverThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about BugBounty MCP Server
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository yesterday. Heuristics on the source, not an audit.
- βREADME presentThe repository ships documentation.
- !License declaredNo license file. Legally you have no right to use it in a product.
- βTests presentA test suite exists in the repository.
- !Dependencies pinnedNo lockfile. Installs can pull different code over time.
- βNo dynamic code executionSource scan found no eval, dynamic Function, or shell execution.
1 check has not been run yet.
README
Bug Bounty MCP Server v3.1.0
The world's most practical bug bounty setup β works with Claude Desktop, Claude CLI, Gemini CLI, and standalone terminal mode. One command to install. AI-guided findings with next steps built in.
β οΈ Legal Notice: Only test targets you have explicit written permission to test.
Unauthorized scanning is illegal. This tool is for authorized security testing only.
What's inside
| Tool | Purpose |
|---|---|
subfinder |
Passive subdomain discovery (50+ sources) |
httpx |
HTTP probing, tech detection, screenshots |
nuclei |
10,000+ vulnerability templates |
naabu |
Fast port scanner |
katana |
Web crawler |
ffuf |
Directory/parameter fuzzer |
gobuster |
Brute-force enumeration |
gowitness |
Web screenshots |
dnsx |
DNS enumeration |
waybackurls + gau |
Historical URL discovery |
anew |
Deduplicate output streams |
Everything ships in a single Docker container. No Go, no Python, no tool installation on your host.
Quick Start (3 steps)
git clone https://github.com/Coding-with-Mayank/BugBounty-mcp-server
cd BugBounty-mcp-server
./setup.sh
That's it. Setup auto-handles:
- Checking Docker is running
- Creating
.envwith random passwords - Downloading wordlists
- Building the Docker image
- Configuring Claude Desktop, Claude CLI, and Gemini CLI automatically
- Starting all services
No Docker? ./install-native.sh does the same setup without it β Python
venv, the Go recon tools under bin/tools/, wordlists, Nuclei templates,
and launcher scripts, all under the repo directory. No sudo required.
git clone https://github.com/Coding-with-Mayank/BugBounty-mcp-server
cd BugBounty-mcp-server
./install-native.sh # full install
./install-native.sh --dry-run # see what it would do first
./install-native.sh --check # just verify an existing install
Run ./hunt doctor afterward to confirm everything's on PATH correctly.
Tool versions are not baked into the repo. The installer (and the Docker build) resolve the newest compatible release of each tool when you run them, so installing months after the last commit still gets current tools β see Keeping tools up to date.
Using with Claude Desktop
After setup, restart Claude Desktop. Then just ask naturally:
Find all subdomains of example.com
Run a full recon on example.com and tell me what's interesting
Scan https://example.com for vulnerabilities and explain any findings
Take screenshots of these URLs and tell me what looks like admin panels
Claude will call the right tools automatically and return findings with guidance on what's suspicious and what to do next.
Using with Claude CLI
Setup auto-registers the server. To verify:
claude mcp list
# Should show: bugbounty
Then in a Claude CLI session:
claude> Run full_recon on example.com
Manual registration (if setup didn't catch it):
claude mcp add bugbounty -- docker exec -i bugbounty-mcp python -m mcp_server
Using with Gemini CLI
Setup auto-writes ~/.gemini/settings.json. To verify:
cat ~/.gemini/settings.json
In Gemini CLI:
gemini> @bugbounty subdomain_enum domain=example.com
Manual setup β add to ~/.gemini/settings.json:
{
"mcpServers": {
"bugbounty": {
"command": "docker",
"args": ["exec", "-i", "bugbounty-mcp", "python", "-m", "mcp_server"]
}
}
}
Manual Terminal Mode (no AI client needed)
hunt.py works standalone β no Docker, no MCP client needed β as long as
the required tools (see python hunt.py doctor) are on PATH.
# Check your environment first: Python version, which tools are
# installed, wordlists, native vs Docker
python hunt.py doctor
python hunt.py doctor --json # for scripting
# Full recon pipeline
python hunt.py full example.com
# Just subdomains
python hunt.py recon example.com
# Vulnerability scan
python hunt.py scan https://example.com --severity high,critical
# Directory fuzzing
python hunt.py fuzz https://example.com/FUZZ --ext php,asp,bak
# Historical URLs
python hunt.py wayback example.com
# Screenshots
python hunt.py screenshot example.com
# Certificate transparency
python hunt.py cert example.com
Or run directly inside the Docker container:
docker exec -it bugbounty-mcp bash
# Now you have all tools available
subfinder -d example.com -silent
nuclei -u https://example.com -severity high,critical
Available MCP Tools
Recon
| Tool | Description |
|---|---|
full_recon |
Full automated pipeline: subdomains β alive β ports β tech β screenshots |
subdomain_enum |
Passive subdomain discovery with subfinder |
subdomain_brute |
Active brute-force with wordlist |
alive_check |
Probe hosts with httpx (status, title, tech) |
port_scan |
Fast TCP port scan with naabu |
dns_enum |
DNS records (A, MX, NS, TXT, CNAME) |
screenshot |
Web screenshots with gowitness |
crawl |
Website crawling with katana |
wayback |
Historical URLs from Wayback + gau |
tech_detect |
Technology detection with httpx |
Scanning
| Tool | Description |
|---|---|
nuclei_scan |
Full vulnerability scan with 10k+ templates |
dir_fuzz |
Directory/file fuzzing with ffuf |
xss_scan |
Targeted XSS scanning |
sqli_scan |
SQL injection scanning |
cors_check |
CORS misconfiguration check |
Intelligence
| Tool | Description |
|---|---|
whois_lookup |
WHOIS data |
cert_search |
Certificate transparency (crt.sh) |
shodan_search |
Shodan query (needs API key) |
Analysis
| Tool | Description |
|---|---|
analyze_findings |
Prioritize and chain multiple findings |
explain_finding |
Explain what a finding means + write-up template |
generate_report |
Professional Markdown bug bounty report |
Utility
| Tool | Description |
|---|---|
check_scope |
Check whether a target is allowed under ALLOWED_SCOPE before scanning it |
get_capabilities |
Which tools/Python version/wordlists are actually available right now, and whether this is running natively or in Docker |
Task Management
| Tool | Description |
|---|---|
start_task |
Run any tool above in the background instead of blocking until it finishes |
get_task_status |
Poll a background task's status and elapsed time |
get_task_results |
Fetch a background task's result once it's done |
cancel_task |
Stop a running background task (kills the underlying process) |
list_tasks |
List tracked tasks, optionally filtered by status or tool, without knowing their IDs |
Every tool works fine called directly β start_task is an additional option for
the slow ones (nuclei_scan, dir_fuzz, full_recon, port_scan can run for
minutes). It reuses the exact same handler, scope check, and execution as a
direct call; the only difference is you get a task_id back immediately and
poll for the result instead of blocking.
// start_task
{"tool": "nuclei_scan", "args": {"targets": ["https://example.com"]}}
β {"task_id": "task_28e7fe9d33", "status": "running", ...}
// get_task_status, while it's still going
β {"status": "running", "elapsed_seconds": 75, "soft_timeout_seconds": 60,
"soft_timeout_exceeded": true, "options": ["continue", "cancel"]}
A task past its soft_timeout (default 60s) gets flagged in get_task_status
β that's informational, not a pause; keep polling to wait, or call cancel_task.
Past its hard_timeout (default 900s) it's force-cancelled automatically, the
underlying process killed, and the task marked timed_out.
Partial results on cancellation. If a task is cancelled, hits its hard
timeout, or fails partway through, you still get back whatever its commands
had already produced β not nothing. get_task_status/get_task_results set
partial_results_available: true, and the result looks like:
{"status": "cancelled", "result": {
"partial": true,
"note": "Task stopped (cancelled) before finishing. Below is the raw output ...",
"commands_captured": 2, "captured": [{"cmd": ["subfinder", "-d", "example.com"], "stdout": "..."}]
}}
This is raw, unprocessed command output (whole lines only β any in-flight line fragment is dropped), not the parsed/deduplicated shape a completed run returns.
Task history survives a restart. Tasks are persisted to
DATA_DIR/tasks/<task_id>.json as they run (set PERSIST_TASKS=false for
purely in-memory behavior). On startup, finished tasks are restored with
restored_from_previous_session: true; anything that was still
queued/running when the server stopped is honestly marked interrupted
(its process didn't survive) rather than silently resumed. A graceful
shutdown (Ctrl-C, SIGTERM, or stdin closing) cancels any active tasks first,
so their partial output gets saved instead of lost.
Smart Hunt (AI-guided workflow)
| Tool | Description |
|---|---|
smart_hunt_start |
Start an investigation for a domain β returns a session + the first recommended tool call |
smart_hunt_next |
Report a tool's result (directly, or via a task_id), get the next recommendation |
smart_hunt_status |
Full session state: everything found so far, tools run, pending recommendation |
smart_hunt_close |
Stop a session (keeps its history) |
Smart Hunt isn't a separate AI model and it never calls a tool itself β it's decision support on top of everything above. It tracks what's actually been discovered and recommends one next tool call at a time, instead of your AI client having to plan the whole recon-to-report pipeline upfront:
smart_hunt_start(target="example.com")
β session_id, recommends: subdomain_enum
[you run subdomain_enum yourself]
smart_hunt_next(session_id, tool="subdomain_enum", result={...})
β recommends: alive_check (flagged hosts like admin.* / staging.* go first)
... repeat β nuclei findings jump the queue ahead of further recon;
a critical finding pauses everything else until it's analyzed,
explained, and written up ...
β done: true, with a summary and a manual-review list (logic/auth/
access-control bugs that no automated tool can find)
For the slow tools (nuclei_scan, port_scan, crawl, dir_fuzz, etc.)
Smart Hunt tells you to run them via start_task and hand back the
task_id instead of the raw result β it reads the result itself once the
task finishes (or polls-and-waits if you hand it back too early).
Smart Hunt refuses to start while ALLOWED_SCOPE is empty ("open mode") β
an AI iterating on its own shouldn't be pointed at "scan anything." Every
recommended target is also independently checked against scope at
recommendation time, as a second check beneath whatever the tool itself
enforces when you call it. Sessions persist the same way tasks do, so a
long hunt survives a server restart.
AI Guidance System
Every tool response includes a guidance block explaining:
risk_levelβ info / low / medium / high / criticalsuspicious_itemsβ patterns that warrant investigationnext_stepsβ exactly what to try nextmanual_stepsβ what to do when automated tools can't go furtherlimitationsβ why the tool may have missed things
Example:
{
"tool": "subdomain_enum",
"data": { "count": 0, "subdomains": [] },
"guidance": {
"risk_level": "info",
"summary": "No subdomains found via passive recon.",
"limitations": "Passive-only recon cannot find subdomains with no public footprint.",
"next_steps": [
"Try active brute-force: subdomain_brute tool",
"Search cert logs: https://crt.sh/?q=%.example.com",
"Google dork: site:*.example.com -www",
"Check VirusTotal: https://virustotal.com/gui/domain/example.com/relations"
],
"manual_steps": [
"Zone transfer: dig axfr example.com @ns1.example.com",
"Reverse DNS: for ip in $(host example.com | awk '{print $4}'); do host $ip; done"
]
}
}
n8n Workflow Automation (optional)
n8n gives you visual workflows for scheduled scans and notifications.
# Start with n8n (published on 127.0.0.1 only)
make n8n
# OR
docker compose -f docker-compose.yml -f docker-compose.dev-ports.yml --profile automation up -d
Then open http://127.0.0.1:5678 and create the owner account right away β n8n 1.0+ removed
HTTP basic-auth (older versions of this file set N8N_BASIC_AUTH_*, which did nothing), so the
first visitor becomes the owner. That is why the port is bound to loopback and the image is pinned
(N8N_VERSION, tested default in docker-compose.yml). Never expose 5678 to a network.
Use cases:
- Scheduled daily recon β cron trigger β subdomain_enum β diff against yesterday β alert on new
- Vulnerability alerts β nuclei_scan β filter critical β Slack/Telegram notification
- Report pipeline β findings β generate_report β save to Notion / email
Scoping (legal protection)
Set your allowed targets in .env:
# Only test these β everything else is blocked
ALLOWED_SCOPE=example.com,*.example.com,192.168.1.0/24
Every active-scanning tool (port_scan, alive_check, dns_enum, screenshot,
crawl, tech_detect, nuclei_scan, dir_fuzz, xss_scan, sqli_scan,
cors_check, subdomain_brute) checks the target against ALLOWED_SCOPE before
sending any traffic, and rejects anything that doesn't match. BLOCKED_TARGETS
(localhost, cloud metadata IPs, private ranges, both IPv4 and IPv6) is enforced
unconditionally and can't be overridden by ALLOWED_SCOPE. Leave ALLOWED_SCOPE
empty to run in open mode β the blocklist still applies, but nothing else is
restricted.
The check isn't just string matching on the hostname: the target is resolved
and its current IP is checked against BLOCKED_TARGETS too, so a name that's
in scope but currently resolves into a blocked range (a stale record, or a
DNS-rebinding attempt) is still rejected. A resolution failure doesn't block
the scan β a flaky resolver shouldn't stop an authorized test β but it's
surfaced in the response so you know the IP-level check was inconclusive.
Purely passive lookups that never contact the target directly β subdomain_enum
(passive sources), wayback, whois_lookup, cert_search, shodan_search β
are exempt, since there's nothing to authorize.
Use the check_scope tool to test whether a target is allowed before running a
scan (it returns the resolved IPs too), or to see why one was rejected.
Management
make up # Start everything
make down # Stop everything
make logs # Follow MCP server logs
make status # Check all tools are working
make shell # bash inside the container
make rebuild # Rebuild after code changes
make update # git pull + rebuild with the newest compatible tools + templates
make refresh-tools # rebuild so the image gets current tool versions (cache-busted)
make test # Verify all tools
Keeping tools up to date
Three different things get updated by three different commands β they are independent on purpose:
| Command | Updates | Does not touch |
|---|---|---|
git pull |
this project's code and config/tools.yaml |
installed tools |
./hunt update |
the Go security tools + nuclei templates in this checkout | system packages, copies installed elsewhere |
sudo apt update && sudo apt upgrade |
APT-managed packages | this project |
apt update/apt upgradeupdates APT-managed packages. This project is not automatically upgraded by APT unless the optional signal integration is explicitly enabled (and even then APT itself updates nothing here).
./hunt doctor # installed vs latest compatible, per tool (--json for machines)
./hunt update --check # what's newer? changes nothing
./hunt update --dry-run # exactly what would happen? changes nothing
./hunt update # apply (asks first; --yes to skip the question)
./hunt rollback nuclei # restore the previous version
./hunt snapshot # record exact versions -> config/tool-lock.json
./hunt install --lock config/tool-lock.json # reproduce exactly those versions
./hunt config tools # which copy (system / local / go) of each tool is used
Policy: patch and minor releases are applied only after the staged build
passes a compatibility self-test (the flags the handlers use must still exist);
the old working binary is backed up and restored automatically on any failure.
Major releases are never applied silently β they're reported, and
./hunt update --major tries one explicitly. If the network is down nothing is
changed. Tools you already have (system or ~/go/bin) are never overwritten, and
the newest compatible copy wins.
Automatic updates are opt-in (./hunt auto-update enable β user-level
systemd timer, safe updates only, no root). Docker: a new build is the
only way to update tools inside an image: make refresh-tools.
Everything β the trust model, the exact upgrade policy, APT/systemd details, reproducible installs, Docker behaviour β is in docs/UPDATING.md.
Project Structure
The package is named bugbounty-mcp-server (see pyproject.toml), matching
the repo's name on GitHub:
BugBounty-mcp-server/
βββ Dockerfile # Multi-stage Go tools + Python runtime
βββ docker-compose.yml # MCP server + MongoDB + Redis + optional n8n (no published ports, no default passwords)
βββ docker-compose.dev-ports.yml # opt-in, 127.0.0.1-only port mappings
βββ requirements-dev.txt # test / lint / CI tooling
βββ requirements.txt # Python deps (lower bounds -> fresh installs get current releases)
βββ requirements.lock # Same deps, exact + hash-verified (./install-native.sh --locked-python)
βββ hunt # CLI launcher: ./hunt doctor | update | install | rollback | ...
βββ pyproject.toml # ruff/pytest/mypy config
βββ .env.example # Configuration template
βββ setup.sh # One-command Docker installer
βββ install-native.sh # One-command installer without Docker
βββ Makefile # Convenience commands
βββ hunt.py # Standalone manual CLI
βββ mcp_server/
β βββ __main__.py # Entry point: python -m mcp_server
β βββ server.py # All 32 MCP tools
β βββ core.py # Shared run_cmd/validation (used by server.py AND hunt.py)
β βββ guidance.py # Smart guidance engine
β βββ scope.py # ALLOWED_SCOPE / BLOCKED_TARGETS enforcement
β βββ tasks.py # Background task lifecycle (start_task etc.)
β βββ smart_hunt.py # Smart Hunt: AI-guided next-step recommendations
β βββ persistence.py # JSON file store (tasks + Smart Hunt sessions)
β βββ capabilities.py # Environment/tool diagnostics (get_capabilities, doctor)
β βββ mcp_compat.py # Registers handlers on MCP SDK 1.x AND 2.x
β βββ toolmgr/ # Tool manager: manifest, detection, upstream lookup, install/update/rollback
β βββ config.py # Settings
βββ tests/ # pytest suite (551 tests: scope, tasks, core, persistence,
β # smart_hunt, capabilities, server handlers, native install,
β # toolmgr: policy/rollback/security, MCP 1.x+2.x compat)
βββ config/
β βββ tools.yaml # SINGLE source of truth for external tools (see docs/UPDATING.md)
β βββ tool-lock.json # Exact tool versions, for reproducible installs
β βββ subfinder/ # Subfinder API config
βββ docs/UPDATING.md # How versions/updates/rollback/auto-update work + trust model
βββ scripts/mcp_smoke.py # Live MCP stdio session test (native, launcher, or Docker)
βββ .github/workflows/ # ci.yml (build/test) + freshness.yml (weekly staleness check)
βββ wordlists/ # Downloaded by setup.sh / install-native.sh
βββ n8n-workflows/ # n8n automation workflows
βββ data/ # Task + Smart Hunt session persistence (native install; git-ignored)
βββ reports/ # Generated reports (git-ignored)
βββ screenshots/ # gowitness screenshots (git-ignored)
βββ output/ # Manual hunt output (git-ignored)
Troubleshooting
Not sure what's wrong? Start here:
python hunt.py doctor
# Or, from an MCP client: call the get_capabilities tool
# Or over HTTP from inside the container: docker exec bugbounty-mcp curl -s http://127.0.0.1:8080/capabilities
Shows Python version, every required/optional tool with its installed vs
latest compatible version and status (OK / UPDATE / MAJOR / MISSING / BROKEN /
INCOMPATIBLE), where each binary came from, nuclei templates, PATH problems,
the MCP SDK in use, wordlist presence, and whether it detects Docker or native.
./hunt doctor --json prints the same as a single JSON document.
docker compose says MONGO_PASSWORD is not set: that is intentional β there are no default
passwords. Run ./setup.sh (or just scripts/gen-env.sh) to create .env with random secrets.
Build fails:
docker compose build --no-cache
Reproducible image (exact Python dependencies):
make build-locked # = PYTHON_DEPS=locked docker compose build β requirements.lock, sha256-verified
docker run --rm bugbounty-mcp:latest head -1 /opt/bbmcp/python-deps.txt # mode=locked | mode=latest
The default build uses the newest releases requirements.txt allows (mode=latest) β the same
policy as the native installer. The image always records which one you got.
Tool missing inside container:
make shell
# Check: which subfinder
# If missing, the Go build step for that tool failed.
# Usually a network issue during build β retry: make refresh-tools
# (a plain rebuild reuses the cached tool layer; refresh-tools forces a fresh resolve)
MCP server not showing in Claude:
# Verify container is running
docker ps | grep bugbounty-mcp
# Restart Claude Desktop after running setup.sh
Need MongoDB / Redis / the health API from the host? Nothing is published by default (the MCP
client reaches the server through docker exec -i bugbounty-mcp python -m mcp_server, not a port).
Opt in, loopback only:
docker compose -f docker-compose.yml -f docker-compose.dev-ports.yml up -d
# 127.0.0.1:8080 health Β· 127.0.0.1:27017 mongo Β· 127.0.0.1:6379 redis Β· 127.0.0.1:5678 n8n (profile)
# Port already in use? Edit the left-hand number in docker-compose.dev-ports.yml.
Screenshots not working:
# gowitness needs Chromium (included in Dockerfile)
# If still failing:
docker exec bugbounty-mcp gowitness single -u https://example.com --disable-db
License
MIT β use freely for authorized security testing.
Remember: Authorization first, always.
README fetched from the repository yesterday. The original is authoritative.