VFA — Visual & Functional Web Auditor (MCP Server)
Autonomous black-box web QA agent exposed as an MCP server over stdio.
Pure hexagonal architecture (Ports & Adapters), one unified inference engine
powered by LiteLLM, and a local-first Playwright Chromium sandbox —
no Docker, no sidecars, no infrastructure to maintain.
Why VFA
| Pillar | What it means |
|---|---|
| Native Zero-Docker | Playwright downloads and manages Chromium on the host itself. A built-in ephemeral BrowserContext sandbox plus an Anti-SSRF firewall ship by default, and local development is first-class (localhost, 127.0.0.1, *.local). |
| Universal LiteLLM Engine | A single adapter drives DeepSeek, OpenAI, Anthropic, Gemini, xAI/Grok, OpenRouter and Ollama. Switching providers is a two-variable change. |
| Active Visual Grounding | Multimodal vision inspects real UIs, reads raster image CAPTCHAs and clicks by visual coordinates whenever the accessibility tree is not enough. |
| 1-Click Execution | Plug-and-play: uvx vfa-mcp starts the server with no permanent install, pip install vfa-mcp if you prefer an isolated virtualenv. |
uvx vfa-mcp
Quickstart
⚠️ License Key (Required)
VFA is a commercial tool. An active VFA Pro License is required to execute audits and user flows.
- Get your license: Purchase on Creem
- Configuration: Set the
VFA_LICENSE_KEYenvironment variable in your MCP client or.env.
1. Install
One command, permanent install in your own virtualenv:
pip install vfa-mcp && playwright install chromium
Zero-install alternative (recommended for MCP clients):
uvx vfa-mcp
playwright install chromium # first run only
2. Pick a provider
VFA resolves the API key from VFA_LLM_API_KEY first, then from the standard
variable of the selected provider (OPENAI_API_KEY, ANTHROPIC_API_KEY,
GEMINI_API_KEY, DEEPSEEK_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY).
| Provider | VFA_LLM_PROVIDER |
Example VFA_LLM_MODEL |
API key variable | Base URL / Endpoint variable |
|---|---|---|---|---|
| OpenAI | openai |
gpt-4o |
OPENAI_API_KEY |
— (Default) |
| Anthropic | anthropic |
claude-3-5-sonnet-20241022 |
ANTHROPIC_API_KEY |
— (Default) |
| Google Gemini | gemini |
gemini/gemini-2.0-flash |
GEMINI_API_KEY |
— (Default) |
| DeepSeek (Direct) | deepseek |
deepseek/deepseek-chat |
DEEPSEEK_API_KEY |
— (Default) |
| DeepSeek Vision | deepseek |
deepseek/deepseek-flash |
DEEPSEEK_API_KEY |
— (Default) |
| DeepSeek (OpenAI-compat) | openai |
openai/deepseek-chat (o deepseek-flash) |
DEEPSEEK_API_KEY / OPENAI_API_KEY |
OPENAI_API_BASE="https://api.deepseek.com" |
| DeepSeek (Anthropic-compat) | anthropic |
claude-3-5-sonnet (alias) |
DEEPSEEK_API_KEY / ANTHROPIC_API_KEY |
ANTHROPIC_BASE_URL="https://api.deepseek.com/anthropic" |
| xAI / Grok | xai |
xai/grok-2-vision-1212 |
XAI_API_KEY |
— (Default) |
| OpenRouter | openrouter |
openrouter/anthropic/claude-3.5-sonnet |
OPENROUTER_API_KEY |
— (Default) |
| Ollama (local) | ollama |
ollama/llama3.2-vision |
— (Placeholder) | OLLAMA_API_BASE="http://localhost:11434" |
Aliases accepted by the provider resolver: google (Gemini) and grok (xAI).
Vision falls back to the reasoning model when VFA_VISION_PROVIDER /
VFA_VISION_MODEL are left empty.
3. Wire it into your client
Zoo Code / Roo Code (mcpSettings.json)
Click the MCP Servers plug icon ⚙️ → Edit MCP Settings, then add:
{
"mcpServers": {
"vfa": {
"command": "uvx",
"args": ["vfa-mcp"],
"env": {
"VFA_LICENSE_KEY": "PRO-XXXX-XXXX",
"OPENAI_API_KEY": "sk-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
}
}
}
Claude Desktop (claude_desktop_config.json)
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"vfa": {
"command": "uvx",
"args": ["vfa-mcp"],
"env": {
"OPENAI_API_KEY": "sk-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
}
}
}
Cursor (.cursor/mcp.json or Settings → Features → MCP)
{
"mcpServers": {
"vfa": {
"command": "uvx",
"args": ["vfa-mcp"],
"env": {
"OPENAI_API_KEY": "sk-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
}
}
}
Provider Drop-in Presets (Replace the "env" block)
Simply swap the "env" object in any configuration above with your preferred model:
⚡ DeepSeek (Reasoning + Fast Vision)
"env": {
"VFA_LLM_PROVIDER": "deepseek",
"VFA_LLM_MODEL": "deepseek-chat",
"VFA_VISION_PROVIDER": "deepseek",
"VFA_VISION_MODEL": "deepseek-flash",
"DEEPSEEK_API_KEY": "sk-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
🧠 Anthropic Claude 3.5 Sonnet
"env": {
"VFA_LLM_PROVIDER": "anthropic",
"VFA_LLM_MODEL": "claude-3-5-sonnet-20241022",
"ANTHROPIC_API_KEY": "sk-ant-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
🚀 Google Gemini 2.0 Flash (Multimodal)
"env": {
"VFA_LLM_PROVIDER": "gemini",
"VFA_LLM_MODEL": "gemini/gemini-2.0-flash",
"GEMINI_API_KEY": "AIzaSy...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
🌐 OpenRouter (Universal Gateway)
"env": {
"VFA_LLM_PROVIDER": "openrouter",
"VFA_LLM_MODEL": "openrouter/anthropic/claude-3.5-sonnet",
"VFA_VISION_MODEL": "openrouter/google/gemini-2.0-flash-001",
"OPENROUTER_API_KEY": "sk-or-v1-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
🏠 Ollama (100% Local / Offline)
"env": {
"VFA_LLM_PROVIDER": "ollama",
"VFA_LLM_MODEL": "ollama/llama3.2-vision",
"OLLAMA_API_BASE": "http://localhost:11434",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
⚙️ OpenAI-compatible:
"env": {
"VFA_LLM_PROVIDER": "openai",
"VFA_LLM_MODEL": "openai/deepseek-v4-pro",
"VFA_VISION_PROVIDER": "openai",
"VFA_VISION_MODEL": "openai/deepseek-flash",
"OPENAI_API_BASE": "https://api.deepseek.com",
"OPENAI_API_KEY": "sk-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
⚙️ Anthropic-compatible:
"env": {
"VFA_LLM_PROVIDER": "anthropic",
"VFA_LLM_MODEL": "claude-3-5-sonnet",
"ANTHROPIC_BASE_URL": "https://api.deepseek.com/anthropic",
"ANTHROPIC_API_KEY": "sk-...",
"HEADLESS": "true",
"VFA_ALLOW_LOCAL_HOST": "true",
"LITELLM_LOG": "ERROR"
}
Agent-facing usage guide (when to invoke each tool, report template, security rules):
SKILL.md. Marketplace manifest:skill.json.
Tool reference
qa_audit_url(url)
Full-page screenshot, vision analysis and runtime error collection in a single call.
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | yes | Reachable URL to audit. Private/loopback targets are rejected unless VFA_ALLOW_LOCAL_HOST is enabled. |
Returns url, ok (true only when the audit passed and no runtime event was
captured), summary, screenshot_base64 (PNG), issues[] (visual,
accessibility and layout findings) and errors[].
qa_execute_user_flow(url, steps)
Replays a user journey described in plain language (English or Spanish).
| Parameter | Type | Required | Description |
|---|---|---|---|
url |
string | yes | Starting URL of the flow. |
steps |
string[] | yes | Ordered natural-language steps, e.g. ["Click 'Sign in'", "Verify ..."]. |
Returns url, success, ok, steps[{step, ok, detail}], screenshots[]
(base64 PNG milestones) and errors[].
qa_get_runtime_errors()
Drains the browser error buffer: console.error, uncaught exceptions
(pageerror) and HTTP responses with status >= 400.
| Parameter | Type | Required | Description |
|---|---|---|---|
| — | — | — | No arguments. |
Returns count and errors[{kind, message, url, status}], where kind is one
of console, pageerror or http. The call is read-destructive: it empties
the buffer, so invoke it once at the end of a sequence.
Configuration reference
VFA is configured exclusively through environment variables — there are no config files, no database and no flags to pass. Variables are grouped in three independent layers:
| Layer | Variables | Consumed by |
|---|---|---|
| Reasoning engine | VFA_LLM_PROVIDER, VFA_LLM_MODEL, VFA_LLM_API_KEY, VFA_LLM_BASE_URL |
The Deep Agent (LiteLLM tool-calling loop) |
| Vision engine | VFA_VISION_PROVIDER, VFA_VISION_MODEL, VFA_VISION_API_KEY |
Visual grounding / CAPTCHA fallback |
| Browser & runtime | HEADLESS, VFA_ALLOW_LOCAL_HOST, BROWSER_CDP_*, VFA_SESSION_*, … |
Playwright adapter, session pool, Anti-SSRF |
Is anything mandatory?
Only two things are strictly mandatory:
VFA_LICENSE_KEY: Your commercial license key purchased from Creem.- An inference API key for your chosen provider (e.g.
OPENAI_API_KEY,DEEPSEEK_API_KEY, etc.).
# Minimal production setup — works because the defaults are openai/gpt-4o
export OPENAI_API_KEY="sk-..."
Does VFA_LLM_MODEL drive the Deep Agent?
Yes. VFA_LLM_MODEL is the model used by the Deep Agent — the component that
executes qa_execute_user_flow and performs the reasoning step of
qa_audit_url. Both tools go through LiteLLM with
VFA_LLM_PROVIDER + VFA_LLM_MODEL, so:
- You may omit them when you use OpenAI
gpt-4oand exportOPENAI_API_KEY(defaults:openai/gpt-4o). - You must set them when you use any other provider, any other model, or an OpenAI-compatible gateway (DeepSeek, OpenRouter, Ollama, vLLM, Azure proxy…).
- The model must support tool calling (native function calling), because the ReAct loop sends a JSON action schema on every iteration.
For OpenAI-compatible endpoints, pair the model with a base URL:
export VFA_LLM_PROVIDER="openai" # protocol, not the vendor
export VFA_LLM_MODEL="openai/deepseek-chat"
export OPENAI_API_KEY="sk-..." # or VFA_LLM_API_KEY
export OPENAI_BASE_URL="https://api.deepseek.com"
Credential and endpoint precedence
| What | Resolution order |
|---|---|
| Reasoning API key | VFA_LLM_API_KEY → provider standard variable (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, DEEPSEEK_API_KEY, XAI_API_KEY, OPENROUTER_API_KEY) → OPENAI_API_KEY |
| Reasoning base URL | VFA_LLM_BASE_URL → OPENAI_BASE_URL → OLLAMA_API_BASE (only when the provider is ollama) |
| Vision provider | VFA_VISION_PROVIDER → VFA_LLM_PROVIDER → openai |
| Vision model | VFA_VISION_MODEL → VFA_LLM_MODEL → gpt-4o |
| Vision API key | VFA_VISION_API_KEY → VFA_LLM_API_KEY → provider standard variable → OPENAI_API_KEY |
VFA_VISION_* is therefore optional by design: if the reasoning model is
multimodal (gpt-4o, claude-3-5-sonnet, gemini/gemini-2.0-flash,
xai/grok-2-vision-1212), leave it empty and one model does everything. Set it
only when you want a cheaper/faster model for screenshots or when your
reasoning model is text-only.
Full variable list
| Variable | Default | Required when | Purpose |
|---|---|---|---|
VFA_LICENSE_KEY |
(empty) | Mandatory for all tool executions | Commercial Pro license key from Creem |
VFA_LLM_PROVIDER |
openai |
Using a non-OpenAI provider | SDK protocol/routing for the Deep Agent (openai, anthropic, gemini, deepseek, xai, openrouter, ollama; aliases google, grok) |
VFA_LLM_MODEL |
gpt-4o |
Using a model other than gpt-4o |
Reasoning model (must support tool calling) |
VFA_LLM_API_KEY |
(empty) | Sharing one credential across providers | Generic key that overrides the provider standard variable |
VFA_LLM_BASE_URL |
(empty) | OpenAI-compatible gateways, self-hosted or proxied endpoints | Base URL override (OPENAI_BASE_URL also honoured) |
VFA_VISION_PROVIDER |
inherits | Vision served by a different vendor | Routing for multimodal calls |
VFA_VISION_MODEL |
inherits | Reasoning model lacks image input, or cost split desired | Model used for screenshots and visual grounding |
VFA_VISION_API_KEY |
inherits | Vision served by a different vendor | Credential for multimodal calls |
OLLAMA_API_BASE |
(empty) | VFA_LLM_PROVIDER=ollama |
Local endpoint, e.g. http://localhost:11434 (no key needed, a placeholder is used) |
VFA_AGENT_MAX_ITERATIONS |
12 |
Long flows that exceed the ReAct budget | Iteration ceiling of the Deep Agent loop |
VFA_LLM_TEMPERATURE |
0.0 |
Never (deterministic QA by default) | Sampling temperature |
VFA_LLM_TIMEOUT |
60 |
Slow/self-hosted models | Per-call timeout in seconds |
VFA_LLM_REQUESTS_PER_SECOND |
0 |
Free tiers or strict quotas | Token-bucket rate limiter; 0 disables it |
VFA_LLM_CHECKS_PER_SECOND |
10.0 |
Never | Rate limiter polling frequency |
HEADLESS |
true |
Local debugging of the flow step by step | Run Chromium without a window |
VFA_ALLOW_LOCAL_HOST |
true |
Must be false for internet-facing production deployments |
Allow localhost, 127.0.0.1, 0.0.0.0, *.local; false restores strict Anti-SSRF blocking of loopback, private ranges and cloud metadata |
BROWSER_CDP_URL |
(empty) | Attaching to an already running Chromium | Optional custom CDP endpoint; empty uses the managed local browser |
BROWSER_CDP_TIMEOUT_MS |
2000 |
Remote CDP endpoint slower than 2s | Timeout of the CDP connection attempt |
VFA_ACTION_TIMEOUT_MS |
10000 |
Heavy SPAs with slow first paint | Timeout of every individual Playwright action |
VFA_SCREENSHOT_FULL_PAGE |
true |
Never (viewport-only reports needed) | Full-page screenshots instead of viewport only |
VFA_SESSION_POOL_ENABLED |
true |
Stateless/multi-tenant deployments may disable it | Reuse browser sessions between tool calls |
VFA_SESSION_POOL_TTL |
300 |
Never | Idle seconds before a pooled session is closed |
VFA_SESSION_POOL_MAX_SIZE |
5 |
High-concurrency hosts | Maximum concurrent pooled sessions (LRU eviction) |
VFA_SESSION_STORAGE_STATE |
(empty) | Replaying an authenticated journey | Imported Playwright storageState; empty keeps cookies in memory only |
SSL_VERIFY |
true |
Self-signed certificates in local/staging | TLS verification switch (leave empty to skip verification; any non-empty value keeps it on) |
A copy-ready template with the same defaults lives in .env.example.
Production checklist
- One credential per deployment, injected by your secret manager — never committed and never placed in the MCP client JSON for shared machines.
- Keep
VFA_ALLOW_LOCAL_HOST=falseon any host that can reach an internal network; it is the only guard against SSRF from an LLM-driven navigation. - Pin
VFA_LLM_MODELexplicitly instead of relying ongpt-4o; upstream providers rotate model names and a silent change alters QA verdicts. - Set
VFA_LLM_REQUESTS_PER_SECONDwhen you run parallel audits against a rate-limited provider, and raiseVFA_LLM_TIMEOUTfor slow self-hosted models. - Keep
HEADLESS=trueon servers (no X server is available) and keepSSL_VERIFYenabled outside staging. - Do not mount
VFA_SESSION_STORAGE_STATEunless the flow needs a reused login: it writes real session cookies to the host filesystem.
Architecture
app/
domain/ # Pure domain (no I/O, no frameworks)
models.py # AuditReport, StepResult, RuntimeErrorEvent, VFAContext
parser.py # Deterministic NLP parser (ES/EN)
semantic.py # Cascade: regex -> a11y snapshot -> LLM fallback
vision.py # Visual primitives and grounding contracts
ports/ # Abstract contracts
browser.py # BrowserSessionPort / BrowserGatewayPort
llm.py # LLMPort / UsageLimiterPort
adapters/
browser/
playwright_adapter.py # Local sandboxed Chromium + ephemeral context
network_guard.py # Anti-SSRF firewall (private ranges / loopback)
session_pool.py # TTL + LRU pool, cookies in memory only
capture.py # console.error, pageerror, HTTP >= 400
visual_primitives.py # Screenshot and coordinate helpers
llm/
litellm_adapter.py # Multi-provider acompletion + base64 vision
deep_agent.py # ReAct loop with native tool calling
agent_tools.py # Agent tool JSON schemas
vision_grounding.py # Visual grounding adapter
rate_limiter.py # Async token bucket
visual_fallback.py # Heuristic fallback chain
tools/qa_tools.py # The three QA tools
config.py # Single source of truth for configuration
server_mcp.py # FastMCP entrypoint (stdio)
Security model
- Chromium starts with strict isolation flags (
--disable-background-networking,--disable-default-apps,--disable-sync,--no-first-run,--hide-scrollbars). - Every session uses an ephemeral in-memory
BrowserContext, destroyed on close: no cache, cookies or storage are persisted to the host disk. - A context-level Anti-SSRF firewall aborts (
accessdenied) requests to loopback, private ranges (10/8,172.16/12,192.168/16) and cloud metadata (169.254.169.254). Local development targets are allowed only whileVFA_ALLOW_LOCAL_HOSTistrue; set it tofalsefor strict production mode. - The LLM credential never reaches the audited page: only the server process talks to the inference provider.
Development
pip install -e ".[dev]"
playwright install chromium
pytest
Tests run without a real browser or network: every adapter is replaced by an
in-memory double (tests/conftest.py, tests/visual_fakes.py).
License
Commercial, single-seat Pro license. Distributed on PyPI as vfa-mcp.
Acquire your permanent license key at Creem Checkout.
Metadata
Release files for vfa-mcp 1.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vfa_mcp-1.0.3.tar.gz | 79.4 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vfa_mcp-1.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 148.0 kB
Release files / vfa_mcp-1.0.3.tar.gz
| Download URL | vfa_mcp-1.0.3.tar.gz |
|---|---|
| Size | 79.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
eaddb0daf968607c11cde445b47b345c9a9a875dd959ae7d393714a6a300b42d
|
|
BLAKE2b-256 checksum How to use checksums |
48efc438dee333e735e1989c966abb8dffc8ee14327db78acff89b99c40be0fa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Release files / vfa_mcp-1.0.3-py3-none-any.whl
| Download URL | vfa_mcp-1.0.3-py3-none-any.whl |
|---|---|
| Size | 68.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
62afcff40ab6e1d75c5b0a4e8e454347ffb2b254c5b0d686e5b70d73a5a339ba
|
|
BLAKE2b-256 checksum How to use checksums |
441ac2bdd0709ca958e1b7a072307dd5dde887058aa46be1b725e66036b763b2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.10
|
Security for vfa-mcp 1.0.3
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.