capguru-mcp 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: capguru-mcp
3
+ Version: 0.2.0
4
+ Summary: MCP server for the Cap.Guru captcha solving service: lets AI assistants open pages and solve reCAPTCHA v2/v3, Cloudflare Turnstile, text captchas, hCaptcha, GeeTest, FunCaptcha, TikTok, AWS WAF and Yandex SmartCaptcha
5
+ Author: Cap.Guru
6
+ Project-URL: Homepage, https://cap.guru
7
+ Project-URL: Documentation, https://docs.cap.guru
8
+ Keywords: mcp,captcha,solver,playwright,cap.guru,claude,cursor,turnstile,recaptcha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: mcp>=1.2
14
+ Requires-Dist: capguru>=0.4.0
15
+ Requires-Dist: playwright>=1.40
16
+
17
+ # capguru-mcp
18
+
19
+ **English** | [Русский](#русский)
20
+
21
+ MCP server for the [Cap.Guru](https://cap.guru) captcha solving service.
22
+ It lets AI assistants (Claude Desktop, Claude Code, Cursor and other MCP clients) open web pages in a browser and solve captchas on them.
23
+
24
+ Supported: reCAPTCHA v2/v3, Cloudflare Turnstile, text (image) captchas, hCaptcha, GeeTest v3/v4, FunCaptcha (Arkose Labs), TikTok, AWS WAF, Yandex SmartCaptcha.
25
+
26
+ ## Installation
27
+
28
+ ```
29
+ pip install capguru-mcp
30
+ playwright install chromium
31
+ ```
32
+
33
+ ## Connecting to an MCP client
34
+
35
+ Claude Desktop (`claude_desktop_config.json`), Cursor (`.cursor/mcp.json`) and most other clients:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "capguru": {
41
+ "command": "capguru-mcp",
42
+ "env": {
43
+ "CAPGURU_API_KEY": "YOUR_KEY"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ Claude Code:
51
+
52
+ ```
53
+ claude mcp add capguru --env CAPGURU_API_KEY=YOUR_KEY -- capguru-mcp
54
+ ```
55
+
56
+ Without installing (via [uv](https://docs.astral.sh/uv/)): use `"command": "uvx", "args": ["capguru-mcp"]`.
57
+
58
+ ## Tools
59
+
60
+ | Tool | Description |
61
+ |---|---|
62
+ | `open_browser` | Launch a browser (`headless`, `channel`: `chrome`/`msedge`, `proxy`) |
63
+ | `connect_browser` | Connect to a running Chrome started with `--remote-debugging-port` |
64
+ | `goto` | Open a URL |
65
+ | `click`, `fill` | Click an element, type text into an input |
66
+ | `screenshot` | Screenshot of the page |
67
+ | `page_info` | Current URL and title |
68
+ | `detect_captcha` | Detect the captcha type without solving |
69
+ | `solve_captcha` | Solve the captcha: `auto` or a specific type |
70
+ | `solve_recaptcha_token` | Get a reCAPTCHA v2 / v2 Invisible / v3 token (`sitekey`, `pageurl`, `version`, `min_score`); works without a browser too |
71
+ | `solve_turnstile_token` | Get a Cloudflare Turnstile token (`sitekey`, `pageurl`); works without a browser too |
72
+ | `solve_image_captcha` | Recognize a text captcha from an image: element `selector`, `image_base64`, `image_path` or `image_url`; optional `vernet`, `fill_selector` |
73
+ | `close_browser` | Close the browser |
74
+
75
+ Example requests to the assistant:
76
+
77
+ - *"Open https://site.com/login, solve the captcha and take a screenshot."*
78
+ - *"Get a reCAPTCHA token for sitekey 6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ- on https://www.google.com/recaptcha/api2/demo."*
79
+ - *"Get a reCAPTCHA v3 token for sitekey 6Ldb4ccaAAAAACMxv8uxUIMckZy1og98rs_h3AmO on http://learn.captcha.guru/ln/recap3/."*
80
+ - *"Open https://react-turnstile.vercel.app/basic and solve the Turnstile."*
81
+ - *"Recognize the captcha image #captcha_img and type the answer into #captcha_input."*
82
+
83
+ `solve_recaptcha_token` uses the Cap.Guru token method (`userrecaptcha`): it returns the `g-recaptcha-response` token. If a page is open, the sitekey, URL and User-Agent are taken from it; for v2 the token is inserted into the page and the site callback is called. For v3 pass `version: v3` (optionally `min_score`, service default 0.1); the token is only returned, because v3 sites request it themselves.
84
+
85
+ `solve_turnstile_token` works the same way for Cloudflare Turnstile (`method=turnstile`): with an open page the token is put into `cf-turnstile-response` and the Turnstile callback is called. Typical solve time is 10–30 seconds.
86
+
87
+ ## Settings (environment variables)
88
+
89
+ | Variable | Default | Description |
90
+ |---|---|---|
91
+ | `CAPGURU_API_KEY` | — | Cap.Guru API key (required) |
92
+ | `CAPGURU_SERVER` | `https://api.cap.guru` | API server |
93
+ | `CAPGURU_ATTEMPTS` | `5` | Max number of solving attempts |
94
+ | `CAPGURU_DEBUG` | `1` | Include the solver log in tool results |
95
+ | `CAPGURU_BROWSER_CHANNEL` | — | `chrome` or `msedge` to use an installed browser |
96
+ | `CAPGURU_BROWSER_PATH` | — | Path to a browser executable |
97
+ | `CAPGURU_BROWSER_ARGS` | — | Extra browser arguments, space separated |
98
+
99
+ Full documentation: https://docs.cap.guru
100
+
101
+ ---
102
+
103
+ ## Русский
104
+
105
+ MCP-сервер для сервиса решения капчи [Cap.Guru](https://cap.guru).
106
+ Позволяет ИИ-ассистентам (Claude Desktop, Claude Code, Cursor и другим MCP-клиентам) открывать страницы в браузере и решать на них капчу.
107
+
108
+ Поддерживаются: reCAPTCHA v2/v3, Cloudflare Turnstile, текстовые капчи (картинкой), hCaptcha, GeeTest v3/v4, FunCaptcha (Arkose Labs), TikTok, AWS WAF, Яндекс SmartCaptcha.
109
+
110
+ ### Установка
111
+
112
+ ```
113
+ pip install capguru-mcp
114
+ playwright install chromium
115
+ ```
116
+
117
+ ### Подключение к MCP-клиенту
118
+
119
+ Claude Desktop (`claude_desktop_config.json`), Cursor (`.cursor/mcp.json`) и большинство других клиентов:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "capguru": {
125
+ "command": "capguru-mcp",
126
+ "env": {
127
+ "CAPGURU_API_KEY": "ВАШ_КЛЮЧ"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ Claude Code:
135
+
136
+ ```
137
+ claude mcp add capguru --env CAPGURU_API_KEY=ВАШ_КЛЮЧ -- capguru-mcp
138
+ ```
139
+
140
+ Без установки (через [uv](https://docs.astral.sh/uv/)): `"command": "uvx", "args": ["capguru-mcp"]`.
141
+
142
+ ### Инструменты
143
+
144
+ | Инструмент | Описание |
145
+ |---|---|
146
+ | `open_browser` | Запустить браузер (`headless`, `channel`: `chrome`/`msedge`, `proxy`) |
147
+ | `connect_browser` | Подключиться к запущенному Chrome с `--remote-debugging-port` |
148
+ | `goto` | Открыть адрес |
149
+ | `click`, `fill` | Нажать на элемент, ввести текст в поле |
150
+ | `screenshot` | Скриншот страницы |
151
+ | `page_info` | Текущий адрес и заголовок |
152
+ | `detect_captcha` | Определить тип капчи, не решая |
153
+ | `solve_captcha` | Решить капчу: `auto` или конкретный тип |
154
+ | `solve_recaptcha_token` | Получить токен reCAPTCHA v2 / v2 Invisible / v3 (`sitekey`, `pageurl`, `version`, `min_score`); работает и без браузера |
155
+ | `solve_turnstile_token` | Получить токен Cloudflare Turnstile (`sitekey`, `pageurl`); работает и без браузера |
156
+ | `solve_image_captcha` | Распознать текстовую капчу с картинки: элемент `selector`, `image_base64`, `image_path` или `image_url`; по желанию `vernet`, `fill_selector` |
157
+ | `close_browser` | Закрыть браузер |
158
+
159
+ Примеры запросов ассистенту:
160
+
161
+ - *«Открой https://site.com/login, реши капчу и сделай скриншот».*
162
+ - *«Получи токен reCAPTCHA для sitekey 6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ- на https://www.google.com/recaptcha/api2/demo».*
163
+ - *«Получи токен reCAPTCHA v3 для sitekey 6Ldb4ccaAAAAACMxv8uxUIMckZy1og98rs_h3AmO на http://learn.captcha.guru/ln/recap3/».*
164
+ - *«Открой https://react-turnstile.vercel.app/basic и реши Turnstile».*
165
+ - *«Распознай капчу с картинки #captcha_img и введи ответ в #captcha_input».*
166
+
167
+ `solve_recaptcha_token` использует токенный метод Cap.Guru (`userrecaptcha`) и возвращает токен `g-recaptcha-response`. Если страница открыта, sitekey, адрес и User-Agent берутся с неё; для v2 токен вставляется на страницу и вызывается callback сайта. Для v3 укажите `version: v3` (и при необходимости `min_score`, по умолчанию у сервиса 0.1); токен только возвращается, потому что сайты с v3 запрашивают его сами.
168
+
169
+ `solve_turnstile_token` так же работает для Cloudflare Turnstile (`method=turnstile`): если страница открыта, токен вставляется в `cf-turnstile-response` и вызывается callback Turnstile. Обычное время решения — 10–30 секунд.
170
+
171
+ ### Настройки (переменные окружения)
172
+
173
+ | Переменная | По умолчанию | Описание |
174
+ |---|---|---|
175
+ | `CAPGURU_API_KEY` | — | API-ключ Cap.Guru (обязательно) |
176
+ | `CAPGURU_SERVER` | `https://api.cap.guru` | Сервер API |
177
+ | `CAPGURU_ATTEMPTS` | `5` | Макс. количество попыток |
178
+ | `CAPGURU_DEBUG` | `1` | Добавлять лог солвера в ответ инструмента |
179
+ | `CAPGURU_BROWSER_CHANNEL` | — | `chrome` или `msedge`, чтобы использовать установленный браузер |
180
+ | `CAPGURU_BROWSER_PATH` | — | Путь к исполняемому файлу браузера |
181
+ | `CAPGURU_BROWSER_ARGS` | — | Дополнительные аргументы браузера через пробел |
182
+
183
+ Полная документация: https://docs.cap.guru
@@ -0,0 +1,167 @@
1
+ # capguru-mcp
2
+
3
+ **English** | [Русский](#русский)
4
+
5
+ MCP server for the [Cap.Guru](https://cap.guru) captcha solving service.
6
+ It lets AI assistants (Claude Desktop, Claude Code, Cursor and other MCP clients) open web pages in a browser and solve captchas on them.
7
+
8
+ Supported: reCAPTCHA v2/v3, Cloudflare Turnstile, text (image) captchas, hCaptcha, GeeTest v3/v4, FunCaptcha (Arkose Labs), TikTok, AWS WAF, Yandex SmartCaptcha.
9
+
10
+ ## Installation
11
+
12
+ ```
13
+ pip install capguru-mcp
14
+ playwright install chromium
15
+ ```
16
+
17
+ ## Connecting to an MCP client
18
+
19
+ Claude Desktop (`claude_desktop_config.json`), Cursor (`.cursor/mcp.json`) and most other clients:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "capguru": {
25
+ "command": "capguru-mcp",
26
+ "env": {
27
+ "CAPGURU_API_KEY": "YOUR_KEY"
28
+ }
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ Claude Code:
35
+
36
+ ```
37
+ claude mcp add capguru --env CAPGURU_API_KEY=YOUR_KEY -- capguru-mcp
38
+ ```
39
+
40
+ Without installing (via [uv](https://docs.astral.sh/uv/)): use `"command": "uvx", "args": ["capguru-mcp"]`.
41
+
42
+ ## Tools
43
+
44
+ | Tool | Description |
45
+ |---|---|
46
+ | `open_browser` | Launch a browser (`headless`, `channel`: `chrome`/`msedge`, `proxy`) |
47
+ | `connect_browser` | Connect to a running Chrome started with `--remote-debugging-port` |
48
+ | `goto` | Open a URL |
49
+ | `click`, `fill` | Click an element, type text into an input |
50
+ | `screenshot` | Screenshot of the page |
51
+ | `page_info` | Current URL and title |
52
+ | `detect_captcha` | Detect the captcha type without solving |
53
+ | `solve_captcha` | Solve the captcha: `auto` or a specific type |
54
+ | `solve_recaptcha_token` | Get a reCAPTCHA v2 / v2 Invisible / v3 token (`sitekey`, `pageurl`, `version`, `min_score`); works without a browser too |
55
+ | `solve_turnstile_token` | Get a Cloudflare Turnstile token (`sitekey`, `pageurl`); works without a browser too |
56
+ | `solve_image_captcha` | Recognize a text captcha from an image: element `selector`, `image_base64`, `image_path` or `image_url`; optional `vernet`, `fill_selector` |
57
+ | `close_browser` | Close the browser |
58
+
59
+ Example requests to the assistant:
60
+
61
+ - *"Open https://site.com/login, solve the captcha and take a screenshot."*
62
+ - *"Get a reCAPTCHA token for sitekey 6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ- on https://www.google.com/recaptcha/api2/demo."*
63
+ - *"Get a reCAPTCHA v3 token for sitekey 6Ldb4ccaAAAAACMxv8uxUIMckZy1og98rs_h3AmO on http://learn.captcha.guru/ln/recap3/."*
64
+ - *"Open https://react-turnstile.vercel.app/basic and solve the Turnstile."*
65
+ - *"Recognize the captcha image #captcha_img and type the answer into #captcha_input."*
66
+
67
+ `solve_recaptcha_token` uses the Cap.Guru token method (`userrecaptcha`): it returns the `g-recaptcha-response` token. If a page is open, the sitekey, URL and User-Agent are taken from it; for v2 the token is inserted into the page and the site callback is called. For v3 pass `version: v3` (optionally `min_score`, service default 0.1); the token is only returned, because v3 sites request it themselves.
68
+
69
+ `solve_turnstile_token` works the same way for Cloudflare Turnstile (`method=turnstile`): with an open page the token is put into `cf-turnstile-response` and the Turnstile callback is called. Typical solve time is 10–30 seconds.
70
+
71
+ ## Settings (environment variables)
72
+
73
+ | Variable | Default | Description |
74
+ |---|---|---|
75
+ | `CAPGURU_API_KEY` | — | Cap.Guru API key (required) |
76
+ | `CAPGURU_SERVER` | `https://api.cap.guru` | API server |
77
+ | `CAPGURU_ATTEMPTS` | `5` | Max number of solving attempts |
78
+ | `CAPGURU_DEBUG` | `1` | Include the solver log in tool results |
79
+ | `CAPGURU_BROWSER_CHANNEL` | — | `chrome` or `msedge` to use an installed browser |
80
+ | `CAPGURU_BROWSER_PATH` | — | Path to a browser executable |
81
+ | `CAPGURU_BROWSER_ARGS` | — | Extra browser arguments, space separated |
82
+
83
+ Full documentation: https://docs.cap.guru
84
+
85
+ ---
86
+
87
+ ## Русский
88
+
89
+ MCP-сервер для сервиса решения капчи [Cap.Guru](https://cap.guru).
90
+ Позволяет ИИ-ассистентам (Claude Desktop, Claude Code, Cursor и другим MCP-клиентам) открывать страницы в браузере и решать на них капчу.
91
+
92
+ Поддерживаются: reCAPTCHA v2/v3, Cloudflare Turnstile, текстовые капчи (картинкой), hCaptcha, GeeTest v3/v4, FunCaptcha (Arkose Labs), TikTok, AWS WAF, Яндекс SmartCaptcha.
93
+
94
+ ### Установка
95
+
96
+ ```
97
+ pip install capguru-mcp
98
+ playwright install chromium
99
+ ```
100
+
101
+ ### Подключение к MCP-клиенту
102
+
103
+ Claude Desktop (`claude_desktop_config.json`), Cursor (`.cursor/mcp.json`) и большинство других клиентов:
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "capguru": {
109
+ "command": "capguru-mcp",
110
+ "env": {
111
+ "CAPGURU_API_KEY": "ВАШ_КЛЮЧ"
112
+ }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ Claude Code:
119
+
120
+ ```
121
+ claude mcp add capguru --env CAPGURU_API_KEY=ВАШ_КЛЮЧ -- capguru-mcp
122
+ ```
123
+
124
+ Без установки (через [uv](https://docs.astral.sh/uv/)): `"command": "uvx", "args": ["capguru-mcp"]`.
125
+
126
+ ### Инструменты
127
+
128
+ | Инструмент | Описание |
129
+ |---|---|
130
+ | `open_browser` | Запустить браузер (`headless`, `channel`: `chrome`/`msedge`, `proxy`) |
131
+ | `connect_browser` | Подключиться к запущенному Chrome с `--remote-debugging-port` |
132
+ | `goto` | Открыть адрес |
133
+ | `click`, `fill` | Нажать на элемент, ввести текст в поле |
134
+ | `screenshot` | Скриншот страницы |
135
+ | `page_info` | Текущий адрес и заголовок |
136
+ | `detect_captcha` | Определить тип капчи, не решая |
137
+ | `solve_captcha` | Решить капчу: `auto` или конкретный тип |
138
+ | `solve_recaptcha_token` | Получить токен reCAPTCHA v2 / v2 Invisible / v3 (`sitekey`, `pageurl`, `version`, `min_score`); работает и без браузера |
139
+ | `solve_turnstile_token` | Получить токен Cloudflare Turnstile (`sitekey`, `pageurl`); работает и без браузера |
140
+ | `solve_image_captcha` | Распознать текстовую капчу с картинки: элемент `selector`, `image_base64`, `image_path` или `image_url`; по желанию `vernet`, `fill_selector` |
141
+ | `close_browser` | Закрыть браузер |
142
+
143
+ Примеры запросов ассистенту:
144
+
145
+ - *«Открой https://site.com/login, реши капчу и сделай скриншот».*
146
+ - *«Получи токен reCAPTCHA для sitekey 6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ- на https://www.google.com/recaptcha/api2/demo».*
147
+ - *«Получи токен reCAPTCHA v3 для sitekey 6Ldb4ccaAAAAACMxv8uxUIMckZy1og98rs_h3AmO на http://learn.captcha.guru/ln/recap3/».*
148
+ - *«Открой https://react-turnstile.vercel.app/basic и реши Turnstile».*
149
+ - *«Распознай капчу с картинки #captcha_img и введи ответ в #captcha_input».*
150
+
151
+ `solve_recaptcha_token` использует токенный метод Cap.Guru (`userrecaptcha`) и возвращает токен `g-recaptcha-response`. Если страница открыта, sitekey, адрес и User-Agent берутся с неё; для v2 токен вставляется на страницу и вызывается callback сайта. Для v3 укажите `version: v3` (и при необходимости `min_score`, по умолчанию у сервиса 0.1); токен только возвращается, потому что сайты с v3 запрашивают его сами.
152
+
153
+ `solve_turnstile_token` так же работает для Cloudflare Turnstile (`method=turnstile`): если страница открыта, токен вставляется в `cf-turnstile-response` и вызывается callback Turnstile. Обычное время решения — 10–30 секунд.
154
+
155
+ ### Настройки (переменные окружения)
156
+
157
+ | Переменная | По умолчанию | Описание |
158
+ |---|---|---|
159
+ | `CAPGURU_API_KEY` | — | API-ключ Cap.Guru (обязательно) |
160
+ | `CAPGURU_SERVER` | `https://api.cap.guru` | Сервер API |
161
+ | `CAPGURU_ATTEMPTS` | `5` | Макс. количество попыток |
162
+ | `CAPGURU_DEBUG` | `1` | Добавлять лог солвера в ответ инструмента |
163
+ | `CAPGURU_BROWSER_CHANNEL` | — | `chrome` или `msedge`, чтобы использовать установленный браузер |
164
+ | `CAPGURU_BROWSER_PATH` | — | Путь к исполняемому файлу браузера |
165
+ | `CAPGURU_BROWSER_ARGS` | — | Дополнительные аргументы браузера через пробел |
166
+
167
+ Полная документация: https://docs.cap.guru
@@ -0,0 +1 @@
1
+ __version__ = '0.2.0'
@@ -0,0 +1,3 @@
1
+ from .server import main
2
+
3
+ main()
@@ -0,0 +1,591 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ import contextlib
5
+ import functools
6
+ import io
7
+ import os
8
+ import sys
9
+ from typing import Optional
10
+
11
+ try:
12
+ from mcp.server.mcpserver import MCPServer as _Server, Image
13
+ from mcp.server.mcpserver.exceptions import ToolError
14
+ except ImportError:
15
+ from mcp.server.fastmcp import FastMCP as _Server, Image
16
+ from mcp.server.fastmcp.exceptions import ToolError
17
+
18
+ from captcha_solver import CaptchaSolver, CapGuruFatalError
19
+ from captcha_solver.capguru_client import CapGuruClient
20
+
21
+ INSTRUCTIONS = (
22
+ 'Cap.Guru captcha solver. Typical flow: open_browser (or connect_browser) -> goto(url) -> '
23
+ 'interact with the page (click/fill) until a captcha appears -> solve_captcha(). '
24
+ 'Use screenshot() to see the page. Supported: reCAPTCHA v2, hCaptcha, GeeTest v3/v4, '
25
+ 'FunCaptcha, TikTok, AWS WAF, Yandex SmartCaptcha. '
26
+ 'For reCAPTCHA v2 / v2 Invisible / v3 you can also get a token without solving images: '
27
+ 'solve_recaptcha_token(sitekey, pageurl, version), it works with or without an open browser. '
28
+ 'Cloudflare Turnstile is solved the same way with solve_turnstile_token(sitekey, pageurl). '
29
+ 'Text (image) captchas: solve_image_captcha(selector or image), optionally fill_selector to type the answer.'
30
+ )
31
+
32
+ mcp = _Server('capguru', instructions=INSTRUCTIONS)
33
+
34
+
35
+ def tool(fn):
36
+ @functools.wraps(fn)
37
+ async def wrapper(*args, **kwargs):
38
+ try:
39
+ return await fn(*args, **kwargs)
40
+ except ToolError:
41
+ raise
42
+ except Exception as e:
43
+ raise ToolError(f'{type(e).__name__}: {e}') from e
44
+ return mcp.tool()(wrapper)
45
+
46
+ SOLVE_METHODS = {
47
+ 'auto': None,
48
+ 'recaptcha2': 'solve_recaptcha2',
49
+ 'hcaptcha': 'solve_hcaptcha',
50
+ 'yandex': 'solve_other',
51
+ 'geetest': 'solve_geetest',
52
+ 'funcaptcha': 'solve_funcaptcha',
53
+ 'tiktok': 'solve_tiktok',
54
+ 'aws': 'solve_aws',
55
+ }
56
+
57
+
58
+ class _State:
59
+ def __init__(self) -> None:
60
+ self.pw = None
61
+ self.browser = None
62
+ self.context = None
63
+ self.page = None
64
+ self.solver: Optional[CaptchaSolver] = None
65
+ self.owns_browser = False
66
+
67
+
68
+ S = _State()
69
+
70
+
71
+ class _Tee(io.TextIOBase):
72
+ def __init__(self) -> None:
73
+ self.buf = io.StringIO()
74
+
75
+ def write(self, s: str) -> int:
76
+ self.buf.write(s)
77
+ sys.stderr.write(s)
78
+ return len(s)
79
+
80
+
81
+ @contextlib.contextmanager
82
+ def _capture():
83
+ tee = _Tee()
84
+ with contextlib.redirect_stdout(tee):
85
+ yield tee
86
+
87
+
88
+ def _api_key() -> str:
89
+ key = os.environ.get('CAPGURU_API_KEY', '').strip()
90
+ if not key:
91
+ raise RuntimeError('CAPGURU_API_KEY environment variable is not set in the MCP server config')
92
+ return key
93
+
94
+
95
+ def _server_url() -> str:
96
+ return os.environ.get('CAPGURU_SERVER', '').strip() or 'https://api.cap.guru'
97
+
98
+
99
+ def _debug() -> bool:
100
+ return os.environ.get('CAPGURU_DEBUG', '1').strip().lower() not in ('0', 'false', 'no', '')
101
+
102
+
103
+ def _attempts() -> int:
104
+ try:
105
+ return max(1, int(os.environ.get('CAPGURU_ATTEMPTS', '5')))
106
+ except ValueError:
107
+ return 5
108
+
109
+
110
+ async def _ensure_playwright():
111
+ if S.pw is None:
112
+ from playwright.async_api import async_playwright
113
+ S.pw = await async_playwright().start()
114
+ return S.pw
115
+
116
+
117
+ TURNSTILE_HOOK_JS = """(() => {
118
+ if (window.__cgTurnstileHooked) return;
119
+ window.__cgTurnstileHooked = true;
120
+ window.__cgTurnstile = [];
121
+ const wrap = (ts) => {
122
+ if (!ts || ts.__cgWrapped || typeof ts.render !== 'function') return ts;
123
+ const orig = ts.render.bind(ts);
124
+ ts.render = function (el, params) {
125
+ try { window.__cgTurnstile.push(Object.assign({}, params || {})); } catch (e) {}
126
+ return orig(el, params);
127
+ };
128
+ ts.__cgWrapped = true;
129
+ return ts;
130
+ };
131
+ let current = window.turnstile;
132
+ try {
133
+ Object.defineProperty(window, 'turnstile', {
134
+ configurable: true,
135
+ get() { return current; },
136
+ set(v) { current = wrap(v); },
137
+ });
138
+ } catch (e) {}
139
+ if (current) wrap(current);
140
+ })();"""
141
+
142
+
143
+ async def _attach(page) -> None:
144
+ S.page = page
145
+ try:
146
+ await page.context.add_init_script(TURNSTILE_HOOK_JS)
147
+ except Exception:
148
+ pass
149
+ with _capture():
150
+ S.solver = CaptchaSolver(
151
+ page=page,
152
+ api_key=_api_key(),
153
+ server=_server_url(),
154
+ attempts=_attempts(),
155
+ debug=_debug(),
156
+ )
157
+ await S.solver.setup()
158
+
159
+
160
+ def _require_page():
161
+ if S.page is None or S.page.is_closed():
162
+ raise RuntimeError('No browser page. Call open_browser or connect_browser first.')
163
+ return S.page
164
+
165
+
166
+ async def _close() -> None:
167
+ try:
168
+ if S.solver is not None:
169
+ S.solver.close()
170
+ except Exception:
171
+ pass
172
+ try:
173
+ if S.owns_browser and S.browser is not None:
174
+ await S.browser.close()
175
+ except Exception:
176
+ pass
177
+ S.browser = S.context = S.page = S.solver = None
178
+ S.owns_browser = False
179
+
180
+
181
+ @tool
182
+ async def open_browser(headless: bool = False, channel: str = '', proxy: str = '') -> str:
183
+ """Launch a new Chromium browser with one page and attach the captcha solver to it.
184
+
185
+ headless: run without a visible window.
186
+ channel: 'chrome' or 'msedge' to use an installed browser instead of bundled Chromium.
187
+ proxy: optional proxy, e.g. 'http://user:pass@host:port'.
188
+ """
189
+ await _close()
190
+ pw = await _ensure_playwright()
191
+ kwargs = {'headless': headless}
192
+ channel = channel or os.environ.get('CAPGURU_BROWSER_CHANNEL', '')
193
+ if channel:
194
+ kwargs['channel'] = channel
195
+ exe = os.environ.get('CAPGURU_BROWSER_PATH', '')
196
+ if exe:
197
+ kwargs['executable_path'] = exe
198
+ args = [a for a in os.environ.get('CAPGURU_BROWSER_ARGS', '').split() if a]
199
+ if args:
200
+ kwargs['args'] = args
201
+ if proxy:
202
+ from urllib.parse import urlsplit
203
+ u = urlsplit(proxy)
204
+ p = {'server': f'{u.scheme}://{u.hostname}:{u.port}' if u.port else f'{u.scheme}://{u.hostname}'}
205
+ if u.username:
206
+ p['username'] = u.username
207
+ p['password'] = u.password or ''
208
+ kwargs['proxy'] = p
209
+ S.browser = await pw.chromium.launch(**kwargs)
210
+ S.owns_browser = True
211
+ S.context = await S.browser.new_context()
212
+ page = await S.context.new_page()
213
+ await _attach(page)
214
+ return 'Browser opened. Use goto(url) to open a page.'
215
+
216
+
217
+ @tool
218
+ async def connect_browser(cdp_url: str = 'http://127.0.0.1:9222') -> str:
219
+ """Connect to an already running Chrome started with --remote-debugging-port and attach the solver
220
+ to its first tab. Use this to solve captchas in a browser controlled by another tool."""
221
+ await _close()
222
+ pw = await _ensure_playwright()
223
+ S.browser = await pw.chromium.connect_over_cdp(cdp_url)
224
+ S.owns_browser = False
225
+ S.context = S.browser.contexts[0] if S.browser.contexts else await S.browser.new_context()
226
+ page = S.context.pages[0] if S.context.pages else await S.context.new_page()
227
+ await _attach(page)
228
+ return f'Connected to {cdp_url}. Current page: {page.url}'
229
+
230
+
231
+ @tool
232
+ async def goto(url: str, wait_until: str = 'load', timeout: int = 60) -> str:
233
+ """Open a URL in the browser page. wait_until: load | domcontentloaded | networkidle."""
234
+ page = _require_page()
235
+ await page.goto(url, wait_until=wait_until, timeout=timeout * 1000)
236
+ return f'Opened {page.url} — title: {await page.title()}'
237
+
238
+
239
+ @tool
240
+ async def detect_captcha() -> str:
241
+ """Detect which captcha is visible on the page without solving it.
242
+ Returns recaptcha2, hcaptcha, yandex, geetest, funcaptcha, tiktok, aws or 'none'."""
243
+ _require_page()
244
+ with _capture():
245
+ kind = await S.solver.detect()
246
+ return kind or 'none'
247
+
248
+
249
+ @tool
250
+ async def solve_captcha(captcha_type: str = 'auto', timeout: int = 30) -> str:
251
+ """Solve the captcha on the current page through the Cap.Guru service.
252
+
253
+ captcha_type: auto (detect automatically) | recaptcha2 | hcaptcha | yandex | geetest | funcaptcha | tiktok | aws.
254
+ timeout: for auto, how many seconds to wait for a captcha to appear.
255
+ """
256
+ _require_page()
257
+ captcha_type = (captcha_type or 'auto').strip().lower()
258
+ if captcha_type not in SOLVE_METHODS:
259
+ return f'Unknown captcha_type "{captcha_type}". Use one of: {", ".join(SOLVE_METHODS)}'
260
+ with _capture() as log:
261
+ try:
262
+ if captcha_type == 'auto':
263
+ kind = await S.solver.auto(timeout=timeout)
264
+ result = f'Solved: {kind}' if kind else f'No captcha found within {timeout} s'
265
+ else:
266
+ await getattr(S.solver, SOLVE_METHODS[captcha_type])()
267
+ result = f'Solved: {captcha_type}'
268
+ except CapGuruFatalError as e:
269
+ result = f'Cap.Guru account error (API key or balance): {e}'
270
+ except Exception as e:
271
+ result = f'Not solved: {e}'
272
+ tail = log.buf.getvalue().strip().splitlines()[-15:]
273
+ return result + ('\n\nLog:\n' + '\n'.join(tail) if tail else '')
274
+
275
+
276
+ RECAPTCHA_INFO_JS = """() => {
277
+ const out = { sitekey: '', callback: '', dataS: '' };
278
+ const el = document.querySelector('.g-recaptcha[data-sitekey], [data-sitekey]');
279
+ if (el) {
280
+ out.sitekey = el.getAttribute('data-sitekey') || '';
281
+ out.callback = el.getAttribute('data-callback') || '';
282
+ out.dataS = el.getAttribute('data-s') || '';
283
+ }
284
+ if (!out.sitekey) {
285
+ for (const sc of document.querySelectorAll('script[src*="recaptcha"]')) {
286
+ const m = /[?&]render=([^&]+)/.exec(sc.src);
287
+ if (m && m[1] !== 'explicit') { out.sitekey = decodeURIComponent(m[1]); out.v3 = true; break; }
288
+ }
289
+ }
290
+ if (!out.sitekey) {
291
+ for (const f of document.querySelectorAll('iframe[src*="recaptcha"]')) {
292
+ const m = /[?&]k=([^&]+)/.exec(f.src);
293
+ if (m) { out.sitekey = decodeURIComponent(m[1]); break; }
294
+ }
295
+ }
296
+ return out;
297
+ }"""
298
+
299
+ APPLY_TOKEN_JS = """([token, callback]) => {
300
+ const done = [];
301
+ for (const ta of document.querySelectorAll('textarea[name="g-recaptcha-response"], #g-recaptcha-response')) {
302
+ ta.value = token;
303
+ ta.innerHTML = token;
304
+ done.push('textarea');
305
+ }
306
+ const call = (fn) => { try { fn(token); return true; } catch (e) { return false; } };
307
+ if (callback && typeof window[callback] === 'function' && call(window[callback])) done.push('callback:' + callback);
308
+ else {
309
+ const cfg = window.___grecaptcha_cfg && window.___grecaptcha_cfg.clients;
310
+ const seen = new Set();
311
+ const walk = (o, depth) => {
312
+ if (!o || typeof o !== 'object' || depth > 5 || seen.has(o)) return;
313
+ seen.add(o);
314
+ for (const k of Object.keys(o)) {
315
+ const v = o[k];
316
+ if (k === 'callback') {
317
+ if (typeof v === 'function' && call(v)) done.push('callback');
318
+ else if (typeof v === 'string' && typeof window[v] === 'function' && call(window[v])) done.push('callback:' + v);
319
+ } else walk(v, depth + 1);
320
+ }
321
+ };
322
+ if (cfg) walk(cfg, 0);
323
+ }
324
+ return done.join(', ') || 'nothing found';
325
+ }"""
326
+
327
+
328
+ @tool
329
+ async def solve_recaptcha_token(
330
+ sitekey: str = '',
331
+ pageurl: str = '',
332
+ version: str = 'v2',
333
+ min_score: float = 0.0,
334
+ data_s: str = '',
335
+ user_agent: str = '',
336
+ cookies: str = '',
337
+ proxy: str = '',
338
+ proxytype: str = '',
339
+ apply_to_page: bool = True,
340
+ ) -> str:
341
+ """Get a reCAPTCHA token from Cap.Guru (method userrecaptcha) without solving images.
342
+
343
+ version: v2 (also v2 Invisible) or v3. min_score: for v3 only, required score (service default 0.1).
344
+ sitekey: the k / data-sitekey value (for v3 also the render= value of recaptcha/api.js).
345
+ pageurl: full URL of the page with the captcha.
346
+ If a browser page is open, empty sitekey/pageurl/user_agent are taken from it, and with
347
+ apply_to_page=true the token is put into g-recaptcha-response and the site callback is called (v2).
348
+ For v3 the site requests the token itself, so the token is only returned.
349
+ data_s: data-s value (Google services). cookies, user_agent: passed to the worker.
350
+ proxy: 'login:password@ip:port', proxytype: HTTP | HTTPS | SOCKS4 | SOCKS5.
351
+ """
352
+ page = S.page if (S.page is not None and not S.page.is_closed()) else None
353
+ callback = ''
354
+ if page is not None:
355
+ info = await page.evaluate(RECAPTCHA_INFO_JS)
356
+ sitekey = sitekey or info.get('sitekey', '')
357
+ data_s = data_s or info.get('dataS', '')
358
+ callback = info.get('callback', '')
359
+ pageurl = pageurl or page.url
360
+ if not user_agent:
361
+ user_agent = await page.evaluate('() => navigator.userAgent')
362
+ version = (version or 'v2').strip().lower()
363
+ if version not in ('v2', 'v3'):
364
+ return 'version must be v2 or v3'
365
+ if not sitekey:
366
+ return 'sitekey not found: pass sitekey (the k / data-sitekey value) explicitly'
367
+ if not pageurl:
368
+ return 'pageurl is required when no browser page is open'
369
+ payload = {'method': 'userrecaptcha', 'googlekey': sitekey, 'pageurl': pageurl}
370
+ if version == 'v3':
371
+ payload['version'] = 'v3'
372
+ if min_score and min_score > 0:
373
+ payload['min_score'] = min_score
374
+ for k, v in (('data-s', data_s), ('userAgent', user_agent), ('cookies', cookies),
375
+ ('proxy', proxy), ('proxytype', proxytype)):
376
+ if v:
377
+ payload[k] = v
378
+ client = CapGuruClient(_api_key(), _server_url())
379
+ with _capture():
380
+ try:
381
+ token = await client.click(payload, debug=_debug())
382
+ except CapGuruFatalError as e:
383
+ return f'Cap.Guru account error (API key or balance): {e}'
384
+ if not token or 'ERROR' in str(token).upper():
385
+ return f'Not solved: {token or "timeout"}'
386
+ token = str(token).strip()
387
+ applied = ''
388
+ if apply_to_page and page is not None and version == 'v2':
389
+ applied = await page.evaluate(APPLY_TOKEN_JS, [token, callback])
390
+ lines = [f'Token: {token}', f'version: {version}', f'sitekey: {sitekey}', f'pageurl: {pageurl}']
391
+ if applied:
392
+ lines.append(f'Applied to page: {applied}')
393
+ return '\n'.join(lines)
394
+
395
+
396
+ TURNSTILE_INFO_JS = r"""() => {
397
+ const out = { sitekey: '', callback: '' };
398
+ const el = document.querySelector('.cf-turnstile[data-sitekey]');
399
+ if (el) {
400
+ out.sitekey = el.getAttribute('data-sitekey') || '';
401
+ out.callback = el.getAttribute('data-callback') || '';
402
+ }
403
+ const hooked = (window.__cgTurnstile || []).filter(p => p && p.sitekey);
404
+ if (!out.sitekey && hooked.length) out.sitekey = hooked[hooked.length - 1].sitekey;
405
+ if (!out.sitekey) {
406
+ for (const f of document.querySelectorAll('iframe[src*="challenges.cloudflare.com"]')) {
407
+ const m = /\/([0-3]x[0-9A-Za-z_-]{10,})\//.exec(f.src);
408
+ if (m) { out.sitekey = m[1]; break; }
409
+ }
410
+ }
411
+ if (!out.sitekey) {
412
+ const any = document.querySelector('[data-sitekey^="0x"], [data-sitekey^="1x"], [data-sitekey^="2x"], [data-sitekey^="3x"]');
413
+ if (any) { out.sitekey = any.getAttribute('data-sitekey'); out.callback = out.callback || any.getAttribute('data-callback') || ''; }
414
+ }
415
+ return out;
416
+ }"""
417
+
418
+ APPLY_TURNSTILE_JS = """([token, callback, sitekey]) => {
419
+ const done = [];
420
+ for (const el of document.querySelectorAll('input[name="cf-turnstile-response"], textarea[name="cf-turnstile-response"]')) {
421
+ el.value = token;
422
+ done.push(el.tagName.toLowerCase());
423
+ }
424
+ const call = (fn) => { try { fn(token); return true; } catch (e) { return false; } };
425
+ let called = false;
426
+ if (callback && typeof window[callback] === 'function') called = call(window[callback]) && !!done.push('callback:' + callback);
427
+ if (!called) {
428
+ for (const p of (window.__cgTurnstile || [])) {
429
+ if (sitekey && p.sitekey && p.sitekey !== sitekey) continue;
430
+ const cb = p.callback;
431
+ if (typeof cb === 'function' && call(cb)) { done.push('callback (render)'); called = true; break; }
432
+ if (typeof cb === 'string' && typeof window[cb] === 'function' && call(window[cb])) { done.push('callback:' + cb); called = true; break; }
433
+ }
434
+ }
435
+ return done.join(', ') || 'nothing found';
436
+ }"""
437
+
438
+
439
+ @tool
440
+ async def solve_turnstile_token(sitekey: str = '', pageurl: str = '', apply_to_page: bool = True) -> str:
441
+ """Get a Cloudflare Turnstile token from Cap.Guru (method turnstile).
442
+
443
+ sitekey: the data-sitekey value (e.g. 0x4AAAA... or 1x000...). pageurl: URL of the page with Turnstile.
444
+ If a browser page is open, empty sitekey/pageurl are taken from it, and with apply_to_page=true the
445
+ token is put into cf-turnstile-response and the Turnstile callback is called.
446
+ Typical solve time is 10-30 seconds.
447
+ """
448
+ page = S.page if (S.page is not None and not S.page.is_closed()) else None
449
+ callback = ''
450
+ if page is not None:
451
+ info = await page.evaluate(TURNSTILE_INFO_JS)
452
+ sitekey = sitekey or info.get('sitekey', '')
453
+ callback = info.get('callback', '')
454
+ pageurl = pageurl or page.url
455
+ if not sitekey:
456
+ return 'sitekey not found: pass sitekey (the data-sitekey value) explicitly'
457
+ if not pageurl:
458
+ return 'pageurl is required when no browser page is open'
459
+ payload = {'method': 'turnstile', 'sitekey': sitekey, 'pageurl': pageurl}
460
+ client = CapGuruClient(_api_key(), _server_url())
461
+ with _capture():
462
+ try:
463
+ token = await client.click(payload, debug=_debug())
464
+ except CapGuruFatalError as e:
465
+ return f'Cap.Guru account error (API key or balance): {e}'
466
+ if not token or 'ERROR' in str(token).upper():
467
+ return f'Not solved: {token or "timeout"}'
468
+ token = str(token).strip()
469
+ applied = ''
470
+ if apply_to_page and page is not None:
471
+ applied = await page.evaluate(APPLY_TURNSTILE_JS, [token, callback, sitekey])
472
+ lines = [f'Token: {token}', f'sitekey: {sitekey}', f'pageurl: {pageurl}']
473
+ if applied:
474
+ lines.append(f'Applied to page: {applied}')
475
+ return '\n'.join(lines)
476
+
477
+
478
+ @tool
479
+ async def solve_image_captcha(
480
+ selector: str = '',
481
+ image_base64: str = '',
482
+ image_path: str = '',
483
+ image_url: str = '',
484
+ vernet: int = 0,
485
+ fill_selector: str = '',
486
+ ) -> str:
487
+ """Recognize a text (image) captcha through Cap.Guru (method base64) and return the text.
488
+
489
+ Image source, one of:
490
+ selector: the captcha image element on the open page (screenshot of the element is sent),
491
+ image_base64: image in base64 (a data: URL is also accepted),
492
+ image_path: path to an image file,
493
+ image_url: image URL (downloaded with the page cookies if a page is open).
494
+ vernet: optional captcha kind: 2 General v1, 6 General v2, 22 Arithmetic, 12 Rambler, 18 General Yandex,
495
+ 20 Yandex 6 ch., 16 VKontakte, 14 SolveMedia, 13 Gibdd, 9 Ok.ru, 17 World of Tanks, 5 Microsoft Hotmail.
496
+ fill_selector: if set and a page is open, the recognized text is typed into this input.
497
+ """
498
+ page = S.page if (S.page is not None and not S.page.is_closed()) else None
499
+ body = ''
500
+ if image_base64:
501
+ body = image_base64.split(',', 1)[1] if image_base64.startswith('data:') else image_base64
502
+ elif image_path:
503
+ with open(os.path.expanduser(image_path), 'rb') as f:
504
+ body = base64.b64encode(f.read()).decode()
505
+ elif image_url:
506
+ if page is not None:
507
+ resp = await page.context.request.get(image_url)
508
+ data = await resp.body()
509
+ else:
510
+ import httpx
511
+ async with httpx.AsyncClient(follow_redirects=True, timeout=30) as http:
512
+ data = (await http.get(image_url)).content
513
+ body = base64.b64encode(data).decode()
514
+ elif selector:
515
+ if page is None:
516
+ return 'selector requires an open page (open_browser / connect_browser)'
517
+ el = await page.wait_for_selector(selector, timeout=15000)
518
+ src = await el.get_attribute('src') or ''
519
+ if src.startswith('data:image') and ',' in src:
520
+ body = src.split(',', 1)[1]
521
+ else:
522
+ body = base64.b64encode(await el.screenshot(type='png')).decode()
523
+ else:
524
+ return 'Pass one image source: selector, image_base64, image_path or image_url'
525
+ if not body:
526
+ return 'Image is empty'
527
+ payload = {'method': 'base64', 'body': body}
528
+ if vernet:
529
+ payload['vernet'] = int(vernet)
530
+ client = CapGuruClient(_api_key(), _server_url())
531
+ with _capture():
532
+ try:
533
+ text = await client.click(payload, debug=_debug())
534
+ except CapGuruFatalError as e:
535
+ return f'Cap.Guru account error (API key or balance): {e}'
536
+ if not text or 'ERROR' in str(text).upper():
537
+ return f'Not solved: {text or "timeout"}'
538
+ text = str(text).strip()
539
+ lines = [f'Text: {text}']
540
+ if fill_selector:
541
+ if page is None:
542
+ lines.append('fill_selector ignored: no open page')
543
+ else:
544
+ await page.fill(fill_selector, text, timeout=15000)
545
+ lines.append(f'Filled {fill_selector}')
546
+ return '\n'.join(lines)
547
+
548
+
549
+ @tool
550
+ async def screenshot(full_page: bool = False) -> Image:
551
+ """Take a screenshot of the current page."""
552
+ page = _require_page()
553
+ return Image(data=await page.screenshot(full_page=full_page, type='png'), format='png')
554
+
555
+
556
+ @tool
557
+ async def click(selector: str) -> str:
558
+ """Click an element by CSS selector (or Playwright selector such as text=Sign in)."""
559
+ page = _require_page()
560
+ await page.click(selector, timeout=15000)
561
+ return f'Clicked {selector}'
562
+
563
+
564
+ @tool
565
+ async def fill(selector: str, text: str) -> str:
566
+ """Type text into an input found by selector."""
567
+ page = _require_page()
568
+ await page.fill(selector, text, timeout=15000)
569
+ return f'Filled {selector}'
570
+
571
+
572
+ @tool
573
+ async def page_info() -> str:
574
+ """Return the current URL and title of the page."""
575
+ page = _require_page()
576
+ return f'URL: {page.url}\nTitle: {await page.title()}'
577
+
578
+
579
+ @tool
580
+ async def close_browser() -> str:
581
+ """Close the browser opened by open_browser (or disconnect from a connected one)."""
582
+ await _close()
583
+ return 'Browser closed.'
584
+
585
+
586
+ def main() -> None:
587
+ mcp.run()
588
+
589
+
590
+ if __name__ == '__main__':
591
+ main()
@@ -0,0 +1,183 @@
1
+ Metadata-Version: 2.4
2
+ Name: capguru-mcp
3
+ Version: 0.2.0
4
+ Summary: MCP server for the Cap.Guru captcha solving service: lets AI assistants open pages and solve reCAPTCHA v2/v3, Cloudflare Turnstile, text captchas, hCaptcha, GeeTest, FunCaptcha, TikTok, AWS WAF and Yandex SmartCaptcha
5
+ Author: Cap.Guru
6
+ Project-URL: Homepage, https://cap.guru
7
+ Project-URL: Documentation, https://docs.cap.guru
8
+ Keywords: mcp,captcha,solver,playwright,cap.guru,claude,cursor,turnstile,recaptcha
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ Requires-Dist: mcp>=1.2
14
+ Requires-Dist: capguru>=0.4.0
15
+ Requires-Dist: playwright>=1.40
16
+
17
+ # capguru-mcp
18
+
19
+ **English** | [Русский](#русский)
20
+
21
+ MCP server for the [Cap.Guru](https://cap.guru) captcha solving service.
22
+ It lets AI assistants (Claude Desktop, Claude Code, Cursor and other MCP clients) open web pages in a browser and solve captchas on them.
23
+
24
+ Supported: reCAPTCHA v2/v3, Cloudflare Turnstile, text (image) captchas, hCaptcha, GeeTest v3/v4, FunCaptcha (Arkose Labs), TikTok, AWS WAF, Yandex SmartCaptcha.
25
+
26
+ ## Installation
27
+
28
+ ```
29
+ pip install capguru-mcp
30
+ playwright install chromium
31
+ ```
32
+
33
+ ## Connecting to an MCP client
34
+
35
+ Claude Desktop (`claude_desktop_config.json`), Cursor (`.cursor/mcp.json`) and most other clients:
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "capguru": {
41
+ "command": "capguru-mcp",
42
+ "env": {
43
+ "CAPGURU_API_KEY": "YOUR_KEY"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ ```
49
+
50
+ Claude Code:
51
+
52
+ ```
53
+ claude mcp add capguru --env CAPGURU_API_KEY=YOUR_KEY -- capguru-mcp
54
+ ```
55
+
56
+ Without installing (via [uv](https://docs.astral.sh/uv/)): use `"command": "uvx", "args": ["capguru-mcp"]`.
57
+
58
+ ## Tools
59
+
60
+ | Tool | Description |
61
+ |---|---|
62
+ | `open_browser` | Launch a browser (`headless`, `channel`: `chrome`/`msedge`, `proxy`) |
63
+ | `connect_browser` | Connect to a running Chrome started with `--remote-debugging-port` |
64
+ | `goto` | Open a URL |
65
+ | `click`, `fill` | Click an element, type text into an input |
66
+ | `screenshot` | Screenshot of the page |
67
+ | `page_info` | Current URL and title |
68
+ | `detect_captcha` | Detect the captcha type without solving |
69
+ | `solve_captcha` | Solve the captcha: `auto` or a specific type |
70
+ | `solve_recaptcha_token` | Get a reCAPTCHA v2 / v2 Invisible / v3 token (`sitekey`, `pageurl`, `version`, `min_score`); works without a browser too |
71
+ | `solve_turnstile_token` | Get a Cloudflare Turnstile token (`sitekey`, `pageurl`); works without a browser too |
72
+ | `solve_image_captcha` | Recognize a text captcha from an image: element `selector`, `image_base64`, `image_path` or `image_url`; optional `vernet`, `fill_selector` |
73
+ | `close_browser` | Close the browser |
74
+
75
+ Example requests to the assistant:
76
+
77
+ - *"Open https://site.com/login, solve the captcha and take a screenshot."*
78
+ - *"Get a reCAPTCHA token for sitekey 6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ- on https://www.google.com/recaptcha/api2/demo."*
79
+ - *"Get a reCAPTCHA v3 token for sitekey 6Ldb4ccaAAAAACMxv8uxUIMckZy1og98rs_h3AmO on http://learn.captcha.guru/ln/recap3/."*
80
+ - *"Open https://react-turnstile.vercel.app/basic and solve the Turnstile."*
81
+ - *"Recognize the captcha image #captcha_img and type the answer into #captcha_input."*
82
+
83
+ `solve_recaptcha_token` uses the Cap.Guru token method (`userrecaptcha`): it returns the `g-recaptcha-response` token. If a page is open, the sitekey, URL and User-Agent are taken from it; for v2 the token is inserted into the page and the site callback is called. For v3 pass `version: v3` (optionally `min_score`, service default 0.1); the token is only returned, because v3 sites request it themselves.
84
+
85
+ `solve_turnstile_token` works the same way for Cloudflare Turnstile (`method=turnstile`): with an open page the token is put into `cf-turnstile-response` and the Turnstile callback is called. Typical solve time is 10–30 seconds.
86
+
87
+ ## Settings (environment variables)
88
+
89
+ | Variable | Default | Description |
90
+ |---|---|---|
91
+ | `CAPGURU_API_KEY` | — | Cap.Guru API key (required) |
92
+ | `CAPGURU_SERVER` | `https://api.cap.guru` | API server |
93
+ | `CAPGURU_ATTEMPTS` | `5` | Max number of solving attempts |
94
+ | `CAPGURU_DEBUG` | `1` | Include the solver log in tool results |
95
+ | `CAPGURU_BROWSER_CHANNEL` | — | `chrome` or `msedge` to use an installed browser |
96
+ | `CAPGURU_BROWSER_PATH` | — | Path to a browser executable |
97
+ | `CAPGURU_BROWSER_ARGS` | — | Extra browser arguments, space separated |
98
+
99
+ Full documentation: https://docs.cap.guru
100
+
101
+ ---
102
+
103
+ ## Русский
104
+
105
+ MCP-сервер для сервиса решения капчи [Cap.Guru](https://cap.guru).
106
+ Позволяет ИИ-ассистентам (Claude Desktop, Claude Code, Cursor и другим MCP-клиентам) открывать страницы в браузере и решать на них капчу.
107
+
108
+ Поддерживаются: reCAPTCHA v2/v3, Cloudflare Turnstile, текстовые капчи (картинкой), hCaptcha, GeeTest v3/v4, FunCaptcha (Arkose Labs), TikTok, AWS WAF, Яндекс SmartCaptcha.
109
+
110
+ ### Установка
111
+
112
+ ```
113
+ pip install capguru-mcp
114
+ playwright install chromium
115
+ ```
116
+
117
+ ### Подключение к MCP-клиенту
118
+
119
+ Claude Desktop (`claude_desktop_config.json`), Cursor (`.cursor/mcp.json`) и большинство других клиентов:
120
+
121
+ ```json
122
+ {
123
+ "mcpServers": {
124
+ "capguru": {
125
+ "command": "capguru-mcp",
126
+ "env": {
127
+ "CAPGURU_API_KEY": "ВАШ_КЛЮЧ"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ Claude Code:
135
+
136
+ ```
137
+ claude mcp add capguru --env CAPGURU_API_KEY=ВАШ_КЛЮЧ -- capguru-mcp
138
+ ```
139
+
140
+ Без установки (через [uv](https://docs.astral.sh/uv/)): `"command": "uvx", "args": ["capguru-mcp"]`.
141
+
142
+ ### Инструменты
143
+
144
+ | Инструмент | Описание |
145
+ |---|---|
146
+ | `open_browser` | Запустить браузер (`headless`, `channel`: `chrome`/`msedge`, `proxy`) |
147
+ | `connect_browser` | Подключиться к запущенному Chrome с `--remote-debugging-port` |
148
+ | `goto` | Открыть адрес |
149
+ | `click`, `fill` | Нажать на элемент, ввести текст в поле |
150
+ | `screenshot` | Скриншот страницы |
151
+ | `page_info` | Текущий адрес и заголовок |
152
+ | `detect_captcha` | Определить тип капчи, не решая |
153
+ | `solve_captcha` | Решить капчу: `auto` или конкретный тип |
154
+ | `solve_recaptcha_token` | Получить токен reCAPTCHA v2 / v2 Invisible / v3 (`sitekey`, `pageurl`, `version`, `min_score`); работает и без браузера |
155
+ | `solve_turnstile_token` | Получить токен Cloudflare Turnstile (`sitekey`, `pageurl`); работает и без браузера |
156
+ | `solve_image_captcha` | Распознать текстовую капчу с картинки: элемент `selector`, `image_base64`, `image_path` или `image_url`; по желанию `vernet`, `fill_selector` |
157
+ | `close_browser` | Закрыть браузер |
158
+
159
+ Примеры запросов ассистенту:
160
+
161
+ - *«Открой https://site.com/login, реши капчу и сделай скриншот».*
162
+ - *«Получи токен reCAPTCHA для sitekey 6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ- на https://www.google.com/recaptcha/api2/demo».*
163
+ - *«Получи токен reCAPTCHA v3 для sitekey 6Ldb4ccaAAAAACMxv8uxUIMckZy1og98rs_h3AmO на http://learn.captcha.guru/ln/recap3/».*
164
+ - *«Открой https://react-turnstile.vercel.app/basic и реши Turnstile».*
165
+ - *«Распознай капчу с картинки #captcha_img и введи ответ в #captcha_input».*
166
+
167
+ `solve_recaptcha_token` использует токенный метод Cap.Guru (`userrecaptcha`) и возвращает токен `g-recaptcha-response`. Если страница открыта, sitekey, адрес и User-Agent берутся с неё; для v2 токен вставляется на страницу и вызывается callback сайта. Для v3 укажите `version: v3` (и при необходимости `min_score`, по умолчанию у сервиса 0.1); токен только возвращается, потому что сайты с v3 запрашивают его сами.
168
+
169
+ `solve_turnstile_token` так же работает для Cloudflare Turnstile (`method=turnstile`): если страница открыта, токен вставляется в `cf-turnstile-response` и вызывается callback Turnstile. Обычное время решения — 10–30 секунд.
170
+
171
+ ### Настройки (переменные окружения)
172
+
173
+ | Переменная | По умолчанию | Описание |
174
+ |---|---|---|
175
+ | `CAPGURU_API_KEY` | — | API-ключ Cap.Guru (обязательно) |
176
+ | `CAPGURU_SERVER` | `https://api.cap.guru` | Сервер API |
177
+ | `CAPGURU_ATTEMPTS` | `5` | Макс. количество попыток |
178
+ | `CAPGURU_DEBUG` | `1` | Добавлять лог солвера в ответ инструмента |
179
+ | `CAPGURU_BROWSER_CHANNEL` | — | `chrome` или `msedge`, чтобы использовать установленный браузер |
180
+ | `CAPGURU_BROWSER_PATH` | — | Путь к исполняемому файлу браузера |
181
+ | `CAPGURU_BROWSER_ARGS` | — | Дополнительные аргументы браузера через пробел |
182
+
183
+ Полная документация: https://docs.cap.guru
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ capguru_mcp/__init__.py
4
+ capguru_mcp/__main__.py
5
+ capguru_mcp/server.py
6
+ capguru_mcp.egg-info/PKG-INFO
7
+ capguru_mcp.egg-info/SOURCES.txt
8
+ capguru_mcp.egg-info/dependency_links.txt
9
+ capguru_mcp.egg-info/entry_points.txt
10
+ capguru_mcp.egg-info/requires.txt
11
+ capguru_mcp.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ capguru-mcp = capguru_mcp.server:main
@@ -0,0 +1,3 @@
1
+ mcp>=1.2
2
+ capguru>=0.4.0
3
+ playwright>=1.40
@@ -0,0 +1 @@
1
+ capguru_mcp
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "capguru-mcp"
7
+ version = "0.2.0"
8
+ description = "MCP server for the Cap.Guru captcha solving service: lets AI assistants open pages and solve reCAPTCHA v2/v3, Cloudflare Turnstile, text captchas, hCaptcha, GeeTest, FunCaptcha, TikTok, AWS WAF and Yandex SmartCaptcha"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [{ name = "Cap.Guru" }]
12
+ dependencies = [
13
+ "mcp>=1.2",
14
+ "capguru>=0.4.0",
15
+ "playwright>=1.40",
16
+ ]
17
+ keywords = ["mcp", "captcha", "solver", "playwright", "cap.guru", "claude", "cursor", "turnstile", "recaptcha"]
18
+ classifiers = [
19
+ "Programming Language :: Python :: 3",
20
+ "Operating System :: OS Independent",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://cap.guru"
25
+ Documentation = "https://docs.cap.guru"
26
+
27
+ [project.scripts]
28
+ capguru-mcp = "capguru_mcp.server:main"
29
+
30
+ [tool.setuptools]
31
+ packages = ["capguru_mcp"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+