DryHack-MCP
by HarshXor
README.md
# 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`, …) |
| `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
```bash
python3 -m pip install .
```
This installs the `dryhack-mcp` console script (the MCP server).
### Run without installing (uvx)
With [uv](https://docs.astral.sh/uv/) you can run the server directly from PyPI
— no manual install needed:
```bash
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:
```bash
python3 -m pip install -e ".[dev]"
```
## Run
Two transports are supported. Select with `--transport` (argparse).
### stdio (default)
```bash
dryhack-mcp
# or explicitly
dryhack-mcp --transport stdio
# or
python3 -m dryhack_mcp
```
### http (streamable HTTP)
```bash
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
```json
{
"mcpServers": {
"dryhack": {
"command": "dryhack-mcp",
"env": {
"DRYHACK_COMMAND_TIMEOUT": "120"
}
}
}
}
```
### stdio via uvx (no install)
```json
{
"mcpServers": {
"dryhack": {
"command": "uvx",
"args": ["dryhack-mcp"],
"env": {
"DRYHACK_COMMAND_TIMEOUT": "120"
}
}
}
}
```
### http
```json
{
"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.
## 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.
```python
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.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues