mcp-hcaptcha 2026.10.8.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.
- mcp_hcaptcha-2026.10.8.0/.env.example +6 -0
- mcp_hcaptcha-2026.10.8.0/.gitignore +2 -0
- mcp_hcaptcha-2026.10.8.0/LICENSE +21 -0
- mcp_hcaptcha-2026.10.8.0/PKG-INFO +112 -0
- mcp_hcaptcha-2026.10.8.0/README.md +56 -0
- mcp_hcaptcha-2026.10.8.0/README.pypi.md +57 -0
- mcp_hcaptcha-2026.10.8.0/core/__init__.py +15 -0
- mcp_hcaptcha-2026.10.8.0/core/client.py +144 -0
- mcp_hcaptcha-2026.10.8.0/core/config.py +53 -0
- mcp_hcaptcha-2026.10.8.0/core/exceptions.py +29 -0
- mcp_hcaptcha-2026.10.8.0/core/oauth.py +416 -0
- mcp_hcaptcha-2026.10.8.0/core/oauth_state.py +149 -0
- mcp_hcaptcha-2026.10.8.0/core/server.py +42 -0
- mcp_hcaptcha-2026.10.8.0/main.py +198 -0
- mcp_hcaptcha-2026.10.8.0/prompts/__init__.py +13 -0
- mcp_hcaptcha-2026.10.8.0/pyproject.toml +66 -0
- mcp_hcaptcha-2026.10.8.0/tests/__init__.py +0 -0
- mcp_hcaptcha-2026.10.8.0/tests/conftest.py +13 -0
- mcp_hcaptcha-2026.10.8.0/tests/test_smoke.py +130 -0
- mcp_hcaptcha-2026.10.8.0/tools/__init__.py +5 -0
- mcp_hcaptcha-2026.10.8.0/tools/captcha_tools.py +80 -0
- mcp_hcaptcha-2026.10.8.0/tools/info_tools.py +27 -0
- mcp_hcaptcha-2026.10.8.0/tools/task_tools.py +32 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 AceDataCloud
|
|
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,112 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcp-hcaptcha
|
|
3
|
+
Version: 2026.10.8.0
|
|
4
|
+
Summary: MCP server for AceDataCloud hCaptcha captcha APIs
|
|
5
|
+
Project-URL: Homepage, https://github.com/AceDataCloud/MCPs
|
|
6
|
+
Project-URL: Documentation, https://platform.acedata.cloud/services/018c653e-4f1b-433f-82f9-732ef2767040?utm_source=pypi&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_package_quick_start
|
|
7
|
+
Project-URL: Repository, https://github.com/AceDataCloud/MCPs
|
|
8
|
+
Project-URL: Issues, https://github.com/AceDataCloud/MCPs/issues
|
|
9
|
+
Author-email: AceDataCloud <support@acedata.cloud>
|
|
10
|
+
Maintainer-email: AceDataCloud <support@acedata.cloud>
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: acedata,captcha,hcaptcha,mcp,model-context-protocol
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Requires-Dist: cryptography>=42.0.0
|
|
26
|
+
Requires-Dist: httpx>=0.27.0
|
|
27
|
+
Requires-Dist: loguru>=0.7.0
|
|
28
|
+
Requires-Dist: mcp<2,>=1.2.0
|
|
29
|
+
Requires-Dist: pydantic>=2.0.0
|
|
30
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
31
|
+
Requires-Dist: redis<7,>=5.0.0
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: build>=1.2.0; extra == 'all'
|
|
34
|
+
Requires-Dist: mypy>=1.10.0; extra == 'all'
|
|
35
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'all'
|
|
36
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'all'
|
|
37
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'all'
|
|
38
|
+
Requires-Dist: pytest>=8.0.0; extra == 'all'
|
|
39
|
+
Requires-Dist: respx>=0.21.0; extra == 'all'
|
|
40
|
+
Requires-Dist: ruff>=0.4.0; extra == 'all'
|
|
41
|
+
Requires-Dist: twine>=5.0.0; extra == 'all'
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: mypy>=1.10.0; extra == 'dev'
|
|
44
|
+
Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
|
|
45
|
+
Requires-Dist: ruff>=0.4.0; extra == 'dev'
|
|
46
|
+
Provides-Extra: release
|
|
47
|
+
Requires-Dist: build>=1.2.0; extra == 'release'
|
|
48
|
+
Requires-Dist: twine>=5.0.0; extra == 'release'
|
|
49
|
+
Provides-Extra: test
|
|
50
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'test'
|
|
51
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'test'
|
|
52
|
+
Requires-Dist: pytest>=8.0.0; extra == 'test'
|
|
53
|
+
Requires-Dist: respx>=0.21.0; extra == 'test'
|
|
54
|
+
Description-Content-Type: text/markdown
|
|
55
|
+
|
|
56
|
+
<!-- Generated by scripts/marketing_readmes.py from README.md; do not edit. -->
|
|
57
|
+
# MCP HCaptcha Server
|
|
58
|
+
|
|
59
|
+
A Model Context Protocol (MCP) server for AceDataCloud's hCaptcha captcha-solving APIs.
|
|
60
|
+
|
|
61
|
+
## Features
|
|
62
|
+
- Solve hCaptcha image challenges and retrieve hCaptcha site tokens.
|
|
63
|
+
- Shared async task polling via `/captcha/tasks`
|
|
64
|
+
- Bearer-token authentication through AceDataCloud
|
|
65
|
+
|
|
66
|
+
## Connect locally
|
|
67
|
+
|
|
68
|
+
Use the local stdio package and an AceDataCloud API token. The public hosted HTTPS endpoint could not be verified, so this README does not offer it as a connection option. The local MCP process still calls AceDataCloud's API; it does not run the underlying service offline.
|
|
69
|
+
|
|
70
|
+
Get an API credential through [AceDataCloud Platform](https://platform.acedata.cloud?utm_source=pypi&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_package_platform), keep it out of committed files, and review current pricing before a billable call.
|
|
71
|
+
|
|
72
|
+
### Local stdio
|
|
73
|
+
|
|
74
|
+
Install the package and give the local process an API token:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
python -m pip install mcp-hcaptcha
|
|
78
|
+
export ACEDATACLOUD_API_TOKEN='YOUR_API_TOKEN'
|
|
79
|
+
mcp-hcaptcha
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For Claude Desktop local MCP, merge this entry into the file opened by its developer settings (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS). `uvx` requires [uv](https://docs.astral.sh/uv/) on `PATH`:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"hcaptcha": {
|
|
88
|
+
"command": "uvx",
|
|
89
|
+
"args": ["mcp-hcaptcha"],
|
|
90
|
+
"env": {"ACEDATACLOUD_API_TOKEN": "YOUR_API_TOKEN"}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Keep this user-level file private. Self-hosted HTTP uses `mcp-hcaptcha --transport http --port 8000`; expose it only with suitable network and TLS controls. Local execution still calls the AceDataCloud API.
|
|
97
|
+
|
|
98
|
+
### Check the connection
|
|
99
|
+
|
|
100
|
+
Confirm that the MCP client loads its tools. `hcaptcha_get_usage_guide` returns reference information; it does not prove downstream API access or balance. A real service request can be billed. If it returns 401, check the API token; if it returns 403 or an account/balance error, read that response before retrying.
|
|
101
|
+
|
|
102
|
+
## Tools
|
|
103
|
+
- `hcaptcha_recognize` — Recognize hCaptcha image challenges
|
|
104
|
+
- `hcaptcha_get_token` — Get an hCaptcha token for a website, optionally with `rqdata`
|
|
105
|
+
- `hcaptcha_get_task` — Poll a captcha task result
|
|
106
|
+
- `hcaptcha_get_usage_guide` — Get hCaptcha usage guide
|
|
107
|
+
- `hcaptcha_get_api_info` — Get hCaptcha API information
|
|
108
|
+
|
|
109
|
+
## Service details
|
|
110
|
+
|
|
111
|
+
<!-- canonical-documentation -->
|
|
112
|
+
[Service details](https://platform.acedata.cloud/services/018c653e-4f1b-433f-82f9-732ef2767040?utm_source=pypi&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_package_quick_start)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# MCP HCaptcha Server
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server for AceDataCloud's hCaptcha captcha-solving APIs.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- Solve hCaptcha image challenges and retrieve hCaptcha site tokens.
|
|
7
|
+
- Shared async task polling via `/captcha/tasks`
|
|
8
|
+
- Bearer-token authentication through AceDataCloud
|
|
9
|
+
|
|
10
|
+
## Connect locally
|
|
11
|
+
|
|
12
|
+
Use the local stdio package and an AceDataCloud API token. The public hosted HTTPS endpoint could not be verified, so this README does not offer it as a connection option. The local MCP process still calls AceDataCloud's API; it does not run the underlying service offline.
|
|
13
|
+
|
|
14
|
+
Get an API credential through [AceDataCloud Platform](https://platform.acedata.cloud?utm_source=github&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_readme_platform), keep it out of committed files, and review current pricing before a billable call.
|
|
15
|
+
|
|
16
|
+
### Local stdio
|
|
17
|
+
|
|
18
|
+
Install the package and give the local process an API token:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
python -m pip install mcp-hcaptcha
|
|
22
|
+
export ACEDATACLOUD_API_TOKEN='YOUR_API_TOKEN'
|
|
23
|
+
mcp-hcaptcha
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
For Claude Desktop local MCP, merge this entry into the file opened by its developer settings (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS). `uvx` requires [uv](https://docs.astral.sh/uv/) on `PATH`:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcpServers": {
|
|
31
|
+
"hcaptcha": {
|
|
32
|
+
"command": "uvx",
|
|
33
|
+
"args": ["mcp-hcaptcha"],
|
|
34
|
+
"env": {"ACEDATACLOUD_API_TOKEN": "YOUR_API_TOKEN"}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Keep this user-level file private. Self-hosted HTTP uses `mcp-hcaptcha --transport http --port 8000`; expose it only with suitable network and TLS controls. Local execution still calls the AceDataCloud API.
|
|
41
|
+
|
|
42
|
+
### Check the connection
|
|
43
|
+
|
|
44
|
+
Confirm that the MCP client loads its tools. `hcaptcha_get_usage_guide` returns reference information; it does not prove downstream API access or balance. A real service request can be billed. If it returns 401, check the API token; if it returns 403 or an account/balance error, read that response before retrying.
|
|
45
|
+
|
|
46
|
+
## Tools
|
|
47
|
+
- `hcaptcha_recognize` — Recognize hCaptcha image challenges
|
|
48
|
+
- `hcaptcha_get_token` — Get an hCaptcha token for a website, optionally with `rqdata`
|
|
49
|
+
- `hcaptcha_get_task` — Poll a captcha task result
|
|
50
|
+
- `hcaptcha_get_usage_guide` — Get hCaptcha usage guide
|
|
51
|
+
- `hcaptcha_get_api_info` — Get hCaptcha API information
|
|
52
|
+
|
|
53
|
+
## Service details
|
|
54
|
+
|
|
55
|
+
<!-- canonical-documentation -->
|
|
56
|
+
[Service details](https://platform.acedata.cloud/services/018c653e-4f1b-433f-82f9-732ef2767040?utm_source=github&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_readme_quick_start)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!-- Generated by scripts/marketing_readmes.py from README.md; do not edit. -->
|
|
2
|
+
# MCP HCaptcha Server
|
|
3
|
+
|
|
4
|
+
A Model Context Protocol (MCP) server for AceDataCloud's hCaptcha captcha-solving APIs.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
- Solve hCaptcha image challenges and retrieve hCaptcha site tokens.
|
|
8
|
+
- Shared async task polling via `/captcha/tasks`
|
|
9
|
+
- Bearer-token authentication through AceDataCloud
|
|
10
|
+
|
|
11
|
+
## Connect locally
|
|
12
|
+
|
|
13
|
+
Use the local stdio package and an AceDataCloud API token. The public hosted HTTPS endpoint could not be verified, so this README does not offer it as a connection option. The local MCP process still calls AceDataCloud's API; it does not run the underlying service offline.
|
|
14
|
+
|
|
15
|
+
Get an API credential through [AceDataCloud Platform](https://platform.acedata.cloud?utm_source=pypi&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_package_platform), keep it out of committed files, and review current pricing before a billable call.
|
|
16
|
+
|
|
17
|
+
### Local stdio
|
|
18
|
+
|
|
19
|
+
Install the package and give the local process an API token:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
python -m pip install mcp-hcaptcha
|
|
23
|
+
export ACEDATACLOUD_API_TOKEN='YOUR_API_TOKEN'
|
|
24
|
+
mcp-hcaptcha
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For Claude Desktop local MCP, merge this entry into the file opened by its developer settings (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS). `uvx` requires [uv](https://docs.astral.sh/uv/) on `PATH`:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"hcaptcha": {
|
|
33
|
+
"command": "uvx",
|
|
34
|
+
"args": ["mcp-hcaptcha"],
|
|
35
|
+
"env": {"ACEDATACLOUD_API_TOKEN": "YOUR_API_TOKEN"}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Keep this user-level file private. Self-hosted HTTP uses `mcp-hcaptcha --transport http --port 8000`; expose it only with suitable network and TLS controls. Local execution still calls the AceDataCloud API.
|
|
42
|
+
|
|
43
|
+
### Check the connection
|
|
44
|
+
|
|
45
|
+
Confirm that the MCP client loads its tools. `hcaptcha_get_usage_guide` returns reference information; it does not prove downstream API access or balance. A real service request can be billed. If it returns 401, check the API token; if it returns 403 or an account/balance error, read that response before retrying.
|
|
46
|
+
|
|
47
|
+
## Tools
|
|
48
|
+
- `hcaptcha_recognize` — Recognize hCaptcha image challenges
|
|
49
|
+
- `hcaptcha_get_token` — Get an hCaptcha token for a website, optionally with `rqdata`
|
|
50
|
+
- `hcaptcha_get_task` — Poll a captcha task result
|
|
51
|
+
- `hcaptcha_get_usage_guide` — Get hCaptcha usage guide
|
|
52
|
+
- `hcaptcha_get_api_info` — Get hCaptcha API information
|
|
53
|
+
|
|
54
|
+
## Service details
|
|
55
|
+
|
|
56
|
+
<!-- canonical-documentation -->
|
|
57
|
+
[Service details](https://platform.acedata.cloud/services/018c653e-4f1b-433f-82f9-732ef2767040?utm_source=pypi&utm_medium=referral&utm_campaign=evergreen&utm_content=hcaptcha_mcp_package_quick_start)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Core module for MCP HCaptcha server."""
|
|
2
|
+
|
|
3
|
+
from core.client import HCaptchaClient
|
|
4
|
+
from core.config import settings
|
|
5
|
+
from core.exceptions import HCaptchaAPIError, HCaptchaAuthError, HCaptchaValidationError
|
|
6
|
+
from core.server import mcp
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"HCaptchaClient",
|
|
10
|
+
"settings",
|
|
11
|
+
"mcp",
|
|
12
|
+
"HCaptchaAPIError",
|
|
13
|
+
"HCaptchaAuthError",
|
|
14
|
+
"HCaptchaValidationError",
|
|
15
|
+
]
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""HTTP client for HCaptcha API."""
|
|
2
|
+
|
|
3
|
+
import contextvars
|
|
4
|
+
import json
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from loguru import logger
|
|
9
|
+
|
|
10
|
+
from core.config import settings
|
|
11
|
+
from core.exceptions import HCaptchaAPIError, HCaptchaAuthError, HCaptchaError, HCaptchaTimeoutError
|
|
12
|
+
|
|
13
|
+
_request_api_token: contextvars.ContextVar[str | None] = contextvars.ContextVar(
|
|
14
|
+
"_request_api_token", default=None
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def set_request_api_token(token: str | None) -> None:
|
|
19
|
+
_request_api_token.set(token)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_request_api_token() -> str | None:
|
|
23
|
+
return _request_api_token.get()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _apply_async_flag(payload: dict[str, Any], async_: bool | None) -> None:
|
|
27
|
+
if async_ is not None:
|
|
28
|
+
payload["async"] = async_
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class HCaptchaClient:
|
|
32
|
+
"""Async HTTP client for the AceDataCloud HCaptcha API."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, api_token: str | None = None, base_url: str | None = None):
|
|
35
|
+
self.api_token = api_token if api_token is not None else settings.api_token
|
|
36
|
+
self.base_url = base_url or settings.api_base_url
|
|
37
|
+
self.timeout = settings.request_timeout
|
|
38
|
+
logger.info(f"HCaptchaClient initialized with base_url: {self.base_url}")
|
|
39
|
+
|
|
40
|
+
def _get_headers(self) -> dict[str, str]:
|
|
41
|
+
token = get_request_api_token() or self.api_token
|
|
42
|
+
if not token:
|
|
43
|
+
raise HCaptchaAuthError("API token not configured")
|
|
44
|
+
return {
|
|
45
|
+
"accept": "application/json",
|
|
46
|
+
"authorization": "******",
|
|
47
|
+
"content-type": "application/json",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
def _handle_error_response(self, response: httpx.Response) -> None:
|
|
51
|
+
status = response.status_code
|
|
52
|
+
try:
|
|
53
|
+
body = response.json()
|
|
54
|
+
except Exception:
|
|
55
|
+
body = {}
|
|
56
|
+
error_obj = body.get("error", {}) if isinstance(body, dict) else {}
|
|
57
|
+
code = error_obj.get("code", f"http_{status}")
|
|
58
|
+
message = (
|
|
59
|
+
error_obj.get("message")
|
|
60
|
+
or (body.get("detail") if isinstance(body, dict) else None)
|
|
61
|
+
or response.text
|
|
62
|
+
or f"HTTP {status}"
|
|
63
|
+
)
|
|
64
|
+
if status in (401, 403):
|
|
65
|
+
raise HCaptchaAuthError(message)
|
|
66
|
+
raise HCaptchaAPIError(message=message, code=code, status_code=status)
|
|
67
|
+
|
|
68
|
+
async def request(
|
|
69
|
+
self,
|
|
70
|
+
method: str,
|
|
71
|
+
endpoint: str,
|
|
72
|
+
*,
|
|
73
|
+
payload: dict[str, Any] | None = None,
|
|
74
|
+
timeout: float | None = None,
|
|
75
|
+
allowed_error_statuses: set[int] | None = None,
|
|
76
|
+
) -> dict[str, Any]:
|
|
77
|
+
method_upper = method.upper()
|
|
78
|
+
url = f"{self.base_url}{endpoint}"
|
|
79
|
+
request_timeout = timeout or self.timeout
|
|
80
|
+
if payload is not None:
|
|
81
|
+
logger.debug(f"Request payload: {json.dumps(payload, ensure_ascii=False, indent=2)}")
|
|
82
|
+
async with httpx.AsyncClient() as http_client:
|
|
83
|
+
try:
|
|
84
|
+
response = await http_client.request(
|
|
85
|
+
method_upper,
|
|
86
|
+
url,
|
|
87
|
+
json=payload,
|
|
88
|
+
headers=self._get_headers(),
|
|
89
|
+
timeout=request_timeout,
|
|
90
|
+
)
|
|
91
|
+
if response.status_code >= 400 and response.status_code not in (
|
|
92
|
+
allowed_error_statuses or set()
|
|
93
|
+
):
|
|
94
|
+
self._handle_error_response(response)
|
|
95
|
+
return response.json() # type: ignore[no-any-return]
|
|
96
|
+
except httpx.TimeoutException as e:
|
|
97
|
+
raise HCaptchaTimeoutError(
|
|
98
|
+
f"Request to {endpoint} timed out after {request_timeout}s"
|
|
99
|
+
) from e
|
|
100
|
+
except HCaptchaError:
|
|
101
|
+
raise
|
|
102
|
+
except Exception as e:
|
|
103
|
+
raise HCaptchaAPIError(message=str(e)) from e
|
|
104
|
+
|
|
105
|
+
async def recognize(
|
|
106
|
+
self,
|
|
107
|
+
queries: list[str] | None = None,
|
|
108
|
+
question: str | None = None,
|
|
109
|
+
async_: bool | None = None,
|
|
110
|
+
) -> dict[str, Any]:
|
|
111
|
+
payload: dict[str, Any] = {}
|
|
112
|
+
if queries is not None:
|
|
113
|
+
payload["queries"] = queries
|
|
114
|
+
if question is not None:
|
|
115
|
+
payload["question"] = question
|
|
116
|
+
_apply_async_flag(payload, async_)
|
|
117
|
+
return await self.request("POST", "/captcha/recognition/hcaptcha", payload=payload)
|
|
118
|
+
|
|
119
|
+
async def get_token(
|
|
120
|
+
self,
|
|
121
|
+
website_key: str,
|
|
122
|
+
website_url: str,
|
|
123
|
+
rqdata: str | None = None,
|
|
124
|
+
proxy: str | None = None,
|
|
125
|
+
async_: bool | None = None,
|
|
126
|
+
) -> dict[str, Any]:
|
|
127
|
+
payload: dict[str, Any] = {"website_key": website_key, "website_url": website_url}
|
|
128
|
+
if rqdata is not None:
|
|
129
|
+
payload["rqdata"] = rqdata
|
|
130
|
+
if proxy is not None:
|
|
131
|
+
payload["proxy"] = proxy
|
|
132
|
+
_apply_async_flag(payload, async_)
|
|
133
|
+
return await self.request("POST", "/captcha/token/hcaptcha", payload=payload)
|
|
134
|
+
|
|
135
|
+
async def get_task(self, task_id: str) -> dict[str, Any]:
|
|
136
|
+
return await self.request(
|
|
137
|
+
"POST",
|
|
138
|
+
"/captcha/tasks",
|
|
139
|
+
payload={"task_id": task_id},
|
|
140
|
+
allowed_error_statuses={504},
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
client = HCaptchaClient()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Configuration management for MCP HCaptcha server."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
9
|
+
_env_path = Path(__file__).parent.parent / ".env"
|
|
10
|
+
load_dotenv(dotenv_path=_env_path)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Settings:
|
|
15
|
+
"""Application settings loaded from environment variables."""
|
|
16
|
+
|
|
17
|
+
api_base_url: str = field(
|
|
18
|
+
default_factory=lambda: os.getenv("ACEDATACLOUD_API_BASE_URL", "https://api.acedata.cloud")
|
|
19
|
+
)
|
|
20
|
+
api_token: str = field(default_factory=lambda: os.getenv("ACEDATACLOUD_API_TOKEN", ""))
|
|
21
|
+
request_timeout: float = field(
|
|
22
|
+
default_factory=lambda: float(os.getenv("HCAPTCHA_REQUEST_TIMEOUT", "120"))
|
|
23
|
+
)
|
|
24
|
+
server_name: str = field(default_factory=lambda: os.getenv("MCP_SERVER_NAME", "hcaptcha"))
|
|
25
|
+
transport: str = field(default_factory=lambda: os.getenv("MCP_TRANSPORT", "stdio"))
|
|
26
|
+
log_level: str = field(default_factory=lambda: os.getenv("LOG_LEVEL", "INFO"))
|
|
27
|
+
server_url: str = field(default_factory=lambda: os.getenv("MCP_SERVER_URL", ""))
|
|
28
|
+
auth_base_url: str = field(
|
|
29
|
+
default_factory=lambda: os.getenv(
|
|
30
|
+
"ACEDATACLOUD_AUTH_BASE_URL", "https://auth.acedata.cloud"
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
platform_base_url: str = field(
|
|
34
|
+
default_factory=lambda: os.getenv(
|
|
35
|
+
"ACEDATACLOUD_PLATFORM_BASE_URL", "https://platform.acedata.cloud"
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
oauth_client_id: str = field(
|
|
39
|
+
default_factory=lambda: os.getenv("ACEDATACLOUD_OAUTH_CLIENT_ID", "")
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def validate(self) -> None:
|
|
43
|
+
if not self.api_token:
|
|
44
|
+
raise ValueError(
|
|
45
|
+
"ACEDATACLOUD_API_TOKEN environment variable is required. Get your token from https://platform.acedata.cloud"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def is_configured(self) -> bool:
|
|
50
|
+
return bool(self.api_token)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
settings = Settings()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Custom exceptions for MCP HCaptcha server."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class HCaptchaError(Exception):
|
|
5
|
+
def __init__(self, message: str, code: str = "unknown"):
|
|
6
|
+
self.message = message
|
|
7
|
+
self.code = code
|
|
8
|
+
super().__init__(self.message)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class HCaptchaAuthError(HCaptchaError):
|
|
12
|
+
def __init__(self, message: str = "Authentication failed"):
|
|
13
|
+
super().__init__(message, code="auth_error")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HCaptchaAPIError(HCaptchaError):
|
|
17
|
+
def __init__(self, message: str, code: str = "api_error", status_code: int | None = None):
|
|
18
|
+
self.status_code = status_code
|
|
19
|
+
super().__init__(message, code)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HCaptchaValidationError(HCaptchaError):
|
|
23
|
+
def __init__(self, message: str):
|
|
24
|
+
super().__init__(message, code="validation_error")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HCaptchaTimeoutError(HCaptchaError):
|
|
28
|
+
def __init__(self, message: str = "Request timed out"):
|
|
29
|
+
super().__init__(message, code="timeout_error")
|