captchaai-mcp 1.0.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.
- captchaai_mcp-1.0.0/LICENSE +21 -0
- captchaai_mcp-1.0.0/PKG-INFO +152 -0
- captchaai_mcp-1.0.0/README.md +122 -0
- captchaai_mcp-1.0.0/pyproject.toml +80 -0
- captchaai_mcp-1.0.0/pyproject.toml.orig +79 -0
- captchaai_mcp-1.0.0/src/captchaai_mcp/__init__.py +9 -0
- captchaai_mcp-1.0.0/src/captchaai_mcp/__main__.py +53 -0
- captchaai_mcp-1.0.0/src/captchaai_mcp/catalog.py +180 -0
- captchaai_mcp-1.0.0/src/captchaai_mcp/errors.py +38 -0
- captchaai_mcp-1.0.0/src/captchaai_mcp/server.py +193 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 CaptchaAI (captchaai-developers)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: captchaai-mcp
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Official MCP server for CaptchaAI — expose token-mode CAPTCHA solving to AI agents.
|
|
5
|
+
Keywords: mcp,mcp-server,model-context-protocol,fastmcp,ai-agents,llm,captcha,captchaai,captcha-solver,captcha-solving,captcha-bypass,recaptcha,recaptcha-v2,recaptcha-v3,recaptcha-enterprise,cloudflare-turnstile,turnstile-solver,cloudflare-challenge,geetest,captchafox,friendly-captcha,lemin,automation,web-scraping
|
|
6
|
+
Author: captchaai-developers
|
|
7
|
+
Author-email: captchaai-developers <dev@captchaai.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Dist: captchaai>=1.0.3
|
|
22
|
+
Requires-Dist: fastmcp>=4.1,<5
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Project-URL: Homepage, https://captchaai.com/
|
|
25
|
+
Project-URL: Documentation, https://docs.captchaai.com/
|
|
26
|
+
Project-URL: Repository, https://github.com/CaptchaAI/captchaai-mcp
|
|
27
|
+
Project-URL: Issues, https://github.com/CaptchaAI/captchaai-mcp/issues
|
|
28
|
+
Project-URL: Changelog, https://github.com/CaptchaAI/captchaai-mcp/releases
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# CaptchaAI MCP Server
|
|
32
|
+
|
|
33
|
+
Official Model Context Protocol (MCP) server for [CaptchaAI](https://captchaai.com/), built with [FastMCP](https://gofastmcp.com/) and backed by the official [`captchaai`](https://github.com/CaptchaAI/captchaai-python) Python SDK.
|
|
34
|
+
|
|
35
|
+
Exposes **token-mode** (pure API, no browser required) tools so AI assistants and automation agents can inspect account capacity, check supported CAPTCHA types, and solve challenges.
|
|
36
|
+
|
|
37
|
+
- **Website:** https://captchaai.com/
|
|
38
|
+
- **API Documentation:** https://docs.captchaai.com/
|
|
39
|
+
- **MCP Client Integration Guide:** [docs/mcp-integration.md](docs/mcp-integration.md) (Claude Code, OpenAI Codex, Cursor, Claude Desktop, VS Code, Windsurf, Antigravity)
|
|
40
|
+
- **Repository:** https://github.com/CaptchaAI/captchaai-mcp
|
|
41
|
+
|
|
42
|
+
## Tools
|
|
43
|
+
|
|
44
|
+
| Tool | Description | API Key Needed |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `get_supported_captchas` | List the 8 token-mode CAPTCHA types with required/optional arguments and proxy/UA notes | No |
|
|
47
|
+
| `get_threads` | Get account thread usage (`threads`, `working_threads`, `free_threads`) | Yes |
|
|
48
|
+
| `get_balance` | Get account balance string (`balance`) | Yes |
|
|
49
|
+
| `solve_captcha` | Solve a CAPTCHA in token mode and return `task_id`, `solution`, and `user_agent` | Yes |
|
|
50
|
+
|
|
51
|
+
## Supported `captcha_type` Values
|
|
52
|
+
|
|
53
|
+
- `recaptcha_v2` (supports `invisible=True` and `enterprise=True`)
|
|
54
|
+
- `recaptcha_v3` (requires `action`; supports `enterprise=True` and `min_score`)
|
|
55
|
+
- `turnstile`
|
|
56
|
+
- `cloudflare_challenge` (requires `proxy` + `proxytype` (`"HTTP"` or `"HTTPS"`); returns `user_agent`)
|
|
57
|
+
- `geetest` (requires `gt` + `challenge`; returns `{challenge, validate, seccode}`)
|
|
58
|
+
- `captchafox` (requires `proxy` + `proxytype` (`"HTTP"` or `"HTTPS"`); returns `user_agent`)
|
|
59
|
+
- `friendly_captcha`
|
|
60
|
+
- `lemin` (requires `captcha_id` + `div_id`; returns `{answer, challenge_uuid}`)
|
|
61
|
+
|
|
62
|
+
## Installation
|
|
63
|
+
|
|
64
|
+
Install directly from PyPI with `pip` or run via `uvx`:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install captchaai-mcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Or run from a local clone:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
git clone https://github.com/CaptchaAI/captchaai-mcp.git
|
|
74
|
+
cd captchaai-mcp
|
|
75
|
+
uv sync
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Set your API key:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
export CAPTCHAAI_API_KEY="YOUR_32_CHAR_API_KEY"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Running
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# STDIO (default — for Claude Desktop, Cursor, Windsurf, etc.)
|
|
88
|
+
uvx captchaai-mcp
|
|
89
|
+
# or from source:
|
|
90
|
+
uv run captchaai-mcp
|
|
91
|
+
|
|
92
|
+
# Streamable HTTP (serves at http://127.0.0.1:8000/mcp)
|
|
93
|
+
uvx captchaai-mcp --transport streamable-http --host 127.0.0.1 --port 8000
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> [!NOTE]
|
|
97
|
+
> Prefer `CAPTCHAAI_API_KEY` in the environment over `--api-key` on multi-user hosts so the key is not visible in process listings. When using `--transport streamable-http`, bind to loopback or place it behind an authenticating TLS reverse proxy.
|
|
98
|
+
|
|
99
|
+
### MCP Client Configuration
|
|
100
|
+
|
|
101
|
+
See the full **[MCP Client Integration Guide](docs/mcp-integration.md)** for step-by-step setup with **Claude Code**, **OpenAI Codex CLI**, **Cursor**, **Claude Desktop**, **VS Code**, **Windsurf**, and **Antigravity**.
|
|
102
|
+
|
|
103
|
+
Using `uvx` (recommended):
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"captchaai": {
|
|
109
|
+
"command": "uvx",
|
|
110
|
+
"args": ["captchaai-mcp"],
|
|
111
|
+
"env": {
|
|
112
|
+
"CAPTCHAAI_API_KEY": "YOUR_32_CHAR_API_KEY"
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Or from a local checkout:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"mcpServers": {
|
|
124
|
+
"captchaai": {
|
|
125
|
+
"command": "uv",
|
|
126
|
+
"args": [
|
|
127
|
+
"run",
|
|
128
|
+
"--directory",
|
|
129
|
+
"/path/to/captchaai-mcp",
|
|
130
|
+
"captchaai-mcp"
|
|
131
|
+
],
|
|
132
|
+
"env": {
|
|
133
|
+
"CAPTCHAAI_API_KEY": "YOUR_32_CHAR_API_KEY"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Development & Tests
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Run MCP server tests
|
|
144
|
+
uv run pytest -q
|
|
145
|
+
|
|
146
|
+
# Inspect the MCP server manifest
|
|
147
|
+
uv run fastmcp inspect src/captchaai_mcp/server.py:create_server
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# CaptchaAI MCP Server
|
|
2
|
+
|
|
3
|
+
Official Model Context Protocol (MCP) server for [CaptchaAI](https://captchaai.com/), built with [FastMCP](https://gofastmcp.com/) and backed by the official [`captchaai`](https://github.com/CaptchaAI/captchaai-python) Python SDK.
|
|
4
|
+
|
|
5
|
+
Exposes **token-mode** (pure API, no browser required) tools so AI assistants and automation agents can inspect account capacity, check supported CAPTCHA types, and solve challenges.
|
|
6
|
+
|
|
7
|
+
- **Website:** https://captchaai.com/
|
|
8
|
+
- **API Documentation:** https://docs.captchaai.com/
|
|
9
|
+
- **MCP Client Integration Guide:** [docs/mcp-integration.md](docs/mcp-integration.md) (Claude Code, OpenAI Codex, Cursor, Claude Desktop, VS Code, Windsurf, Antigravity)
|
|
10
|
+
- **Repository:** https://github.com/CaptchaAI/captchaai-mcp
|
|
11
|
+
|
|
12
|
+
## Tools
|
|
13
|
+
|
|
14
|
+
| Tool | Description | API Key Needed |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `get_supported_captchas` | List the 8 token-mode CAPTCHA types with required/optional arguments and proxy/UA notes | No |
|
|
17
|
+
| `get_threads` | Get account thread usage (`threads`, `working_threads`, `free_threads`) | Yes |
|
|
18
|
+
| `get_balance` | Get account balance string (`balance`) | Yes |
|
|
19
|
+
| `solve_captcha` | Solve a CAPTCHA in token mode and return `task_id`, `solution`, and `user_agent` | Yes |
|
|
20
|
+
|
|
21
|
+
## Supported `captcha_type` Values
|
|
22
|
+
|
|
23
|
+
- `recaptcha_v2` (supports `invisible=True` and `enterprise=True`)
|
|
24
|
+
- `recaptcha_v3` (requires `action`; supports `enterprise=True` and `min_score`)
|
|
25
|
+
- `turnstile`
|
|
26
|
+
- `cloudflare_challenge` (requires `proxy` + `proxytype` (`"HTTP"` or `"HTTPS"`); returns `user_agent`)
|
|
27
|
+
- `geetest` (requires `gt` + `challenge`; returns `{challenge, validate, seccode}`)
|
|
28
|
+
- `captchafox` (requires `proxy` + `proxytype` (`"HTTP"` or `"HTTPS"`); returns `user_agent`)
|
|
29
|
+
- `friendly_captcha`
|
|
30
|
+
- `lemin` (requires `captcha_id` + `div_id`; returns `{answer, challenge_uuid}`)
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
Install directly from PyPI with `pip` or run via `uvx`:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install captchaai-mcp
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or run from a local clone:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone https://github.com/CaptchaAI/captchaai-mcp.git
|
|
44
|
+
cd captchaai-mcp
|
|
45
|
+
uv sync
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Set your API key:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
export CAPTCHAAI_API_KEY="YOUR_32_CHAR_API_KEY"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Running
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# STDIO (default — for Claude Desktop, Cursor, Windsurf, etc.)
|
|
58
|
+
uvx captchaai-mcp
|
|
59
|
+
# or from source:
|
|
60
|
+
uv run captchaai-mcp
|
|
61
|
+
|
|
62
|
+
# Streamable HTTP (serves at http://127.0.0.1:8000/mcp)
|
|
63
|
+
uvx captchaai-mcp --transport streamable-http --host 127.0.0.1 --port 8000
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
> [!NOTE]
|
|
67
|
+
> Prefer `CAPTCHAAI_API_KEY` in the environment over `--api-key` on multi-user hosts so the key is not visible in process listings. When using `--transport streamable-http`, bind to loopback or place it behind an authenticating TLS reverse proxy.
|
|
68
|
+
|
|
69
|
+
### MCP Client Configuration
|
|
70
|
+
|
|
71
|
+
See the full **[MCP Client Integration Guide](docs/mcp-integration.md)** for step-by-step setup with **Claude Code**, **OpenAI Codex CLI**, **Cursor**, **Claude Desktop**, **VS Code**, **Windsurf**, and **Antigravity**.
|
|
72
|
+
|
|
73
|
+
Using `uvx` (recommended):
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"captchaai": {
|
|
79
|
+
"command": "uvx",
|
|
80
|
+
"args": ["captchaai-mcp"],
|
|
81
|
+
"env": {
|
|
82
|
+
"CAPTCHAAI_API_KEY": "YOUR_32_CHAR_API_KEY"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or from a local checkout:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"mcpServers": {
|
|
94
|
+
"captchaai": {
|
|
95
|
+
"command": "uv",
|
|
96
|
+
"args": [
|
|
97
|
+
"run",
|
|
98
|
+
"--directory",
|
|
99
|
+
"/path/to/captchaai-mcp",
|
|
100
|
+
"captchaai-mcp"
|
|
101
|
+
],
|
|
102
|
+
"env": {
|
|
103
|
+
"CAPTCHAAI_API_KEY": "YOUR_32_CHAR_API_KEY"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Development & Tests
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Run MCP server tests
|
|
114
|
+
uv run pytest -q
|
|
115
|
+
|
|
116
|
+
# Inspect the MCP server manifest
|
|
117
|
+
uv run fastmcp inspect src/captchaai_mcp/server.py:create_server
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT — see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "captchaai-mcp"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Official MCP server for CaptchaAI — expose token-mode CAPTCHA solving to AI agents."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = [
|
|
10
|
+
"captchaai>=1.0.3",
|
|
11
|
+
"fastmcp>=4.1,<5",
|
|
12
|
+
]
|
|
13
|
+
keywords = [
|
|
14
|
+
"mcp",
|
|
15
|
+
"mcp-server",
|
|
16
|
+
"model-context-protocol",
|
|
17
|
+
"fastmcp",
|
|
18
|
+
"ai-agents",
|
|
19
|
+
"llm",
|
|
20
|
+
"captcha",
|
|
21
|
+
"captchaai",
|
|
22
|
+
"captcha-solver",
|
|
23
|
+
"captcha-solving",
|
|
24
|
+
"captcha-bypass",
|
|
25
|
+
"recaptcha",
|
|
26
|
+
"recaptcha-v2",
|
|
27
|
+
"recaptcha-v3",
|
|
28
|
+
"recaptcha-enterprise",
|
|
29
|
+
"cloudflare-turnstile",
|
|
30
|
+
"turnstile-solver",
|
|
31
|
+
"cloudflare-challenge",
|
|
32
|
+
"geetest",
|
|
33
|
+
"captchafox",
|
|
34
|
+
"friendly-captcha",
|
|
35
|
+
"lemin",
|
|
36
|
+
"automation",
|
|
37
|
+
"web-scraping",
|
|
38
|
+
]
|
|
39
|
+
classifiers = [
|
|
40
|
+
"Development Status :: 5 - Production/Stable",
|
|
41
|
+
"Intended Audience :: Developers",
|
|
42
|
+
"Operating System :: OS Independent",
|
|
43
|
+
"Programming Language :: Python :: 3",
|
|
44
|
+
"Programming Language :: Python :: 3.10",
|
|
45
|
+
"Programming Language :: Python :: 3.11",
|
|
46
|
+
"Programming Language :: Python :: 3.12",
|
|
47
|
+
"Programming Language :: Python :: 3.13",
|
|
48
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
49
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
50
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[[project.authors]]
|
|
54
|
+
name = "captchaai-developers"
|
|
55
|
+
email = "dev@captchaai.com"
|
|
56
|
+
|
|
57
|
+
[project.urls]
|
|
58
|
+
Homepage = "https://captchaai.com/"
|
|
59
|
+
Documentation = "https://docs.captchaai.com/"
|
|
60
|
+
Repository = "https://github.com/CaptchaAI/captchaai-mcp"
|
|
61
|
+
Issues = "https://github.com/CaptchaAI/captchaai-mcp/issues"
|
|
62
|
+
Changelog = "https://github.com/CaptchaAI/captchaai-mcp/releases"
|
|
63
|
+
|
|
64
|
+
[project.scripts]
|
|
65
|
+
captchaai-mcp = "captchaai_mcp.__main__:main"
|
|
66
|
+
|
|
67
|
+
[build-system]
|
|
68
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
69
|
+
build-backend = "uv_build"
|
|
70
|
+
|
|
71
|
+
[dependency-groups]
|
|
72
|
+
dev = [
|
|
73
|
+
"pytest>=9.2.0",
|
|
74
|
+
"pytest-asyncio>=1.4.1.post1",
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
[tool.pytest.ini_options]
|
|
78
|
+
asyncio_mode = "auto"
|
|
79
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
80
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "captchaai-mcp"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Official MCP server for CaptchaAI — expose token-mode CAPTCHA solving to AI agents."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "captchaai-developers", email = "dev@captchaai.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"captchaai>=1.0.3",
|
|
14
|
+
"fastmcp>=4.1,<5",
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"mcp",
|
|
18
|
+
"mcp-server",
|
|
19
|
+
"model-context-protocol",
|
|
20
|
+
"fastmcp",
|
|
21
|
+
"ai-agents",
|
|
22
|
+
"llm",
|
|
23
|
+
"captcha",
|
|
24
|
+
"captchaai",
|
|
25
|
+
"captcha-solver",
|
|
26
|
+
"captcha-solving",
|
|
27
|
+
"captcha-bypass",
|
|
28
|
+
"recaptcha",
|
|
29
|
+
"recaptcha-v2",
|
|
30
|
+
"recaptcha-v3",
|
|
31
|
+
"recaptcha-enterprise",
|
|
32
|
+
"cloudflare-turnstile",
|
|
33
|
+
"turnstile-solver",
|
|
34
|
+
"cloudflare-challenge",
|
|
35
|
+
"geetest",
|
|
36
|
+
"captchafox",
|
|
37
|
+
"friendly-captcha",
|
|
38
|
+
"lemin",
|
|
39
|
+
"automation",
|
|
40
|
+
"web-scraping",
|
|
41
|
+
]
|
|
42
|
+
classifiers = [
|
|
43
|
+
"Development Status :: 5 - Production/Stable",
|
|
44
|
+
"Intended Audience :: Developers",
|
|
45
|
+
"Operating System :: OS Independent",
|
|
46
|
+
"Programming Language :: Python :: 3",
|
|
47
|
+
"Programming Language :: Python :: 3.10",
|
|
48
|
+
"Programming Language :: Python :: 3.11",
|
|
49
|
+
"Programming Language :: Python :: 3.12",
|
|
50
|
+
"Programming Language :: Python :: 3.13",
|
|
51
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
52
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
53
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
[project.urls]
|
|
57
|
+
Homepage = "https://captchaai.com/"
|
|
58
|
+
Documentation = "https://docs.captchaai.com/"
|
|
59
|
+
Repository = "https://github.com/CaptchaAI/captchaai-mcp"
|
|
60
|
+
Issues = "https://github.com/CaptchaAI/captchaai-mcp/issues"
|
|
61
|
+
Changelog = "https://github.com/CaptchaAI/captchaai-mcp/releases"
|
|
62
|
+
|
|
63
|
+
[project.scripts]
|
|
64
|
+
captchaai-mcp = "captchaai_mcp.__main__:main"
|
|
65
|
+
|
|
66
|
+
[build-system]
|
|
67
|
+
requires = ["uv_build>=0.13.7,<0.14.0"]
|
|
68
|
+
build-backend = "uv_build"
|
|
69
|
+
|
|
70
|
+
[dependency-groups]
|
|
71
|
+
dev = [
|
|
72
|
+
"pytest>=9.2.0",
|
|
73
|
+
"pytest-asyncio>=1.4.1.post1",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
[tool.pytest.ini_options]
|
|
77
|
+
asyncio_mode = "auto"
|
|
78
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
79
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""CLI entry point for `python -m captchaai_mcp` and the `captchaai-mcp` script."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
|
|
7
|
+
from captchaai_mcp.server import create_server
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main() -> None:
|
|
11
|
+
"""Parse CLI arguments and run the CaptchaAI MCP server."""
|
|
12
|
+
parser = argparse.ArgumentParser(
|
|
13
|
+
description="CaptchaAI MCP Server — expose token-mode captcha solving to AI agents.",
|
|
14
|
+
)
|
|
15
|
+
parser.add_argument(
|
|
16
|
+
"--transport",
|
|
17
|
+
choices=["stdio", "streamable-http"],
|
|
18
|
+
default="stdio",
|
|
19
|
+
help="Transport protocol: stdio (default) or streamable-http.",
|
|
20
|
+
)
|
|
21
|
+
parser.add_argument(
|
|
22
|
+
"--host",
|
|
23
|
+
default="127.0.0.1",
|
|
24
|
+
help="Host to bind for streamable-http transport (default: 127.0.0.1).",
|
|
25
|
+
)
|
|
26
|
+
parser.add_argument(
|
|
27
|
+
"--port",
|
|
28
|
+
type=int,
|
|
29
|
+
default=8000,
|
|
30
|
+
help="Port to bind for streamable-http transport (default: 8000).",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"--api-key",
|
|
34
|
+
default=None,
|
|
35
|
+
help="CaptchaAI API key. Falls back to CAPTCHAAI_API_KEY env var.",
|
|
36
|
+
)
|
|
37
|
+
parser.add_argument(
|
|
38
|
+
"--name",
|
|
39
|
+
default="captchaai",
|
|
40
|
+
help="Server name advertised to MCP clients (default: captchaai).",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
args = parser.parse_args()
|
|
44
|
+
server = create_server(api_key=args.api_key, name=args.name)
|
|
45
|
+
|
|
46
|
+
if args.transport == "stdio":
|
|
47
|
+
server.run()
|
|
48
|
+
else:
|
|
49
|
+
server.run(transport="http", host=args.host, port=args.port)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
main()
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Catalog of supported token-mode CAPTCHA types and SDK call dispatch."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from captchaai import SUPPORTED_PROXY_TYPES, supported_types
|
|
9
|
+
from fastmcp.exceptions import ToolError
|
|
10
|
+
|
|
11
|
+
_COMMON_ARGS = ("cookies", "user_agent", "proxy", "proxytype")
|
|
12
|
+
_PROXY_ONLY_ARGS = ("proxy", "proxytype")
|
|
13
|
+
_ARG_TO_SDK_KWARG = {
|
|
14
|
+
"website_url": "url",
|
|
15
|
+
"website_key": "sitekey",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True)
|
|
20
|
+
class PublicType:
|
|
21
|
+
"""Definition of one public token-mode CAPTCHA type exposed by MCP."""
|
|
22
|
+
|
|
23
|
+
sdk_method: str
|
|
24
|
+
description: str
|
|
25
|
+
registry_names: tuple[str, ...]
|
|
26
|
+
required: tuple[str, ...]
|
|
27
|
+
optional: tuple[str, ...]
|
|
28
|
+
variants: dict[str, str]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
CATALOG: dict[str, PublicType] = {
|
|
32
|
+
"recaptcha_v2": PublicType(
|
|
33
|
+
sdk_method="recaptcha_v2",
|
|
34
|
+
description="Google reCAPTCHA v2 checkbox, invisible, or Enterprise.",
|
|
35
|
+
registry_names=("recaptcha_v2", "recaptcha_v2_invisible", "recaptcha_v2_enterprise"),
|
|
36
|
+
required=("website_url", "website_key"),
|
|
37
|
+
optional=("invisible", "enterprise", "action", *_COMMON_ARGS),
|
|
38
|
+
variants={
|
|
39
|
+
"invisible": "Invisible reCAPTCHA v2 widget",
|
|
40
|
+
"enterprise": "reCAPTCHA v2 Enterprise (returns user_agent to reuse)",
|
|
41
|
+
},
|
|
42
|
+
),
|
|
43
|
+
"recaptcha_v3": PublicType(
|
|
44
|
+
sdk_method="recaptcha_v3",
|
|
45
|
+
description="Google reCAPTCHA v3 score-based challenge (standard or Enterprise).",
|
|
46
|
+
registry_names=("recaptcha_v3", "recaptcha_v3_enterprise"),
|
|
47
|
+
required=("website_url", "website_key", "action"),
|
|
48
|
+
optional=("enterprise", "min_score", *_COMMON_ARGS),
|
|
49
|
+
variants={
|
|
50
|
+
"enterprise": "reCAPTCHA v3 Enterprise (returns user_agent to reuse)",
|
|
51
|
+
},
|
|
52
|
+
),
|
|
53
|
+
"turnstile": PublicType(
|
|
54
|
+
sdk_method="turnstile",
|
|
55
|
+
description="Cloudflare Turnstile widget.",
|
|
56
|
+
registry_names=("turnstile",),
|
|
57
|
+
required=("website_url", "website_key"),
|
|
58
|
+
optional=_COMMON_ARGS,
|
|
59
|
+
variants={},
|
|
60
|
+
),
|
|
61
|
+
"cloudflare_challenge": PublicType(
|
|
62
|
+
sdk_method="cloudflare_challenge",
|
|
63
|
+
description="Cloudflare interstitial challenge page (returns cf_clearance + user_agent).",
|
|
64
|
+
registry_names=("cloudflare_challenge",),
|
|
65
|
+
required=("website_url", "proxy", "proxytype"),
|
|
66
|
+
optional=("cookies", "user_agent"),
|
|
67
|
+
variants={},
|
|
68
|
+
),
|
|
69
|
+
"geetest": PublicType(
|
|
70
|
+
sdk_method="geetest",
|
|
71
|
+
description="GeeTest v3 slide/puzzle challenge (returns dict with challenge/validate/seccode).",
|
|
72
|
+
registry_names=("geetest",),
|
|
73
|
+
required=("website_url", "gt", "challenge"),
|
|
74
|
+
optional=_COMMON_ARGS,
|
|
75
|
+
variants={},
|
|
76
|
+
),
|
|
77
|
+
"captchafox": PublicType(
|
|
78
|
+
sdk_method="captchafox",
|
|
79
|
+
description="CaptchaFox slider challenge (proxy required; returns user_agent to reuse).",
|
|
80
|
+
registry_names=("captchafox",),
|
|
81
|
+
required=("website_url", "website_key", "proxy", "proxytype"),
|
|
82
|
+
optional=("cookies", "user_agent"),
|
|
83
|
+
variants={},
|
|
84
|
+
),
|
|
85
|
+
"friendly_captcha": PublicType(
|
|
86
|
+
sdk_method="friendly_captcha",
|
|
87
|
+
description="Friendly Captcha proof-of-work challenge.",
|
|
88
|
+
registry_names=("friendly_captcha",),
|
|
89
|
+
required=("website_url", "website_key"),
|
|
90
|
+
optional=("version", *_PROXY_ONLY_ARGS),
|
|
91
|
+
variants={},
|
|
92
|
+
),
|
|
93
|
+
"lemin": PublicType(
|
|
94
|
+
sdk_method="lemin",
|
|
95
|
+
description="Lemin puzzle challenge (returns dict with answer and challenge_uuid).",
|
|
96
|
+
registry_names=("lemin",),
|
|
97
|
+
required=("website_url", "captcha_id", "div_id"),
|
|
98
|
+
optional=("api_server", *_PROXY_ONLY_ARGS),
|
|
99
|
+
variants={},
|
|
100
|
+
),
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _is_missing(value: Any) -> bool:
|
|
105
|
+
return value is None or (isinstance(value, str) and not value.strip())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def build_call(captcha_type: str, args: dict[str, Any]) -> tuple[str, dict[str, Any]]:
|
|
109
|
+
"""Validate MCP arguments and build `(sdk_method_name, sdk_kwargs)`.
|
|
110
|
+
|
|
111
|
+
Raises ToolError before any network call if `captcha_type` is unknown,
|
|
112
|
+
required arguments are missing, proxy/proxytype are mismatched, or arguments
|
|
113
|
+
not supported by the chosen type are supplied.
|
|
114
|
+
"""
|
|
115
|
+
spec = CATALOG.get(captcha_type)
|
|
116
|
+
if spec is None:
|
|
117
|
+
supported = ", ".join(CATALOG)
|
|
118
|
+
raise ToolError(f"Unknown captcha_type {captcha_type!r}. Supported: {supported}.")
|
|
119
|
+
|
|
120
|
+
allowed = set(spec.required) | set(spec.optional)
|
|
121
|
+
|
|
122
|
+
# Reject inapplicable arguments rather than silently dropping them, while
|
|
123
|
+
# skipping default False on boolean flags (SDK defaults to False as well).
|
|
124
|
+
provided: dict[str, Any] = {}
|
|
125
|
+
for key, value in args.items():
|
|
126
|
+
if value is None:
|
|
127
|
+
continue
|
|
128
|
+
if key in ("invisible", "enterprise") and value is False:
|
|
129
|
+
continue
|
|
130
|
+
provided[key] = value
|
|
131
|
+
|
|
132
|
+
inapplicable = sorted(key for key in provided if key not in allowed)
|
|
133
|
+
if inapplicable:
|
|
134
|
+
raise ToolError(
|
|
135
|
+
f"Arguments not supported for {captcha_type!r}: {', '.join(inapplicable)}. "
|
|
136
|
+
"Call get_supported_captchas() to see valid parameters."
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
missing = [key for key in spec.required if _is_missing(provided.get(key))]
|
|
140
|
+
if missing:
|
|
141
|
+
raise ToolError(
|
|
142
|
+
f"Missing required arguments for {captcha_type!r}: {', '.join(missing)}. "
|
|
143
|
+
"Call get_supported_captchas() for parameter details."
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
has_proxy = not _is_missing(provided.get("proxy"))
|
|
147
|
+
has_proxytype = not _is_missing(provided.get("proxytype"))
|
|
148
|
+
if has_proxy and not has_proxytype:
|
|
149
|
+
raise ToolError("proxytype is required when proxy is set (HTTP or HTTPS).")
|
|
150
|
+
if has_proxytype and not has_proxy:
|
|
151
|
+
raise ToolError("proxy is required when proxytype is set (format: 'user:pass@host:port').")
|
|
152
|
+
if has_proxytype:
|
|
153
|
+
normalized_proxytype = str(provided["proxytype"]).strip().upper()
|
|
154
|
+
if normalized_proxytype not in SUPPORTED_PROXY_TYPES:
|
|
155
|
+
supported = ", ".join(SUPPORTED_PROXY_TYPES)
|
|
156
|
+
raise ToolError(
|
|
157
|
+
f"Unsupported proxytype {provided['proxytype']!r}. Supported values: {supported}."
|
|
158
|
+
)
|
|
159
|
+
provided["proxytype"] = normalized_proxytype
|
|
160
|
+
|
|
161
|
+
sdk_kwargs = {_ARG_TO_SDK_KWARG.get(key, key): value for key, value in provided.items()}
|
|
162
|
+
return spec.sdk_method, sdk_kwargs
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def describe_captchas() -> dict[str, dict[str, Any]]:
|
|
166
|
+
"""Return per-type metadata combining MCP parameters with live SDK registry facts."""
|
|
167
|
+
sdk_info = supported_types()
|
|
168
|
+
out: dict[str, dict[str, Any]] = {}
|
|
169
|
+
for name, spec in CATALOG.items():
|
|
170
|
+
entries = [sdk_info[reg] for reg in spec.registry_names if reg in sdk_info]
|
|
171
|
+
out[name] = {
|
|
172
|
+
"description": spec.description,
|
|
173
|
+
"required_args": list(spec.required),
|
|
174
|
+
"optional_args": list(spec.optional),
|
|
175
|
+
"variants": dict(spec.variants),
|
|
176
|
+
"requires_proxy": any(item["requires_proxy"] for item in entries),
|
|
177
|
+
"returns_user_agent": any(item["returns_user_agent"] for item in entries),
|
|
178
|
+
"supported_proxy_types": list(SUPPORTED_PROXY_TYPES),
|
|
179
|
+
}
|
|
180
|
+
return out
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Map CaptchaAI SDK exceptions to structured FastMCP ToolError instances."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from captchaai import (
|
|
6
|
+
APIError,
|
|
7
|
+
CaptchaAIError,
|
|
8
|
+
InvalidKeyError,
|
|
9
|
+
NetworkError,
|
|
10
|
+
NoThreadsError,
|
|
11
|
+
ProxyError,
|
|
12
|
+
ThreadLimitError,
|
|
13
|
+
TimeoutError,
|
|
14
|
+
UnsolvableError,
|
|
15
|
+
ValidationError,
|
|
16
|
+
)
|
|
17
|
+
from fastmcp.exceptions import ToolError
|
|
18
|
+
|
|
19
|
+
_HINTS: dict[type[CaptchaAIError], str] = {
|
|
20
|
+
InvalidKeyError: "Check CAPTCHAAI_API_KEY or --api-key.",
|
|
21
|
+
ValidationError: "Fix the arguments; see get_supported_captchas().",
|
|
22
|
+
ThreadLimitError: "All threads busy; check get_threads() and retry later.",
|
|
23
|
+
NoThreadsError: "Account has no threads; check your plan at captchaai.com.",
|
|
24
|
+
UnsolvableError: "Workers could not solve it; verify website_key/website_url and retry.",
|
|
25
|
+
ProxyError: "Proxy failed; check proxy format 'user:pass@host:port' and proxytype.",
|
|
26
|
+
TimeoutError: "Solve took too long; retry.",
|
|
27
|
+
NetworkError: "Could not reach CaptchaAI; retry.",
|
|
28
|
+
APIError: "Unexpected API response.",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_tool_error(exc: CaptchaAIError) -> ToolError:
|
|
33
|
+
"""Convert a CaptchaAI SDK exception into an agent-readable ToolError."""
|
|
34
|
+
hint = next((text for cls, text in _HINTS.items() if isinstance(exc, cls)), "")
|
|
35
|
+
message = f"{type(exc).__name__}: {exc}".rstrip(".")
|
|
36
|
+
if hint:
|
|
37
|
+
message = f"{message}. {hint}"
|
|
38
|
+
return ToolError(message)
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""FastMCP server definition, lazy client holder, and token-mode MCP tools."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
from collections.abc import Awaitable, Callable
|
|
8
|
+
from typing import Any, Literal, TypeVar
|
|
9
|
+
|
|
10
|
+
from captchaai import AsyncCaptchaAI, CaptchaAIError
|
|
11
|
+
from fastmcp import Context, FastMCP
|
|
12
|
+
from fastmcp.exceptions import ToolError
|
|
13
|
+
from fastmcp.server.lifespan import lifespan
|
|
14
|
+
|
|
15
|
+
from captchaai_mcp.catalog import build_call, describe_captchas
|
|
16
|
+
from captchaai_mcp.errors import to_tool_error
|
|
17
|
+
|
|
18
|
+
_T = TypeVar("_T")
|
|
19
|
+
|
|
20
|
+
CaptchaTypeLiteral = Literal[
|
|
21
|
+
"recaptcha_v2",
|
|
22
|
+
"recaptcha_v3",
|
|
23
|
+
"turnstile",
|
|
24
|
+
"cloudflare_challenge",
|
|
25
|
+
"geetest",
|
|
26
|
+
"captchafox",
|
|
27
|
+
"friendly_captcha",
|
|
28
|
+
"lemin",
|
|
29
|
+
]
|
|
30
|
+
ProxyTypeLiteral = Literal["HTTP", "HTTPS"]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ClientHolder:
|
|
34
|
+
"""Lazily builds and caches one AsyncCaptchaAI instance for the server lifetime."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, api_key: str | None) -> None:
|
|
37
|
+
self._api_key = api_key
|
|
38
|
+
self._client: AsyncCaptchaAI | None = None
|
|
39
|
+
self._lock = asyncio.Lock()
|
|
40
|
+
|
|
41
|
+
async def get(self) -> AsyncCaptchaAI:
|
|
42
|
+
"""Return the cached AsyncCaptchaAI client, creating it on first use."""
|
|
43
|
+
async with self._lock:
|
|
44
|
+
if self._client is None:
|
|
45
|
+
if not self._api_key:
|
|
46
|
+
raise ToolError("CAPTCHAAI_API_KEY is not set (or pass --api-key).")
|
|
47
|
+
try:
|
|
48
|
+
self._client = await AsyncCaptchaAI.create(api_key=self._api_key)
|
|
49
|
+
except CaptchaAIError as exc:
|
|
50
|
+
raise to_tool_error(exc) from exc
|
|
51
|
+
return self._client
|
|
52
|
+
|
|
53
|
+
async def aclose(self) -> None:
|
|
54
|
+
"""Close the cached client if it was created."""
|
|
55
|
+
if self._client is not None:
|
|
56
|
+
client = self._client
|
|
57
|
+
self._client = None
|
|
58
|
+
await client.aclose()
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
async def _call(ctx: Context, fn: Callable[[AsyncCaptchaAI], Awaitable[_T]]) -> _T:
|
|
62
|
+
"""Retrieve the shared SDK client, run `fn`, and map SDK errors to ToolError."""
|
|
63
|
+
holder: ClientHolder = ctx.lifespan_context["holder"]
|
|
64
|
+
client = await holder.get()
|
|
65
|
+
try:
|
|
66
|
+
return await fn(client)
|
|
67
|
+
except CaptchaAIError as exc:
|
|
68
|
+
raise to_tool_error(exc) from exc
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def create_server(api_key: str | None = None, name: str = "captchaai") -> FastMCP:
|
|
72
|
+
"""Create and configure the CaptchaAI FastMCP server."""
|
|
73
|
+
import captchaai_mcp
|
|
74
|
+
|
|
75
|
+
resolved_key = api_key if api_key is not None else os.environ.get("CAPTCHAAI_API_KEY")
|
|
76
|
+
holder = ClientHolder(resolved_key)
|
|
77
|
+
|
|
78
|
+
@lifespan
|
|
79
|
+
async def app_lifespan(server: FastMCP):
|
|
80
|
+
try:
|
|
81
|
+
yield {"holder": holder}
|
|
82
|
+
finally:
|
|
83
|
+
await holder.aclose()
|
|
84
|
+
|
|
85
|
+
mcp = FastMCP(
|
|
86
|
+
name,
|
|
87
|
+
version=captchaai_mcp.__version__,
|
|
88
|
+
lifespan=app_lifespan,
|
|
89
|
+
mask_error_details=True,
|
|
90
|
+
instructions=(
|
|
91
|
+
"CaptchaAI token-mode captcha solving. "
|
|
92
|
+
"Call get_supported_captchas() first to inspect required and optional parameters per type."
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
@mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": True})
|
|
97
|
+
async def get_threads(ctx: Context) -> dict[str, int]:
|
|
98
|
+
"""Get CaptchaAI thread usage: total threads, active working_threads, and free_threads."""
|
|
99
|
+
info = await _call(ctx, lambda client: client.threads_info())
|
|
100
|
+
return {
|
|
101
|
+
"threads": info["threads"],
|
|
102
|
+
"working_threads": info["working_threads"],
|
|
103
|
+
"free_threads": info["threads"] - info["working_threads"],
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@mcp.tool(annotations={"readOnlyHint": True, "openWorldHint": True})
|
|
107
|
+
async def get_balance(ctx: Context) -> dict[str, str]:
|
|
108
|
+
"""Get the CaptchaAI account balance as returned by the API."""
|
|
109
|
+
balance = await _call(ctx, lambda client: client.get_balance())
|
|
110
|
+
return {"balance": balance}
|
|
111
|
+
|
|
112
|
+
@mcp.tool(annotations={"readOnlyHint": True, "idempotentHint": True, "openWorldHint": False})
|
|
113
|
+
def get_supported_captchas() -> dict[str, dict[str, Any]]:
|
|
114
|
+
"""List all supported token-mode CAPTCHA types with required/optional arguments and capabilities.
|
|
115
|
+
|
|
116
|
+
Works without an API key.
|
|
117
|
+
"""
|
|
118
|
+
return {"captchas": describe_captchas()}
|
|
119
|
+
|
|
120
|
+
@mcp.tool(annotations={"readOnlyHint": False, "openWorldHint": True})
|
|
121
|
+
async def solve_captcha(
|
|
122
|
+
ctx: Context,
|
|
123
|
+
captcha_type: CaptchaTypeLiteral,
|
|
124
|
+
website_url: str,
|
|
125
|
+
website_key: str | None = None,
|
|
126
|
+
action: str | None = None,
|
|
127
|
+
min_score: float | None = None,
|
|
128
|
+
invisible: bool = False,
|
|
129
|
+
enterprise: bool = False,
|
|
130
|
+
gt: str | None = None,
|
|
131
|
+
challenge: str | None = None,
|
|
132
|
+
captcha_id: str | None = None,
|
|
133
|
+
div_id: str | None = None,
|
|
134
|
+
api_server: str | None = None,
|
|
135
|
+
version: str | None = None,
|
|
136
|
+
cookies: str | None = None,
|
|
137
|
+
user_agent: str | None = None,
|
|
138
|
+
proxy: str | None = None,
|
|
139
|
+
proxytype: ProxyTypeLiteral | None = None,
|
|
140
|
+
) -> dict[str, Any]:
|
|
141
|
+
"""Solve a CAPTCHA in token mode (no browser) and return the solved token or payload.
|
|
142
|
+
|
|
143
|
+
Blocks until solved (typically 10-80s). Call get_supported_captchas() first to check
|
|
144
|
+
which arguments are required for the chosen captcha_type. When `user_agent` is returned
|
|
145
|
+
(Enterprise, Cloudflare Challenge, CaptchaFox), reuse it when submitting the token.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
captcha_type: CAPTCHA challenge type (e.g. "recaptcha_v2", "recaptcha_v3", "turnstile").
|
|
149
|
+
website_url: Full URL of the page containing the CAPTCHA.
|
|
150
|
+
website_key: Site key (data-sitekey) for reCAPTCHA, Turnstile, CaptchaFox, or Friendly Captcha.
|
|
151
|
+
action: Action name (required for recaptcha_v3; optional `sa=` for Enterprise v2).
|
|
152
|
+
min_score: Minimum score threshold (0.0-1.0) for recaptcha_v3.
|
|
153
|
+
invisible: Enable invisible mode (recaptcha_v2 only).
|
|
154
|
+
enterprise: Enable Enterprise mode (recaptcha_v2 or recaptcha_v3).
|
|
155
|
+
gt: GeeTest public `gt` parameter (required for geetest).
|
|
156
|
+
challenge: GeeTest `challenge` parameter (required for geetest).
|
|
157
|
+
captcha_id: Lemin captcha identifier (required for lemin).
|
|
158
|
+
div_id: Lemin target container element ID (required for lemin).
|
|
159
|
+
api_server: Optional Lemin API server URL.
|
|
160
|
+
version: Optional Friendly Captcha version string.
|
|
161
|
+
cookies: Optional cookies forwarded to the solver ("KEY1:Val1;KEY2:Val2;").
|
|
162
|
+
user_agent: Optional User-Agent string forwarded to the solver.
|
|
163
|
+
proxy: Proxy string in "user:pass@host:port" format (required for cloudflare_challenge and captchafox).
|
|
164
|
+
proxytype: Proxy protocol when proxy is set ("HTTP" or "HTTPS").
|
|
165
|
+
"""
|
|
166
|
+
args: dict[str, Any] = {
|
|
167
|
+
"website_url": website_url,
|
|
168
|
+
"website_key": website_key,
|
|
169
|
+
"action": action,
|
|
170
|
+
"min_score": min_score,
|
|
171
|
+
"invisible": invisible,
|
|
172
|
+
"enterprise": enterprise,
|
|
173
|
+
"gt": gt,
|
|
174
|
+
"challenge": challenge,
|
|
175
|
+
"captcha_id": captcha_id,
|
|
176
|
+
"div_id": div_id,
|
|
177
|
+
"api_server": api_server,
|
|
178
|
+
"version": version,
|
|
179
|
+
"cookies": cookies,
|
|
180
|
+
"user_agent": user_agent,
|
|
181
|
+
"proxy": proxy,
|
|
182
|
+
"proxytype": proxytype,
|
|
183
|
+
}
|
|
184
|
+
method_name, sdk_kwargs = build_call(captcha_type, args)
|
|
185
|
+
result = await _call(ctx, lambda client: getattr(client, method_name)(**sdk_kwargs))
|
|
186
|
+
return {
|
|
187
|
+
"captcha_type": captcha_type,
|
|
188
|
+
"task_id": result.task_id,
|
|
189
|
+
"solution": result.solution,
|
|
190
|
+
"user_agent": result.user_agent,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return mcp
|