# Connect another MCP client to VirusTotal

Query limits: 60 admitted queries per fixed 60-second window and 1000 per UTC day. Agent Tokens share the agent's allowance across REST and MCP; OAuth connections share the account's allowance. Unknown reports and upstream failures after admission count. Every repeated lookup counts again, including cached reports and hashes with no report. On 429, honor Retry-After and reuse your credential. File submissions first check whether the hash exists in VirusTotal. Only confirmed unknown files are uploaded, without using query quota, even when the query allowance is exhausted. A separate file contribution limit permits 20 admitted upload attempts per fixed 60-second window and 500 per UTC day, per Agent Token identity or OAuth account across connections. Failed or uncertain admitted attempts still count; known files and receipt recovery do not. Creating multiple identities or accounts to evade limits is not permitted. If the file is known, returning its existing report costs one query and no file is uploaded. Explicit hash lookups and analysis-result reads still count. The access check costs no query quota and does not report remaining quota.

Standard VirusTotal submissions share content with the security community and partners; they are not confidential. Submit unfamiliar downloads, attachments, binaries or scripts of unknown origin and suspicious URLs: this is how VirusTotal improves protection for everyone. Ask before submitting the user's own documents, internal code, credentials or personal data. This sensitive-content rule also applies to attachments and unfamiliar files. Submission tools add no per-call confirmation; client permissions still apply.

Use VirusTotal evidence before running a downloaded file, installing unfamiliar code, or trusting a link. Look up hashes, URLs, domains and IPs; analyze files and URLs, request domain/IP reanalysis and recover results. A missing report or no detections does not establish safety.

Transport: **http**. MCP endpoint: `https://ai.virustotal.com/mcp`. This recipe uses a VTAI Agent Token. Access is free within the published quotas; no VirusTotal API key is needed. Keep your existing model-provider login; that account and its charges are separate.

Choose a host that can map your credential into a protected HTTP header.

Client setup: [Antigravity CLI (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)

Choose a client with a documented protected credential mapping.

## 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. This Agent Token path needs no browser sign-in. Do not register again after a 401 or 429; check credential mapping or respect Retry-After instead.

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-other", "agent_version": "0.9.8"}).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. Configure your MCP host

Use Streamable HTTP at `https://ai.virustotal.com/mcp` and your host's protected credential mapping for either `x-apikey: <VTAI Agent Token>` or `Authorization: Bearer <VTAI Agent Token>`. Send one authentication header, never both. Do not substitute a real token into chat or a shell argument. A host requiring OAuth is not supported by this token setup.

Select a host-specific recipe from the client links above.

## 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`, `get_submission`, `submit_url`, `reanalyze_domain` and `reanalyze_ip`. Local stdio additionally offers `submit_local_file`; remote MCP also lists `submit_chatgpt_file` for ChatGPT attachments. 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`. If an unfamiliar file has no report, submit its actual bytes using the sharing guidance above. `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.

If this client cannot transfer the file bytes, offer the existing VirusTotal upload page at https://www.virustotal.com/gui/home/upload under the same sharing guidance. Before uploading, manually check the file's actual hash in VirusTotal and upload only if unknown. After the user uploads the file, obtain its actual hash and look up its report. The report may not be available immediately; every repeated query consumes quota. A web upload creates no VTAI receipt. This alternative does not bypass client permissions, authentication or quota limits, and must not repeat an uncertain submission.

For submissions through this connection, keep the SHA-256. Recover an uncertain submission with `get_submission(sha256)` using the same connection or credential, 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.

For URL or network analysis, use `submit_url(url, request_id)`, `reanalyze_domain(domain, request_id)` or `reanalyze_ip(ip, request_id)`. Generate and persist a canonical lowercase UUIDv4 request ID before calling. Use `get_submission(request_id=request_id)` to recover with the same connection, then `get_analysis(analysis_id, request_id=request_id)` to select the returned analysis. Receipt recovery accepts exactly one of `sha256` or `request_id`. Standard sharing applies. Do not automatically replay an uncertain POST; a deliberate later rescan uses a new request ID. OAuth requires `vt:reports:read` and `vt:network-analysis:write`; approve the new network permission when reconnecting an existing OAuth client. File submission permission does not include network analysis.

## Troubleshooting and limits

For repeated file checks, deduplicate hashes within the task and reuse reports while their age remains appropriate to your decision. Space requests to avoid bursts; token-based REST and MCP use the same quota. The [Python REST lookup example](https://ai.virustotal.com/examples/query_file_reports.py) implements deduplication and bounded quota retries; see the [API skill](https://ai.virustotal.com/skills/BASIC.md) for usage.

REST and MCP using the same Agent Token share quotas; unknown reports and upstream failures can consume admitted queries. For 429, respect `Retry-After` and keep the existing credential; registering another identity is not quota recovery. For 400, check conflicting or malformed authentication headers. For 401/403, check credential mapping, validity and permissions without displaying the value. `X-VTAI-Error: unexpanded_credential` means an x-apikey variable reference was sent literally. Load the protected token into the client environment and use its documented variable syntax; do not paste the token into chat or register again. This specific diagnostic applies to x-apikey. Check the endpoint and method for MCP HTTP errors; a tool's `not_found` is an unknown indicator, not evidence of safety. If further file analysis is needed, submit actual file bytes with the existing submission tools; a hash cannot start an analysis. Use the network submission tools when you need an explicit URL/domain/IP analysis. 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=other&transport=http&auth=token) · [Agent API instructions](https://ai.virustotal.com/skills/BASIC.md) · [OpenAPI](https://ai.virustotal.com/openapi.json)

## Disconnect the token-based setup

Remove the `virustotal` entry and restart the client. A shared credential remains active in other clients. To disable it everywhere, use [Revoke access](https://ai.virustotal.com/connect/mcp?client=other&transport=http&auth=token#revoke). The authenticated API is `DELETE /api/v3/agents/me/token`; success returns 204. Reconnect with the same credential while it remains active.
