Reflect the previously-deferred items as done: gemini provider is fully wired to native generateContent (vision + audio), unit-tested, with a note that live use needs a real Google key. Document that get_stealth_script now takes os/browser/locale/seed and returns a fingerprint, and that the evasion script is templated to match it (coherent) — inject both together or share a seed. Note reasoning-model <think> blocks are stripped before json parse. Record the CAPTCHA_E2E_HEADED toggle, drop the done roadmap items, keep the honest note that headless reCAPTCHA v2 usually won't mint a token, and call out the single package-lock.json lockfile.
13 KiB
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 (all four fully implemented) |
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.
Gemini: the
geminiprovider is fully implemented against Google's nativegenerateContentwire format (vision and audio) and is unit-tested. Live use needs a real Google Gemini API key (generativelanguage.googleapis.com) — the project's OpenAI-compatible test gateway can't exercise the native Gemini path, so it is covered by unit tests against a mock endpoint rather than the live e2e suite.
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, os?, browser?, locale?, seed? |
{script,apply,fingerprint,included_evasions} — JS init script (templated to match fingerprint) + 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.get_stealth_scriptacceptsos/browser/locale/seedand returns afingerprintfield. The injected evasion script's WebGL vendor/renderer,navigator.platform, andnavigator.languagesare templated to match that fingerprint (coherent), so the script and the identity tell the same story. Inject thescriptand apply the returnedfingerprinttogether — or callget_stealth_scriptandgenerate_fingerprintwith the sameseedto get an identical, coherent pair.
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
Node deps are pinned by a single lockfile,
package-lock.json(npm). Usenpmto install so the lockfile stays canonical.
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. SetCAPTCHA_E2E_HEADED=1to launch a visible, slow-mo browser for a real-profile attempt that improves the odds of Google minting a token. Headless reCAPTCHA v2 usually won't mint a token — that is inherent to Google's defenses, not a bug in this harness.
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>(or<thinking>…</thinking>) are now handled — the scratchpad block is stripped before JSON extraction, so a JSON-looking object inside the reasoning never gets mistaken for the answer. Non-reasoning vision models still give the lowest latency. - 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
The Gemini provider and WebGL/fingerprint coherence are now done (see the Provider and Tools sections above); they are no longer on this list.
One piece remains 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 (with an optionalCAPTCHA_E2E_HEADED=1real-profile attempt). The remaining work is the offline, deterministic local-page variant for CI.
Note: headless reCAPTCHA v2 usually won't mint a token regardless of harness quality — that is Google's anti-automation defense working as intended, not a defect here.