Prototyp · Vorschau: alle Server aus der offiziellen Registry, alle 30 Minuten synchronisiert. Bewertungen und Benutzerkonten folgen in einem späteren Schritt.
PopNx MCPMCP-Server für alle, die KI nutzen · in einfachen Worten erklärt

ScanMalware.com URL Scanner

von scanmalwareVersion 0.1.6i★ 1iLäuft in der Cloud
Name in der Registrycom.scanmalware.mcp/scanmalware-mcp

Installieren

Überblick

Beschreibung vom Entwickler (englisches Original)

MCP server for ScanMalware.com URL scanning, malware detection, and analysis.

Die drei Säulen von PopNx MCP

Diese drei werden nie zu einer Punktzahl zusammengefasst: Meinungen, Messungen und Beliebtheit sind verschiedene Dinge.

Was du damit machen kannst

Für diesen Server haben wir noch keine Zusammenfassung in einfacher Sprache geschrieben. Unten stehen die Fakten, die wir aus der Registry gelesen haben.

  • Verbindung: Läuft in der Cloud
  • Transport: Streamable HTTP
  • Kategorie: Cybersicherheit
  • Das musst du eintragen: Authorization

Installation

Wähle die KI-App, die du nutzt, und kopiere die Einstellungen.

Bevor du installierst

Als geheim markierte Werte sind Schlüssel zu deinem Konto. Gib sie nie weiter und füge sie nie in einen Chat ein.

Du brauchst

Welche App nutzt du?

Wähle oben deine App. Wir merken sie uns, damit der Button beim nächsten Mal „In … installieren“ anzeigt.

Diese Adresse braucht einen Schlüssel in einem Header der Anfrage, und das Menü zum Hinzufügen von Connectors kann ihn nicht setzen. Nutze stattdessen Claude Code, Cursor oder VS Code.

Führe das in deinem Terminal aus

claude mcp add --transport http scanmalware https://mcp.scanmalware.com/mcp --header 'Authorization: your-value'

Trag die leeren Werte ein, bevor du ihn startest.

In Cursor öffnen

Oder kopier die Einstellungen selbst

In .cursor/mcp.json in deinem Projekt oder in ~/.cursor/mcp.json einfügen

{
  "mcpServers": {
    "scanmalware": {
      "url": "https://mcp.scanmalware.com/mcp",
      "headers": {
        "Authorization": "your-value"
      }
    }
  }
}

Dein Browser fragt, ob er die App öffnen darf, dann zeigt die App die Einstellungen und bittet dich um Bestätigung. Schlüsselwerte bleiben leer, damit du sie selbst einträgst.

In VS Code öffnen

Oder kopier die Einstellungen selbst

In .vscode/mcp.json in deinem Projekt einfügen

{
  "servers": {
    "scanmalware": {
      "type": "http",
      "url": "https://mcp.scanmalware.com/mcp",
      "headers": {
        "Authorization": "your-value"
      }
    }
  }
}

Dein Browser fragt, ob er die App öffnen darf, dann zeigt die App die Einstellungen und bittet dich um Bestätigung. Schlüsselwerte bleiben leer, damit du sie selbst einträgst.

Diese Adresse braucht einen Schlüssel in einem Header der Anfrage, und das Menü zum Hinzufügen von Connectors kann ihn nicht setzen. Nutze stattdessen Claude Code, Cursor oder VS Code.

Deine Auswahl bleibt in einem Cookie auf diesem Gerät; kein Konto nötig.

Werte, die du selbst einträgst

  • Authorization geheim HTTP header
    Optional Bearer token when MCP_AUTH_TOKEN is set.

Diese Einstellungen werden aus dem Registry-Eintrag des Entwicklers erzeugt. Wenn in der README etwas anderes steht, folge der README.

Welche Apps ihn nutzen können

Basierend darauf, wie sich dieser Server verbindet, und auf der eigenen Doku jeder App, geprüft am 10. Nov. 2026.

Verbinden sich über das Internet in

