# Connect VS Code to VirusTotal MCP

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. Upload files or attachments, submit URLs, or request domain/IP reanalysis only when the user explicitly requests that action. A missing report, an unfamiliar file or a suspicious indicator is not permission to submit it. Explain the sharing and ask before submitting the user's own documents, internal code, credentials or personal data. 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.

This VS Code recipe uses http.

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)

Native HTTP tool calls and restart verified locally; model workflow against VTAI 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. 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-vscode", "agent_version": "0.9.9"}).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 `.vscode/mcp.json` or MCP: Open User Configuration. Preserve other settings and use one `virustotal` entry. The snippet contains only paths and protected environment, input or file references.

```json
{
  "inputs": [
    {
      "type": "promptString",
      "id": "vtai-token",
      "description": "VTAI Agent Token from https://ai.virustotal.com/connect/mcp?client=vscode&transport=http&auth=token#access-heading",
      "password": true
    }
  ],
  "servers": {
    "virustotal": {
      "type": "http",
      "url": "https://ai.virustotal.com/mcp",
      "headers": {
        "Authorization": "Bearer ${input:vtai-token}"
      }
    }
  }
}
```

Run MCP: List Servers, start virustotal and enter the VTAI token in its password input, outside chat. Keep the input reference in JSON. This recipe targets the Copilot Extension Host; interactive inputs are not forwarded to the Agent Host. Use the separate Copilot CLI recipe for that runtime. Local configuration and transport checks cover VS Code 1.107.1.

## 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_attachment` for attachment descriptors with a public HTTPS download_url. The optional fields are file_id, file_name and mime_type. The client must supply the original file's URL; never invent one. Native attachment availability depends on the client. 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`. Submit actual bytes only when the user explicitly requests the upload, using the sharing guidance above. A missing report is not permission to upload. `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 for an explicitly requested upload, 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.

Only when the user explicitly requests URL submission or network reanalysis, 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. Only when the user explicitly requests an upload, submit actual file bytes with the existing submission tools; a hash cannot start an analysis. Network submissions and reanalysis also require the user's explicit request. 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=vscode&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=vscode&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.
