Skip to main content
Glama

PortHunter MCP

by Abysswalkr

PortHunter MCP — Local MCP server for port-scan analysis (PCAP/PCAPNG)

PortHunter es un servidor MCP local (transport STDIO) que:

  • analiza capturas PCAP/PCAPNG,

  • detecta técnicas comunes de escaneo (SYN, FIN/NULL/Xmas),

  • clasifica patrones (horizontal / vertical),

  • lista sospechosos y obtiene el primer evento relevante,

  • puede enriquecer IPs públicas (OTX/GreyNoise/ASN/Geo) y correlacionarlas.

Está pensado para ser consumido por cualquier host/chatbot MCP.


Requisitos

  • Python 3.11+

  • Windows, Linux o macOS

  • (Opcional) Docker


Instalación

python -m venv .venv # Windows PowerShell: .\.venv\Scripts\Activate.ps1 # Linux/macOS: source .venv/bin/activate pip install -U pip pip install -e .

El -e . instala el paquete porthunter en editable desde este repo.


Ejecución (STDIO)

Windows PowerShell (recomendado)

$env:PORT_HUNTER_TOKEN = "TEST_TOKEN" $env:PORT_HUNTER_ALLOWED_DIR = (Get-Location).Path python -m porthunter.server

Windows CMD

set PORT_HUNTER_TOKEN=TEST_TOKEN set PORT_HUNTER_ALLOWED_DIR=%CD% python -m porthunter.server

Linux/macOS

export PORT_HUNTER_TOKEN=TEST_TOKEN export PORT_HUNTER_ALLOWED_DIR="$PWD" python -m porthunter.server

El servidor queda escuchando por STDIO a la espera de llamadas MCP call_tool.


Variables de entorno (seguridad y límites)

Variable

Default

Descripción

PORT_HUNTER_TOKEN

TEST_TOKEN

Token requerido si

PORT_HUNTER_REQUIRE_TOKEN=true

.

PORT_HUNTER_REQUIRE_TOKEN

true

Exige

auth_token

en cada llamada de tool.

PORT_HUNTER_ALLOWED_DIR

.

Directorio raíz permitido

para leer PCAP/PCAPNG.

PORT_HUNTER_MAX_PCAP_MB

50

Tamaño máximo del archivo a procesar.

PORT_HUNTER_ALLOW_PRIVATE

false

Si

true

, permite enriquecer IPs privadas (por defecto se

omite

).


mcp.json (ejemplo listo para usar)

{ "name": "porthunter", "version": "0.1.0", "transport": { "stdio": { "command": "python", "args": ["-m", "porthunter.server"] } }, "env": { "PORT_HUNTER_TOKEN": "TEST_TOKEN", "PORT_HUNTER_ALLOWED_DIR": ".", "PORT_HUNTER_REQUIRE_TOKEN": "true", "PORT_HUNTER_MAX_PCAP_MB": "50" }, "tools": [ "scan_overview", "list_suspects", "first_scan_event", "enrich_ip", "correlate" ] }

Tools (API)

Todas las herramientas devuelven UTC ISO-8601 en generated_at.

1) scan_overview(path, time_window_s=60, top_k=20)

Input

{ "path": "captures/scan-demo.pcapng", "time_window_s": 60, "top_k": 20, "auth_token": "TEST_TOKEN" }

Return

{ "ok": true, "overview": { /* ver ejemplo */ }, "generated_at": "..." }

2) list_suspects(path, min_ports=10, min_rate_pps=5.0)

Input

{ "path": "captures/scan-demo.pcapng", "min_ports": 10, "min_rate_pps": 5.0, "auth_token": "TEST_TOKEN" }

Return

{ "ok": true, "suspects": [ /* items */ ], "generated_at": "..." }

3) first_scan_event(path)

Input

{ "path": "captures/scan-demo.pcapng", "auth_token": "TEST_TOKEN" }

Return

{ "ok": true, "first_event": { /* o null */ }, "generated_at": "..." }

4) enrich_ip(ip)

Input

{ "ip": "8.8.8.8", "auth_token": "TEST_TOKEN" }

Return (ok)

{ "ok": true, "enrichment": { "asn": "...", "org": "...", "geo": { "country": "US" }, "threat": { "otx": {...}, "greynoise": {...} } }, "generated_at": "..." }

Return (error)

{ "ok": false, "error": "invalid_ip", "generated_at": "..." }

5) correlate(ips[])

Input

{ "ips": ["abc", "192.168.0.10", "8.8.8.8"], "auth_token": "TEST_TOKEN" }

Return

{ "ok": true, "results": [ { "ip": "abc", "ok": false, "error": "invalid_ip" }, { "ip": "192.168.0.10", "skipped": true, "reason": "private_ip" }, { "ip": "8.8.8.8", "ok": true, "kind": "public", "enrichment": {/*...*/} } ], "generated_at": "..." }

Ejemplos de JSON (respuestas reales)

scan_overview (ejemplo)

{ "ok": true, "overview": { "file": "captures/scan.pcapng", "total_pkts": 12345, "interval_s": 600, "scanners": [ { "ip": "1.2.3.4", "pkts": 500, "distinct_ports": 120, "distinct_hosts": 30, "flag_stats": { "SYN": 480, "FIN": 15, "XMAS": 5 } } ], "targets": [ { "ip": "10.0.0.5", "pkts": 320, "ports_hit": [22, 80, 443] } ], "port_distribution": [ { "port": 80, "hits": 450 }, { "port": 22, "hits": 120 } ], "suspected_patterns": ["syn_scan", "xmas_scan"] }, "generated_at": "2025-09-20T23:00:02Z" }

