# Connect Cascade / Windsurf to VirusTotal MCP

Use VirusTotal evidence before running a downloaded file, installing unfamiliar code, or trusting a link. Look up hashes, URLs, domains and IPs; submit authorized files for analysis and recover results. A missing report or no detections does not establish safety.

Transport: **http**. MCP endpoint: `https://ai.virustotal.com/mcp`. Free VTAI access requires an Agent Token; no VirusTotal API key is needed for basic use. Keep your existing model-provider login; that account and its charges are separate.

This Cascade / Windsurf recipe uses http.

Client setup: [Agy](https://ai.virustotal.com/connect/mcp?client=agy&format=markdown) · [Claude Code](https://ai.virustotal.com/connect/mcp?client=claude&format=markdown) · [Codex](https://ai.virustotal.com/connect/mcp?client=codex&format=markdown) · [Cursor](https://ai.virustotal.com/connect/mcp?client=cursor&format=markdown) · [VS Code](https://ai.virustotal.com/connect/mcp?client=vscode&format=markdown) · [GitHub Copilot CLI](https://ai.virustotal.com/connect/mcp?client=copilot&format=markdown) · [Devin Local / CLI](https://ai.virustotal.com/connect/mcp?client=devin&format=markdown) · [Cascade / Windsurf](https://ai.virustotal.com/connect/mcp?client=cascade&format=markdown)

Documented setup; native tool calls and model workflow pending.

## 1. Reuse or create access

Reuse `~/.config/vt-mcp/token` if configured. Changing clients does not require registration. Keep credentials out of prompts, tool arguments, URLs and printed command output. An authorized setup process may store the token directly without exposing it to model context.

If no credential exists and setup is authorized, this POSIX Python 3 command registers once and saves it with owner-only permissions. The handle and activity totals may appear on the public leaderboard. It sends one registration request, with no automatic retry on an uncertain result. Windows needs equivalent user-only file permissions.

```bash
python3 - <<'PY'
import json
import os
import re
from pathlib import Path
from urllib.request import HTTPRedirectHandler, Request, build_opener

class NoRedirect(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None

path = Path.home() / ".config" / "vt-mcp" / "token"
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
path.parent.chmod(0o700)
if path.exists() or path.is_symlink():
    raise SystemExit("Reuse the existing credential; registration was not sent.")
request = Request(
    "https://ai.virustotal.com/api/v3/agents/register",
    data=json.dumps({"agent_family": "vt-mcp-cascade", "agent_version": "0.8.0"}).encode(),
    headers={"Content-Type": "application/json"}, method="POST",
)
try:
    with build_opener(NoRedirect).open(request, timeout=20) as response:
        if response.status != 200:
            raise ValueError("Unexpected status")
        raw = response.read(8193)
        if len(raw) > 8192:
            raise ValueError("Response limit")
        token = json.loads(raw)["agent_token"]
        if not isinstance(token, str) or not re.fullmatch(r"vtai_[A-Za-z0-9_-]{1,507}", token):
            raise ValueError("Credential format")
except Exception:
    raise SystemExit("Registration is uncertain. Do not retry automatically.") from None
try:
    with os.fdopen(os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600), "w") as output:
        output.write(token + "\n")
except OSError:
    raise SystemExit("Access was created but saving failed. Resolve storage before retrying.") from None
print("Credential saved to protected storage. Its value was not displayed.")
PY
```

## 2. Connect without a local server

Remote HTTP requires no local vt-mcp or Python installation. The optional terminal registration example above uses Python; browser registration is also available.

Merge the following into `Cascade MCP settings → raw mcp_config.json`. Preserve other settings and use one `virustotal` entry. The snippet contains only paths and protected environment, input or file references.

```json
{
  "mcpServers": {
    "virustotal": {
      "serverUrl": "https://ai.virustotal.com/mcp",
      "headers": {
        "Authorization": "Bearer ${file:~/.config/vt-mcp/token}"
      }
    }
  }
}
```

In Cascade, open MCP Servers and its raw configuration file. Merge the virustotal entry into the file your version opens, preserving existing settings, then refresh the MCP servers. The documented legacy path is ~/.codeium/windsurf/mcp_config.json; Devin Desktop 3.10.23 on Linux opens ~/.config/devin/mcp_config.json. Use the file opened by the app, without adding duplicate entries. Its file expansion reads and trims the protected token file: store only the token, without Bearer or dotenv syntax. An unreadable file leaves the reference unresolved. Use the separate stdio recipe for Devin Local.

## 3. Verify and use

Confirm that your client discovers `get_file_report`, `get_url_report`, `get_domain_report`, `get_ip_report`, `get_analysis`, `submit_file` and `get_submission`. Local stdio additionally offers `submit_local_file`. Call `get_domain_report` once for `virustotal.com`. Check the returned source, analysis date, coverage and report link. This consumes a query. Tool discovery alone does not verify authenticated access.

For files, hash first and use `get_file_report`. When your task authorizes standard public sharing, `submit_local_file(path, expected_sha256=None)` accepts up to 32,000,000 bytes over stdio. `submit_file(sha256, content_base64)` accepts up to 24,000,000 decoded bytes over either transport. Remote HTTP cannot read a local path. Submitted content may be shared with the VirusTotal community and security partners; base64 also passes through your MCP host. Host permissions apply; the tools add no per-call confirmation argument.

Keep the SHA-256. Recover an uncertain submission with `get_submission(sha256)` on the same account, without sending bytes again. `exists` means an existing report; `submitted` supplies an analysis ID. Read that ID using `get_analysis` within a finite polling budget. Unknown can remain unknown permanently; do not repeat submission merely to resolve uncertainty.

## Troubleshooting and limits

REST and MCP share account quotas; unknown reports and upstream failures can consume admitted queries. For 429, respect `Retry-After`. For 400, check conflicting or malformed authentication headers. For 401/403, check credential mapping, validity and permissions without displaying the value. Check the endpoint and method for MCP HTTP errors; a tool's `not_found` is an unknown indicator. A service failure is not a clean report. Avoid URLs containing private paths or query parameters; use a domain lookup when appropriate.

[Current access limits and browser setup](https://ai.virustotal.com/connect/mcp?client=cascade&transport=http) · [Agent API instructions](https://ai.virustotal.com/skills/BASIC.md) · [OpenAPI](https://ai.virustotal.com/openapi.json)

## Disconnect

Remove the `virustotal` entry and restart the client. A shared credential remains active in other clients. To disable it everywhere, use the revocation controls in [browser setup](https://ai.virustotal.com/connect/mcp) when available. Reconnect with the same credential while it remains active.
