ScanMalware.com URL Scanner
Servidor MCP para escaneo de URL, detección de malware y análisis de ScanMalware.com
Documentación
scanmalware-mcp
Servidor MCP mínimo en Python que envuelve la API pública de ScanMalware.com.
Operaciones
Consulta docs/OPERATIONS.md para el despliegue, TLS, logging y cómo conectarse al droplet de DigitalOcean.
Ejecutar localmente (HTTP streamable)
python -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install .
export MCP_TRANSPORT=streamable-http
export MCP_HOST=127.0.0.1
export MCP_PORT=8000
scanmalware-mcp
Ejecutar con Docker
docker build -t scanmalware-mcp .
docker run --rm -p 127.0.0.1:8000:8000 \\
-e MCP_TRANSPORT=streamable-http \\
-e MCP_HOST=0.0.0.0 \\
-e MCP_PORT=8000 \\
scanmalware-mcp
Opcional: establece MCP_AUTH_TOKEN para requerir Authorization: Bearer <MCP_AUTH_TOKEN> para transportes HTTP.
Variables de entorno de autenticación opcionales (solo necesarias para endpoints protegidos por autenticación):
SCANMALWARE_BEARER_TOKEN
Otras variables de entorno:
SCANMALWARE_BASE_URL(por defecto:https://scanmalware.com)SCANMALWARE_ALLOW_HTTP(por defecto:false)SCANMALWARE_TIMEOUT_S(por defecto:30)SCANMALWARE_MAX_DOWNLOAD_BYTES(por defecto:10485760)SCANMALWARE_ALLOW_PRIVATE_TARGETS(por defecto:false)SCANMALWARE_CA_CERT(opcional; ruta a un paquete de CA para SSL bump)
Variables de entorno de seguridad del servidor MCP:
MCP_AUTH_TOKEN(si se establece, los transportes HTTP requierenAuthorization: Bearer <token>)MCP_RESOURCE_SERVER_URL/MCP_ISSUER_URL(opcional; solo se usan cuandoMCP_AUTH_TOKENestá establecido)
Nota sobre herramientas: submit_scan no llama a /api/v1/csrf-token; no existe una herramienta de token CSRF.
Nota sobre herramientas: algunos endpoints upstream están deshabilitados y excluidos de la lista de herramientas (p. ej., get_improvements, find_screenshot_duplicates, get_ai_stats, search_js_fingerprinter2_code_hash, search_js_segments_by_tlsh).
Algunas herramientas de búsqueda requieren al menos un filtro y generarán un error de validación si no se proporciona ninguno.
Ejemplos de prompts
Triaje de phishing (enviar → esperar → resumir):
Submit a scan for https://example-login-update.com, wait for completion, and
return status, risk_score, and the top indicators. If high risk, include the
AI analysis and screenshot resource.
Monitoreo de abuso de marca:
Search scans for "acme login" (limit 5). For each result, list scan_id,
status, risk_score, and URL. Highlight anything marked high risk.
Inspección de TLS/certificados:
For scan_id 1234...abcd, fetch TLS details and the certificate PEM download.
Summarize issuer, subject, validity dates, and SANs; flag mismatches.
Desplegar en DigitalOcean (Debian + Docker + Nginx)
El paquete de despliegue se encuentra en deploy/ y ejecuta dos contenedores:
mcp(este servidor, HTTP streamable en el puerto 8000)nginx(frontend en el puerto 80; hace de proxy de/mcpal servidor MCP)
Requisitos previos
doctlautenticado (doctl auth init)- Clave SSH subida a DigitalOcean (usada por
doctl compute droplet create)
Crear un droplet pequeño en Alemania (Fráncfort)
DROPLET_NAME=scanmalware-mcp-small
REGION=fra1
SIZE=s-1vcpu-2gb
IMAGE=debian-12-x64
SSH_KEYS=$(doctl compute ssh-key list --format ID --no-header | paste -sd, -)
doctl compute droplet create "$DROPLET_NAME" \
--region "$REGION" \
--size "$SIZE" \
--image "$IMAGE" \
--ssh-keys "$SSH_KEYS" \
--tag-name scanmalware-mcp \
--wait
Firewall (HTTP/HTTPS público + SSH)
doctl compute firewall create \
--name scanmalware-mcp-fw \
--inbound-rules "protocol:tcp,ports:22,address:0.0.0.0/0,address:::0/0" \
--inbound-rules "protocol:tcp,ports:80,address:0.0.0.0/0,address:::0/0" \
--inbound-rules "protocol:tcp,ports:443,address:0.0.0.0/0,address:::0/0" \
--outbound-rules "protocol:icmp,ports:0,address:0.0.0.0/0,address:::0/0" \
--outbound-rules "protocol:tcp,ports:0,address:0.0.0.0/0,address:::0/0" \
--outbound-rules "protocol:udp,ports:0,address:0.0.0.0/0,address:::0/0" \
--droplet-ids <droplet-id>
Instalar Docker + compose en el droplet
ssh -i /path/to/key root@<droplet-ip> \
"apt-get update -y && apt-get install -y docker.io docker-compose"
Subir y ejecutar
tar --exclude=.git --exclude=.venv --exclude=__pycache__ -czf /tmp/scanmalware-mcp.tar.gz -C . .
scp -i /path/to/key /tmp/scanmalware-mcp.tar.gz root@<droplet-ip>:/tmp/
ssh -i /path/to/key root@<droplet-ip> \
"mkdir -p /opt/scanmalware-mcp && tar -xzf /tmp/scanmalware-mcp.tar.gz -C /opt/scanmalware-mcp"
ssh -i /path/to/key root@<droplet-ip> \
"cd /opt/scanmalware-mcp && docker-compose -f deploy/docker-compose.yml up -d --build"
Verificar
curl -I https://mcp.scanmalware.com/
curl -I https://mcp.scanmalware.com/mcp
/ debería devolver 200 desde Nginx. /mcp devuelve 406 en GET sin cabeceras MCP Accept, lo cual es esperado.
Prueba de humo (MCP initialize + tools/list)
python - <<'PY'
import json
import httpx
URL = "http://<droplet-ip>/mcp"
HEADERS = {
"accept": "application/json, text/event-stream",
"content-type": "application/json",
}
init_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": {"name": "mcp-smoke-test", "version": "0.1.0"},
},
}
with httpx.Client(timeout=10) as client:
init_resp = client.post(URL, headers=HEADERS, json=init_payload)
init_resp.raise_for_status()
session_id = init_resp.headers.get("mcp-session-id")
def extract_sse_data(text: str) -> dict:
for line in text.splitlines():
if line.startswith("data: "):
return json.loads(line[len("data: "):])
raise ValueError("No SSE data line found")
init_message = extract_sse_data(init_resp.text)
protocol_version = init_message["result"]["protocolVersion"]
# Send initialized notification
client.post(
URL,
headers={
**HEADERS,
"mcp-session-id": session_id,
"mcp-protocol-version": protocol_version,
},
json={"jsonrpc": "2.0", "method": "notifications/initialized"},
)
tools_resp = client.post(
URL,
headers={
**HEADERS,
"mcp-session-id": session_id,
"mcp-protocol-version": protocol_version,
},
json={"jsonrpc": "2.0", "id": 2, "method": "tools/list"},
)
tools_resp.raise_for_status()
tools_message = extract_sse_data(tools_resp.text)
tool_names = [tool["name"] for tool in tools_message["result"]["tools"]]
print("protocol_version:", protocol_version)
print("tool_count:", len(tool_names))
print("tools:", ", ".join(tool_names))
PY
Redesplegar / nuevos despliegues
Dos flujos comunes:
- Actualización in situ (mismo droplet)
tar --exclude=.git --exclude=.venv --exclude=__pycache__ -czf /tmp/scanmalware-mcp.tar.gz -C . .
scp -i /path/to/key /tmp/scanmalware-mcp.tar.gz root@<droplet-ip>:/tmp/
ssh -i /path/to/key root@<droplet-ip> \
"bash /opt/scanmalware-mcp/deploy/redeploy.sh /tmp/scanmalware-mcp.tar.gz"
El script de redespliegue detiene los contenedores antes de intercambiar archivos para evitar problemas de inode con los bind-mounts. Si el script aún no está en el droplet, ejecuta el comando heredado de tar + docker-compose una vez para instalarlo.
Helper opcional de un solo uso desde la raíz del repositorio:
./deploy/push-redeploy.sh root@<droplet-ip> /path/to/key
- Despliegue continuo (nuevo droplet)
- Crear un nuevo droplet (pasos anteriores)
- Desplegar el mismo paquete
- Cambiar el DNS a la nueva IP
- Destruir el droplet antiguo cuando esté listo
doctl compute droplet delete <old-droplet-id> --force