capskip-mcp 1.1.0 → 1.2.0
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.
- package/CHANGELOG.md +40 -0
- package/README.md +22 -8
- package/dist/schemas.js +27 -1
- package/dist/schemas.js.map +1 -1
- package/dist/server.js +9 -2
- package/dist/server.js.map +1 -1
- package/dist/tools/captchafox.js +89 -0
- package/dist/tools/captchafox.js.map +1 -0
- package/dist/tools/capy.js +94 -0
- package/dist/tools/capy.js.map +1 -0
- package/dist/tools/friendlycaptcha.js +113 -0
- package/dist/tools/friendlycaptcha.js.map +1 -0
- package/docs/API_REFERENCE.md +217 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,46 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
10
10
|
Nothing yet. Add entries here as changes land; they move into a version
|
|
11
11
|
section at release time.
|
|
12
12
|
|
|
13
|
+
## [1.2.0] - 2026-09-22
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- **`capskip_solve_capy`** — solve a Capy Puzzle captcha. Unlike every other
|
|
18
|
+
solve tool, the answer is **not a token**: the result carries `captchakey`,
|
|
19
|
+
`challengekey` and `answer`, which all go into the target form's
|
|
20
|
+
`capy_captchakey`, `capy_challengekey` and `capy_answer` fields. The text reply
|
|
21
|
+
names all three, since an agent acting on `code` alone would post a JSON blob
|
|
22
|
+
into a field expecting a drag path. Optional `api_server` and `proxy`.
|
|
23
|
+
- **`capskip_solve_captchafox`** — solve a CaptchaFox challenge. Returns `token`
|
|
24
|
+
for the form's `cf-captcha-response` field, plus `userAgent` when the solve
|
|
25
|
+
reported one: the UA the browser actually minted the token under, which is not
|
|
26
|
+
one the caller sent. Optional `api_server` selects the widget source (the MAM
|
|
27
|
+
package returns a `MAM_` prefixed token) and `proxy`.
|
|
28
|
+
- **`capskip_solve_friendly_captcha`** — solve a Friendly Captcha proof-of-work
|
|
29
|
+
challenge. Takes `version` (`v1`/`v2`) or `module_script` / `nomodule_script`
|
|
30
|
+
so CapSkip can read the version off the build the site loads, plus optional
|
|
31
|
+
`api_server` for the data-residency tenant and `proxy`. The text reply names
|
|
32
|
+
the form field for the version that was actually solved —
|
|
33
|
+
`frc-captcha-solution` on v1, `frc-captcha-response` on v2 — because mixing
|
|
34
|
+
those up is the single most common way a working integration breaks.
|
|
35
|
+
- Output schemas `capyOutput`, `captchaFoxOutput` and `friendlyCaptchaOutput`.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- Requires `capskip` (the Node SDK) `^1.3.0`, which is where the three new solve
|
|
40
|
+
methods live.
|
|
41
|
+
- The server's instructions now list Capy, CaptchaFox and Friendly Captcha among
|
|
42
|
+
the types it can solve, so a client choosing a tool from the listing sees them.
|
|
43
|
+
|
|
44
|
+
### Notes
|
|
45
|
+
|
|
46
|
+
- CaptchaFox and Friendly Captcha use `CAPSKIP_RECAPTCHA_TIMEOUT`: the first is a
|
|
47
|
+
real browser solve, and the second has its difficulty set per request by the
|
|
48
|
+
service (and always solves in a browser on v2). Capy uses
|
|
49
|
+
`CAPSKIP_TIMEOUT` — it is one HTTP fetch plus pixel math, though it still takes
|
|
50
|
+
about two seconds because Capy refuses answers that arrive faster than a human
|
|
51
|
+
could have produced them.
|
|
52
|
+
|
|
13
53
|
## [1.1.0] - 2026-09-12
|
|
14
54
|
|
|
15
55
|
### Added
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://github.com/capskip/capskip-mcp/actions/workflows/ci.yml)
|
|
6
6
|
[](https://www.npmjs.com/package/capskip-mcp)
|
|
7
7
|
|
|
8
|
-
**A captcha solver [MCP](https://modelcontextprotocol.io) server that lets AI agents solve reCAPTCHA, Cloudflare Turnstile, GeeTest, ALTCHA and image captchas instead of stalling on them.**
|
|
8
|
+
**A captcha solver [MCP](https://modelcontextprotocol.io) server that lets AI agents solve reCAPTCHA, Cloudflare Turnstile, GeeTest, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha and image captchas instead of stalling on them.**
|
|
9
9
|
|
|
10
10
|
Works with Claude Desktop, Claude Code, Cursor, VS Code, and any Model Context Protocol client. Powered by [CapSkip](https://capskip.com) — a **local captcha solver** that runs on your own machine, licensed once rather than billed per solve.
|
|
11
11
|
|
|
@@ -17,7 +17,7 @@ npx -y capskip-mcp
|
|
|
17
17
|
|
|
18
18
|
## What this solves
|
|
19
19
|
|
|
20
|
-
An AI agent driving a browser hits a captcha and stops. This server gives it
|
|
20
|
+
An AI agent driving a browser hits a captcha and stops. This server gives it nine tools so it can read the sitekey, solve the challenge, and carry on — without a human stepping in and without a per-solve API bill.
|
|
21
21
|
|
|
22
22
|
CapSkip runs as a desktop app exposing a captcha-solving HTTP API on `127.0.0.1:8080`. `capskip-mcp` is a thin translation layer over that API: the fifth official CapSkip client, alongside the [Python](https://github.com/capskip/capskip-python), [Node.js](https://github.com/capskip/capskip-node), [PHP](https://github.com/capskip/capskip-php) and [.NET](https://github.com/capskip/capskip-dotnet) SDKs.
|
|
23
23
|
|
|
@@ -32,6 +32,9 @@ CapSkip runs as a desktop app exposing a captcha-solving HTTP API on `127.0.0.1:
|
|
|
32
32
|
| **Cloudflare Turnstile solver** | `capskip_solve_turnstile` | Widget and interstitial challenge pages |
|
|
33
33
|
| **GeeTest v3 solver** | `capskip_solve_geetest` | Slide puzzle; returns challenge/validate/seccode |
|
|
34
34
|
| **ALTCHA solver** | `capskip_solve_altcha` | Proof-of-work; returns the token for the `altcha` field |
|
|
35
|
+
| **Capy Puzzle solver** | `capskip_solve_capy` | Slide puzzle; returns three form values, not a token |
|
|
36
|
+
| **CaptchaFox solver** | `capskip_solve_captchafox` | Returns the token for the `cf-captcha-response` field |
|
|
37
|
+
| **Friendly Captcha solver** | `capskip_solve_friendly_captcha` | Proof-of-work; say which version (v1 or v2) |
|
|
35
38
|
| **Image captcha solver** (text/OCR) | `capskip_solve_image_captcha` | File path, URL, data URI, or base64 |
|
|
36
39
|
|
|
37
40
|
**Not supported: hCaptcha and FunCaptcha/Arkose.** There is no tool for them and `capskip_solve_recaptcha` will not work on one. hCaptcha is the easiest to misidentify since it also carries a `data-sitekey` — check for `class="h-captcha"` or a `js.hcaptcha.com` script first.
|
|
@@ -77,7 +80,7 @@ No install step — `npx` fetches and runs it on demand.
|
|
|
77
80
|
|
|
78
81
|
### 3. Restart your client
|
|
79
82
|
|
|
80
|
-
It should list
|
|
83
|
+
It should list nine tools, all prefixed `capskip_`.
|
|
81
84
|
|
|
82
85
|
### 4. Ask your agent to solve a captcha
|
|
83
86
|
|
|
@@ -114,6 +117,9 @@ CapSkip exposes the familiar `in.php` / `res.php` endpoints, so it works as a **
|
|
|
114
117
|
| `capskip_solve_turnstile` | Solve a Cloudflare Turnstile widget or challenge page | `sitekey`, `url` |
|
|
115
118
|
| `capskip_solve_geetest` | Solve a GeeTest v3 slide-puzzle captcha | `gt`, `challenge`, `url` |
|
|
116
119
|
| `capskip_solve_altcha` | Solve an ALTCHA proof-of-work challenge | `url`, and one of `challenge_url` / `challenge_json` |
|
|
120
|
+
| `capskip_solve_capy` | Solve a Capy Puzzle captcha | `sitekey`, `url` |
|
|
121
|
+
| `capskip_solve_captchafox` | Solve a CaptchaFox challenge | `sitekey`, `url` |
|
|
122
|
+
| `capskip_solve_friendly_captcha` | Solve a Friendly Captcha proof-of-work challenge | `sitekey`, `url` |
|
|
117
123
|
|
|
118
124
|
> **There is no `min_score` parameter on `capskip_solve_recaptcha`.** reCAPTCHA v3 scores are assigned by Google from signals no solver has access to — local or cloud, none can raise a score after the fact. A `min_score` option would promise control that does not exist, so it is deliberately left out. Passing it anyway is rejected as an unrecognized key, not silently ignored.
|
|
119
125
|
|
|
@@ -153,10 +159,15 @@ For non-agent scripts, use the language SDKs directly — see the [Playwright](h
|
|
|
153
159
|
| `CAPSKIP_HOST` | `127.0.0.1` | CapSkip host |
|
|
154
160
|
| `CAPSKIP_PORT` | `8080` | API port from CapSkip settings |
|
|
155
161
|
| `CAPSKIP_TIMEOUT` | `120` | Default `timeout` for `capskip_solve_image_captcha`, seconds |
|
|
156
|
-
| `CAPSKIP_RECAPTCHA_TIMEOUT` | `300` | Default `timeout` for the reCAPTCHA / Turnstile / GeeTest tools, seconds |
|
|
162
|
+
| `CAPSKIP_RECAPTCHA_TIMEOUT` | `300` | Default `timeout` for the reCAPTCHA / Turnstile / GeeTest / CaptchaFox / Friendly Captcha tools, seconds |
|
|
157
163
|
|
|
158
|
-
ALTCHA
|
|
159
|
-
proof-of-work measured in milliseconds
|
|
164
|
+
ALTCHA and Capy use `CAPSKIP_DEFAULT_TIMEOUT`, not the reCAPTCHA one — neither
|
|
165
|
+
is a browser solve. ALTCHA is CPU proof-of-work measured in milliseconds; a Capy
|
|
166
|
+
solve is one HTTP fetch plus pixel math, held back to roughly two seconds because
|
|
167
|
+
Capy refuses answers that arrive faster than a human could have produced them.
|
|
168
|
+
|
|
169
|
+
Friendly Captcha is proof-of-work too but takes the longer timeout: the service
|
|
170
|
+
sets the difficulty per request, and v2 always solves in a browser.
|
|
160
171
|
|
|
161
172
|
| `CAPSKIP_POLLING_INTERVAL` | `5` | Max seconds between polls |
|
|
162
173
|
|
|
@@ -186,6 +197,9 @@ Every solve tool returns a human-readable text block and `structuredContent` mat
|
|
|
186
197
|
- **`capskip_solve_turnstile`** adds `userAgent`. Submit the token with this exact User-Agent — Cloudflare rejects a token replayed under a different one.
|
|
187
198
|
- **`capskip_solve_geetest`** adds `challenge`, `validate` and `seccode`, to post back exactly as the site's own front-end would; `code` keeps the raw JSON string CapSkip returns.
|
|
188
199
|
- **`capskip_solve_altcha`** adds `token`, the base64 payload to submit verbatim in the site's form field named `altcha`, and `number`, the counter that solved the challenge; `code` holds the same string as `token`.
|
|
200
|
+
- **`capskip_solve_capy`** adds `captchakey`, `challengekey` and `answer` — **not** a token. All three go into the target form, in fields named `capy_captchakey`, `capy_challengekey` and `capy_answer`; `code` keeps the raw answer as JSON. The `answer` is the drag path the widget would have recorded, so submit it verbatim, and promptly: the challenge key is single-use and short-lived.
|
|
201
|
+
- **`capskip_solve_captchafox`** adds `token`, for the form field named `cf-captcha-response`, and `userAgent` when the solve reported one — the UA the browser minted the token under, not any you sent. Submit under that one.
|
|
202
|
+
- **`capskip_solve_friendly_captcha`** adds `token`. The field it goes into differs by version: `frc-captcha-solution` on v1, `frc-captcha-response` on v2. The text reply names the right one for the version that was solved.
|
|
189
203
|
|
|
190
204
|
Long solves emit MCP progress notifications, so a 45-second reCAPTCHA does not trip your client's tool-call timeout.
|
|
191
205
|
|
|
@@ -228,11 +242,11 @@ The MCP server is MIT-licensed and free. It requires the CapSkip desktop app, wh
|
|
|
228
242
|
|
|
229
243
|
### Which captchas can it solve?
|
|
230
244
|
|
|
231
|
-
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, and image/text captchas. hCaptcha and FunCaptcha/Arkose are not supported.
|
|
245
|
+
reCAPTCHA v2 (checkbox and invisible), reCAPTCHA v3, reCAPTCHA Enterprise, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox, Friendly Captcha, and image/text captchas. hCaptcha and FunCaptcha/Arkose are not supported.
|
|
232
246
|
|
|
233
247
|
### Why does my reCAPTCHA v3 token get a low score?
|
|
234
248
|
|
|
235
|
-
Google assigns v3 scores from signals such as IP reputation and browsing history. A solver returns a valid token, but cannot raise the score. If a site enforces a high threshold, solve from a cleaner IP — a proxy is supported on
|
|
249
|
+
Google assigns v3 scores from signals such as IP reputation and browsing history. A solver returns a valid token, but cannot raise the score. If a site enforces a high threshold, solve from a cleaner IP — a proxy is supported on every solve tool except the image one (for ALTCHA it applies only to the challenge fetch, and for Capy only to the puzzle-image fetch).
|
|
236
250
|
|
|
237
251
|
### Does it need my captcha to be on a public page?
|
|
238
252
|
|
package/dist/schemas.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.statusOutput = exports.altchaOutput = exports.geetestOutput = exports.turnstileOutput = exports.baseSolveOutput = exports.pageUrlSchema = exports.timeoutSchema = exports.proxySchema = void 0;
|
|
3
|
+
exports.statusOutput = exports.friendlyCaptchaOutput = exports.captchaFoxOutput = exports.capyOutput = exports.altchaOutput = exports.geetestOutput = exports.turnstileOutput = exports.baseSolveOutput = exports.pageUrlSchema = exports.timeoutSchema = exports.proxySchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
/**
|
|
6
6
|
* CapSkip maps only these four proxy schemes and answers ERROR_BAD_PARAMETERS
|
|
@@ -48,6 +48,32 @@ exports.altchaOutput = exports.baseSolveOutput.extend({
|
|
|
48
48
|
/** The counter that satisfied the challenge. Absent if the payload did not decode. */
|
|
49
49
|
number: zod_1.z.number().optional(),
|
|
50
50
|
});
|
|
51
|
+
exports.capyOutput = exports.baseSolveOutput.extend({
|
|
52
|
+
/** The value for the target form's `capy_captchakey` field. */
|
|
53
|
+
captchakey: zod_1.z.string().optional(),
|
|
54
|
+
/** The value for the target form's `capy_challengekey` field. Single-use. */
|
|
55
|
+
challengekey: zod_1.z.string().optional(),
|
|
56
|
+
/** The value for the target form's `capy_answer` field — the drag path, verbatim. */
|
|
57
|
+
answer: zod_1.z.string().optional(),
|
|
58
|
+
/** Empty for a puzzle solve; present only for shape compatibility. */
|
|
59
|
+
respKey: zod_1.z.string().optional(),
|
|
60
|
+
});
|
|
61
|
+
exports.captchaFoxOutput = exports.baseSolveOutput.extend({
|
|
62
|
+
/** The token to submit in the site's `cf-captcha-response` field, verbatim. */
|
|
63
|
+
token: zod_1.z.string(),
|
|
64
|
+
/**
|
|
65
|
+
* The User-Agent the token was minted under — the browser's own, not one the
|
|
66
|
+
* caller sent. Absent when the solve captured none rather than guessed.
|
|
67
|
+
*/
|
|
68
|
+
userAgent: zod_1.z.string().optional(),
|
|
69
|
+
});
|
|
70
|
+
exports.friendlyCaptchaOutput = exports.baseSolveOutput.extend({
|
|
71
|
+
/**
|
|
72
|
+
* The token to submit verbatim — in `frc-captcha-solution` on v1, or
|
|
73
|
+
* `frc-captcha-response` on v2. The field names differ between versions.
|
|
74
|
+
*/
|
|
75
|
+
token: zod_1.z.string(),
|
|
76
|
+
});
|
|
51
77
|
exports.statusOutput = zod_1.z.object({
|
|
52
78
|
reachable: zod_1.z.boolean(),
|
|
53
79
|
host: zod_1.z.string(),
|
package/dist/schemas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB;;;GAGG;AACU,QAAA,WAAW,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,OAAC;SACJ,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;SAC5C,QAAQ,CAAC,iDAAiD,CAAC;IAC9D,GAAG,EAAE,OAAC;SACH,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,6DAA6D,CAAC;CAC3E,CAAC,CAAC;AAIH;;;GAGG;AACU,QAAA,aAAa,GAAG,OAAC;KAC3B,MAAM,EAAE;KACR,GAAG,EAAE;KACL,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,QAAQ,EAAE;KACV,QAAQ,CAAC,gDAAgD,CAAC,CAAC;AAEjD,QAAA,aAAa,GAAG,OAAC;KAC3B,GAAG,EAAE;KACL,QAAQ,CAAC,gEAAgE,CAAC,CAAC;AAEjE,QAAA,eAAe,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;CACzB,CAAC,CAAC;AAEU,QAAA,eAAe,GAAG,uBAAe,CAAC,MAAM,CAAC;IACpD,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC;AAEU,QAAA,aAAa,GAAG,uBAAe,CAAC,MAAM,CAAC;IAClD,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC;AAEU,QAAA,YAAY,GAAG,uBAAe,CAAC,MAAM,CAAC;IACjD,gFAAgF;IAChF,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;IACjB,sFAAsF;IACtF,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEU,QAAA,YAAY,GAAG,OAAC,CAAC,MAAM,CAAC;IACnC,SAAS,EAAE,OAAC,CAAC,OAAO,EAAE;IACtB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAC","sourcesContent":["import { z } from 'zod';\n\n/**\n * CapSkip maps only these four proxy schemes and answers ERROR_BAD_PARAMETERS\n * for anything else, SOCKS4 included.\n */\nexport const proxySchema = z.object({\n type: z\n .enum(['HTTP', 'HTTPS', 'SOCKS5', 'SOCKS5H'])\n .describe('Proxy scheme. CapSkip supports only these four.'),\n uri: z\n .string()\n .min(1)\n .describe('Proxy address as `host:port` or `login:password@host:port`.'),\n});\n\nexport type ProxyInput = z.infer<typeof proxySchema>;\n\n/**\n * Bounded at 600s. A larger value is rejected rather than clamped so the caller\n * learns the limit instead of silently getting a shorter wait.\n */\nexport const timeoutSchema = z\n .number()\n .int()\n .min(1)\n .max(600)\n .optional()\n .describe('Seconds to wait before giving up. Maximum 600.');\n\nexport const pageUrlSchema = z\n .url()\n .describe('Full URL of the page the captcha appears on, including scheme.');\n\nexport const baseSolveOutput = z.object({\n captchaId: z.string(),\n code: z.string(),\n solveSeconds: z.number(),\n});\n\nexport const turnstileOutput = baseSolveOutput.extend({\n userAgent: z.string().optional(),\n});\n\nexport const geetestOutput = baseSolveOutput.extend({\n challenge: z.string().optional(),\n validate: z.string().optional(),\n seccode: z.string().optional(),\n});\n\nexport const altchaOutput = baseSolveOutput.extend({\n /** The base64 payload to submit in the site's `altcha` form field, verbatim. */\n token: z.string(),\n /** The counter that satisfied the challenge. Absent if the payload did not decode. */\n number: z.number().optional(),\n});\n\nexport const statusOutput = z.object({\n reachable: z.boolean(),\n host: z.string(),\n port: z.number(),\n latencyMs: z.number().optional(),\n detail: z.string(),\n});\n"]}
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAExB;;;GAGG;AACU,QAAA,WAAW,GAAG,OAAC,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,OAAC;SACJ,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;SAC5C,QAAQ,CAAC,iDAAiD,CAAC;IAC9D,GAAG,EAAE,OAAC;SACH,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CAAC,6DAA6D,CAAC;CAC3E,CAAC,CAAC;AAIH;;;GAGG;AACU,QAAA,aAAa,GAAG,OAAC;KAC3B,MAAM,EAAE;KACR,GAAG,EAAE;KACL,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,QAAQ,EAAE;KACV,QAAQ,CAAC,gDAAgD,CAAC,CAAC;AAEjD,QAAA,aAAa,GAAG,OAAC;KAC3B,GAAG,EAAE;KACL,QAAQ,CAAC,gEAAgE,CAAC,CAAC;AAEjE,QAAA,eAAe,GAAG,OAAC,CAAC,MAAM,CAAC;IACtC,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE;IACrB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE;CACzB,CAAC,CAAC;AAEU,QAAA,eAAe,GAAG,uBAAe,CAAC,MAAM,CAAC;IACpD,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC;AAEU,QAAA,aAAa,GAAG,uBAAe,CAAC,MAAM,CAAC;IAClD,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC/B,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC;AAEU,QAAA,YAAY,GAAG,uBAAe,CAAC,MAAM,CAAC;IACjD,gFAAgF;IAChF,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;IACjB,sFAAsF;IACtF,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC9B,CAAC,CAAC;AAEU,QAAA,UAAU,GAAG,uBAAe,CAAC,MAAM,CAAC;IAC/C,+DAA+D;IAC/D,UAAU,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,6EAA6E;IAC7E,YAAY,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,qFAAqF;IACrF,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC7B,sEAAsE;IACtE,OAAO,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC/B,CAAC,CAAC;AAEU,QAAA,gBAAgB,GAAG,uBAAe,CAAC,MAAM,CAAC;IACrD,+EAA+E;IAC/E,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;IACjB;;;OAGG;IACH,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CACjC,CAAC,CAAC;AAEU,QAAA,qBAAqB,GAAG,uBAAe,CAAC,MAAM,CAAC;IAC1D;;;OAGG;IACH,KAAK,EAAE,OAAC,CAAC,MAAM,EAAE;CAClB,CAAC,CAAC;AAEU,QAAA,YAAY,GAAG,OAAC,CAAC,MAAM,CAAC;IACnC,SAAS,EAAE,OAAC,CAAC,OAAO,EAAE;IACtB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE;IAChB,SAAS,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAChC,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE;CACnB,CAAC,CAAC","sourcesContent":["import { z } from 'zod';\n\n/**\n * CapSkip maps only these four proxy schemes and answers ERROR_BAD_PARAMETERS\n * for anything else, SOCKS4 included.\n */\nexport const proxySchema = z.object({\n type: z\n .enum(['HTTP', 'HTTPS', 'SOCKS5', 'SOCKS5H'])\n .describe('Proxy scheme. CapSkip supports only these four.'),\n uri: z\n .string()\n .min(1)\n .describe('Proxy address as `host:port` or `login:password@host:port`.'),\n});\n\nexport type ProxyInput = z.infer<typeof proxySchema>;\n\n/**\n * Bounded at 600s. A larger value is rejected rather than clamped so the caller\n * learns the limit instead of silently getting a shorter wait.\n */\nexport const timeoutSchema = z\n .number()\n .int()\n .min(1)\n .max(600)\n .optional()\n .describe('Seconds to wait before giving up. Maximum 600.');\n\nexport const pageUrlSchema = z\n .url()\n .describe('Full URL of the page the captcha appears on, including scheme.');\n\nexport const baseSolveOutput = z.object({\n captchaId: z.string(),\n code: z.string(),\n solveSeconds: z.number(),\n});\n\nexport const turnstileOutput = baseSolveOutput.extend({\n userAgent: z.string().optional(),\n});\n\nexport const geetestOutput = baseSolveOutput.extend({\n challenge: z.string().optional(),\n validate: z.string().optional(),\n seccode: z.string().optional(),\n});\n\nexport const altchaOutput = baseSolveOutput.extend({\n /** The base64 payload to submit in the site's `altcha` form field, verbatim. */\n token: z.string(),\n /** The counter that satisfied the challenge. Absent if the payload did not decode. */\n number: z.number().optional(),\n});\n\nexport const capyOutput = baseSolveOutput.extend({\n /** The value for the target form's `capy_captchakey` field. */\n captchakey: z.string().optional(),\n /** The value for the target form's `capy_challengekey` field. Single-use. */\n challengekey: z.string().optional(),\n /** The value for the target form's `capy_answer` field — the drag path, verbatim. */\n answer: z.string().optional(),\n /** Empty for a puzzle solve; present only for shape compatibility. */\n respKey: z.string().optional(),\n});\n\nexport const captchaFoxOutput = baseSolveOutput.extend({\n /** The token to submit in the site's `cf-captcha-response` field, verbatim. */\n token: z.string(),\n /**\n * The User-Agent the token was minted under — the browser's own, not one the\n * caller sent. Absent when the solve captured none rather than guessed.\n */\n userAgent: z.string().optional(),\n});\n\nexport const friendlyCaptchaOutput = baseSolveOutput.extend({\n /**\n * The token to submit verbatim — in `frc-captcha-solution` on v1, or\n * `frc-captcha-response` on v2. The field names differ between versions.\n */\n token: z.string(),\n});\n\nexport const statusOutput = z.object({\n reachable: z.boolean(),\n host: z.string(),\n port: z.number(),\n latencyMs: z.number().optional(),\n detail: z.string(),\n});\n"]}
|
package/dist/server.js
CHANGED
|
@@ -5,13 +5,16 @@ exports.createServer = createServer;
|
|
|
5
5
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6
6
|
const solver_js_1 = require("./solver.js");
|
|
7
7
|
const altcha_js_1 = require("./tools/altcha.js");
|
|
8
|
+
const captchafox_js_1 = require("./tools/captchafox.js");
|
|
9
|
+
const capy_js_1 = require("./tools/capy.js");
|
|
10
|
+
const friendlycaptcha_js_1 = require("./tools/friendlycaptcha.js");
|
|
8
11
|
const geetest_js_1 = require("./tools/geetest.js");
|
|
9
12
|
const image_js_1 = require("./tools/image.js");
|
|
10
13
|
const recaptcha_js_1 = require("./tools/recaptcha.js");
|
|
11
14
|
const status_js_1 = require("./tools/status.js");
|
|
12
15
|
const turnstile_js_1 = require("./tools/turnstile.js");
|
|
13
16
|
exports.SERVER_NAME = 'capskip';
|
|
14
|
-
exports.SERVER_VERSION = '1.
|
|
17
|
+
exports.SERVER_VERSION = '1.2.0';
|
|
15
18
|
/** Build a fully-registered MCP server. Does not connect a transport. */
|
|
16
19
|
function createServer(config) {
|
|
17
20
|
const server = new mcp_js_1.McpServer({ name: exports.SERVER_NAME, version: exports.SERVER_VERSION }, {
|
|
@@ -19,7 +22,8 @@ function createServer(config) {
|
|
|
19
22
|
+ 'tools when browser automation is blocked by a captcha: read the sitekey '
|
|
20
23
|
+ 'from the page, call the matching tool, then place the returned token in '
|
|
21
24
|
+ "the page's response field. CapSkip solves image captchas, reCAPTCHA v2 "
|
|
22
|
-
+ 'and v3, Cloudflare Turnstile, GeeTest v3,
|
|
25
|
+
+ 'and v3, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox '
|
|
26
|
+
+ 'and Friendly Captcha — it cannot solve hCaptcha '
|
|
23
27
|
+ 'or FunCaptcha/Arkose, so do not call these tools for those. An hCaptcha '
|
|
24
28
|
+ 'widget renders as <div class="h-captcha" data-sitekey="…">, which is '
|
|
25
29
|
+ 'easy to mistake for reCAPTCHA when scanning the DOM for data-sitekey. '
|
|
@@ -33,6 +37,9 @@ function createServer(config) {
|
|
|
33
37
|
(0, turnstile_js_1.registerTurnstileTool)(server, ctx);
|
|
34
38
|
(0, geetest_js_1.registerGeetestTool)(server, ctx);
|
|
35
39
|
(0, altcha_js_1.registerAltchaTool)(server, ctx);
|
|
40
|
+
(0, capy_js_1.registerCapyTool)(server, ctx);
|
|
41
|
+
(0, captchafox_js_1.registerCaptchaFoxTool)(server, ctx);
|
|
42
|
+
(0, friendlycaptcha_js_1.registerFriendlyCaptchaTool)(server, ctx);
|
|
36
43
|
return server;
|
|
37
44
|
}
|
|
38
45
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;AAkBA,oCAgCC;AAlDD,oEAAoE;AAGpE,2CAA4C;AAC5C,iDAAuD;AACvD,yDAA+D;AAC/D,6CAAmD;AACnD,mEAAyE;AACzE,mDAAyD;AACzD,+CAAqD;AACrD,uDAA6D;AAC7D,iDAAuD;AACvD,uDAA6D;AAEhD,QAAA,WAAW,GAAG,SAAS,CAAC;AACxB,QAAA,cAAc,GAAG,OAAO,CAAC;AAEtC,yEAAyE;AACzE,SAAgB,YAAY,CAAC,MAAqB;IAChD,MAAM,MAAM,GAAG,IAAI,kBAAS,CAC1B,EAAE,IAAI,EAAE,mBAAW,EAAE,OAAO,EAAE,sBAAc,EAAE,EAC9C;QACE,YAAY,EACV,0EAA0E;cACxE,0EAA0E;cAC1E,0EAA0E;cAC1E,yEAAyE;cACzE,4EAA4E;cAC5E,kDAAkD;cAClD,0EAA0E;cAC1E,uEAAuE;cACvE,wEAAwE;cACxE,yEAAyE;cACzE,UAAU;KACf,CACF,CAAC;IAEF,MAAM,GAAG,GAAG,IAAA,yBAAa,EAAC,MAAM,CAAC,CAAC;IAElC,IAAA,8BAAkB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,IAAA,4BAAiB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAA,oCAAqB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,IAAA,oCAAqB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACnC,IAAA,gCAAmB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,IAAA,8BAAkB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAChC,IAAA,0BAAgB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC9B,IAAA,sCAAsB,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACpC,IAAA,gDAA2B,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEzC,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport type { CapSkipConfig } from './config.js';\nimport { createContext } from './solver.js';\nimport { registerAltchaTool } from './tools/altcha.js';\nimport { registerCaptchaFoxTool } from './tools/captchafox.js';\nimport { registerCapyTool } from './tools/capy.js';\nimport { registerFriendlyCaptchaTool } from './tools/friendlycaptcha.js';\nimport { registerGeetestTool } from './tools/geetest.js';\nimport { registerImageTool } from './tools/image.js';\nimport { registerRecaptchaTool } from './tools/recaptcha.js';\nimport { registerStatusTool } from './tools/status.js';\nimport { registerTurnstileTool } from './tools/turnstile.js';\n\nexport const SERVER_NAME = 'capskip';\nexport const SERVER_VERSION = '1.2.0';\n\n/** Build a fully-registered MCP server. Does not connect a transport. */\nexport function createServer(config: CapSkipConfig): McpServer {\n const server = new McpServer(\n { name: SERVER_NAME, version: SERVER_VERSION },\n {\n instructions:\n 'Solve captchas through a locally-running CapSkip desktop app. Use these '\n + 'tools when browser automation is blocked by a captcha: read the sitekey '\n + 'from the page, call the matching tool, then place the returned token in '\n + \"the page's response field. CapSkip solves image captchas, reCAPTCHA v2 \"\n + 'and v3, Cloudflare Turnstile, GeeTest v3, ALTCHA, Capy Puzzle, CaptchaFox '\n + 'and Friendly Captcha — it cannot solve hCaptcha '\n + 'or FunCaptcha/Arkose, so do not call these tools for those. An hCaptcha '\n + 'widget renders as <div class=\"h-captcha\" data-sitekey=\"…\">, which is '\n + 'easy to mistake for reCAPTCHA when scanning the DOM for data-sitekey. '\n + 'If a solve fails unexpectedly, call capskip_status to check CapSkip is '\n + 'running.',\n },\n );\n\n const ctx = createContext(config);\n\n registerStatusTool(server, ctx);\n registerImageTool(server, ctx);\n registerRecaptchaTool(server, ctx);\n registerTurnstileTool(server, ctx);\n registerGeetestTool(server, ctx);\n registerAltchaTool(server, ctx);\n registerCapyTool(server, ctx);\n registerCaptchaFoxTool(server, ctx);\n registerFriendlyCaptchaTool(server, ctx);\n\n return server;\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCaptchaFoxTool = registerCaptchaFoxTool;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const schemas_js_1 = require("../schemas.js");
|
|
6
|
+
const solve_js_1 = require("../solve.js");
|
|
7
|
+
const inputSchema = zod_1.z.strictObject({
|
|
8
|
+
sitekey: zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe("The public key the widget renders with, conventionally prefixed 'sk_'. "
|
|
12
|
+
+ "Read it from data-sitekey on the widget container, from the "
|
|
13
|
+
+ 'captchafox.render(...) call, or — if the page builds the widget at '
|
|
14
|
+
+ 'runtime — from the path segment after /captcha/ in the request to '
|
|
15
|
+
+ 'api.captchafox.com.'),
|
|
16
|
+
url: schemas_js_1.pageUrlSchema,
|
|
17
|
+
api_server: zod_1.z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe('The widget entry point the target page loads. Defaults to '
|
|
22
|
+
+ "'https://cdn.captchafox.com/'. Send the MAM package path instead when "
|
|
23
|
+
+ 'the page loads that build — it returns a MAM_ prefixed token, and the '
|
|
24
|
+
+ 'two are not interchangeable.'),
|
|
25
|
+
proxy: schemas_js_1.proxySchema
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Solve from this network. CaptchaFox scores the address a widget runs on '
|
|
28
|
+
+ 'as well as the browser, so repeated solves from one address drift '
|
|
29
|
+
+ 'toward interactive challenges and then refusals.'),
|
|
30
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
31
|
+
});
|
|
32
|
+
function registerCaptchaFoxTool(server, ctx) {
|
|
33
|
+
server.registerTool('capskip_solve_captchafox', {
|
|
34
|
+
title: 'Solve CaptchaFox',
|
|
35
|
+
description: 'Solve a CaptchaFox challenge. CaptchaFox scores the browser itself '
|
|
36
|
+
+ 'rather than asking the visitor to read anything, so most solves draw '
|
|
37
|
+
+ 'no puzzle at all. Returns a token to put in the form field named '
|
|
38
|
+
+ "'cf-captcha-response', verbatim — it is verified server side against "
|
|
39
|
+
+ 'the session that produced it, so any edit invalidates it. IMPORTANT: '
|
|
40
|
+
+ 'the url must be the page the widget actually runs on. CaptchaFox '
|
|
41
|
+
+ 'checks it against the domains the key is registered for and refuses a '
|
|
42
|
+
+ 'mismatch permanently, not intermittently — so a sitekey that fails '
|
|
43
|
+
+ 'immediately and consistently usually means the wrong page URL, not a '
|
|
44
|
+
+ 'bad key. The result also carries the User-Agent the token was minted '
|
|
45
|
+
+ 'under; submit under that one, not your own.',
|
|
46
|
+
inputSchema,
|
|
47
|
+
outputSchema: schemas_js_1.captchaFoxOutput,
|
|
48
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
49
|
+
// omitting it would advertise this tool as potentially destructive and cost
|
|
50
|
+
// it auto-approval in clients that read the hint. Solving a captcha
|
|
51
|
+
// destroys nothing. idempotentHint is deliberately left at its false
|
|
52
|
+
// default: each call mints a new, short-lived token.
|
|
53
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
54
|
+
}, async (args, extra) => {
|
|
55
|
+
// A real browser session, like reCAPTCHA and GeeTest, and longer again when
|
|
56
|
+
// an interactive challenge is drawn — so it takes the browser timeout.
|
|
57
|
+
const timeout = args.timeout ?? ctx.config.recaptchaTimeout;
|
|
58
|
+
const options = {};
|
|
59
|
+
if (args.api_server !== undefined)
|
|
60
|
+
options.api_server = args.api_server;
|
|
61
|
+
if (args.proxy !== undefined)
|
|
62
|
+
options.proxy = args.proxy;
|
|
63
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: 'Solving CaptchaFox', timeoutSeconds: timeout }, (client) => client.captchafox(args.sitekey, args.url, options), (result, seconds) => {
|
|
64
|
+
const token = result.token ?? String(result.code ?? '');
|
|
65
|
+
const userAgent = result.userAgent;
|
|
66
|
+
const structured = {
|
|
67
|
+
captchaId: String(result.captchaId ?? ''),
|
|
68
|
+
code: String(result.code ?? ''),
|
|
69
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
70
|
+
token,
|
|
71
|
+
...(userAgent !== undefined ? { userAgent } : {}),
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
structured,
|
|
75
|
+
text: `Solved CaptchaFox in ${structured.solveSeconds}s.\n`
|
|
76
|
+
+ 'Submit this verbatim in the form field the widget uses, named '
|
|
77
|
+
+ '`cf-captcha-response` — treat it as opaque, since it is '
|
|
78
|
+
+ 'verified against the session that produced it:\n'
|
|
79
|
+
+ `cf-captcha-response: ${token}\n`
|
|
80
|
+
+ (userAgent !== undefined
|
|
81
|
+
? `Submit it under this User-Agent, which is the one that minted `
|
|
82
|
+
+ `it (not any you sent): ${userAgent}\n`
|
|
83
|
+
: '')
|
|
84
|
+
+ `Tokens are short lived — submit promptly. (CapSkip captcha id ${structured.captchaId})`,
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=captchafox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"captchafox.js","sourceRoot":"","sources":["../../src/tools/captchafox.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { captchaFoxOutput, pageUrlSchema, proxySchema, timeoutSchema } from '../schemas.js';\nimport { runSolve } from '../solve.js';\nimport type { ToolContext } from '../solver.js';\n\nconst inputSchema = z.strictObject({\n sitekey: z\n .string()\n .min(1)\n .describe(\n \"The public key the widget renders with, conventionally prefixed 'sk_'. \"\n + \"Read it from data-sitekey on the widget container, from the \"\n + 'captchafox.render(...) call, or — if the page builds the widget at '\n + 'runtime — from the path segment after /captcha/ in the request to '\n + 'api.captchafox.com.',\n ),\n url: pageUrlSchema,\n api_server: z\n .string()\n .min(1)\n .optional()\n .describe(\n 'The widget entry point the target page loads. Defaults to '\n + \"'https://cdn.captchafox.com/'. Send the MAM package path instead when \"\n + 'the page loads that build — it returns a MAM_ prefixed token, and the '\n + 'two are not interchangeable.',\n ),\n proxy: proxySchema\n .optional()\n .describe(\n 'Solve from this network. CaptchaFox scores the address a widget runs on '\n + 'as well as the browser, so repeated solves from one address drift '\n + 'toward interactive challenges and then refusals.',\n ),\n timeout: timeoutSchema,\n});\n\nexport function registerCaptchaFoxTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_captchafox',\n {\n title: 'Solve CaptchaFox',\n description:\n 'Solve a CaptchaFox challenge. CaptchaFox scores the browser itself '\n + 'rather than asking the visitor to read anything, so most solves draw '\n + 'no puzzle at all. Returns a token to put in the form field named '\n + \"'cf-captcha-response', verbatim — it is verified server side against \"\n + 'the session that produced it, so any edit invalidates it. IMPORTANT: '\n + 'the url must be the page the widget actually runs on. CaptchaFox '\n + 'checks it against the domains the key is registered for and refuses a '\n + 'mismatch permanently, not intermittently — so a sitekey that fails '\n + 'immediately and consistently usually means the wrong page URL, not a '\n + 'bad key. The result also carries the User-Agent the token was minted '\n + 'under; submit under that one, not your own.',\n inputSchema,\n outputSchema: captchaFoxOutput,\n // destructiveHint defaults to *true* whenever readOnlyHint is false, so\n // omitting it would advertise this tool as potentially destructive and cost\n // it auto-approval in clients that read the hint. Solving a captcha\n // destroys nothing. idempotentHint is deliberately left at its false\n // default: each call mints a new, short-lived token.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args, extra) => {\n // A real browser session, like reCAPTCHA and GeeTest, and longer again when\n // an interactive challenge is drawn — so it takes the browser timeout.\n const timeout = args.timeout ?? ctx.config.recaptchaTimeout;\n\n const options: Record<string, unknown> = {};\n if (args.api_server !== undefined) options.api_server = args.api_server;\n if (args.proxy !== undefined) options.proxy = args.proxy;\n\n return runSolve(\n ctx,\n extra,\n { label: 'Solving CaptchaFox', timeoutSeconds: timeout },\n (client) => client.captchafox(args.sitekey, args.url, options as never),\n (result, seconds) => {\n const token = (result.token as string | undefined) ?? String(result.code ?? '');\n const userAgent = result.userAgent as string | undefined;\n\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n code: String(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n token,\n ...(userAgent !== undefined ? { userAgent } : {}),\n };\n\n return {\n structured,\n text:\n `Solved CaptchaFox in ${structured.solveSeconds}s.\\n`\n + 'Submit this verbatim in the form field the widget uses, named '\n + '`cf-captcha-response` — treat it as opaque, since it is '\n + 'verified against the session that produced it:\\n'\n + `cf-captcha-response: ${token}\\n`\n + (userAgent !== undefined\n ? `Submit it under this User-Agent, which is the one that minted `\n + `it (not any you sent): ${userAgent}\\n`\n : '')\n + `Tokens are short lived — submit promptly. (CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCapyTool = registerCapyTool;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const schemas_js_1 = require("../schemas.js");
|
|
6
|
+
const solve_js_1 = require("../solve.js");
|
|
7
|
+
const inputSchema = zod_1.z.strictObject({
|
|
8
|
+
sitekey: zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe("The site's public Capy key, conventionally prefixed 'PUZZLE_'. Read it "
|
|
12
|
+
+ "from 'capy_captchakey' in the page source, or from the widget script "
|
|
13
|
+
+ "URL: <script src='https://jp.api.capy.me/puzzle/get_js/?k=PUZZLE_XXXX'>."),
|
|
14
|
+
url: schemas_js_1.pageUrlSchema,
|
|
15
|
+
api_server: zod_1.z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.optional()
|
|
19
|
+
.describe('The root of the Capy API the key lives behind, taken from the widget '
|
|
20
|
+
+ "script URL. Defaults to 'https://jp.api.capy.me'. Note that "
|
|
21
|
+
+ "'api.capy.me' no longer resolves, although several solver services "
|
|
22
|
+
+ 'still document it.'),
|
|
23
|
+
proxy: schemas_js_1.proxySchema
|
|
24
|
+
.optional()
|
|
25
|
+
.describe('Draw the puzzle image through this proxy. That fetch is the only request '
|
|
26
|
+
+ 'a Capy solve makes.'),
|
|
27
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
28
|
+
});
|
|
29
|
+
function registerCapyTool(server, ctx) {
|
|
30
|
+
server.registerTool('capskip_solve_capy', {
|
|
31
|
+
title: 'Solve Capy Puzzle',
|
|
32
|
+
description: 'Solve a Capy Puzzle captcha — a slide puzzle where a piece is dragged '
|
|
33
|
+
+ 'into a hole cut out of a photograph. IMPORTANT: unlike every other '
|
|
34
|
+
+ 'captcha tool here, the answer is NOT a single token. It is three '
|
|
35
|
+
+ 'values — captchakey, challengekey and answer — which all go into the '
|
|
36
|
+
+ "target form, in fields named 'capy_captchakey', 'capy_challengekey' "
|
|
37
|
+
+ "and 'capy_answer'. Submit the answer verbatim: it is the drag path "
|
|
38
|
+
+ 'the widget would have recorded, so trimming or re-encoding it '
|
|
39
|
+
+ 'invalidates the solve. The challenge key is single-use and '
|
|
40
|
+
+ 'short-lived, so submit promptly rather than caching the three values. '
|
|
41
|
+
+ 'A solve takes about two seconds — Capy refuses answers that arrive '
|
|
42
|
+
+ 'faster than a human could have produced them, so CapSkip holds the '
|
|
43
|
+
+ 'result back deliberately.',
|
|
44
|
+
inputSchema,
|
|
45
|
+
outputSchema: schemas_js_1.capyOutput,
|
|
46
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
47
|
+
// omitting it would advertise this tool as potentially destructive and cost
|
|
48
|
+
// it auto-approval in clients that read the hint. Solving a captcha
|
|
49
|
+
// destroys nothing. idempotentHint is deliberately left at its false
|
|
50
|
+
// default: each call draws a fresh puzzle bound to a new challenge key.
|
|
51
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
52
|
+
}, async (args, extra) => {
|
|
53
|
+
// Capy is one HTTP fetch plus pixel math, not a browser solve, so it takes
|
|
54
|
+
// the default timeout rather than the longer browser one.
|
|
55
|
+
const timeout = args.timeout ?? ctx.config.defaultTimeout;
|
|
56
|
+
const options = {};
|
|
57
|
+
if (args.api_server !== undefined)
|
|
58
|
+
options.api_server = args.api_server;
|
|
59
|
+
if (args.proxy !== undefined)
|
|
60
|
+
options.proxy = args.proxy;
|
|
61
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: 'Solving Capy Puzzle', timeoutSeconds: timeout }, (client) => client.capy(args.sitekey, args.url, options), (result, seconds) => {
|
|
62
|
+
const captchakey = result.captchakey;
|
|
63
|
+
const challengekey = result.challengekey;
|
|
64
|
+
const answer = result.answer;
|
|
65
|
+
const respKey = result.respKey;
|
|
66
|
+
const structured = {
|
|
67
|
+
captchaId: String(result.captchaId ?? ''),
|
|
68
|
+
// Capy's `code` is the raw answer object, which the Node SDK leaves
|
|
69
|
+
// as-is. Stringify it so the declared string output holds the same
|
|
70
|
+
// JSON a plain-text poll would have delivered.
|
|
71
|
+
code: typeof result.code === 'string'
|
|
72
|
+
? result.code
|
|
73
|
+
: JSON.stringify(result.code ?? ''),
|
|
74
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
75
|
+
...(captchakey !== undefined ? { captchakey } : {}),
|
|
76
|
+
...(challengekey !== undefined ? { challengekey } : {}),
|
|
77
|
+
...(answer !== undefined ? { answer } : {}),
|
|
78
|
+
...(respKey !== undefined ? { respKey } : {}),
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
structured,
|
|
82
|
+
text: `Solved Capy Puzzle in ${structured.solveSeconds}s.\n`
|
|
83
|
+
+ 'This is NOT a token — put all three values in the form, '
|
|
84
|
+
+ 'verbatim, and submit promptly (the challenge key is '
|
|
85
|
+
+ 'single-use and short-lived):\n'
|
|
86
|
+
+ `capy_captchakey: ${captchakey ?? '(missing)'}\n`
|
|
87
|
+
+ `capy_challengekey: ${challengekey ?? '(missing)'}\n`
|
|
88
|
+
+ `capy_answer: ${answer ?? '(missing)'}\n`
|
|
89
|
+
+ `(CapSkip captcha id ${structured.captchaId})`,
|
|
90
|
+
};
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=capy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capy.js","sourceRoot":"","sources":["../../src/tools/capy.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { capyOutput, pageUrlSchema, proxySchema, timeoutSchema } from '../schemas.js';\nimport { runSolve } from '../solve.js';\nimport type { ToolContext } from '../solver.js';\n\nconst inputSchema = z.strictObject({\n sitekey: z\n .string()\n .min(1)\n .describe(\n \"The site's public Capy key, conventionally prefixed 'PUZZLE_'. Read it \"\n + \"from 'capy_captchakey' in the page source, or from the widget script \"\n + \"URL: <script src='https://jp.api.capy.me/puzzle/get_js/?k=PUZZLE_XXXX'>.\",\n ),\n url: pageUrlSchema,\n api_server: z\n .string()\n .min(1)\n .optional()\n .describe(\n 'The root of the Capy API the key lives behind, taken from the widget '\n + \"script URL. Defaults to 'https://jp.api.capy.me'. Note that \"\n + \"'api.capy.me' no longer resolves, although several solver services \"\n + 'still document it.',\n ),\n proxy: proxySchema\n .optional()\n .describe(\n 'Draw the puzzle image through this proxy. That fetch is the only request '\n + 'a Capy solve makes.',\n ),\n timeout: timeoutSchema,\n});\n\nexport function registerCapyTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_capy',\n {\n title: 'Solve Capy Puzzle',\n description:\n 'Solve a Capy Puzzle captcha — a slide puzzle where a piece is dragged '\n + 'into a hole cut out of a photograph. IMPORTANT: unlike every other '\n + 'captcha tool here, the answer is NOT a single token. It is three '\n + 'values — captchakey, challengekey and answer — which all go into the '\n + \"target form, in fields named 'capy_captchakey', 'capy_challengekey' \"\n + \"and 'capy_answer'. Submit the answer verbatim: it is the drag path \"\n + 'the widget would have recorded, so trimming or re-encoding it '\n + 'invalidates the solve. The challenge key is single-use and '\n + 'short-lived, so submit promptly rather than caching the three values. '\n + 'A solve takes about two seconds — Capy refuses answers that arrive '\n + 'faster than a human could have produced them, so CapSkip holds the '\n + 'result back deliberately.',\n inputSchema,\n outputSchema: capyOutput,\n // destructiveHint defaults to *true* whenever readOnlyHint is false, so\n // omitting it would advertise this tool as potentially destructive and cost\n // it auto-approval in clients that read the hint. Solving a captcha\n // destroys nothing. idempotentHint is deliberately left at its false\n // default: each call draws a fresh puzzle bound to a new challenge key.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args, extra) => {\n // Capy is one HTTP fetch plus pixel math, not a browser solve, so it takes\n // the default timeout rather than the longer browser one.\n const timeout = args.timeout ?? ctx.config.defaultTimeout;\n\n const options: Record<string, unknown> = {};\n if (args.api_server !== undefined) options.api_server = args.api_server;\n if (args.proxy !== undefined) options.proxy = args.proxy;\n\n return runSolve(\n ctx,\n extra,\n { label: 'Solving Capy Puzzle', timeoutSeconds: timeout },\n (client) => client.capy(args.sitekey, args.url, options as never),\n (result, seconds) => {\n const captchakey = result.captchakey as string | undefined;\n const challengekey = result.challengekey as string | undefined;\n const answer = result.answer as string | undefined;\n const respKey = result.respKey as string | undefined;\n\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n // Capy's `code` is the raw answer object, which the Node SDK leaves\n // as-is. Stringify it so the declared string output holds the same\n // JSON a plain-text poll would have delivered.\n code: typeof result.code === 'string'\n ? result.code\n : JSON.stringify(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n ...(captchakey !== undefined ? { captchakey } : {}),\n ...(challengekey !== undefined ? { challengekey } : {}),\n ...(answer !== undefined ? { answer } : {}),\n ...(respKey !== undefined ? { respKey } : {}),\n };\n\n return {\n structured,\n text:\n `Solved Capy Puzzle in ${structured.solveSeconds}s.\\n`\n + 'This is NOT a token — put all three values in the form, '\n + 'verbatim, and submit promptly (the challenge key is '\n + 'single-use and short-lived):\\n'\n + `capy_captchakey: ${captchakey ?? '(missing)'}\\n`\n + `capy_challengekey: ${challengekey ?? '(missing)'}\\n`\n + `capy_answer: ${answer ?? '(missing)'}\\n`\n + `(CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerFriendlyCaptchaTool = registerFriendlyCaptchaTool;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const schemas_js_1 = require("../schemas.js");
|
|
6
|
+
const solve_js_1 = require("../solve.js");
|
|
7
|
+
const inputSchema = zod_1.z.strictObject({
|
|
8
|
+
sitekey: zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe("The data-sitekey attribute of the widget element — the one carrying "
|
|
12
|
+
+ "class='frc-captcha'."),
|
|
13
|
+
url: schemas_js_1.pageUrlSchema,
|
|
14
|
+
version: zod_1.z
|
|
15
|
+
.enum(['v1', 'v2'])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe('The protocol version the site uses. Two entirely different protocols '
|
|
18
|
+
+ 'ship under this one name and a sitekey does not say which, so send '
|
|
19
|
+
+ 'this or module_script. Defaults to v1 when neither is given.'),
|
|
20
|
+
module_script: zod_1.z
|
|
21
|
+
.string()
|
|
22
|
+
.min(1)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("The src of the widget script tag carrying type='module'. The most "
|
|
25
|
+
+ 'reliable version signal there is, because it is the build the site '
|
|
26
|
+
+ "actually loads: 'widget.module.min.js' means v1, 'site.min.js' v2."),
|
|
27
|
+
nomodule_script: zod_1.z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("The src of the widget script tag carrying 'nomodule'. Read for the same reason."),
|
|
32
|
+
api_server: zod_1.z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("The data-residency tenant the sitekey belongs to: 'global' (the "
|
|
37
|
+
+ "default), 'eu', or a full URL. Both tenants mint a token for the same "
|
|
38
|
+
+ "sitekey, so the wrong one is only caught by the site's own check."),
|
|
39
|
+
proxy: schemas_js_1.proxySchema
|
|
40
|
+
.optional()
|
|
41
|
+
.describe('Solve from this network. Worth setting sooner here than almost anywhere '
|
|
42
|
+
+ 'else: the service raises the difficulty for addresses it has already '
|
|
43
|
+
+ 'seen a lot of.'),
|
|
44
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
45
|
+
});
|
|
46
|
+
function registerFriendlyCaptchaTool(server, ctx) {
|
|
47
|
+
server.registerTool('capskip_solve_friendly_captcha', {
|
|
48
|
+
title: 'Solve Friendly Captcha',
|
|
49
|
+
description: 'Solve a Friendly Captcha proof-of-work challenge. Nothing is shown on '
|
|
50
|
+
+ 'screen — the widget does arithmetic instead of asking the visitor to '
|
|
51
|
+
+ 'do anything. IMPORTANT: two entirely different protocols ship under '
|
|
52
|
+
+ 'this one name, sharing a brand and a sitekey namespace and nothing '
|
|
53
|
+
+ 'else, and a sitekey does not tell you which a site uses. Send version '
|
|
54
|
+
+ '(v1 or v2), or send module_script and let CapSkip read the version off '
|
|
55
|
+
+ 'the build the site loads. Solving the wrong one returns a well-formed '
|
|
56
|
+
+ 'token the site rejects, with nothing to indicate the version was the '
|
|
57
|
+
+ 'problem. The form field also differs by version: v1 uses '
|
|
58
|
+
+ "'frc-captcha-solution', v2 uses 'frc-captcha-response'. Solve time is "
|
|
59
|
+
+ 'not a constant — the service sets the difficulty per request — so '
|
|
60
|
+
+ 'budget for that rather than assuming a fixed duration.',
|
|
61
|
+
inputSchema,
|
|
62
|
+
outputSchema: schemas_js_1.friendlyCaptchaOutput,
|
|
63
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
64
|
+
// omitting it would advertise this tool as potentially destructive and cost
|
|
65
|
+
// it auto-approval in clients that read the hint. Solving a captcha
|
|
66
|
+
// destroys nothing. idempotentHint is deliberately left at its false
|
|
67
|
+
// default: each call mints a new token against fresh work.
|
|
68
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
69
|
+
}, async (args, extra) => {
|
|
70
|
+
// Proof-of-work, but not the millisecond kind ALTCHA does: the difficulty
|
|
71
|
+
// is set per request, and v2 always solves in a browser. Both make solve
|
|
72
|
+
// time variable enough to want the longer timeout.
|
|
73
|
+
const timeout = args.timeout ?? ctx.config.recaptchaTimeout;
|
|
74
|
+
const options = {};
|
|
75
|
+
if (args.version !== undefined)
|
|
76
|
+
options.version = args.version;
|
|
77
|
+
if (args.module_script !== undefined)
|
|
78
|
+
options.module_script = args.module_script;
|
|
79
|
+
if (args.nomodule_script !== undefined)
|
|
80
|
+
options.nomodule_script = args.nomodule_script;
|
|
81
|
+
if (args.api_server !== undefined)
|
|
82
|
+
options.api_server = args.api_server;
|
|
83
|
+
if (args.proxy !== undefined)
|
|
84
|
+
options.proxy = args.proxy;
|
|
85
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: 'Solving Friendly Captcha', timeoutSeconds: timeout }, (client) => client.friendlyCaptcha(args.sitekey, args.url, options), (result, seconds) => {
|
|
86
|
+
const token = result.token ?? String(result.code ?? '');
|
|
87
|
+
const structured = {
|
|
88
|
+
captchaId: String(result.captchaId ?? ''),
|
|
89
|
+
code: String(result.code ?? ''),
|
|
90
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
91
|
+
token,
|
|
92
|
+
};
|
|
93
|
+
// The field the token goes into differs per version, and getting it
|
|
94
|
+
// wrong is the single most common way a working v1 integration breaks
|
|
95
|
+
// on a v2 site — so name the right one rather than leaving it implied.
|
|
96
|
+
// A v2 token is a single opaque string beginning AQQA.; a v1 token is
|
|
97
|
+
// four dot-separated parts. Fall back to the token's own shape when no
|
|
98
|
+
// explicit version was sent.
|
|
99
|
+
const isV2 = args.version === 'v2'
|
|
100
|
+
|| (args.version === undefined && token.startsWith('AQQA.'));
|
|
101
|
+
const field = isV2 ? 'frc-captcha-response' : 'frc-captcha-solution';
|
|
102
|
+
return {
|
|
103
|
+
structured,
|
|
104
|
+
text: `Solved Friendly Captcha in ${structured.solveSeconds}s.\n`
|
|
105
|
+
+ `Submit this verbatim in the form field named \`${field}\` `
|
|
106
|
+
+ `(${isV2 ? 'v2' : 'v1'}) — do not trim or re-encode it:\n`
|
|
107
|
+
+ `${field}: ${token}\n`
|
|
108
|
+
+ `(${token.length} characters; CapSkip captcha id ${structured.captchaId})`,
|
|
109
|
+
};
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=friendlycaptcha.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"friendlycaptcha.js","sourceRoot":"","sources":["../../src/tools/friendlycaptcha.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport {\n friendlyCaptchaOutput, pageUrlSchema, proxySchema, timeoutSchema,\n} from '../schemas.js';\nimport { runSolve } from '../solve.js';\nimport type { ToolContext } from '../solver.js';\n\nconst inputSchema = z.strictObject({\n sitekey: z\n .string()\n .min(1)\n .describe(\n \"The data-sitekey attribute of the widget element — the one carrying \"\n + \"class='frc-captcha'.\",\n ),\n url: pageUrlSchema,\n version: z\n .enum(['v1', 'v2'])\n .optional()\n .describe(\n 'The protocol version the site uses. Two entirely different protocols '\n + 'ship under this one name and a sitekey does not say which, so send '\n + 'this or module_script. Defaults to v1 when neither is given.',\n ),\n module_script: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"The src of the widget script tag carrying type='module'. The most \"\n + 'reliable version signal there is, because it is the build the site '\n + \"actually loads: 'widget.module.min.js' means v1, 'site.min.js' v2.\",\n ),\n nomodule_script: z\n .string()\n .min(1)\n .optional()\n .describe(\"The src of the widget script tag carrying 'nomodule'. Read for the same reason.\"),\n api_server: z\n .string()\n .min(1)\n .optional()\n .describe(\n \"The data-residency tenant the sitekey belongs to: 'global' (the \"\n + \"default), 'eu', or a full URL. Both tenants mint a token for the same \"\n + \"sitekey, so the wrong one is only caught by the site's own check.\",\n ),\n proxy: proxySchema\n .optional()\n .describe(\n 'Solve from this network. Worth setting sooner here than almost anywhere '\n + 'else: the service raises the difficulty for addresses it has already '\n + 'seen a lot of.',\n ),\n timeout: timeoutSchema,\n});\n\nexport function registerFriendlyCaptchaTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_friendly_captcha',\n {\n title: 'Solve Friendly Captcha',\n description:\n 'Solve a Friendly Captcha proof-of-work challenge. Nothing is shown on '\n + 'screen — the widget does arithmetic instead of asking the visitor to '\n + 'do anything. IMPORTANT: two entirely different protocols ship under '\n + 'this one name, sharing a brand and a sitekey namespace and nothing '\n + 'else, and a sitekey does not tell you which a site uses. Send version '\n + '(v1 or v2), or send module_script and let CapSkip read the version off '\n + 'the build the site loads. Solving the wrong one returns a well-formed '\n + 'token the site rejects, with nothing to indicate the version was the '\n + 'problem. The form field also differs by version: v1 uses '\n + \"'frc-captcha-solution', v2 uses 'frc-captcha-response'. Solve time is \"\n + 'not a constant — the service sets the difficulty per request — so '\n + 'budget for that rather than assuming a fixed duration.',\n inputSchema,\n outputSchema: friendlyCaptchaOutput,\n // destructiveHint defaults to *true* whenever readOnlyHint is false, so\n // omitting it would advertise this tool as potentially destructive and cost\n // it auto-approval in clients that read the hint. Solving a captcha\n // destroys nothing. idempotentHint is deliberately left at its false\n // default: each call mints a new token against fresh work.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args, extra) => {\n // Proof-of-work, but not the millisecond kind ALTCHA does: the difficulty\n // is set per request, and v2 always solves in a browser. Both make solve\n // time variable enough to want the longer timeout.\n const timeout = args.timeout ?? ctx.config.recaptchaTimeout;\n\n const options: Record<string, unknown> = {};\n if (args.version !== undefined) options.version = args.version;\n if (args.module_script !== undefined) options.module_script = args.module_script;\n if (args.nomodule_script !== undefined) options.nomodule_script = args.nomodule_script;\n if (args.api_server !== undefined) options.api_server = args.api_server;\n if (args.proxy !== undefined) options.proxy = args.proxy;\n\n return runSolve(\n ctx,\n extra,\n { label: 'Solving Friendly Captcha', timeoutSeconds: timeout },\n (client) => client.friendlyCaptcha(args.sitekey, args.url, options as never),\n (result, seconds) => {\n const token = (result.token as string | undefined) ?? String(result.code ?? '');\n\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n code: String(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n token,\n };\n\n // The field the token goes into differs per version, and getting it\n // wrong is the single most common way a working v1 integration breaks\n // on a v2 site — so name the right one rather than leaving it implied.\n // A v2 token is a single opaque string beginning AQQA.; a v1 token is\n // four dot-separated parts. Fall back to the token's own shape when no\n // explicit version was sent.\n const isV2 = args.version === 'v2'\n || (args.version === undefined && token.startsWith('AQQA.'));\n const field = isV2 ? 'frc-captcha-response' : 'frc-captcha-solution';\n\n return {\n structured,\n text:\n `Solved Friendly Captcha in ${structured.solveSeconds}s.\\n`\n + `Submit this verbatim in the form field named \\`${field}\\` `\n + `(${isV2 ? 'v2' : 'v1'}) — do not trim or re-encode it:\\n`\n + `${field}: ${token}\\n`\n + `(${token.length} characters; CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# CapSkip MCP Server — API Reference
|
|
2
2
|
|
|
3
|
-
Complete reference for all
|
|
3
|
+
Complete reference for all nine tools `capskip-mcp` registers. Parameter descriptions below are copied verbatim from each tool's schema in `src/tools/` so this document cannot drift from what a client actually sees when it lists tools.
|
|
4
4
|
|
|
5
5
|
Every solve tool declares an `outputSchema` and returns MCP `structuredContent` matching it, plus a human-readable text block for clients that do not render structured output. Every tool schema rejects unknown keys — a misspelled or unsupported parameter is rejected by name rather than silently ignored.
|
|
6
6
|
|
|
@@ -16,6 +16,9 @@ Every solve tool declares an `outputSchema` and returns MCP `structuredContent`
|
|
|
16
16
|
| `capskip_solve_turnstile` | Solve a Cloudflare Turnstile widget or challenge page | Yes |
|
|
17
17
|
| `capskip_solve_geetest` | Solve a GeeTest v3 slide-puzzle captcha | Yes |
|
|
18
18
|
| `capskip_solve_altcha` | Solve an ALTCHA proof-of-work challenge | Yes (challenge fetch only) |
|
|
19
|
+
| `capskip_solve_capy` | Solve a Capy Puzzle captcha | Yes (puzzle-image fetch only) |
|
|
20
|
+
| `capskip_solve_captchafox` | Solve a CaptchaFox challenge | Yes |
|
|
21
|
+
| `capskip_solve_friendly_captcha` | Solve a Friendly Captcha proof-of-work challenge | Yes |
|
|
19
22
|
|
|
20
23
|
---
|
|
21
24
|
|
|
@@ -154,7 +157,7 @@ Solve a Google reCAPTCHA v2 or v3 widget, including invisible and Enterprise var
|
|
|
154
157
|
| `proxy` | object | No | — | "Solve through this proxy so the token is issued against its IP." |
|
|
155
158
|
| `timeout` | integer | No | `CAPSKIP_RECAPTCHA_TIMEOUT` (300) | "Seconds to wait before giving up. Maximum 600." |
|
|
156
159
|
|
|
157
|
-
`proxy` shape (shared with
|
|
160
|
+
`proxy` shape (shared with every solve tool except `capskip_solve_image_captcha`):
|
|
158
161
|
|
|
159
162
|
| Field | Type | Required | Description |
|
|
160
163
|
|---|---|---|---|
|
|
@@ -385,6 +388,218 @@ CapSkip solves the legacy scheme (SHA-1/256/384/512) and PoW v2 with PBKDF2 or S
|
|
|
385
388
|
|
|
386
389
|
---
|
|
387
390
|
|
|
391
|
+
## `capskip_solve_capy`
|
|
392
|
+
|
|
393
|
+
Solve a Capy Puzzle captcha — a slide puzzle where a piece is dragged into a hole cut out of a photograph. IMPORTANT: unlike every other captcha tool here, the answer is NOT a single token. It is three values — `captchakey`, `challengekey` and `answer` — which all go into the target form, in fields named `capy_captchakey`, `capy_challengekey` and `capy_answer`. Submit the answer verbatim: it is the drag path the widget would have recorded, so trimming or re-encoding it invalidates the solve. The challenge key is single-use and short-lived, so submit promptly rather than caching the three values.
|
|
394
|
+
|
|
395
|
+
### Parameters
|
|
396
|
+
|
|
397
|
+
| Name | Type | Required | Default | Description |
|
|
398
|
+
|---|---|---|---|---|
|
|
399
|
+
| `sitekey` | string | Yes | — | "The site's public Capy key, conventionally prefixed 'PUZZLE_'." |
|
|
400
|
+
| `url` | string (URL) | Yes | — | "Full URL of the page the captcha appears on, including scheme." |
|
|
401
|
+
| `api_server` | string | No | `https://jp.api.capy.me` | "The root of the Capy API the key lives behind, taken from the widget script URL." |
|
|
402
|
+
| `proxy` | object | No | — | "Draw the puzzle image through this proxy. That fetch is the only request a Capy solve makes." |
|
|
403
|
+
| `timeout` | integer | No | `CAPSKIP_TIMEOUT` (120) | "Seconds to wait before giving up. Maximum 600." |
|
|
404
|
+
|
|
405
|
+
`proxy` has the same `{ type, uri }` shape documented under `capskip_solve_recaptcha` above.
|
|
406
|
+
|
|
407
|
+
> `api.capy.me` no longer resolves, although several solver services still document it. The live host is `jp.api.capy.me`; if a site's widget points somewhere else, pass that as `api_server`.
|
|
408
|
+
|
|
409
|
+
### Finding the captcha key
|
|
410
|
+
|
|
411
|
+
It is in the page source as `capy_captchakey`, or in the widget script URL: `<script src="https://jp.api.capy.me/puzzle/get_js/?k=PUZZLE_XXXX">`.
|
|
412
|
+
|
|
413
|
+
### Output shape
|
|
414
|
+
|
|
415
|
+
| Field | Type | Always present | Description |
|
|
416
|
+
|---|---|---|---|
|
|
417
|
+
| `captchaId` | string | yes | CapSkip's internal id for this solve |
|
|
418
|
+
| `code` | string | yes | The raw answer object, as a JSON string |
|
|
419
|
+
| `solveSeconds` | number | yes | Wall-clock time the solve took |
|
|
420
|
+
| `captchakey` | string | when the answer parsed | The value for the form's `capy_captchakey` field |
|
|
421
|
+
| `challengekey` | string | when the answer parsed | The value for the form's `capy_challengekey` field. Single-use. |
|
|
422
|
+
| `answer` | string | when the answer parsed | The value for the form's `capy_answer` field — the drag path |
|
|
423
|
+
| `respKey` | string | when the answer parsed | Empty for a puzzle solve; present only for shape compatibility |
|
|
424
|
+
|
|
425
|
+
### Example
|
|
426
|
+
|
|
427
|
+
Request:
|
|
428
|
+
|
|
429
|
+
```json
|
|
430
|
+
{
|
|
431
|
+
"sitekey": "PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
|
|
432
|
+
"url": "https://example.com/login"
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
Response (`structuredContent`):
|
|
437
|
+
|
|
438
|
+
```json
|
|
439
|
+
{
|
|
440
|
+
"captchaId": "10433",
|
|
441
|
+
"code": "{\"captchakey\":\"PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v\",\"challengekey\":\"BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP\",\"answer\":\"0xax8ex0xax84x...\",\"respKey\":\"\"}",
|
|
442
|
+
"solveSeconds": 2.14,
|
|
443
|
+
"captchakey": "PUZZLE_Abc1dEFghIJKLM2no34P56q7rStu8v",
|
|
444
|
+
"challengekey": "BalY2gJaI8uA2SGVOZhqBQ3V0CYSNNGP",
|
|
445
|
+
"answer": "0xax8ex0xax84x0xkx7qx0x18x76x...",
|
|
446
|
+
"respKey": ""
|
|
447
|
+
}
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### Why a solve takes about two seconds
|
|
451
|
+
|
|
452
|
+
Capy grades the wall-clock gap between issuing the puzzle and verifying the answer, and refuses anything superhuman with `CAPTCHA verification failed` — the same message a wrong answer gets. CapSkip therefore holds every result until two seconds have elapsed since it drew the puzzle. The detection itself takes about a tenth of a second; the rest is a deliberate wait. Nothing to configure.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
## `capskip_solve_captchafox`
|
|
457
|
+
|
|
458
|
+
Solve a CaptchaFox challenge. CaptchaFox scores the browser itself rather than asking the visitor to read anything, so most solves draw no puzzle at all. Returns a token to put in the form field named `cf-captcha-response`, verbatim — it is verified server side against the session that produced it, so any edit invalidates it.
|
|
459
|
+
|
|
460
|
+
### Parameters
|
|
461
|
+
|
|
462
|
+
| Name | Type | Required | Default | Description |
|
|
463
|
+
|---|---|---|---|---|
|
|
464
|
+
| `sitekey` | string | Yes | — | "The public key the widget renders with, conventionally prefixed 'sk_'." |
|
|
465
|
+
| `url` | string (URL) | Yes | — | "Full URL of the page the captcha appears on, including scheme." |
|
|
466
|
+
| `api_server` | string | No | `https://cdn.captchafox.com/` | "The widget entry point the target page loads." |
|
|
467
|
+
| `proxy` | object | No | — | "Solve from this network." |
|
|
468
|
+
| `timeout` | integer | No | `CAPSKIP_RECAPTCHA_TIMEOUT` (300) | "Seconds to wait before giving up. Maximum 600." |
|
|
469
|
+
|
|
470
|
+
### The page URL has to match the key
|
|
471
|
+
|
|
472
|
+
CaptchaFox keys are registered against a list of allowed domains and the service checks the host before it issues anything. A key that is correct but used on a page outside that list is refused permanently, not intermittently — so a sitekey that fails immediately and consistently usually means the wrong page URL, not a bad key. CapSkip reports that case rather than retrying it, because retrying cannot help.
|
|
473
|
+
|
|
474
|
+
### Choosing the widget source
|
|
475
|
+
|
|
476
|
+
| `api_server` | Token | Default |
|
|
477
|
+
|---|---|---|
|
|
478
|
+
| `https://cdn.captchafox.com/` | Plain | Yes |
|
|
479
|
+
| `https://s.uicdn.com/mampkg/…` | `MAM_` prefixed | No |
|
|
480
|
+
|
|
481
|
+
Read the value from the `<script>` tag that loads the widget. Sending the wrong one still succeeds, but returns a token in a format the target site will not accept — a silent verification failure rather than an error.
|
|
482
|
+
|
|
483
|
+
### Challenge types
|
|
484
|
+
|
|
485
|
+
| Challenge | Frequency | Supported |
|
|
486
|
+
|---|---|---|
|
|
487
|
+
| Invisible | Usually | Yes |
|
|
488
|
+
| Slide | Sometimes | Yes |
|
|
489
|
+
| Image select | Rarely | No |
|
|
490
|
+
| Audio | Rarely | No |
|
|
491
|
+
|
|
492
|
+
The two unsupported ones are uncommon and a retry usually draws a different challenge, so treat an unsolvable result as a signal to resubmit rather than a permanent failure of the key.
|
|
493
|
+
|
|
494
|
+
### Output shape
|
|
495
|
+
|
|
496
|
+
| Field | Type | Always present | Description |
|
|
497
|
+
|---|---|---|---|
|
|
498
|
+
| `captchaId` | string | yes | CapSkip's internal id for this solve |
|
|
499
|
+
| `code` | string | yes | The token — the same string as `token` |
|
|
500
|
+
| `solveSeconds` | number | yes | Wall-clock time the solve took |
|
|
501
|
+
| `token` | string | yes | The value to submit in the `cf-captcha-response` field |
|
|
502
|
+
| `userAgent` | string | when the solve reported one | The UA the token was minted under — the browser's own, not one you sent |
|
|
503
|
+
|
|
504
|
+
### Example
|
|
505
|
+
|
|
506
|
+
Request:
|
|
507
|
+
|
|
508
|
+
```json
|
|
509
|
+
{
|
|
510
|
+
"sitekey": "sk_xtNxpk6fCdFbxh1_xJeGflSdCE9tn99G",
|
|
511
|
+
"url": "https://example.com/signup"
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
Response (`structuredContent`):
|
|
516
|
+
|
|
517
|
+
```json
|
|
518
|
+
{
|
|
519
|
+
"captchaId": "10434",
|
|
520
|
+
"code": "177f50c25b845601e5c779cdb51b040d...",
|
|
521
|
+
"solveSeconds": 6.31,
|
|
522
|
+
"token": "177f50c25b845601e5c779cdb51b040d...",
|
|
523
|
+
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/146.0.0.0 Safari/537.36"
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
Submit the token under the reported `userAgent`, not your own: CapSkip solves in a real browser and the token is bound to that browser's identity.
|
|
528
|
+
|
|
529
|
+
---
|
|
530
|
+
|
|
531
|
+
## `capskip_solve_friendly_captcha`
|
|
532
|
+
|
|
533
|
+
Solve a Friendly Captcha proof-of-work challenge. Nothing is shown on screen — the widget does arithmetic instead of asking the visitor to do anything.
|
|
534
|
+
|
|
535
|
+
### Parameters
|
|
536
|
+
|
|
537
|
+
| Name | Type | Required | Default | Description |
|
|
538
|
+
|---|---|---|---|---|
|
|
539
|
+
| `sitekey` | string | Yes | — | "The data-sitekey attribute of the widget element — the one carrying class='frc-captcha'." |
|
|
540
|
+
| `url` | string (URL) | Yes | — | "Full URL of the page the captcha appears on, including scheme." |
|
|
541
|
+
| `version` | `"v1"` \| `"v2"` | No | `v1` | "The protocol version the site uses. Send this or module_script." |
|
|
542
|
+
| `module_script` | string | No | — | "The src of the widget script tag carrying type='module'." |
|
|
543
|
+
| `nomodule_script` | string | No | — | "The src of the widget script tag carrying 'nomodule'." |
|
|
544
|
+
| `api_server` | string | No | `global` | "The data-residency tenant the sitekey belongs to: 'global', 'eu', or a full URL." |
|
|
545
|
+
| `proxy` | object | No | — | "Solve from this network." |
|
|
546
|
+
| `timeout` | integer | No | `CAPSKIP_RECAPTCHA_TIMEOUT` (300) | "Seconds to wait before giving up. Maximum 600." |
|
|
547
|
+
|
|
548
|
+
### Version 1 and version 2
|
|
549
|
+
|
|
550
|
+
Two entirely different protocols ship under this one name, sharing a brand and a sitekey namespace and nothing else, and a sitekey does not tell you which a site uses. Both are live. Solve the wrong one and you get a well-formed token the target site rejects, with no indication that the version was the problem.
|
|
551
|
+
|
|
552
|
+
| `version` | Widget package | Script | Form field |
|
|
553
|
+
|---|---|---|---|
|
|
554
|
+
| `v1` | `friendly-challenge` | `widget.module.min.js` / `widget.min.js` | `frc-captcha-solution` |
|
|
555
|
+
| `v2` | `@friendlycaptcha/sdk` | `site.min.js` | `frc-captcha-response` |
|
|
556
|
+
|
|
557
|
+
CapSkip decides in this order, stopping at the first answer: the `version` parameter; then the script URL from `module_script` or `nomodule_script`, which is the most reliable signal because it is the build the site actually loads; then v1. The tool's text reply names the form field matching the version that was actually solved, so an agent does not have to work it out.
|
|
558
|
+
|
|
559
|
+
A `version` outside `v1`/`v2` is refused by the schema before the call reaches CapSkip.
|
|
560
|
+
|
|
561
|
+
### Output shape
|
|
562
|
+
|
|
563
|
+
| Field | Type | Always present | Description |
|
|
564
|
+
|---|---|---|---|
|
|
565
|
+
| `captchaId` | string | yes | CapSkip's internal id for this solve |
|
|
566
|
+
| `code` | string | yes | The token — the same string as `token` |
|
|
567
|
+
| `solveSeconds` | number | yes | Wall-clock time the solve took |
|
|
568
|
+
| `token` | string | yes | The value to submit, in `frc-captcha-solution` (v1) or `frc-captcha-response` (v2) |
|
|
569
|
+
|
|
570
|
+
### Example
|
|
571
|
+
|
|
572
|
+
Request:
|
|
573
|
+
|
|
574
|
+
```json
|
|
575
|
+
{
|
|
576
|
+
"sitekey": "FCMGEMUD2M567T8G",
|
|
577
|
+
"url": "https://example.com/signup",
|
|
578
|
+
"version": "v2"
|
|
579
|
+
}
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Response (`structuredContent`):
|
|
583
|
+
|
|
584
|
+
```json
|
|
585
|
+
{
|
|
586
|
+
"captchaId": "10435",
|
|
587
|
+
"code": "AQQA.vW7kd3CujKT8PaQgEcW18QaH...",
|
|
588
|
+
"solveSeconds": 18.4,
|
|
589
|
+
"token": "AQQA.vW7kd3CujKT8PaQgEcW18QaH..."
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### Token sizes differ sharply between versions
|
|
594
|
+
|
|
595
|
+
A v1 token is four dot-separated parts and runs to a few hundred characters. A v2 token is a single opaque string beginning `AQQA.` and is roughly six kilobytes, so make sure whatever carries it — a hidden field, a database column, a proxied request — is sized for that.
|
|
596
|
+
|
|
597
|
+
### Solve time is not a constant
|
|
598
|
+
|
|
599
|
+
The service decides how much work a request is worth at the moment it is made, and raises that figure for addresses it has already seen a lot of: the published range between a fresh address and a heavily used one is close to thirty times the work for the same token. Budget for that in your timeout rather than assuming a fixed duration, and use a proxy pool past casual volume.
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
388
603
|
## Validation errors
|
|
389
604
|
|
|
390
605
|
Every tool's schema is a strict object: any key it does not declare is rejected rather than ignored, and every required key must be present. These failures return `isError: true` before the call ever reaches CapSkip. Examples:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "capskip-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Captcha solver MCP server for AI agents: solve reCAPTCHA v2/v3, Cloudflare Turnstile, GeeTest and image captchas via CapSkip, a local unlimited captcha solver with no per-solve fees.",
|
|
5
5
|
"main": "./dist/server.js",
|
|
6
6
|
"exports": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"files": ["dist", "docs", "examples", "CHANGELOG.md", "SECURITY.md"],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
36
|
-
"capskip": "^1.
|
|
36
|
+
"capskip": "^1.3.0",
|
|
37
37
|
"zod": "^4.4.3"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|