ClaudeChatGPTGooseLM StudioMsty StudioCherry StudioCursorVS Code + GitHub CopilotVisual StudioWindsurf (Devin Desktop)ZedKiroClineContinueJetBrains AI AssistantClaude CodeCodex CLIGemini CLIOpenCodenodecodeWarpLibreChatOpen WebUIRaycast

Alle Apps, mit Vor- und Nachteilen →

Was er kann

Tool-Namen und -Beschreibungen genau so, wie der Server sie am 10. Nov. 2026 gemeldet hat. Geschrieben hat sie der Entwickler, nicht wir; wir zeigen sie als reinen Text.

  • get_recent_scans

    List the most recent public scans, newest first (paginated with page and limit). Each row has the scan ID, URL, status, visibility and submission time. Unlisted and private scans are never included.

  • submit_scan

    Submit a URL to ScanMalware to be rendered and analysed in a sandboxed browser; returns the new scan_id. scan_type is required and sets who can see the result: 'public' lists the URL and its results in the public feed, visible to anyone and to search engines; 'unlisted' keeps the

  • get_scan_summary

    Get a compact summary of one scan: submitted URL, final URL after redirects, status, completion time and the security verdict (verdict text, risk level, confidence and risk factors). Useful as the first look at a scan; the verdict is empty for a few seconds after a scan completes

  • wait_for_scan

    Wait for a submitted scan to finish by polling its status every poll_interval_s seconds, up to timeout_s seconds (default 180). Returns the final compact summary (URL, final URL, status, completion time and the security verdict), or the last known status with timeout set to true

  • get_scan_result

    Get the full result document of one scan: URLs and redirects, page title, load times, links, network requests, IP addresses, JavaScript files, TLS, fuzzy hashes, screenshot hashes, favicon and the security verdict. The response is large; the compact summary is enough for a verdic

  • search_scans

    Search the archive of public scans by text in the URL, domain or page title (paginated); q needs at least 3 characters and search_type narrows the match (default 'all'). Each result has the scan ID, URL, status, title, submission time and hosting ASN.

  • get_ai_analysis

    Get the AI analysis of one scan: classification (LEGITIMATE to CONFIRMED_SCAM), scam type, risk score, confidence, impersonated brand and the key evidence behind it. The analysis is produced a few minutes after the scan completes and returns not found until then. It can disagree

  • get_scan_progress

    Get the progress of a running scan: current step, total steps, step name, percentage and status. Useful for checking whether a scan has finished before reading its results. scan_id is the UUID of a ScanMalware scan.

  • get_scan_ioc

    Check one scan against threat-intelligence indicator feeds: matching IP addresses and domains the page contacted, the threat types, and a summary of total and unique matches. scan_id is the UUID of a ScanMalware scan.

  • get_tls_details

    Get the TLS certificate the scanned site presented: subject, issuer, validity dates, whether it was valid or expired, and related details. scan_id is the UUID of a ScanMalware scan.

  • get_tls_asn1

    Get the raw ASN.1 structure of the TLS certificate from one scan, for detailed certificate inspection. scan_id is the UUID of a ScanMalware scan.

  • get_technologies_by_scan

    List the web technologies detected on a scanned page (frameworks, CMS, CDN, analytics and so on), with versions where known. scan_id is the UUID of a ScanMalware scan.

  • get_bot_protection

    Get the bot-protection and CAPTCHA services detected on a scanned page (for example Cloudflare Turnstile), with a detected flag. Phishing kits often use these to hide from scanners. scan_id is the UUID of a ScanMalware scan.

  • get_yara_matches

    Get the YARA rule matches recorded for one scan, with the matching rules and a summary by severity and category. scan_id is the UUID of a ScanMalware scan.

  • get_jarm_signatures

    Get the JARM TLS server fingerprints measured for the hosts in one scan, with whether each matches a known signature. A shared JARM often points to the same server software or hosting platform, so it is a weak pivot on its own. scan_id is the UUID of a ScanMalware scan.

  • get_analyzer_results

    List which analysis modules ran for one scan (for example JavaScript fingerprinting and AI analysis), with each module's status, success flag and timing. Useful for checking why a result is missing. scan_id is the UUID of a ScanMalware scan.

  • get_clipboard_events

    Get the clipboard writes the page attempted while it was scanned, with the event count and whether collection ran. Empty with an explanatory note when the page wrote nothing. scan_id is the UUID of a ScanMalware scan.

  • get_js_fingerprints

    List the JavaScript files a scanned page loaded, each with its fingerprint: hashes (SHA-256, SHA-1, MD5, normalized and fuzzy), size metrics, detected library and bundler. The include_* options add function lists, HTTP headers or ML vector details. Useful for collecting script ha

  • get_scan_reports

    List the user reports and votes filed about one scan (for example marked safe or malicious), with the total count; report_type filters by kind. Empty when nobody has reported the scan. scan_id is the UUID of a ScanMalware scan.

  • get_domain_scans

    List the recent scans of a domain and its subdomains, newest first, up to limit; status filters by scan status. Each row has matched_on: 'url' when the scan was submitted for a host under this domain, and only 'final_url' when another site redirected there. A verdict on a final_u

  • get_domain_history

    Get the scan history of a domain with the total count and the scans, paginated with page and limit.

  • get_domain_stats

    Get scan counts for a domain: total, unique, completed and failed scans, first and latest scan, and scans in the last 24 hours, 7 days and 30 days.

  • search_by_ip

    List scans of pages served from an IP address (paginated), each with scan ID, URL and time. On shared hosting and CDNs an IP serves many unrelated sites, so a match is not evidence of a connection.

  • get_ip_stats

    Get statistics for an IP address across the archive: total scans, unique URLs, first and last seen, geolocation, ASN and organisation, BGP and DNS data; paginated.

  • search_by_asn

    Get what the archive knows about an autonomous system (ASN number, without 'AS'): organisation, total scans, unique URLs, first and last seen and BGP prefixes; paginated.

  • search_similar_scans

    Find scans that look like this one by screenshot similarity, favicon or fuzzy page hash (methods, default screenshot), each with URL, final URL, title and what it matched by; paginated. Useful for finding copies of a phishing page on other hosts. scan_id is the UUID of a ScanMalw

  • search_semantic

    Search scans by meaning, comparing text with screenshot embeddings (for example 'bank login page asking for card details'). threshold defaults to -0.2 on a -1 to 1 scale; text-to-image scores are usually around -0.4 to 0.1, so useful thresholds are around -0.3 to 0. An empty resu

  • search_by_favicon

    Search scans by favicon hash (paginated): a mmh3 hash by default, or the favicon's MD5 with hash_type='md5'.

  • search_ai_high_risk

    List scans that the AI analysis rated high risk, optionally above min_risk_score and min_confidence, each with URL, title and the analysis. min_risk_score is on the AI's 0-10 risk scale (default 7) and min_confidence is a 0-100 percentage (default 70).

  • search_ai_scam_type

    List scans whose AI analysis named a given scam type, such as 'Phishing', each with URL, title, submission time and the analysis (verdict, classification, risk score). The match is on the scam type text the analysis produced.

  • search_ai_classification

    List scans whose AI analysis gave a specific classification: LEGITIMATE, LOW_RISK, MODERATE_RISK, HIGH_RISK or CONFIRMED_SCAM. Each result has the URL, title, submission time and the analysis.

  • get_analyzer_stats

    Get platform-wide statistics for the scan analysis modules: the modules and their run counts. Describes the service, not a particular scan.

  • search_analyzer_high_risk

    List scans that the security analyzer scored as high risk, highest first (paginated), each with URL, risk score, verdict, confidence and analysis time. min_risk_score raises the threshold.

  • get_favicon

    Get the favicon recorded for a scan_id: its size, MD5 and SHA-256 hashes, and the image base64-encoded. found is false when no favicon is stored for the scan.

  • get_favicon_stats

    Get favicon statistics across the archive: total favicons, those seen in the last 24 hours and the most common hashes with sample URLs. This aggregate query can take up to 90 seconds by default.

  • get_screenshot_stats

    Get screenshot statistics across the archive: total screenshots, hash coverage and the distribution of perceptual hash types.

  • search_similar_screenshots

    Find screenshots within max_distance (Hamming distance, default 3) of an integer perceptual hash (hash_type, default phash), each with scan, URL, title, distance and similarity score.

  • search_ocr

    Search OCR text (paginated). q must contain at least 3 characters after trimming. This query can take up to 90 seconds by default.

  • search_ocr_pattern

    Search the text read by OCR from scan screenshots for a pattern (paginated); case_sensitive controls matching. Useful for finding pages that show the same lure text as an image.

  • search_by_jarm

    List scans whose server presented this JARM TLS fingerprint (paginated). Large hosting platforms share JARMs across many unrelated sites, so check what the matches have in common.

  • search_by_fuzzy_hash

    List scans whose page content has a similar fuzzy hash (hash_type tlsh, ssdeep or sdhash; the value comes from a scan result), each with URL, title and time; paginated. Useful for finding near-identical copies of a page.

  • search_by_screenshot_hash

    List scans whose screenshot has this perceptual hash (hash_type ahash, phash, dhash, whash, color_hash or crop_resistant), optionally within similarity_threshold, each with URL, title and time; paginated. Useful for finding pages that look the same.

  • search_suspicious_clipboard

    List scans where the page wrote suspicious content to the clipboard (for example PowerShell commands), each with URL, event count, reason and time; pattern filters the content; paginated.

  • get_recent_threats

    List malware threats detected in scans during the last hours (default window set by the API), with totals and grouping by threat.

  • get_recent_yara_threats

    List YARA rule matches from scans in the last hours, each with the scan, URL, rule, category, severity and confidence.

  • get_ct_certificates

    Get the certificates issued for a domain according to Certificate Transparency logs, with a found flag.

  • get_ct_dns_records

    List subdomains of a domain found through Certificate Transparency, with their DNS records (type, IP addresses, TTL) and when they were seen. Useful for finding the other hosts of a domain.

  • get_ct_domains_by_ip

    List domains that Certificate Transparency and DNS data associate with an IP address, with record counts and the total.

  • get_ct_similar_domains

    Find domains with names similar to a domain in Certificate Transparency data (for example lookalikes and typosquats), each with a certificate count and the kind of similarity.

  • get_ct_timeline

    Get the certificate history of a domain from Certificate Transparency: when certificates were issued and when the certificate changed.

  • get_api_root

    Get the ScanMalware API's name, version, status, main endpoints and feature list.

  • get_health

    Check whether the ScanMalware API is up. Returns the service status.

  • get_platform_stats

    Get the current scan queue length, running scans and scans completed in the last 24 hours by visibility.

  • get_latest_capabilities

    Get the current size of ScanMalware's detection content: indicator count, malware detection rules and ClamAV signatures.

  • get_capabilities_by_date

    Get the size of ScanMalware's detection content (indicators, detection rules, ClamAV signatures) on a given date (scan_date, YYYY-MM-DD).

  • get_clipboard_stats

    Get clipboard and pastejacking statistics across the archive: event totals, detections and daily activity.

  • get_cpe_stats

    Get CPE statistics across the archive: scans with CPEs, unique CPEs, total detections, top vendors and products.

  • get_cpe_by_scan

    List the CPE identifiers (product and version names in the NVD format) derived from the technologies detected on a scanned page, with their source. Useful for matching against vulnerability data. scan_id is the UUID of a ScanMalware scan.

  • search_cpe

    Find scans with a CPE identifier matching a pattern such as 'cpe:2.3:a:facebook:react' (paginated). Useful for finding sites running a given product.

  • get_ids_alerts

    Get intrusion detection (IDS) alerts raised on the network traffic recorded during one scan, with a severity breakdown and total count. Empty when no rule fired. scan_id is the UUID of a ScanMalware scan.

