Metadata-Version: 2.4
Name: mcp-server-zap
Version: 0.1.0
Summary: MCP server that exposes OWASP ZAP as tools to Claude and other LLM clients
Project-URL: Homepage, https://github.com/Karacali/mcp-zap
Project-URL: Issues, https://github.com/Karacali/mcp-zap/issues
Project-URL: Changelog, https://github.com/Karacali/mcp-zap/blob/main/CHANGELOG.md
Author: Karacali
License: MIT
License-File: LICENSE
Keywords: claude,mcp,model-context-protocol,owasp-zap,pentest,security
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Information Technology
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Testing
Requires-Python: >=3.10
Requires-Dist: mcp>=1.0.0
Requires-Dist: zaproxy>=0.5.0
Provides-Extra: dev
Requires-Dist: httpx>=0.27.0; extra == 'dev'
Requires-Dist: pyjwt>=2.8.0; extra == 'dev'
Requires-Dist: pyright>=1.1.350; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.5.0; extra == 'dev'
Requires-Dist: starlette>=0.37.0; extra == 'dev'
Requires-Dist: uvicorn>=0.30.0; extra == 'dev'
Provides-Extra: http
Requires-Dist: pyjwt>=2.8.0; extra == 'http'
Requires-Dist: starlette>=0.37.0; extra == 'http'
Requires-Dist: uvicorn>=0.30.0; extra == 'http'
Description-Content-Type: text/markdown

# mcp-server-zap

