"When your AI says NOPE, your firewall listens."
NOPE is an MCP-enabled External Dynamic List (EDL) manager for Palo Alto firewalls. It provides a web UI, REST API, and MCP tools for managing IP and domain blocklists that firewalls consume via HTTPS.
- Web UI - Modern React interface for managing lists and IOCs
- REST API - Full CRUD operations for lists and IOCs
- MCP Integration - LLM-friendly tools for blocking threats via natural language
- EDL Serving - NGINX serves text-based lists compatible with Palo Alto firewalls
- Validation - Automatic validation of IPs, CIDRs, and domains
- Exclusions - Built-in protection against blocking TLDs, RFC1918, and localhost
- Deduplication - IOCs are deduplicated with comments from multiple sources
- Docker and Docker Compose
git clone <repository-url>
cd nope-edl
./setup.shsetup.sh will:
- Create
.envfrom.env.example(if it doesn't exist) - Auto-generate all required secrets
- Start the Docker Compose stack
- Print a startup banner with your credentials
Self-signed SSL certificates are automatically generated on first startup.
To use a different port, set NOPE_PORT in .env before running setup.sh, or:
NOPE_PORT=9443 docker compose up -d┌─────────────────────────────────────────────────────────────────┐
│ Docker Compose │
├─────────────────────────────────────────────────────────────────┤
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ NGINX │ │ NOPE API │ │ PostgreSQL │ │
│ │ (HTTPS) │───▶│ (FastAPI) │───▶│ (Database) │ │
│ │ Port 8081 │ │ Port 8000 │ │ Port 5432 │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/lists |
Get all lists with IOC counts |
| POST | /api/lists |
Create a new list |
| GET | /api/lists/{slug} |
Get list details |
| PATCH | /api/lists/{slug} |
Update a list |
| DELETE | /api/lists/{slug} |
Delete a list |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/iocs?q={query} |
Search IOCs |
| POST | /api/iocs |
Add IOC to list(s) |
| GET | /api/iocs/{id} |
Get IOC details |
| DELETE | /api/iocs/{id} |
Delete IOC from all lists |
| DELETE | /api/iocs/{id}/lists/{slug} |
Remove IOC from specific list |
| Method | Endpoint | Description |
|---|---|---|
| GET | /edl/{slug} |
Get list as text (Basic Auth required) |
NOPE exposes MCP tools at /mcp for LLM integration. These tools enable AI assistants to manage blocklists through natural language.
| Tool | Description |
|---|---|
list_lists |
Get all blocklists with IOC counts and descriptions |
get_list |
Get detailed information about a specific list |
create_list |
Create a new blocklist with name, description, and tags |
update_list |
Update list metadata (name, description, tags) |
delete_list |
Delete a list and remove all IOC associations |
list_iocs |
List all IOCs on a specific list with pagination |
| Tool | Description |
|---|---|
block_ioc |
Add an IOC (IP, CIDR, domain, wildcard, or hash) to a blocklist |
unblock_ioc |
Remove an IOC from a specific list or all lists |
search_ioc |
Search for IOCs across all lists (supports partial matching) |
update_ioc |
Add a comment to an existing IOC for audit history |
bulk_block_ioc |
Add multiple IOCs (up to 500) in a single operation |
bulk_unblock_ioc |
Remove multiple IOCs (up to 500) in a single operation |
Exclusions prevent certain IOCs from being added to blocklists (e.g., RFC1918 private ranges, TLDs).
| Tool | Description |
|---|---|
list_exclusions |
List all active exclusion rules (built-in and user-defined) |
preview_exclusion |
Preview impact of adding an exclusion (shows conflicting IOCs) |
add_exclusion |
Add a custom exclusion rule with optional conflict purging |
remove_exclusion |
Remove a user-defined exclusion rule |
NOPE automatically detects IOC types:
| Type | Examples |
|---|---|
| IP Address | 203.0.113.50 |
| CIDR Range | 203.0.113.0/24 |
| Domain | evil.com, malware.example.net |
| Wildcard Domain | *.badsite.com |
| SHA256 Hash | e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 |
| SHA1 Hash | da39a3ee5e6b4b0d3255bfef95601890afd80709 |
| MD5 Hash | d41d8cd98f00b204e9800998ecf8427e |
MCP endpoints require API key authentication. Create an API key in Settings > API Keys and include it in the api-key header.
Claude Desktop only supports stdio transport, so you need mcp-proxy to connect to NOPE's HTTP-based MCP server.
# Using pip
pip install mcp-proxy
# Or using pipx (recommended)
pipx install mcp-proxy- Log in to NOPE at https://your-server
- Go to Settings > API Keys
- Click + ADD API KEY
- Enter a name (e.g., "claude-desktop")
- Copy the generated key (starts with
nope_)
Edit your Claude Desktop config file:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Add the NOPE server configuration:
{
"mcpServers": {
"nope": {
"command": "mcp-proxy",
"args": [
"--transport=streamablehttp",
"--no-verify-ssl",
"--headers", "api-key", "nope_YOUR_API_KEY_HERE",
"https://your-nope-server/mcp"
]
}
}
}Arguments explained:
--transport=streamablehttp- Use HTTP transport (required for NOPE's MCP endpoint)--no-verify-ssl- Skip SSL verification for self-signed certificates--headers api-key ...- Pass the API key for authentication
Note: If Claude Desktop can't find mcp-proxy, use the full path. Run
which mcp-proxy(macOS/Linux) orwhere.exe mcp-proxy(Windows) to get the path.
Completely quit and restart Claude Desktop to load the new configuration.
In Claude Desktop, you should see NOPE's tools available. Try asking:
"What blocklists are available in NOPE?"
Claude Code supports both stdio and HTTP transports.
Add the MCP server via the CLI:
claude mcp add nope -- mcp-proxy \
--transport=streamablehttp \
--no-verify-ssl \
--headers api-key "nope_YOUR_API_KEY" \
https://your-nope-server/mcpRemove --no-verify-ssl if using a trusted certificate.
Edit your Claude Code settings file:
- macOS:
~/.claude/settings.json - Linux:
~/.claude/settings.json
Add the MCP server configuration:
{
"mcpServers": {
"nope": {
"command": "mcp-proxy",
"args": [
"--transport=streamablehttp",
"--no-verify-ssl",
"--headers", "api-key", "nope_YOUR_API_KEY_HERE",
"https://your-nope-server/mcp"
]
}
}
}After adding the server, restart Claude Code and verify with:
/mcp
You should see nope listed with 17 available tools.
Blocking threats:
User: "Block evil.com on the domain blocklist"
Agent: Uses block_ioc(value="evil.com", list_slug="domainblocklist")
→ "Successfully added evil.com (domain) to list 'domainblocklist'"
Investigating IOCs:
User: "Why is that IP blocked?"
Agent: Uses search_ioc(value="1.2.3.4")
→ "Found 1 IOC(s):
- 1.2.3.4 (ip)
Lists: ipblocklist
Comments: Malicious C2 server"
Bulk operations:
User: "Block these IPs from the threat feed: 198.51.100.1, 198.51.100.2, 198.51.100.3"
Agent: Uses bulk_block_ioc(values=["198.51.100.1", "198.51.100.2", "198.51.100.3"],
list_slug="ipblocklist", comment="Threat feed import")
→ "Bulk add complete: Added: 3, Skipped: 0, Failed: 0"
Exclusion handling:
User: "Block 192.168.1.1"
Agent: Uses block_ioc(value="192.168.1.1", list_slug="ipblocklist")
→ "Cannot block: RFC1918 private range (matches exclusion '192.168.0.0/16')"
Managing exclusions:
User: "Add an exclusion for our partner's domain"
Agent: Uses add_exclusion(value="*.partner.com", reason="Trusted partner domain")
→ "Added exclusion '*.partner.com' (wildcard). Reason: Trusted partner domain"
All actions performed via MCP are logged with the API key name used (e.g., mcp:claude-desktop), visible in the IOC audit history.
NOPE requires these environment variables to start. Running ./setup.sh auto-generates all of them. To configure manually, copy .env.example to .env and fill in values:
| Variable | Description |
|---|---|
SECRET_KEY |
JWT signing key. Generate with openssl rand -hex 32 |
DEFAULT_ADMIN_PASSWORD |
Initial admin password (minimum 6 characters) |
DEFAULT_EDL_PASSWORD |
EDL basic auth password (minimum 6 characters) |
DB_PASSWORD |
PostgreSQL password |
All passwords are validated at startup. The app will refuse to start if any required variable is missing or a password does not meet the minimum 6-character length requirement.
| Variable | Default | Description |
|---|---|---|
NOPE_PORT |
8081 | HTTPS port for the web UI and API |
DB_USER |
nope | PostgreSQL username |
DB_PASSWORD |
(required) | PostgreSQL password |
SECRET_KEY |
(required) | JWT signing key |
DEFAULT_ADMIN_USER |
admin | Default UI admin username |
DEFAULT_ADMIN_PASSWORD |
(required) | Default UI admin password |
DEFAULT_EDL_USER |
edl | Default EDL basic auth username |
DEFAULT_EDL_PASSWORD |
(required) | Default EDL basic auth password |
By default, self-signed certificates are generated automatically. For production, provide your own certificates:
-
Place your certificates in a
certsdirectory:mkdir certs cp your-cert.pem certs/cert.pem cp your-key.pem certs/key.pem
-
Uncomment the bind mount in
docker-compose.yml:nginx: volumes: - ./certs:/etc/nginx/certs:ro
-
Restart the stack:
docker compose up -d
- In PAN-OS, go to Objects > External Dynamic Lists
- Create a new EDL:
- Type: IP List or Domain List
- Source:
https://your-nope-server/edl/{list-slug} - Certificate Profile: (configure to trust your cert)
- Username/Password: Your EDL credentials
# Backend
cd backend
pip install -e ".[dev]"
uvicorn app.main:app --reload --port 8000
# Frontend
cd frontend
npm install
npm run devcd backend
pytest- Backend: Python, FastAPI, SQLAlchemy, Alembic, FastMCP
- Frontend: React, TypeScript, Vite, TanStack Query
- Database: PostgreSQL
- Proxy: NGINX
MIT