Basischecksi

Wir prüfen Informationen, die jeder sehen kann; den Code haben wir nicht gelesen. Checks, die nicht zutreffen, werden nicht mitgezählt. · Geprüft am 11. Nov. 2026

  • ✓ Bestanden

    Entwickler passt zum Repo

    Der Registry-Name wurde über die Domain mcp.scanmalware.com bestätigt (die Registry prüft, wem die Domain gehört)

  • ✓ Bestanden

    Wird noch gepflegt

    Code zuletzt geändert: vor 3 Tagen

  • … Noch nicht geprüft

    Hat eine Lizenz

    Dieses Repo haben wir noch nicht von GitHub gelesen

  • ✓ Bestanden

    Endpunkt antwortet

    Antwort in 559 ms (HTTP 200)

  • – Nicht zutreffend

    Anmeldung mit OAuth

    Keine Anmeldung nötig

  • ✓ Bestanden

    Keine versteckten Anweisungen in Tool-Beschreibungen

    Wir haben 60 Tool-Beschreibungen gelesen und keine versteckten Anweisungen an die KI gefunden

Nutzerbewertungen

Noch keine Bewertungen

Im Vergleich mit anderen in Cybersicherheiti

  • Mehr Sterne als 67 % der anderen 1025 Server in dieser Kategorie
  • Neuer in der Registry veröffentlicht als 2 % der anderen 1025 Server in dieser Kategorie
  • Basischecks gleich gut wie oder besser als bei 100 % der anderen 1025 Server in dieser Kategorie

