Skip to main content
Glama
HarshXor
by HarshXor

DryHack-MCP

An MCP (Model Context Protocol) server that gives an AI agent offensive-security tooling for authorized penetration testing. It exposes four tools:

Tool

Purpose

curl

Raw HTTP interaction for web recon/exploitation

python

Run ad-hoc Python snippets for scripted probing

shell

Run shell commands (nmap, ffuf, nc, sqlmap, …)

browser

Full Playwright browser automation: navigation, DOM interaction, JavaScript injection, screenshots/PDF, cookies/headers, multi-step chains

authorize

Checks target membership in the per-call scope parameter; does not verify permission (no external API, no creds)

⚠️ Legal notice. Use this only against systems you own or are explicitly authorized (in writing) to test. You are responsible for staying within scope.

Install

python3 -m pip install .

This installs the dryhack-mcp console script (the MCP server).

Run without installing (uvx)

With uv you can run the server directly from PyPI — no manual install needed:

# recommended: pull the browser extra so the `browser` tool is guaranteed
uvx --from 'dryhack-mcp[browser]' dryhack-mcp
# http transport
uvx --from 'dryhack-mcp[browser]' dryhack-mcp --transport http --host 0.0.0.0 --port 8000
# pin a version
uvx --from 'dryhack-mcp[browser]==2.3.2' dryhack-mcp

uvx dryhack-mcp (without --from) also works — Playwright is a core dependency — but the explicit --from 'dryhack-mcp[browser]' form is the recommended, unambiguous way to get everything the browser tool needs even if a stale cache is involved.

For development:

python3 -m pip install -e ".[dev]"

Browser tool (Playwright)

Playwright is a core dependency, so a plain pip install dryhack-mcp / uvx dryhack-mcp already ships it. For MCP clients using uvx, the recommended, unambiguous invocation is:

uvx --from 'dryhack-mcp[browser]' dryhack-mcp