[![PyPI](https://img.shields.io/pypi/v/mcp-server-zap.svg)](https://pypi.org/project/mcp-server-zap/)
[![Python](https://img.shields.io/pypi/pyversions/mcp-server-zap.svg)](https://pypi.org/project/mcp-server-zap/)
[![CI](https://github.com/Karacali/mcp-zap/actions/workflows/ci.yml/badge.svg)](https://github.com/Karacali/mcp-zap/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![MCP](https://img.shields.io/badge/MCP-server-blueviolet.svg)](https://modelcontextprotocol.io)

**Python MCP server for OWASP ZAP — Docker-first, extensible, ~37 tools.**

A [Model Context Protocol](https://modelcontextprotocol.io) server that exposes [OWASP ZAP](https://www.zaproxy.org/) as tools for Claude Code, Claude Desktop, Cursor, Continue, and any other MCP-compatible LLM client. Spider, active scan, fuzzing, brute force, intercept, schema-driven scanning, the Automation Framework, regex search, and HTML/JSON/Markdown/SARIF reporting — all driven from natural-language conversation.

```mermaid
flowchart LR
    Client[Claude / Cursor / Continue]
    Server[mcp-server-zap]
    ZAP[OWASP ZAP daemon]
    Target[Target Application]

    Client -- "MCP (stdio or HTTP)" --> Server
    Server -- "ZAP REST API" --> ZAP
    ZAP -- "HTTP proxy / scans" --> Target
```

## Why this server?

In April 2026 the OWASP ZAP project shipped an [official MCP add-on](https://www.zaproxy.org/blog/2026-04-02-zap-mcp-server/) that runs **inside the ZAP process** with a deliberately limited tool set. If you primarily drive ZAP from its desktop GUI and want one-click MCP, install that add-on.

This project is the alternative for **teams that prefer a Python, Docker-first deployment with broader API coverage and explicit auth**:

| | Official ZAP MCP add-on | mcp-server-zap *(this project)* |
|---|---|---|
| Process model | Java add-on **inside** ZAP | External Python process |
| Install | ZAP Marketplace (one click) | `uvx mcp-server-zap` or `docker compose up` |
| Tool count | ~17 (intentionally minimal) | ~37 |
| Transport | HTTP only (port 8282) | Stdio (default) + optional HTTP |
| Auth | Optional shared key | API-key + HS256 JWT |
| Source | Closed/binary (alpha) | MIT, public `src/` layout |
| Best for | ZAP Desktop power users | Python/Docker DevOps teams, CI pipelines |

Both projects target the same API. Pick the one that matches your workflow.

## Authorized Use Only

This is a security-testing tool. **Use it only on systems you own or have written permission to test.** Brute-force, fuzzing, and active-scan tools generate traffic indistinguishable from a real attack. Misuse is illegal in most jurisdictions and is solely the operator's responsibility.

## Features

- **Crawl** — Spider and AJAX spider for SPA-heavy targets.
- **Scan** — Active vulnerability scanning, passive scan status, scan policies and scanner controls.
- **Schema import** — OpenAPI, GraphQL, SOAP, and Postman collections feed the scanner.
- **Attack** — Parameter fuzzing and login brute-force.
- **Intercept** — Toggle break mode, add URL-pattern breakpoints.
- **Auth and context** — ZAP contexts, sessions, session-token introspection.
- **Search** — Regex over recorded requests and responses for secret-leak hunting.
- **Automation** — Run ZAP Automation Framework YAML plans end-to-end.
- **Report** — HTML, JSON, Markdown, themed templates, and SARIF for GitHub Code Scanning.

## Quick Start

### Option A — `uvx` (recommended for local Claude clients)

#### 1. Run a ZAP daemon

```bash
docker run -u zap -p 8080:8080 -i ghcr.io/zaproxy/zaproxy:stable \
  zap.sh -daemon -host 0.0.0.0 -port 8080 \
  -config api.disablekey=true \
  -config api.addrs.addr.name=.* -config api.addrs.addr.regex=true
```

> For non-localhost or multi-user setups, drop `api.disablekey=true` and set `ZAP_API_KEY`.

#### 2. Add the MCP server

**Claude Desktop** — edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "zap": {
      "command": "uvx",
      "args": ["mcp-server-zap"],
      "env": {
        "ZAP_URL": "http://localhost:8080",
        "ZAP_API_KEY": ""
      }
    }
  }
}
```

**Claude Code:**

```bash
claude mcp add zap -- uvx mcp-server-zap
```

**Cursor / Continue / other MCP clients** use the same JSON shape under their `mcpServers` block.

### Option B — Docker Compose (bundles ZAP + MCP server)

```bash
git clone https://github.com/Karacali/mcp-zap.git
cd zap-mcp
cp .env.example .env  # set MCP_API_KEY
docker compose up -d
```

This spins up ZAP and `mcp-server-zap` on the same network. The MCP server exposes the **HTTP transport** on `127.0.0.1:8765`. Auto-remap rewrites `localhost` targets to `host.docker.internal` so you can scan apps running on your dev machine from inside the container.

Connect a remote-MCP-aware client to `http://127.0.0.1:8765/` with `Authorization: Bearer $MCP_API_KEY`.

For local debugging (binds ZAP UI to `localhost:8080`):

```bash
docker compose -f docker-compose.yml -f docker-compose.dev.yml up
```

### 3. Try it

```
Run a spider scan against https://target.example.com
What's the status of the active scan?
List High-risk alerts
Search responses for "api_key|token|secret"
Generate an HTML report
```

## Environment Variables

| Variable | Default | Description |
|---|---|---|
| `ZAP_URL` | `http://localhost:8080` | ZAP daemon URL |
| `ZAP_API_KEY` | _(empty)_ | ZAP API key, if configured |
| `ZAP_MCP_REPORT_DIR` | `reports` | Directory where HTML/JSON reports are written |
| `ZAP_MCP_CONTAINER` | _(auto)_ | Force `localhost → host.docker.internal` remap (set automatically when `/.dockerenv` is present) |
| `MCP_TRANSPORT` | `stdio` | `stdio` or `http` |
| `MCP_HTTP_HOST` | `127.0.0.1` | HTTP transport bind host (used when `MCP_TRANSPORT=http`) |
| `MCP_HTTP_PORT` | `8765` | HTTP transport port |
| `MCP_AUTH_MODE` | `disabled` | `disabled`, `apikey`, or `jwt` (HTTP transport only) |
| `MCP_API_KEY` | _(empty)_ | Required when `MCP_AUTH_MODE=apikey` |
| `MCP_JWT_SECRET` | _(empty)_ | HMAC secret (HS256) for `MCP_AUTH_MODE=jwt` |
| `MCP_LOG_LEVEL` | `INFO` | Python logging level |

## Tool Reference

| Tool | Purpose |
|---|---|
| `zap_version` | Connectivity probe; returns ZAP version |
| `spider_scan`, `spider_status` | Classic spider crawl |
| `ajax_spider_scan`, `ajax_spider_status` | AJAX spider for SPAs |
| `forced_browse` | Hidden directory/file discovery |
| `get_sites`, `get_urls`, `open_url` | Site and URL listing, proxy open |
| `active_scan`, `active_scan_status`, `passive_scan_status`, `stop_scan`, `list_scans` | Vulnerability scanning |
| `get_alerts` | List findings (filter by URL / risk) |
| `set_scan_policy`, `list_scan_policies`, `list_scanners` | Scan policy controls |
| `get_messages`, `get_message_by_id` | Inspect proxied traffic |
| `send_request` | Send raw HTTP request through ZAP |
| `search_in_requests`, `search_in_responses` | Regex search across history |
| `fuzz_request` | Parameter fuzzing |
| `brute_force_login` | Login brute-force |
| `set_break`, `add_break_point` | Intercept controls |
| `set_context`, `list_contexts`, `new_session`, `list_sessions`, `get_session_tokens` | Context and session management |
| `import_openapi`, `import_graphql`, `import_soap`, `import_postman` | Schema-driven endpoint discovery |
| `run_automation_plan`, `automation_plan_status` | ZAP Automation Framework YAML plans |
| `generate_html_report`, `generate_json_report`, `generate_markdown_report` | Legacy core report API (HTML / JSON / Markdown) |
| `generate_report` | Modern Reports add-on (themed HTML, PDF, **SARIF for GitHub Code Scanning**, etc.) |

## Example Scenario

A typical conversation that runs an end-to-end scan:

```
You: Open https://juice-shop.example via the proxy and start a spider on it.
Claude: [calls open_url, then spider_scan → returns scan_id 0]

You: How's the spider going?
Claude: [calls spider_status with scan_id=0 → "100%, 184 URLs found"]

You: Now run an active scan on the same target.
Claude: [calls active_scan → scan_id 1]

You: Show me the High-risk alerts when it's done.
Claude: [polls active_scan_status, then get_alerts(risk_level="High")]

You: Generate an HTML report.
Claude: [calls generate_html_report → writes ./reports/zap-report.html]
```

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `zap_version` returns `disconnected` | ZAP daemon not reachable from the MCP server | Check `ZAP_URL`. From inside Docker the value should be `http://zap:8080`, not `http://localhost:8080` |
| Spider scan returns 0 URLs after `100%` | Some sites need AJAX spider | Use `ajax_spider_scan` instead |
| Scanning `localhost:3000` from compose stack hits the wrong target | Container's loopback isn't your host | We auto-remap to `host.docker.internal`. Ensure `ZAP_MCP_CONTAINER=1` is set (already set in our Dockerfile) |
| HTTP transport returns 401 | Auth mode mismatch | Set `MCP_AUTH_MODE=apikey` and `MCP_API_KEY=<value>`, then send `Authorization: Bearer <value>` |
| `import_openapi` says "add-on not installed" | OpenAPI add-on missing in your ZAP build | `zap.sh -addoninstall openapi` or install from the ZAP Marketplace |
| `fuzz_request` payloads all return `error` | ZAP probably can't reach the original `message_id` host | Re-record the request (`open_url`), then use the new ID |
| Reports missing | Output directory unwritable | Set `ZAP_MCP_REPORT_DIR` to a writable path |

## Local Development

```bash
git clone https://github.com/Karacali/mcp-zap.git
cd zap-mcp

# uv (recommended)
uv sync --all-extras
uv run mcp-server-zap

# or pip
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
mcp-server-zap
```

Quality gates:

```bash
uv run pytest -q
uv run ruff check src tests
uv run pyright src
```

## Security

For responsible-disclosure procedures and reporting vulnerabilities in this MCP wrapper, see [SECURITY.md](SECURITY.md). ZAP itself is maintained by OWASP — vulnerabilities in ZAP go to the [ZAP project](https://github.com/zaproxy/zaproxy/security).

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — run `ruff`, `pyright`, and `pytest` before submitting.

## License

[MIT](LICENSE).