Verglichen mit allen aktiven Servern dieser Kategorie in der Registry. Sterne zählen nur, wenn dem Herausgeber das Repo gehört.

Häufige Fragen

Ist ScanMalware.com URL Scanner sicher?

Er hat 4 von 4 zutreffenden Basischecks bestanden. Den Code haben wir nicht gelesen. Bevor du einen Schlüssel oder Token hinzufügst, sieh nach, welche Zugriffe der Server verlangt, und nutze einen Schlüssel, der nur die Berechtigungen hat, die du brauchst.

Welche Schlüssel oder Codes braucht er?

Das musst du eintragen: Authorization

Läuft er auf meinem Computer oder in der Cloud?

Er läuft in der Cloud des Entwicklers. Du verbindest dich übers Internet, ohne etwas zu installieren, aber was du sendest, läuft über die Server des Entwicklers.

Wird er noch gepflegt?

Das GitHub-Repo wurde zuletzt vor 3 Tagen geändert, und die neueste Version (0.1.6) kam vor 10 Monaten in die Registry.

Welche Apps können ihn nutzen?

Apps, die MCP unterstützen, etwa Claude Desktop, Claude Code, Cursor und VS Code. Er verbindet sich per URL, also funktionieren auch Apps, die Remote-MCP-Server akzeptieren, zum Beispiel ChatGPT im Entwicklermodus.

