capskip-mcp 1.0.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 +28 -0
- package/LICENSE +21 -0
- package/README.md +255 -0
- package/SECURITY.md +35 -0
- package/dist/config.js +97 -0
- package/dist/config.js.map +1 -0
- package/dist/errors.js +103 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/progress.js +54 -0
- package/dist/progress.js.map +1 -0
- package/dist/schemas.js +52 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.js +36 -0
- package/dist/server.js.map +1 -0
- package/dist/solve.js +54 -0
- package/dist/solve.js.map +1 -0
- package/dist/solver.js +31 -0
- package/dist/solver.js.map +1 -0
- package/dist/tools/geetest.js +77 -0
- package/dist/tools/geetest.js.map +1 -0
- package/dist/tools/image.js +50 -0
- package/dist/tools/image.js.map +1 -0
- package/dist/tools/recaptcha.js +90 -0
- package/dist/tools/recaptcha.js.map +1 -0
- package/dist/tools/status.js +119 -0
- package/dist/tools/status.js.map +1 -0
- package/dist/tools/turnstile.js +78 -0
- package/dist/tools/turnstile.js.map +1 -0
- package/docs/API_REFERENCE.md +345 -0
- package/docs/GETTING_STARTED.md +161 -0
- package/docs/TROUBLESHOOTING.md +236 -0
- package/docs/TUTORIAL.md +284 -0
- package/examples/claude-code.md +19 -0
- package/examples/claude-desktop.json +13 -0
- package/examples/cursor.json +13 -0
- package/examples/vscode.json +10 -0
- package/package.json +43 -0
package/dist/progress.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startProgress = startProgress;
|
|
4
|
+
const DEFAULT_INTERVAL_MS = 2000;
|
|
5
|
+
/**
|
|
6
|
+
* Report elapsed time against an expected total while a solve runs.
|
|
7
|
+
*
|
|
8
|
+
* Clients use these notifications to hold their tool-call timeout open. When the
|
|
9
|
+
* caller supplied no progressToken there is nothing to report against, so no
|
|
10
|
+
* timer is created at all.
|
|
11
|
+
*
|
|
12
|
+
* Returns a `stop()` that is safe to call more than once.
|
|
13
|
+
*/
|
|
14
|
+
function startProgress(opts) {
|
|
15
|
+
const { sendNotification, progressToken, totalSeconds, label } = opts;
|
|
16
|
+
if (progressToken === undefined) {
|
|
17
|
+
return () => { };
|
|
18
|
+
}
|
|
19
|
+
const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
20
|
+
const startedAt = Date.now();
|
|
21
|
+
let stopped = false;
|
|
22
|
+
const timer = setInterval(() => {
|
|
23
|
+
if (stopped) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const elapsed = (Date.now() - startedAt) / 1000;
|
|
27
|
+
// Never report past the total: a solve that runs long would otherwise
|
|
28
|
+
// render as a progress bar beyond 100%.
|
|
29
|
+
const progress = Math.min(elapsed, totalSeconds);
|
|
30
|
+
// The transport can close mid-solve. A failed progress ping must not take
|
|
31
|
+
// down the solve that is still usefully running.
|
|
32
|
+
void sendNotification({
|
|
33
|
+
method: 'notifications/progress',
|
|
34
|
+
params: {
|
|
35
|
+
progressToken,
|
|
36
|
+
progress,
|
|
37
|
+
total: totalSeconds,
|
|
38
|
+
message: `${label} — ${Math.round(elapsed)}s elapsed`,
|
|
39
|
+
},
|
|
40
|
+
}).catch(() => { });
|
|
41
|
+
}, intervalMs);
|
|
42
|
+
// Do not hold the event loop open on account of progress reporting.
|
|
43
|
+
if (typeof timer.unref === 'function') {
|
|
44
|
+
timer.unref();
|
|
45
|
+
}
|
|
46
|
+
return () => {
|
|
47
|
+
if (stopped) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
stopped = true;
|
|
51
|
+
clearInterval(timer);
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=progress.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.js","sourceRoot":"","sources":["../src/progress.ts"],"names":[],"mappings":";;AAgCA,sCA6CC;AAxDD,MAAM,mBAAmB,GAAG,IAAI,CAAC;AAEjC;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,IAAqB;IACjD,MAAM,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IAEtE,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,GAAG,EAAE,GAAE,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;QACT,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;QAChD,sEAAsE;QACtE,wCAAwC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAEjD,0EAA0E;QAC1E,iDAAiD;QACjD,KAAK,gBAAgB,CAAC;YACpB,MAAM,EAAE,wBAAwB;YAChC,MAAM,EAAE;gBACN,aAAa;gBACb,QAAQ;gBACR,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,GAAG,KAAK,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW;aACtD;SACF,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACrB,CAAC,EAAE,UAAU,CAAC,CAAC;IAEf,oEAAoE;IACpE,IAAI,OAAO,KAAK,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACtC,KAAK,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAED,OAAO,GAAG,EAAE;QACV,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO;QACT,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;QACf,aAAa,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC","sourcesContent":["export interface ProgressNotification {\n method: 'notifications/progress';\n params: {\n progressToken: string | number;\n progress: number;\n total?: number;\n message?: string;\n };\n}\n\nexport type SendNotification = (notification: ProgressNotification) => Promise<void>;\n\nexport interface ProgressOptions {\n sendNotification: SendNotification;\n progressToken: string | number | undefined;\n totalSeconds: number;\n label: string;\n /** Exposed for tests; production callers use the 2s default. */\n intervalMs?: number;\n}\n\nconst DEFAULT_INTERVAL_MS = 2000;\n\n/**\n * Report elapsed time against an expected total while a solve runs.\n *\n * Clients use these notifications to hold their tool-call timeout open. When the\n * caller supplied no progressToken there is nothing to report against, so no\n * timer is created at all.\n *\n * Returns a `stop()` that is safe to call more than once.\n */\nexport function startProgress(opts: ProgressOptions): () => void {\n const { sendNotification, progressToken, totalSeconds, label } = opts;\n\n if (progressToken === undefined) {\n return () => {};\n }\n\n const intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;\n const startedAt = Date.now();\n let stopped = false;\n\n const timer = setInterval(() => {\n if (stopped) {\n return;\n }\n const elapsed = (Date.now() - startedAt) / 1000;\n // Never report past the total: a solve that runs long would otherwise\n // render as a progress bar beyond 100%.\n const progress = Math.min(elapsed, totalSeconds);\n\n // The transport can close mid-solve. A failed progress ping must not take\n // down the solve that is still usefully running.\n void sendNotification({\n method: 'notifications/progress',\n params: {\n progressToken,\n progress,\n total: totalSeconds,\n message: `${label} — ${Math.round(elapsed)}s elapsed`,\n },\n }).catch(() => {});\n }, intervalMs);\n\n // Do not hold the event loop open on account of progress reporting.\n if (typeof timer.unref === 'function') {\n timer.unref();\n }\n\n return () => {\n if (stopped) {\n return;\n }\n stopped = true;\n clearInterval(timer);\n };\n}\n"]}
|
package/dist/schemas.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.statusOutput = exports.geetestOutput = exports.turnstileOutput = exports.baseSolveOutput = exports.pageUrlSchema = exports.timeoutSchema = exports.proxySchema = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
/**
|
|
6
|
+
* CapSkip maps only these four proxy schemes and answers ERROR_BAD_PARAMETERS
|
|
7
|
+
* for anything else, SOCKS4 included.
|
|
8
|
+
*/
|
|
9
|
+
exports.proxySchema = zod_1.z.object({
|
|
10
|
+
type: zod_1.z
|
|
11
|
+
.enum(['HTTP', 'HTTPS', 'SOCKS5', 'SOCKS5H'])
|
|
12
|
+
.describe('Proxy scheme. CapSkip supports only these four.'),
|
|
13
|
+
uri: zod_1.z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.describe('Proxy address as `host:port` or `login:password@host:port`.'),
|
|
17
|
+
});
|
|
18
|
+
/**
|
|
19
|
+
* Bounded at 600s. A larger value is rejected rather than clamped so the caller
|
|
20
|
+
* learns the limit instead of silently getting a shorter wait.
|
|
21
|
+
*/
|
|
22
|
+
exports.timeoutSchema = zod_1.z
|
|
23
|
+
.number()
|
|
24
|
+
.int()
|
|
25
|
+
.min(1)
|
|
26
|
+
.max(600)
|
|
27
|
+
.optional()
|
|
28
|
+
.describe('Seconds to wait before giving up. Maximum 600.');
|
|
29
|
+
exports.pageUrlSchema = zod_1.z
|
|
30
|
+
.url()
|
|
31
|
+
.describe('Full URL of the page the captcha appears on, including scheme.');
|
|
32
|
+
exports.baseSolveOutput = zod_1.z.object({
|
|
33
|
+
captchaId: zod_1.z.string(),
|
|
34
|
+
code: zod_1.z.string(),
|
|
35
|
+
solveSeconds: zod_1.z.number(),
|
|
36
|
+
});
|
|
37
|
+
exports.turnstileOutput = exports.baseSolveOutput.extend({
|
|
38
|
+
userAgent: zod_1.z.string().optional(),
|
|
39
|
+
});
|
|
40
|
+
exports.geetestOutput = exports.baseSolveOutput.extend({
|
|
41
|
+
challenge: zod_1.z.string().optional(),
|
|
42
|
+
validate: zod_1.z.string().optional(),
|
|
43
|
+
seccode: zod_1.z.string().optional(),
|
|
44
|
+
});
|
|
45
|
+
exports.statusOutput = zod_1.z.object({
|
|
46
|
+
reachable: zod_1.z.boolean(),
|
|
47
|
+
host: zod_1.z.string(),
|
|
48
|
+
port: zod_1.z.number(),
|
|
49
|
+
latencyMs: zod_1.z.number().optional(),
|
|
50
|
+
detail: zod_1.z.string(),
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=schemas.js.map
|
|
@@ -0,0 +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,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 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
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SERVER_VERSION = exports.SERVER_NAME = void 0;
|
|
4
|
+
exports.createServer = createServer;
|
|
5
|
+
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
6
|
+
const solver_js_1 = require("./solver.js");
|
|
7
|
+
const geetest_js_1 = require("./tools/geetest.js");
|
|
8
|
+
const image_js_1 = require("./tools/image.js");
|
|
9
|
+
const recaptcha_js_1 = require("./tools/recaptcha.js");
|
|
10
|
+
const status_js_1 = require("./tools/status.js");
|
|
11
|
+
const turnstile_js_1 = require("./tools/turnstile.js");
|
|
12
|
+
exports.SERVER_NAME = 'capskip';
|
|
13
|
+
exports.SERVER_VERSION = '1.0.0';
|
|
14
|
+
/** Build a fully-registered MCP server. Does not connect a transport. */
|
|
15
|
+
function createServer(config) {
|
|
16
|
+
const server = new mcp_js_1.McpServer({ name: exports.SERVER_NAME, version: exports.SERVER_VERSION }, {
|
|
17
|
+
instructions: 'Solve captchas through a locally-running CapSkip desktop app. Use these '
|
|
18
|
+
+ 'tools when browser automation is blocked by a captcha: read the sitekey '
|
|
19
|
+
+ 'from the page, call the matching tool, then place the returned token in '
|
|
20
|
+
+ "the page's response field. CapSkip solves image captchas, reCAPTCHA v2 "
|
|
21
|
+
+ 'and v3, Cloudflare Turnstile, and GeeTest v3 — it cannot solve hCaptcha '
|
|
22
|
+
+ 'or FunCaptcha/Arkose, so do not call these tools for those. An hCaptcha '
|
|
23
|
+
+ 'widget renders as <div class="h-captcha" data-sitekey="…">, which is '
|
|
24
|
+
+ 'easy to mistake for reCAPTCHA when scanning the DOM for data-sitekey. '
|
|
25
|
+
+ 'If a solve fails unexpectedly, call capskip_status to check CapSkip is '
|
|
26
|
+
+ 'running.',
|
|
27
|
+
});
|
|
28
|
+
const ctx = (0, solver_js_1.createContext)(config);
|
|
29
|
+
(0, status_js_1.registerStatusTool)(server, ctx);
|
|
30
|
+
(0, image_js_1.registerImageTool)(server, ctx);
|
|
31
|
+
(0, recaptcha_js_1.registerRecaptchaTool)(server, ctx);
|
|
32
|
+
(0, turnstile_js_1.registerTurnstileTool)(server, ctx);
|
|
33
|
+
(0, geetest_js_1.registerGeetestTool)(server, ctx);
|
|
34
|
+
return server;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";;;AAcA,oCA2BC;AAzCD,oEAAoE;AAGpE,2CAA4C;AAC5C,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,0EAA0E;cAC1E,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;IAEjC,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 { 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.0.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, and GeeTest v3 — 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\n return server;\n}\n"]}
|
package/dist/solve.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runSolve = runSolve;
|
|
4
|
+
const errors_js_1 = require("./errors.js");
|
|
5
|
+
const progress_js_1 = require("./progress.js");
|
|
6
|
+
const solver_js_1 = require("./solver.js");
|
|
7
|
+
/**
|
|
8
|
+
* Run a solve with timing, progress reporting, and error translation.
|
|
9
|
+
*
|
|
10
|
+
* Every failure becomes an `isError` tool result — a solve tool must never
|
|
11
|
+
* throw, or the model sees a protocol error it cannot reason about.
|
|
12
|
+
*/
|
|
13
|
+
async function runSolve(ctx, extra, opts, fn, format) {
|
|
14
|
+
const startedAt = Date.now();
|
|
15
|
+
const client = (0, solver_js_1.createClient)(ctx.config, opts.timeoutSeconds);
|
|
16
|
+
// The SDK's TimeoutException carries no captcha id, and solve() does not
|
|
17
|
+
// catch it, so the id from send() would otherwise be lost exactly when it is
|
|
18
|
+
// most useful. Recording it here is what lets a timeout error name it.
|
|
19
|
+
// Safe because this client belongs to this call alone.
|
|
20
|
+
let captchaId;
|
|
21
|
+
const originalSend = client.send.bind(client);
|
|
22
|
+
client.send = async (params) => {
|
|
23
|
+
const id = await originalSend(params);
|
|
24
|
+
captchaId = id;
|
|
25
|
+
return id;
|
|
26
|
+
};
|
|
27
|
+
const stop = (0, progress_js_1.startProgress)({
|
|
28
|
+
sendNotification: extra.sendNotification,
|
|
29
|
+
progressToken: extra._meta?.progressToken,
|
|
30
|
+
totalSeconds: opts.timeoutSeconds,
|
|
31
|
+
label: opts.label,
|
|
32
|
+
});
|
|
33
|
+
try {
|
|
34
|
+
const result = await fn(client);
|
|
35
|
+
const seconds = (Date.now() - startedAt) / 1000;
|
|
36
|
+
const { text, structured } = format(result, seconds);
|
|
37
|
+
return {
|
|
38
|
+
content: [{ type: 'text', text }],
|
|
39
|
+
structuredContent: structured,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
return (0, errors_js_1.mapError)(err, {
|
|
44
|
+
host: ctx.config.host,
|
|
45
|
+
port: ctx.config.port,
|
|
46
|
+
elapsedSeconds: (Date.now() - startedAt) / 1000,
|
|
47
|
+
captchaId,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
stop();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=solve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solve.js","sourceRoot":"","sources":["../src/solve.ts"],"names":[],"mappings":";;AA6CA,4BA+CC;AAzFD,2CAAuC;AACvC,+CAA8C;AAE9C,2CAA2C;AAiC3C;;;;;GAKG;AACI,KAAK,UAAU,QAAQ,CAC5B,GAAgB,EAChB,KAAiB,EACjB,IAAkB,EAClB,EAAiD,EACjD,MAA+D;IAE/D,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAG,IAAA,wBAAY,EAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAE7D,yEAAyE;IACzE,6EAA6E;IAC7E,uEAAuE;IACvE,uDAAuD;IACvD,IAAI,SAA6B,CAAC;IAClC,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,IAAI,GAAG,KAAK,EAAE,MAAgC,EAAmB,EAAE;QACxE,MAAM,EAAE,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC,CAAC;QACtC,SAAS,GAAG,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,IAAA,2BAAa,EAAC;QACzB,gBAAgB,EAAE,KAAK,CAAC,gBAAgB;QACxC,aAAa,EAAE,KAAK,CAAC,KAAK,EAAE,aAAa;QACzC,YAAY,EAAE,IAAI,CAAC,cAAc;QACjC,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC;QAChD,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrD,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;YACjC,iBAAiB,EAAE,UAAU;SAC9B,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,IAAA,oBAAQ,EAAC,GAAG,EAAE;YACnB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;YACrB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;YACrB,cAAc,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI;YAC/C,SAAS;SACV,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,IAAI,EAAE,CAAC;IACT,CAAC;AACH,CAAC","sourcesContent":["import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';\nimport type { CapSkip, SolveResult } from 'capskip';\n\nimport { mapError } from './errors.js';\nimport { startProgress } from './progress.js';\nimport type { ProgressNotification } from './progress.js';\nimport { createClient } from './solver.js';\nimport type { ToolContext } from './solver.js';\n\n/**\n * The subset of the MCP request extra that a solve needs.\n *\n * `sendNotification` is typed against our own ProgressNotification rather than\n * `never` so the progress payload is checked at the one place it is built. The\n * `never` spelling silently accepted anything.\n */\nexport interface SolveExtra {\n _meta?: { progressToken?: string | number };\n sendNotification: (notification: ProgressNotification) => Promise<void>;\n}\n\nexport interface SolveOptions {\n label: string;\n timeoutSeconds: number;\n}\n\n// The capskip SDK's normal()/recaptcha()/turnstile()/geetest() all resolve to\n// this same interface, so aliasing it (rather than a hand-rolled\n// Record<string, unknown>) lets every tool pass its client call straight\n// through with no cast: a plain `interface` with no index signature is never\n// structurally assignable to a Record<string, unknown>-shaped type in strict\n// TypeScript, no matter how that type is spelled.\nexport type SolveResultLike = SolveResult;\n\nexport interface Formatted {\n text: string;\n structured: Record<string, unknown>;\n}\n\n/**\n * Run a solve with timing, progress reporting, and error translation.\n *\n * Every failure becomes an `isError` tool result — a solve tool must never\n * throw, or the model sees a protocol error it cannot reason about.\n */\nexport async function runSolve(\n ctx: ToolContext,\n extra: SolveExtra,\n opts: SolveOptions,\n fn: (client: CapSkip) => Promise<SolveResultLike>,\n format: (result: SolveResultLike, seconds: number) => Formatted,\n): Promise<CallToolResult> {\n const startedAt = Date.now();\n const client = createClient(ctx.config, opts.timeoutSeconds);\n\n // The SDK's TimeoutException carries no captcha id, and solve() does not\n // catch it, so the id from send() would otherwise be lost exactly when it is\n // most useful. Recording it here is what lets a timeout error name it.\n // Safe because this client belongs to this call alone.\n let captchaId: string | undefined;\n const originalSend = client.send.bind(client);\n client.send = async (params?: Record<string, unknown>): Promise<string> => {\n const id = await originalSend(params);\n captchaId = id;\n return id;\n };\n\n const stop = startProgress({\n sendNotification: extra.sendNotification,\n progressToken: extra._meta?.progressToken,\n totalSeconds: opts.timeoutSeconds,\n label: opts.label,\n });\n\n try {\n const result = await fn(client);\n const seconds = (Date.now() - startedAt) / 1000;\n const { text, structured } = format(result, seconds);\n return {\n content: [{ type: 'text', text }],\n structuredContent: structured,\n };\n } catch (err) {\n return mapError(err, {\n host: ctx.config.host,\n port: ctx.config.port,\n elapsedSeconds: (Date.now() - startedAt) / 1000,\n captchaId,\n });\n } finally {\n stop();\n }\n}\n"]}
|
package/dist/solver.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createContext = createContext;
|
|
4
|
+
exports.createClient = createClient;
|
|
5
|
+
const capskip_1 = require("capskip");
|
|
6
|
+
function createContext(config) {
|
|
7
|
+
return { config };
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Build a CapSkip client for a single solve.
|
|
11
|
+
*
|
|
12
|
+
* Per-call rather than per-process for two reasons. `runSolve` wraps this
|
|
13
|
+
* client's `send` to capture the captcha id, which a shared client could not do
|
|
14
|
+
* safely under concurrent tool calls. And `normal()` rejects every option except
|
|
15
|
+
* `json`, so the caller's timeout cannot be passed as an argument — setting it
|
|
16
|
+
* on the client is what makes one `timeout` parameter work for all four captcha
|
|
17
|
+
* types.
|
|
18
|
+
*
|
|
19
|
+
* The constructor performs no I/O, so this is cheap.
|
|
20
|
+
*/
|
|
21
|
+
function createClient(config, timeoutSeconds) {
|
|
22
|
+
return new capskip_1.CapSkip({
|
|
23
|
+
apiKey: config.apiKey,
|
|
24
|
+
host: config.host,
|
|
25
|
+
port: config.port,
|
|
26
|
+
defaultTimeout: timeoutSeconds,
|
|
27
|
+
recaptchaTimeout: timeoutSeconds,
|
|
28
|
+
pollingInterval: config.pollingInterval,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=solver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"solver.js","sourceRoot":"","sources":["../src/solver.ts"],"names":[],"mappings":";;AASA,sCAEC;AAcD,oCASC;AAlCD,qCAAkC;AASlC,SAAgB,aAAa,CAAC,MAAqB;IACjD,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,YAAY,CAAC,MAAqB,EAAE,cAAsB;IACxE,OAAO,IAAI,iBAAO,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,cAAc,EAAE,cAAc;QAC9B,gBAAgB,EAAE,cAAc;QAChC,eAAe,EAAE,MAAM,CAAC,eAAe;KACxC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { CapSkip } from 'capskip';\n\nimport type { CapSkipConfig } from './config.js';\n\n/** Shared state handed to every tool registration. */\nexport interface ToolContext {\n config: CapSkipConfig;\n}\n\nexport function createContext(config: CapSkipConfig): ToolContext {\n return { config };\n}\n\n/**\n * Build a CapSkip client for a single solve.\n *\n * Per-call rather than per-process for two reasons. `runSolve` wraps this\n * client's `send` to capture the captcha id, which a shared client could not do\n * safely under concurrent tool calls. And `normal()` rejects every option except\n * `json`, so the caller's timeout cannot be passed as an argument — setting it\n * on the client is what makes one `timeout` parameter work for all four captcha\n * types.\n *\n * The constructor performs no I/O, so this is cheap.\n */\nexport function createClient(config: CapSkipConfig, timeoutSeconds: number): CapSkip {\n return new CapSkip({\n apiKey: config.apiKey,\n host: config.host,\n port: config.port,\n defaultTimeout: timeoutSeconds,\n recaptchaTimeout: timeoutSeconds,\n pollingInterval: config.pollingInterval,\n });\n}\n"]}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerGeetestTool = registerGeetestTool;
|
|
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
|
+
gt: zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe('The gt value. Static per site, so it can be reused.'),
|
|
12
|
+
challenge: zod_1.z
|
|
13
|
+
.string()
|
|
14
|
+
.min(1)
|
|
15
|
+
.describe('The challenge value. Single-use and expires in about a minute — fetch a fresh '
|
|
16
|
+
+ 'one immediately before calling this.'),
|
|
17
|
+
url: schemas_js_1.pageUrlSchema,
|
|
18
|
+
api_server: zod_1.z
|
|
19
|
+
.string()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("A non-default GeeTest API domain, e.g. 'api-na.geetest.com'."),
|
|
22
|
+
proxy: schemas_js_1.proxySchema
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('Solve through this proxy so the answer is produced from its IP.'),
|
|
25
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
26
|
+
});
|
|
27
|
+
function registerGeetestTool(server, ctx) {
|
|
28
|
+
server.registerTool('capskip_solve_geetest', {
|
|
29
|
+
title: 'Solve GeeTest v3',
|
|
30
|
+
description: 'Solve a GeeTest v3 slide-puzzle captcha. Returns geetest_challenge, '
|
|
31
|
+
+ 'geetest_validate, and geetest_seccode to post back exactly as the site\'s '
|
|
32
|
+
+ 'own front-end would. IMPORTANT: the challenge value is single-use and '
|
|
33
|
+
+ 'expires in roughly a minute, so fetch gt and challenge from the page '
|
|
34
|
+
+ 'immediately before calling. A stale challenge is the most common failure.',
|
|
35
|
+
inputSchema,
|
|
36
|
+
outputSchema: schemas_js_1.geetestOutput,
|
|
37
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
38
|
+
// omitting it advertised this tool as potentially destructive and cost it
|
|
39
|
+
// auto-approval in clients that read the hint. Solving a captcha destroys
|
|
40
|
+
// nothing. idempotentHint is deliberately left at its false default: each
|
|
41
|
+
// call consumes a fresh, single-use challenge.
|
|
42
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
43
|
+
}, async (args, extra) => {
|
|
44
|
+
const timeout = args.timeout ?? ctx.config.recaptchaTimeout;
|
|
45
|
+
const options = {};
|
|
46
|
+
if (args.api_server !== undefined)
|
|
47
|
+
options.api_server = args.api_server;
|
|
48
|
+
if (args.proxy !== undefined)
|
|
49
|
+
options.proxy = args.proxy;
|
|
50
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: 'Solving GeeTest', timeoutSeconds: timeout }, (client) => client.geetest(args.gt, args.challenge, args.url, options), (result, seconds) => {
|
|
51
|
+
const challenge = result.challenge;
|
|
52
|
+
const validate = result.validate;
|
|
53
|
+
const seccode = result.seccode;
|
|
54
|
+
const structured = {
|
|
55
|
+
captchaId: String(result.captchaId ?? ''),
|
|
56
|
+
code: String(result.code ?? ''),
|
|
57
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
58
|
+
...(challenge ? { challenge } : {}),
|
|
59
|
+
...(validate ? { validate } : {}),
|
|
60
|
+
...(seccode ? { seccode } : {}),
|
|
61
|
+
};
|
|
62
|
+
const fields = challenge || validate || seccode
|
|
63
|
+
? `geetest_challenge: ${challenge ?? '(none)'}\n`
|
|
64
|
+
+ `geetest_validate: ${validate ?? '(none)'}\n`
|
|
65
|
+
+ `geetest_seccode: ${seccode ?? '(none)'}\n`
|
|
66
|
+
: `Raw answer: ${structured.code}\n`;
|
|
67
|
+
return {
|
|
68
|
+
structured,
|
|
69
|
+
text: `Solved GeeTest in ${structured.solveSeconds}s.\n`
|
|
70
|
+
+ 'Post these back exactly as the site\'s own front-end would:\n'
|
|
71
|
+
+ fields
|
|
72
|
+
+ `(CapSkip captcha id ${structured.captchaId})`,
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=geetest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"geetest.js","sourceRoot":"","sources":["../../src/tools/geetest.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { geetestOutput, pageUrlSchema, proxySchema, timeoutSchema } from '../schemas.js';\nimport { runSolve } from '../solve.js';\nimport type { ToolContext } from '../solver.js';\n\nconst inputSchema = z.strictObject({\n gt: z\n .string()\n .min(1)\n .describe('The gt value. Static per site, so it can be reused.'),\n challenge: z\n .string()\n .min(1)\n .describe(\n 'The challenge value. Single-use and expires in about a minute — fetch a fresh '\n + 'one immediately before calling this.',\n ),\n url: pageUrlSchema,\n api_server: z\n .string()\n .optional()\n .describe(\"A non-default GeeTest API domain, e.g. 'api-na.geetest.com'.\"),\n proxy: proxySchema\n .optional()\n .describe('Solve through this proxy so the answer is produced from its IP.'),\n timeout: timeoutSchema,\n});\n\nexport function registerGeetestTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_geetest',\n {\n title: 'Solve GeeTest v3',\n description:\n 'Solve a GeeTest v3 slide-puzzle captcha. Returns geetest_challenge, '\n + 'geetest_validate, and geetest_seccode to post back exactly as the site\\'s '\n + 'own front-end would. IMPORTANT: the challenge value is single-use and '\n + 'expires in roughly a minute, so fetch gt and challenge from the page '\n + 'immediately before calling. A stale challenge is the most common failure.',\n inputSchema,\n outputSchema: geetestOutput,\n // destructiveHint defaults to *true* whenever readOnlyHint is false, so\n // omitting it advertised this tool as potentially destructive and cost it\n // auto-approval in clients that read the hint. Solving a captcha destroys\n // nothing. idempotentHint is deliberately left at its false default: each\n // call consumes a fresh, single-use challenge.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args, extra) => {\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 GeeTest', timeoutSeconds: timeout },\n (client) => client.geetest(args.gt, args.challenge, args.url, options as never),\n (result, seconds) => {\n const challenge = result.challenge as string | undefined;\n const validate = result.validate as string | undefined;\n const seccode = result.seccode as string | undefined;\n\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n code: String(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n ...(challenge ? { challenge } : {}),\n ...(validate ? { validate } : {}),\n ...(seccode ? { seccode } : {}),\n };\n\n const fields = challenge || validate || seccode\n ? `geetest_challenge: ${challenge ?? '(none)'}\\n`\n + `geetest_validate: ${validate ?? '(none)'}\\n`\n + `geetest_seccode: ${seccode ?? '(none)'}\\n`\n : `Raw answer: ${structured.code}\\n`;\n\n return {\n structured,\n text:\n `Solved GeeTest in ${structured.solveSeconds}s.\\n`\n + 'Post these back exactly as the site\\'s own front-end would:\\n'\n + fields\n + `(CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerImageTool = registerImageTool;
|
|
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
|
+
image: zod_1.z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1)
|
|
11
|
+
.describe('The captcha image: a local file path, an http(s) URL, a data: URI, or a raw '
|
|
12
|
+
+ 'base64 string.'),
|
|
13
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
14
|
+
});
|
|
15
|
+
function registerImageTool(server, ctx) {
|
|
16
|
+
server.registerTool('capskip_solve_image_captcha', {
|
|
17
|
+
title: 'Solve an image captcha',
|
|
18
|
+
description: 'Read the text out of a distorted-text captcha image. Returns the recognized '
|
|
19
|
+
+ 'text, which you type into the page\'s captcha field. Proxies are not '
|
|
20
|
+
+ 'supported for image captchas.',
|
|
21
|
+
inputSchema,
|
|
22
|
+
outputSchema: schemas_js_1.baseSolveOutput,
|
|
23
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
24
|
+
// omitting it advertised this tool as potentially destructive and cost it
|
|
25
|
+
// auto-approval in clients that read the hint. Solving a captcha destroys
|
|
26
|
+
// nothing. idempotentHint is deliberately left at its false default: each
|
|
27
|
+
// call consumes a fresh, single-use challenge.
|
|
28
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
29
|
+
}, async (args, extra) => {
|
|
30
|
+
const timeout = args.timeout ?? ctx.config.defaultTimeout;
|
|
31
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: 'Solving image captcha', timeoutSeconds: timeout },
|
|
32
|
+
// No options object: normal() rejects every option except `json`, so
|
|
33
|
+
// passing { timeout } here would throw. runSolve applied the timeout to
|
|
34
|
+
// the client instead.
|
|
35
|
+
(client) => client.normal(args.image), (result, seconds) => {
|
|
36
|
+
const structured = {
|
|
37
|
+
captchaId: String(result.captchaId ?? ''),
|
|
38
|
+
code: String(result.code ?? ''),
|
|
39
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
40
|
+
};
|
|
41
|
+
return {
|
|
42
|
+
structured,
|
|
43
|
+
text: `Solved the image captcha in ${structured.solveSeconds}s.\n`
|
|
44
|
+
+ `Text: ${structured.code}\n`
|
|
45
|
+
+ `(CapSkip captcha id ${structured.captchaId})`,
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/tools/image.ts"],"names":[],"mappings":";;AAkBA,8CA8CC;AA/DD,6BAAwB;AAExB,8CAA+D;AAC/D,0CAAuC;AAGvC,MAAM,WAAW,GAAG,OAAC,CAAC,YAAY,CAAC;IACjC,KAAK,EAAE,OAAC;SACL,MAAM,EAAE;SACR,GAAG,CAAC,CAAC,CAAC;SACN,QAAQ,CACP,8EAA8E;UAC5E,gBAAgB,CACnB;IACH,OAAO,EAAE,0BAAa;CACvB,CAAC,CAAC;AAEH,SAAgB,iBAAiB,CAAC,MAAiB,EAAE,GAAgB;IACnE,MAAM,CAAC,YAAY,CACjB,6BAA6B,EAC7B;QACE,KAAK,EAAE,wBAAwB;QAC/B,WAAW,EACT,8EAA8E;cAC5E,uEAAuE;cACvE,+BAA+B;QACnC,WAAW;QACX,YAAY,EAAE,4BAAe;QAC7B,wEAAwE;QACxE,0EAA0E;QAC1E,0EAA0E;QAC1E,0EAA0E;QAC1E,+CAA+C;QAC/C,WAAW,EAAE,EAAE,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE;KAClF,EACD,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE;QACpB,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,cAAc,CAAC;QAE1D,OAAO,IAAA,mBAAQ,EACb,GAAG,EACH,KAAK,EACL,EAAE,KAAK,EAAE,uBAAuB,EAAE,cAAc,EAAE,OAAO,EAAE;QAC3D,qEAAqE;QACrE,wEAAwE;QACxE,sBAAsB;QACtB,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EACrC,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;YAClB,MAAM,UAAU,GAAG;gBACjB,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC;gBACzC,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC/B,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;aACzC,CAAC;YACF,OAAO;gBACL,UAAU;gBACV,IAAI,EACF,+BAA+B,UAAU,CAAC,YAAY,MAAM;sBAC1D,SAAS,UAAU,CAAC,IAAI,IAAI;sBAC5B,uBAAuB,UAAU,CAAC,SAAS,GAAG;aACnD,CAAC;QACJ,CAAC,CACF,CAAC;IACJ,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { baseSolveOutput, timeoutSchema } from '../schemas.js';\nimport { runSolve } from '../solve.js';\nimport type { ToolContext } from '../solver.js';\n\nconst inputSchema = z.strictObject({\n image: z\n .string()\n .min(1)\n .describe(\n 'The captcha image: a local file path, an http(s) URL, a data: URI, or a raw '\n + 'base64 string.',\n ),\n timeout: timeoutSchema,\n});\n\nexport function registerImageTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_image_captcha',\n {\n title: 'Solve an image captcha',\n description:\n 'Read the text out of a distorted-text captcha image. Returns the recognized '\n + 'text, which you type into the page\\'s captcha field. Proxies are not '\n + 'supported for image captchas.',\n inputSchema,\n outputSchema: baseSolveOutput,\n // destructiveHint defaults to *true* whenever readOnlyHint is false, so\n // omitting it advertised this tool as potentially destructive and cost it\n // auto-approval in clients that read the hint. Solving a captcha destroys\n // nothing. idempotentHint is deliberately left at its false default: each\n // call consumes a fresh, single-use challenge.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args, extra) => {\n const timeout = args.timeout ?? ctx.config.defaultTimeout;\n\n return runSolve(\n ctx,\n extra,\n { label: 'Solving image captcha', timeoutSeconds: timeout },\n // No options object: normal() rejects every option except `json`, so\n // passing { timeout } here would throw. runSolve applied the timeout to\n // the client instead.\n (client) => client.normal(args.image),\n (result, seconds) => {\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n code: String(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n };\n return {\n structured,\n text:\n `Solved the image captcha in ${structured.solveSeconds}s.\\n`\n + `Text: ${structured.code}\\n`\n + `(CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerRecaptchaTool = registerRecaptchaTool;
|
|
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 key, from the widget's data-sitekey attribute or the grecaptcha config."),
|
|
12
|
+
url: schemas_js_1.pageUrlSchema,
|
|
13
|
+
version: zod_1.z
|
|
14
|
+
.enum(['v2', 'v3'])
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('Which reCAPTCHA generation the page uses. Defaults to v2.'),
|
|
17
|
+
invisible: zod_1.z
|
|
18
|
+
.boolean()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('v2 only. True when the widget renders with size=invisible.'),
|
|
21
|
+
enterprise: zod_1.z
|
|
22
|
+
.boolean()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('True for reCAPTCHA Enterprise. Works with both v2 and v3.'),
|
|
25
|
+
action: zod_1.z
|
|
26
|
+
.string()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("v3 only. The action passed to grecaptcha.execute(), e.g. 'login'."),
|
|
29
|
+
data_s: zod_1.z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe("v2 only. The data-s value, used by Google's own services. Rarely needed — "
|
|
33
|
+
+ 'CapSkip rejects it on a v3 submit.'),
|
|
34
|
+
proxy: schemas_js_1.proxySchema
|
|
35
|
+
.optional()
|
|
36
|
+
.describe('Solve through this proxy so the token is issued against its IP.'),
|
|
37
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
38
|
+
});
|
|
39
|
+
function registerRecaptchaTool(server, ctx) {
|
|
40
|
+
server.registerTool('capskip_solve_recaptcha', {
|
|
41
|
+
title: 'Solve reCAPTCHA',
|
|
42
|
+
description: 'Solve a Google reCAPTCHA v2 or v3 widget, including invisible and Enterprise '
|
|
43
|
+
+ 'variants. Returns a token to place in the page\'s "g-recaptcha-response" '
|
|
44
|
+
+ 'field before submitting the form. Read the sitekey from the page first — a '
|
|
45
|
+
+ 'guessed sitekey fails. Note that reCAPTCHA v3 returns a score assigned by '
|
|
46
|
+
+ 'Google; no solver can raise it.',
|
|
47
|
+
inputSchema,
|
|
48
|
+
outputSchema: schemas_js_1.baseSolveOutput,
|
|
49
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
50
|
+
// omitting it advertised this tool as potentially destructive and cost it
|
|
51
|
+
// auto-approval in clients that read the hint. Solving a captcha destroys
|
|
52
|
+
// nothing. idempotentHint is deliberately left at its false default: each
|
|
53
|
+
// call consumes a fresh, single-use challenge.
|
|
54
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
55
|
+
}, async (args, extra) => {
|
|
56
|
+
const timeout = args.timeout ?? ctx.config.recaptchaTimeout;
|
|
57
|
+
const version = args.version ?? 'v2';
|
|
58
|
+
// Forward only keys the caller actually set. The SDK validates by key
|
|
59
|
+
// presence, not value, so an `action` key set to undefined would still be
|
|
60
|
+
// rejected on a v2 submit. `invisible: false` and `enterprise: false` are
|
|
61
|
+
// the defaults, so they are omitted rather than sent as 0 — sending
|
|
62
|
+
// invisible:0 on a v3 request would trip the v3 unknown-key check.
|
|
63
|
+
const options = { version };
|
|
64
|
+
if (args.invisible === true)
|
|
65
|
+
options.invisible = 1;
|
|
66
|
+
if (args.enterprise === true)
|
|
67
|
+
options.enterprise = 1;
|
|
68
|
+
if (args.action !== undefined)
|
|
69
|
+
options.action = args.action;
|
|
70
|
+
if (args.data_s !== undefined)
|
|
71
|
+
options.data_s = args.data_s;
|
|
72
|
+
if (args.proxy !== undefined)
|
|
73
|
+
options.proxy = args.proxy;
|
|
74
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: `Solving reCAPTCHA ${version}`, timeoutSeconds: timeout }, (client) => client.recaptcha(args.sitekey, args.url, options), (result, seconds) => {
|
|
75
|
+
const structured = {
|
|
76
|
+
captchaId: String(result.captchaId ?? ''),
|
|
77
|
+
code: String(result.code ?? ''),
|
|
78
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
79
|
+
};
|
|
80
|
+
return {
|
|
81
|
+
structured,
|
|
82
|
+
text: `Solved reCAPTCHA ${version} in ${structured.solveSeconds}s.\n`
|
|
83
|
+
+ 'Put this token in the "g-recaptcha-response" field, then submit the form.\n'
|
|
84
|
+
+ `Token: ${structured.code}\n`
|
|
85
|
+
+ `(CapSkip captcha id ${structured.captchaId})`,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=recaptcha.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recaptcha.js","sourceRoot":"","sources":["../../src/tools/recaptcha.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { baseSolveOutput, 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(\"The site key, from the widget's data-sitekey attribute or the grecaptcha config.\"),\n url: pageUrlSchema,\n version: z\n .enum(['v2', 'v3'])\n .optional()\n .describe('Which reCAPTCHA generation the page uses. Defaults to v2.'),\n invisible: z\n .boolean()\n .optional()\n .describe('v2 only. True when the widget renders with size=invisible.'),\n enterprise: z\n .boolean()\n .optional()\n .describe('True for reCAPTCHA Enterprise. Works with both v2 and v3.'),\n action: z\n .string()\n .optional()\n .describe(\"v3 only. The action passed to grecaptcha.execute(), e.g. 'login'.\"),\n data_s: z\n .string()\n .optional()\n .describe(\n \"v2 only. The data-s value, used by Google's own services. Rarely needed — \"\n + 'CapSkip rejects it on a v3 submit.',\n ),\n proxy: proxySchema\n .optional()\n .describe('Solve through this proxy so the token is issued against its IP.'),\n timeout: timeoutSchema,\n});\n\nexport function registerRecaptchaTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_recaptcha',\n {\n title: 'Solve reCAPTCHA',\n description:\n 'Solve a Google reCAPTCHA v2 or v3 widget, including invisible and Enterprise '\n + 'variants. Returns a token to place in the page\\'s \"g-recaptcha-response\" '\n + 'field before submitting the form. Read the sitekey from the page first — a '\n + 'guessed sitekey fails. Note that reCAPTCHA v3 returns a score assigned by '\n + 'Google; no solver can raise it.',\n inputSchema,\n outputSchema: baseSolveOutput,\n // destructiveHint defaults to *true* whenever readOnlyHint is false, so\n // omitting it advertised this tool as potentially destructive and cost it\n // auto-approval in clients that read the hint. Solving a captcha destroys\n // nothing. idempotentHint is deliberately left at its false default: each\n // call consumes a fresh, single-use challenge.\n annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },\n },\n async (args, extra) => {\n const timeout = args.timeout ?? ctx.config.recaptchaTimeout;\n const version = args.version ?? 'v2';\n\n // Forward only keys the caller actually set. The SDK validates by key\n // presence, not value, so an `action` key set to undefined would still be\n // rejected on a v2 submit. `invisible: false` and `enterprise: false` are\n // the defaults, so they are omitted rather than sent as 0 — sending\n // invisible:0 on a v3 request would trip the v3 unknown-key check.\n const options: Record<string, unknown> = { version };\n if (args.invisible === true) options.invisible = 1;\n if (args.enterprise === true) options.enterprise = 1;\n if (args.action !== undefined) options.action = args.action;\n if (args.data_s !== undefined) options.data_s = args.data_s;\n if (args.proxy !== undefined) options.proxy = args.proxy;\n\n return runSolve(\n ctx,\n extra,\n { label: `Solving reCAPTCHA ${version}`, timeoutSeconds: timeout },\n (client) => client.recaptcha(args.sitekey, args.url, options as never),\n (result, seconds) => {\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n code: String(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n };\n return {\n structured,\n text:\n `Solved reCAPTCHA ${version} in ${structured.solveSeconds}s.\\n`\n + 'Put this token in the \"g-recaptcha-response\" field, then submit the form.\\n'\n + `Token: ${structured.code}\\n`\n + `(CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|