list_suspects (ejemplo)

{ "ok": true, "suspects": [ { "ip": "5.6.7.8", "kind": "horizontal", "distinct_ports": 50, "rate_pps": 7.2, "flags_seen": ["SYN"] }, { "ip": "9.9.9.9", "kind": "vertical", "distinct_ports": 1, "rate_pps": 12.0, "flags_seen": ["SYN","FIN"] } ], "generated_at": "2025-09-20T23:01:12Z" }

first_scan_event (ejemplo)

{ "ok": true, "first_event": { "ts": "2025-09-20T22:59:58Z", "src": "1.2.3.4", "dst": "10.0.0.5", "port": 80, "flags": "S" }, "generated_at": "2025-09-20T23:01:45Z" }

enrich_ip (error por IP inválida)

{ "ok": false, "error": "invalid_ip", "generated_at": "2025-09-20T23:02:10Z" }

correlate (mixto)

{ "ok": true, "results": [ { "ip": "abc", "ok": false, "error": "invalid_ip" }, { "ip": "192.168.0.10", "skipped": true, "reason": "private_ip" }, { "ip": "8.8.8.8", "ok": true, "kind": "public" } ], "generated_at": "2025-09-20T23:02:30Z" }

Errores comunes (contract)

  • Archivo fuera del directorio permitido:

{ "ok": false, "error": "path_outside_allowed_dir", "generated_at": "..." }
  • Extensión no soportada:

{ "ok": false, "error": "unsupported_file_type", "generated_at": "..." }
  • Excede tamaño máximo:

{ "ok": false, "error": "file_too_large", "generated_at": "..." }
  • Token faltante o incorrecto (si se requiere):

{ "ok": false, "error": "unauthorized", "generated_at": "..." }

Uso desde un host MCP (pseudo-cliente)

import asyncio, json from mcp import StdioServerParameters, types from mcp.client.stdio import stdio_client from mcp.client.session import ClientSession async def main(): params = StdioServerParameters( command="python", args=["-m", "porthunter.server"], env={ "PORT_HUNTER_TOKEN": "TEST_TOKEN", "PORT_HUNTER_ALLOWED_DIR": ".", } ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() resp = await session.call_tool( name="scan_overview", arguments={"path": "captures/scan-demo-20250906-1.pcapng", "auth_token": "TEST_TOKEN"} ) # structuredContent preferente sc = getattr(resp, "structuredContent", None) if isinstance(sc, dict): print(json.dumps(sc.get("result", sc), indent=2)) else: text = "".join(b.text for b in resp.content if isinstance(b, types.TextContent)) print(text) asyncio.run(main())

Docker

docker build -t porthunter-mcp . docker run --rm -it \ -e PORT_HUNTER_TOKEN=TEST_TOKEN \ -e PORT_HUNTER_ALLOWED_DIR=/data \ -v "$PWD:/data" \ porthunter-mcp

Benchmark (opcional)

python scripts/benchmark_porthunter.py captures/scan-demo-20250906-1.pcapng

Salida sugerida:

  • tamaño archivo,

  • paquetes totales,

  • duración total (s),

  • pps promedio.

Incluye una tablita de resultados en el README si vas a reportar métricas.


Desarrollo

  • Código fuente del servidor en porthunter/

  • Utilidades de PCAP e inteligencia en porthunter/utils/**

  • Ejecuta linters/tests en tu proyecto principal si los tienes allí.

  • Si subes pruebas mínimas aquí: pytest -q


Licencia

MIT (sugerida). Añade un archivo LICENSE si lo deseas.


Créditos y referencias


TL;DR

Arranca con:

$env:PORT_HUNTER_TOKEN = "TEST_TOKEN" $env:PORT_HUNTER_ALLOWED_DIR = (Get-Location).Path python -m porthunter.server

Llama scan_overview / list_suspects / first_scan_event / enrich_ip / correlate y consume el JSON como en los ejemplos de arriba.

-
security - not tested
F
license - not found
-
quality - not tested

local-only server

The server can only run on the client's local machine because it depends on local resources.

Analyzes PCAP/PCAPNG network capture files to detect port scanning techniques (SYN, FIN, Xmas), classify scan patterns, and enrich suspicious IP addresses with threat intelligence data. Provides comprehensive network security analysis through natural language interactions.

  1. Requisitos
    1. Instalación
      1. Ejecución (STDIO)
        1. Windows PowerShell (recomendado)
        2. Windows CMD
        3. Linux/macOS
      2. Variables de entorno (seguridad y límites)
        1. mcp.json (ejemplo listo para usar)
          1. Tools (API)
            1. 1) scan_overview(path, time_window_s=60, top_k=20)
            2. 2) list_suspects(path, min_ports=10, min_rate_pps=5.0)
            3. 3) first_scan_event(path)
            4. 4) enrich_ip(ip)
            5. 5) correlate(ips[])
          2. Ejemplos de JSON (respuestas reales)
            1. scan_overview (ejemplo)
            2. list_suspects (ejemplo)
            3. first_scan_event (ejemplo)
            4. enrich_ip (error por IP inválida)
            5. correlate (mixto)
          3. Errores comunes (contract)
            1. Uso desde un host MCP (pseudo-cliente)
              1. Docker
                1. Benchmark (opcional)
                  1. Desarrollo
                    1. Licencia
                      1. Créditos y referencias
                        1. TL;DR

                      MCP directory API

                      We provide all the information about MCP servers via our MCP API.

                      curl -X GET 'https://glama.ai/api/mcp/v1/servers/Abysswalkr/porthunter-mcp'

                      If you have feedback or need assistance with the MCP directory API, please join our Discord server