Was kann ich stattdessen nutzen?

In derselben Kategorie: Robin: AI-Powered Dark Web OSINT, HOL Guard, UniFi Network MCP

Alternativen

Server derselben Kategorie von anderen Entwicklern, sortiert nach Sternen.

So liest du eine Karte:↓ Downloads pro Wochei★ GitHub-SterneiDrei Säulen: Bewertungen · Checks · NutzungiAktualisiert = neueste Version in der Registry veröffentlichti

Robin: AI-Powered Dark Web OSINT

Dark web OSINT over Tor: search onion engines, scrape pages, report with your own model.

von apurvsinghgautamWachsend★ 7943Aktualisiert vor 39 Tagen

Läuft auf deinem Computer

HOL Guard

Local-first AI agent security evidence and approval workflows through HOL Guard's stdio MCP server.

von hashgraph-onlineWeit verbreitet↓ 7083/Wo.★ 1081Aktualisiert vor 6 Tagen

Läuft auf deinem Computer

UniFi Network MCP

Manage UniFi Network devices, clients, firewall, VLANs, VPNs, and more via MCP.

von sirkirbyWeit verbreitet↓ 11.326/Wo.★ 1029Aktualisiert vor 8 Monaten

Läuft auf deinem Computer

README vom Entwickler

README öffnen

Unverändert aus dem Repo des Entwicklers übernommen. PopNx MCP hat den Inhalt weder geprüft noch empfiehlt es ihn. HTML, Bilder und Skripte wurden entfernt. Links führen von dieser Website weg.