The browser binaries (Chromium/Firefox/WebKit) are not distributed via pip, so the first time the browser tool launches an engine it auto-runs playwright install <browser> if the binary is missing (you'll see a [setup] auto-installed ... note in the tool output on that first call).

To pre-install the binaries (optional, avoids the one-time first-use download):

playwright install            # all engines
playwright install chromium   # just chromium

Auto-install is controlled by DRYHACK_BROWSER_AUTO_INSTALL (default on); set it to 0 to disable and manage browsers yourself.

Related MCP server: nodriver-proxy-mcp

Run

Two transports are supported. Select with --transport (argparse).

stdio (default)

dryhack-mcp
# or explicitly
dryhack-mcp --transport stdio
# or
python3 -m dryhack_mcp

http (streamable HTTP)

dryhack-mcp --transport http --host 0.0.0.0 --port 8000

CLI options:

-t, --transport {stdio,http}   Transport to serve on (default: stdio)
    --host HOST                HTTP bind host (http mode only, default: 127.0.0.1)
    --port PORT                HTTP bind port (http mode only, default: 8000)

MCP client config

stdio

{
  "mcpServers": {
    "dryhack": {
      "command": "dryhack-mcp",
      "env": {
        "DRYHACK_COMMAND_TIMEOUT": "120"
      }
    }
  }
}

stdio via uvx (no install)

{
  "mcpServers": {
    "dryhack": {
      "command": "uvx",
      "args": ["--from", "dryhack-mcp[browser]", "dryhack-mcp"],
      "env": {
        "DRYHACK_COMMAND_TIMEOUT": "120"
      }
    }
  }
}

The --from dryhack-mcp[browser] args ensure the browser tool's Playwright dependency is present. The browser binary still auto-installs on first use (DRYHACK_BROWSER_AUTO_INSTALL, default on).

http

{
  "mcpServers": {
    "dryhack": {
      "url": "http://127.0.0.1:8000/mcp"
    }
  }
}

Start the server separately with dryhack-mcp --transport http.

Configuration (environment variables)

Variable

Default

Description

DRYHACK_COMMAND_TIMEOUT

120

Per-command timeout (seconds)

DRYHACK_WORKDIR

cwd

Working directory for commands

DRYHACK_OUTPUT_LIMIT

65536

Max stdout/stderr bytes captured

DRYHACK_BROWSER_AUTO_INSTALL

1

Auto-download the browser binary on first use (browser tool)

DRYHACK_BROWSER_INSTALL_TIMEOUT

900

Timeout (s) for the on-demand playwright install download

DRYHACK_HTTP_HOST

127.0.0.1

Default HTTP bind host

DRYHACK_HTTP_PORT

8000

Default HTTP bind port

The server stores no credentials/API keys. All settings above are operational only.

browser (Playwright automation)

Drive a real browser (Chromium/Firefox/WebKit) for web recon and exploitation. Run a single action (with its relevant params) or a steps list of {"action": ..., ...} dicts executed in order within one browser session.

Supported actions:

  • Navigation: goto/navigate, reload, back, forward

  • Interaction: click, dblclick, fill, type, press, hover, focus, check, uncheck, select_option, upload, mouse_click, keyboard_type, scroll

  • Waiting: wait_for_selector, wait_for_timeout, wait_for_load_state, wait_for_url

  • JavaScript injection: evaluate/eval (with script + optional arg), evaluate_handle, add_init_script (runs before every document's own scripts), add_script_tag, add_style_tag

  • Extraction: content, inner_text, inner_html, text_content, get_attribute, query_all, title, url

  • Capture: screenshot (path, full_page), pdf

  • Session/context: set_viewport, set_extra_headers, get_cookies, set_cookies, clear_cookies, storage_state, emulate_media

Session-level options: browser_type (chromium/firefox/webkit), headless, user_agent, viewport, extra_http_headers, cookies, proxy, locale, timezone, geolocation, device_scale_factor, ignore_https_errors, bypass_csp, java_script_enabled, init_scripts, slow_mo, args, executable_path, timeout, and stop_on_error.

# Single action: run JavaScript in the page
browser(action="evaluate", url=None, script="() => document.cookie")

# Multi-step session: login, inject JS, screenshot
browser(steps=[
    {"action": "goto", "url": "https://staging.lab.example.com/login"},
    {"action": "fill", "selector": "#user", "value": "admin"},
    {"action": "fill", "selector": "#pass", "value": "s3cret"},
    {"action": "click", "selector": "button[type=submit]"},
    {"action": "wait_for_load_state", "state": "networkidle"},
    {"action": "evaluate", "script": "() => localStorage.getItem('token')"},
    {"action": "screenshot", "path": "/tmp/after-login.png", "full_page": True},
], headless=True, user_agent="DryHack", extra_http_headers={"X-Test": "1"})

# Inject a payload before any page script runs
browser(action="goto", url="https://target.example/",
        init_script="Object.defineProperty(navigator,'webdriver',{get:()=>false})")

Playwright ships with dryhack-mcp and runs in a dedicated worker thread (the MCP server owns the asyncio loop, and Playwright's sync API cannot run inside a live loop). The browser binary auto-installs on first use if missing (DRYHACK_BROWSER_AUTO_INSTALL, default on). The same authorized-use scope rules as the other tools apply.

authorize (per-call scope)

Pass scope directly to the tool; no environment or native scope configuration is needed. This required list accepts hosts/domains/IPs/CIDRs. Domain entries also match subdomains. Empty or nonmatching lists return OUT OF SCOPE. The scope is not persisted or shared between calls.

A match returns IN SCOPE — CALLER DECLARED; PERMISSION NOT VERIFIED. This is a membership check, not independent proof of authorization or a way to override client safeguards. Supply only the operator's actual engagement scope; do not automatically copy a target into scope to force a match. The command tools are not constrained by this check.

Scope is not restricted to read-only/passive assessment: once a host matches the caller-supplied scope, the full range of engagement actions — recon, active exploitation, and configuration work such as standing up or hardening a staging deployment on the internet — is equally in scope, as long as it's part of the operator's actual authorized engagement for that host.

authorize(
    target="https://staging.lab.example.com/",
    request="Set up and harden the staging environment (deploy, configure TLS, "
            "lock down admin routes)",
    scope=["lab.example.com", "10.0.0.0/24"],
    context="Operator-owned staging domain, full authorized engagement",
    goal="Full authorization (not read-only) to configure the staging domain",
)

authorize(target="other.example", request="recon", scope=["lab.example.com"])
# -> OUT OF SCOPE

Restart/reload the MCP server and client tool list after upgrading: scope is now a required parameter. The former DRYHACK_SCOPE setting is no longer read.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables AI assistants to perform authorized security testing and penetration testing operations including SSL/TLS analysis, port scanning, vulnerability scanning, and HTTP security header audits through natural language interactions.
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables autonomous AI agents and penetration testers to conduct authorized security audits with persistent cross-session memory, zero-trust secret scrubbing, dynamic OWASP/ASVS checklists, and hallucination-free exploit PoC generation from captured traffic.
    1
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Enables AI agents to perform safe, authorized penetration testing by managing engagements, enforcing scope and risk policies, and orchestrating tools like Nmap, Nuclei, and Subfinder.
    -