- Replace stub Vision/Transcribe with real REST implementation
- Endpoint: POST {base}/v1beta/models/{model}:generateContent
- Auth via x-goog-api-key header
- Vision: text part + inline_data per image (mime_type from Image.MediaType)
- Transcribe: transcription prompt + inline_data audio (default audio/wav)
- Model selection: opts.Model > audioModel (transcribe) > c.model
- maxTokens mapped to generationConfig.maxOutputTokens when non-zero
- Add httptest coverage: TestGeminiVision, TestGeminiTranscribe
- Re-enable gemini in tools.go audio capability check
captcha-solver-mcp
A browser-agnostic vision/LLM "brain" + stealth-artifact generator, exposed as a Model Context Protocol (MCP) server over stdio. It looks at captcha challenges (screenshots, audio, page HTML) and tells the caller what to do — which grid tiles match, what the distorted text says, what the audio says, which rotation to pick — and it generates the stealth artifacts (evasion init scripts, coherent fingerprints + headers, humanized interaction timelines) that make automated interaction look human. It is the brain; your automation (Playwright, Lightpanda, or any CDP-driven browser) is the hands that actually click tiles and harvest tokens.
Authorized use only
This is a dual-use tool. It is intended for:
- Testing the anti-bot / captcha integrations on sites and systems you own or are explicitly authorized to test.
- Accessibility automation (e.g. solving an audio captcha for an assistive workflow).
- Sanctioned, contractually-permitted scraping and QA.
It is not for evading protections on sites you do not own or control, for fraud, for account abuse, or for circumventing access controls. You are responsible for ensuring your use complies with the target site's terms of service and applicable law. Misuse is on you.
What it is — and isn't
It is: a stateless solver/advisor. Given an input it returns an answer or an artifact.
It is not an automation driver:
- It never drives a browser — no navigation, no clicking, no DOM access.
- It never mints or submits tokens — the caller's browser harvests
g-recaptcha-response/h-captcha-responseafter clicking. - reCAPTCHA v3 and Cloudflare Turnstile are detect-only. They are
behavioral / proof-of-work challenges, not vision puzzles, so
detect_captchaflags them withllm_solvable:falseand the solver returns nothing for them.
Install / build
Requires Go 1.26.
make build # -> bin/captcha-solver-mcp
The binary is a single stdio MCP server with no external runtime dependencies beyond your configured LLM endpoint.
Configuration
Configured entirely via environment variables, validated at startup (fail-fast).
Copy .env.example as a starting point.
| Variable | Required | Default | Notes |
|---|---|---|---|
CAPTCHA_LLM_PROVIDER |
yes | — | anthropic | openai | openai-compatible | gemini |
CAPTCHA_LLM_BASE_URL |
for openai-compatible |
provider default | Endpoint override (local / Ollama / OpenRouter / gateway) |
CAPTCHA_LLM_API_KEY |
yes* | — | *May be empty for a keyless local/gateway endpoint when BASE_URL is set |
CAPTCHA_LLM_MODEL |
yes | — | Vision-capable model id (see compatibility notes) |
CAPTCHA_LLM_AUDIO_MODEL |
no | uses MODEL |
Separate audio/transcription model (e.g. whisper-1) |
CAPTCHA_LLM_TEMPERATURE |
no | 0 |
|
CAPTCHA_LLM_MAX_TOKENS |
no | 1024 |
|
CAPTCHA_LLM_TIMEOUT |
no | 60s |
Go duration string |
CAPTCHA_LOG_LEVEL |
no | info |
Keyless gateways: if you point
CAPTCHA_LLM_BASE_URLat an OpenAI-compatible gateway that does not require auth, leaveCAPTCHA_LLM_API_KEYempty — startup will accept it.
Tools
All nine tools are registered in internal/mcp/server.go. Every image/audio input accepts
base64, a file path, an http(s) URL, or a data-URI.
| Tool | Inputs | Output |
|---|---|---|
detect_captcha |
html?, url?, screenshot? |
{detected:[{type,sitekey?,iframe_url?,notes?,llm_solvable}],summary} — deterministic; v3/Turnstile → llm_solvable:false |
solve_text_captcha |
image, hint?, charset?, length?, case_sensitive? |
{text,confidence} (OCR) |
solve_audio_captcha |
audio, language? |
{text,confidence}; errors clearly if provider has no transcription |
solve_grid_captcha |
screenshot, instruction, rows, cols, image_width?, image_height?, captcha_type? |
{tiles,centroids?,confidence,recheck_recommended} (reCAPTCHA v2 / hCaptcha) |
solve_rotation_captcha |
screenshot, instruction? |
{choice,rotations,confidence} (FunCaptcha, best-effort) |
get_stealth_script |
engine? = playwright|cdp|generic |
{script,apply,included_evasions} — JS init script + how to inject it |
generate_fingerprint |
os?, browser?, locale?, seed? |
{fingerprint,header_set} — coherent identity + matching headers (seeded = stable) |
plan_interaction |
actions, seed? |
humanized mouse/keyboard timeline (Bézier paths, jitter, cadence) |
solver_info |
— | configured provider/model + per-type capability flags |
solve_grid_captchareturnscentroids(pixel{x,y}click points) only whenimage_width+image_heightare supplied; otherwise the caller maps tile indices to pixels itself.solve_audio_captcharequires an audio-capable provider.anthropicis vision-only here and will return a clear "unsupported" error — useopenaiorgemini(setCAPTCHA_LLM_AUDIO_MODEL).plan_interactionis designed to pair with grid centroids: feed itclickactions at the tile centroids to produce human-looking pointer motion.
Captcha-type matrix
| Captcha | Tool | Who does what |
|---|---|---|
| Image-text (distorted characters) | solve_text_captcha |
Brain reads the text |
| Audio | solve_audio_captcha |
Brain transcribes (needs openai/gemini) |
| reCAPTCHA v2 (image grid) | solve_grid_captcha |
Brain returns tiles/centroids; caller clicks + harvests token |
| hCaptcha (image grid) | solve_grid_captcha |
Brain returns tiles/centroids; caller clicks + harvests token |
| FunCaptcha (rotation) | solve_rotation_captcha |
Brain picks rotation (best-effort); caller applies it |
| reCAPTCHA v3 | detect_captcha only |
Detect-only — behavioral, not LLM-solvable |
| Cloudflare Turnstile | detect_captcha only |
Detect-only — PoW/behavioral, not LLM-solvable |
Browser integration recipe (reCAPTCHA v2 brain/hands loop)
The server is the brain; your browser is the hands. The loop for an image-grid challenge:
- Prep identity — call
generate_fingerprint(with a stableseed) and apply the returned headers; callget_stealth_scriptand inject thescriptbefore any page script runs (page.addInitScriptin Playwright, orPage.addScriptToEvaluateOnNewDocumentover raw CDP). - Trigger + screenshot — open the challenge, screenshot the tile grid, and read the challenge text (e.g. "select all buses") and grid size (e.g. 3×3).
- Ask the brain —
solve_grid_captchawith the screenshot, instruction,rows,cols, andimage_width/image_heightto gettiles+centroids. - Humanize — feed the centroids as
clickactions toplan_interaction; replay the resulting timeline so the pointer moves on a Bézier path with realistic cadence. - Verify — click the verify button.
- Loop if dynamic — if
recheck_recommendedis true (tiles re-render after each click), re-screenshot and repeat steps 3–5 until the grid clears. - Harvest — read the
g-recaptcha-responsetoken from the page and submit your form.
Runnable reference implementations are in examples/.
Usage
Register with an MCP client
The server speaks MCP over stdio. Point your MCP client at the built binary:
{
"mcpServers": {
"captcha-solver": {
"command": "/absolute/path/to/bin/captcha-solver-mcp",
"env": {
"CAPTCHA_LLM_PROVIDER": "openai-compatible",
"CAPTCHA_LLM_BASE_URL": "https://your-gateway.example.com/v1",
"CAPTCHA_LLM_API_KEY": "sk-...",
"CAPTCHA_LLM_MODEL": "openai/gpt-5.4"
}
}
}
}
Copy-pasteable env (vision model)
export CAPTCHA_LLM_PROVIDER=openai-compatible
export CAPTCHA_LLM_BASE_URL=https://your-gateway.example.com/v1
export CAPTCHA_LLM_API_KEY=sk-...
export CAPTCHA_LLM_MODEL=openai/gpt-5.4 # or zai/GLM-4.6V-FlashX, claude-opus-4-8, etc.
./bin/captcha-solver-mcp
Testing
make test # unit tests (go test ./...)
make e2e # deterministic stdio end-to-end with a fake LLM (no network)
make e2e-live # OPT-IN: live test against a real LLM gateway
make e2e-browser # OPT-IN: live Playwright browser e2e against the 2captcha demo
The live e2e is gated behind CAPTCHA_E2E_LIVE=1 and needs a real endpoint:
CAPTCHA_E2E_LIVE=1 \
CAPTCHA_LLM_PROVIDER=openai-compatible \
CAPTCHA_LLM_BASE_URL=https://your-gateway.example.com/v1 \
CAPTCHA_LLM_MODEL=openai/gpt-5.4 \
make e2e-live
Browser e2e (Playwright → 2captcha demo)
make e2e-browser drives a real Chromium (via Playwright) against the public
2captcha demo and solves captchas through the MCP server +
a real vision model. It is network- and browser-gated (build tags e2e,live,
CAPTCHA_E2E_LIVE=1, node + an installed Playwright Chromium); the offline make e2e
suite never runs it. Prereqs:
npm install
npx playwright install chromium
make e2e-browser
Two paths (test/e2e/playwright/*.mjs, wrapped by test/e2e/browser_test.go):
- Text captcha (
solve-text.mjs) — verified end-to-end. Screenshots the demo's distorted-text image, callssolve_text_captcha, types the prediction, clicks Check, and asserts the demo's own "Captcha is passed successfully!" indicator (ground truth). Retries up to 2 fresh captchas before failing, since OCR is stochastic. - reCAPTCHA v2 (
solve-recaptcha-v2.mjs) — best-effort. Applies a stealth script +generate_fingerprintprofile, clicks the checkbox, then loopssolve_grid_captcha→plan_interaction→ humanized clicks over the tile grid, and readsg-recaptcha-response. Real Google reCAPTCHA commonly resists headless automation, so this path logs the outcome and exits 0 even with no token — it fails only on a real script/transport error.
Compatibility notes
max_tokensvsmax_completion_tokens: theopenai-compatibleprovider auto-retries withmax_completion_tokenswhen a model (e.g. GPT-5.x) rejectsmax_tokens. This works transparently across old and new OpenAI-style backends — you don't configure anything.- Prefer a vision-capable model for the image/grid tools (e.g.
openai/gpt-5.4,zai/GLM-4.6V-FlashX). Reasoning models that emit<think>…</think>still work (responses are JSON-extracted), but non-reasoning vision models give cleaner output. - TLS/JA3 + HTTP/2 fingerprints are browser/proxy-level. The stealth tools only advise on these — they cannot change them. To control them, terminate through a fingerprint-aware proxy or a browser build that emits the JA3/H2 profile you want.
Roadmap / not yet built
Two pieces are designed but deferred (see
docs/superpowers/specs/ and
docs/superpowers/plans/):
- Deterministic (offline) browser e2e harness — driving Playwright Chromium and
Lightpanda against a local fake-captcha page, to exercise the full brain/hands loop in
CI without hitting real sites. A live browser harness already exists (
make e2e-browser, see Testing): it targets the 2captcha demo, is network/browser-gated, verifies the text path end-to-end against the demo's success indicator, and runs the reCAPTCHA v2 path best-effort. The remaining work is the offline, deterministic local-page variant for CI. - Gemini provider — a stub exists (
internal/llm/gemini.go); thegenerateContentwire format still needs verification against current Google docs before it ships. Useopenai/openai-compatible/anthropicin the meantime.