scanmalware-mcp

Minimal Python MCP server that wraps the public ScanMalware.com API.

Operations

See the operations runbook for deployment, TLS, logging, security maintenance, and how to connect to the DigitalOcean droplet.

Run locally (Streamable HTTP)

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

Run with 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

Optional: set MCP_AUTH_TOKEN to require Authorization: Bearer for HTTP transports.

Optional auth env vars (only needed for auth-gated endpoints):

  • SCANMALWARE_BEARER_TOKEN

Other env vars:

  • SCANMALWARE_BASE_URL (default: https://scanmalware.com)
  • SCANMALWARE_ALLOW_HTTP (default: false)
  • SCANMALWARE_TIMEOUT_S (default: 30)
  • SCANMALWARE_SLOW_QUERY_TIMEOUT_S (default: 90; favicon and technology statistics, OCR text search, JS fingerprint similarity counts and behavioural signature search)
  • SCANMALWARE_MAX_DOWNLOAD_BYTES (default: 10485760)
  • SCANMALWARE_ALLOW_PRIVATE_TARGETS (default: false)
  • SCANMALWARE_CA_CERT (optional; path to a CA bundle for SSL bump)

MCP server security env vars:

  • MCP_AUTH_TOKEN (if set, HTTP transports require Authorization: Bearer )
  • MCP_RESOURCE_SERVER_URL / MCP_ISSUER_URL (optional; only used when MCP_AUTH_TOKEN is set)

Transport note: the HTTP transports run stateless (stateless_http=True), so responses carry no mcp-session-id header. Clients must not require one. This keeps per-session state - and therefore memory - flat. Both MCP protocol generations are served: initialize (2024-11-05 to 2025-11-25) and server/discover (2026-07-28). Requests use POST; GET on /mcp returns 405 because the server offers no standalone SSE stream.

SSE transport: set MCP_TRANSPORT=sse, and optionally MCP_SSE_MOUNT_PATH to serve it under a path prefix.

Tool note: submit_scan does not call /api/v1/csrf-token; there is no CSRF token tool. Tool note: some upstream endpoints are disabled and excluded from the tool list (e.g., get_improvements, find_screenshot_duplicates, get_ai_stats, search_js_fingerprinter2_code_hash, search_js_segments_by_tlsh). Filters: every search tool can be called with its defaults. search_js_fingerprint_patterns applies has_eval=true when no pattern filter is given, because the API requires one; search_js_obfuscation and search_js_malware_families fall back to the API's own defaults. Score filters use the API's scales and are checked before the request: the AI risk score is 0-10, AI confidence 0-100, JS obfuscation scores 0-1 and the runtime JS risk score 0-100. OCR search requires at least three characters after trimming. Library lookups use detected identifiers from get_js_library_inventory; search_js_fingerprint_by_library accepts Next.js as an alias for the detected identifier nextjs.

Scan visibility

submit_scan requires scan_type; there is no default. A public scan puts the target URL and scan results in ScanMalware's public feed, visible to other users and search engines. Choose deliberately for client targets, confidential URLs, and security engagements:

  • public: publishes the scan. Use for these targets only with explicit approval to publish.
  • unlisted: excluded from public listings, but accessible to anyone with the direct link.
  • private: results are restricted to the authenticated ScanMalware account. Requires a valid SCANMALWARE_BEARER_TOKEN configured on the MCP server; MCP_AUTH_TOKEN only controls access to the MCP server and does not provide ScanMalware authentication.

If visibility has not already been specified for these targets, ask the user to choose unlisted or private before submitting. If submission fails, do not retry with a less restrictive visibility. These modes control scan visibility; requests still go to ScanMalware and may be recorded in server logs (see the MCP privacy policy). ScanMalware documents the visibility options in its privacy policy.

Example prompts

Phishing triage (submit → wait → summarize):

Submit an unlisted 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.

Brand abuse monitoring:

Search scans for "acme login" (limit 5). For each result, list scan_id,

status, risk_score, and URL. Highlight anything marked high risk.

TLS/certificate inspection:

For scan_id 1234...abcd, fetch TLS details and the certificate PEM download.

Summarize issuer, subject, validity dates, and SANs; flag mismatches.

Deploy to DigitalOcean (Debian + Docker + Nginx)

The deploy bundle lives in deploy/ and runs three containers:

  • mcp (this server, streamable HTTP on port 8000)
  • nginx (frontend on ports 80/443; redirects HTTP to HTTPS and proxies /mcp to the MCP server)
  • proxy (mitmproxy, checks ScanMalware destinations and logs upstream HTTP traffic)
Prereqs
  • doctl authenticated (doctl auth init)
  • SSH key uploaded to DigitalOcean (used by doctl compute droplet create)
  • DNS and Let's Encrypt certificates at the paths in HTTPS / TLS before starting Nginx
Create a small droplet in Germany (Frankfurt)
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 (public HTTP/HTTPS + 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

Install Docker + compose on the droplet

Install Docker Engine and the Compose plugin from the official Docker apt repository. Use docker compose; the old Python docker-compose client is incompatible with current Docker Engine.

See the operations runbook for pinned builds and maintenance checks.

Upload and run
# Package exactly one commit; a tar of the working tree also picks up untracked

files (.tools/, caches, egg-info) and uncommitted edits.

git archive --format=tar.gz -o /tmp/scanmalware-mcp.tar.gz origin/main scp -i /path/to/key /tmp/scanmalware-mcp.tar.gz root@ :/tmp/ ssh -i /path/to/key root@ \ "mkdir -p /opt/scanmalware-mcp && tar -xzf /tmp/scanmalware-mcp.tar.gz -C /opt/scanmalware-mcp"

On a fresh host, follow TLS inspection to build the proxy and generate its CA before starting MCP and Nginx. Install the egress rules and Docker startup hook and log rotation as part of host setup.

Verify
curl -I https://mcp.scanmalware.com/

curl -sS -o /dev/null -w '%{http_code}\n' https://mcp.scanmalware.com/mcp

/ should return 200 from Nginx. /mcp returns 405 on GET, which is expected: the server offers no standalone SSE stream, so MCP requests use POST.

Smoke test (MCP initialize + tools/list)
python - <<'PY'

import json import httpx

URL = "https://mcp.scanmalware.com/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()

Stateless deployments return no session header.

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"

headers = {**HEADERS, "mcp-protocol-version": protocol_version} if session_id: headers["mcp-session-id"] = session_id

Send initialized notification

client.post( URL, headers=headers, json={"jsonrpc": "2.0", "method": "notifications/initialized"}, )

tools_resp = client.post( URL, headers=headers, 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"]

print("protocol_version:", protocol_version) print("tool_count:", len(tool_names)) print("tools:", ", ".join(tool_names)) PY

Redeploy / new deploys

Two common flows:

  1. In-place update (same droplet)
# Package exactly one commit; a tar of the working tree also picks up untracked

files (.tools/, caches, egg-info) and uncommitted edits.

git archive --format=tar.gz -o /tmp/scanmalware-mcp.tar.gz origin/main scp -i /path/to/key /tmp/scanmalware-mcp.tar.gz root@ :/tmp/ ssh -i /path/to/key root@ \ "bash /opt/scanmalware-mcp/deploy/redeploy.sh /tmp/scanmalware-mcp.tar.gz"

The redeploy script stops all three containers, replaces the files, and rebuilds

and restarts every image (docker compose up -d --build), preserving the mitmproxy CA. It deletes files on the droplet that are not in git (everything outside logs/). If the script is not on the droplet yet, run the legacy tar + docker compose command once to install it.

Optional one-shot helper from the repo root (it packs the working tree, including uncommitted changes, rather than a commit):

./deploy/push-redeploy.sh root@<droplet-ip> /path/to/key

To update only the MCP server with a few seconds of downtime, see Updating only the MCP server.

  1. Rolling deploy (new droplet)
  • Create a new droplet (steps above)
  • Deploy the same bundle
  • Switch DNS to the new IP
  • Destroy the old droplet when ready
doctl compute droplet delete <old-droplet-id> --force