Skip to main content

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:

uvx dryhack-mcp
# http transport
uvx dryhack-mcp --transport http --host 0.0.0.0 --port 8000
# pin a version
uvx dryhack-mcp@0.1.3

For development:

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

Browser tool (Playwright)

The browser tool needs the optional browser extra plus the Playwright browser binaries:

python3 -m pip install 'dryhack-mcp[browser]'
playwright install        # or: playwright install chromium

If Playwright is not installed, the browser tool returns an install hint instead of failing — the rest of the server keeps working.

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": ["dryhack-mcp"],
      "env": {
        "DRYHACK_COMMAND_TIMEOUT": "120"
      }
    }
  }
}

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_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 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 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.

Metadata

Release files for dryhack-mcp 2.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dryhack-mcp 2.2.0
File Size Uploaded Provenance Actions
dryhack_mcp-2.2.0.tar.gz 21.4 kB Without provenance Details for dryhack_mcp-2.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for dryhack-mcp 2.2.0
File Interpreter ABI Platform Actions
dryhack_mcp-2.2.0-py3-none-any.whl Python 3 none any Details for dryhack_mcp-2.2.0-py3-none-any.whl

Total release size: 42.7 kB

Release files / dryhack_mcp-2.2.0.tar.gz

Download URL dryhack_mcp-2.2.0.tar.gz
Size 21.4 kB
Tags Source
SHA-256 checksum
How to use checksums
73b5a52b6a357b7352c35d89a645a35b874f5fa65adc6f4ab708672b6b59bc24
BLAKE2b-256 checksum
How to use checksums
a06f5e875be83edc28cebc29a8f6fd6265edf6184e1c9644cc3102e25e37107b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / dryhack_mcp-2.2.0-py3-none-any.whl

Download URL dryhack_mcp-2.2.0-py3-none-any.whl
Size 21.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30786a568cd05cd8ab1cbd4852c7106df31f5c94b6179011d8f86566355452e0
BLAKE2b-256 checksum
How to use checksums
a88ae65c03ae5fa0e80d1b64442b1f767cc1920cd4c119f214123d7ec7ee06ce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Security for dryhack-mcp 2.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page