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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerStatusTool = registerStatusTool;
|
|
7
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
8
|
+
const zod_1 = require("zod");
|
|
9
|
+
const schemas_js_1 = require("../schemas.js");
|
|
10
|
+
const PROBE_TIMEOUT_MS = 3000;
|
|
11
|
+
// A CapSkip res.php reply is a few dozen bytes. Anything else on this port could
|
|
12
|
+
// stream indefinitely, so cap what we buffer and destroy the request once the
|
|
13
|
+
// cap is hit. That matters for time, not just memory: `timeout` below only
|
|
14
|
+
// fires on a gap in activity, so a peer that keeps sending data — resetting
|
|
15
|
+
// that inactivity clock — would otherwise hold the probe open indefinitely.
|
|
16
|
+
const MAX_BODY_BYTES = 4096;
|
|
17
|
+
const KEY_REJECTED = /ERROR_(WRONG_USER_KEY|KEY_DOES_NOT_EXIST)/;
|
|
18
|
+
function classify(statusCode, body) {
|
|
19
|
+
if (statusCode !== 200) {
|
|
20
|
+
return 'not-capskip';
|
|
21
|
+
}
|
|
22
|
+
return KEY_REJECTED.test(body) ? 'key-rejected' : 'ok';
|
|
23
|
+
}
|
|
24
|
+
function probe(host, port, apiKey) {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
const startedAt = Date.now();
|
|
27
|
+
const path = `/res.php?key=${encodeURIComponent(apiKey)}&action=get&id=0`;
|
|
28
|
+
// classify() only needs to see the first MAX_BODY_BYTES, so once a caller
|
|
29
|
+
// settles the probe further events (a late 'error' from destroy(), a
|
|
30
|
+
// stray 'end') must be ignored rather than resolving a second time.
|
|
31
|
+
let settled = false;
|
|
32
|
+
const settle = (result) => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return;
|
|
35
|
+
settled = true;
|
|
36
|
+
resolve(result);
|
|
37
|
+
};
|
|
38
|
+
const req = node_http_1.default.request({ host, port, path, method: 'GET', timeout: PROBE_TIMEOUT_MS }, (res) => {
|
|
39
|
+
const chunks = [];
|
|
40
|
+
let size = 0;
|
|
41
|
+
const finishWithBody = () => {
|
|
42
|
+
const body = Buffer.concat(chunks).toString('utf-8');
|
|
43
|
+
settle({
|
|
44
|
+
outcome: classify(res.statusCode, body),
|
|
45
|
+
latencyMs: Date.now() - startedAt,
|
|
46
|
+
statusCode: res.statusCode,
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
res.on('data', (chunk) => {
|
|
50
|
+
if (size >= MAX_BODY_BYTES)
|
|
51
|
+
return;
|
|
52
|
+
chunks.push(chunk);
|
|
53
|
+
size += chunk.length;
|
|
54
|
+
if (size >= MAX_BODY_BYTES) {
|
|
55
|
+
// Enough to classify — stop a peer that streams continuously from
|
|
56
|
+
// keeping the probe pending past PROBE_TIMEOUT_MS.
|
|
57
|
+
req.destroy();
|
|
58
|
+
finishWithBody();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
res.on('end', finishWithBody);
|
|
62
|
+
});
|
|
63
|
+
req.on('timeout', () => {
|
|
64
|
+
req.destroy();
|
|
65
|
+
settle({ outcome: 'no-response', error: `no response within ${PROBE_TIMEOUT_MS}ms` });
|
|
66
|
+
});
|
|
67
|
+
req.on('error', (err) => settle({ outcome: 'no-response', error: err.message }));
|
|
68
|
+
req.end();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function describe(result, host, port) {
|
|
72
|
+
switch (result.outcome) {
|
|
73
|
+
case 'ok':
|
|
74
|
+
return `CapSkip answered at ${host}:${port} in ${result.latencyMs}ms.`;
|
|
75
|
+
case 'key-rejected':
|
|
76
|
+
return (`CapSkip is running at ${host}:${port}, but it rejected the API key. Set `
|
|
77
|
+
+ 'CAPSKIP_API_KEY to the key shown in CapSkip settings, or disable key '
|
|
78
|
+
+ 'validation there. Solves will fail until this is fixed.');
|
|
79
|
+
case 'not-capskip':
|
|
80
|
+
return (`Something is listening on ${host}:${port} but it did not answer as CapSkip `
|
|
81
|
+
+ `(HTTP ${result.statusCode}). Check the API port in CapSkip settings, and `
|
|
82
|
+
+ 'that nothing else has taken that port — override with CAPSKIP_HOST / '
|
|
83
|
+
+ 'CAPSKIP_PORT.');
|
|
84
|
+
default:
|
|
85
|
+
return (`No response from ${host}:${port} (${result.error}). Start the CapSkip `
|
|
86
|
+
+ 'desktop app, then confirm its API port matches — override with '
|
|
87
|
+
+ 'CAPSKIP_HOST / CAPSKIP_PORT.');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function registerStatusTool(server, ctx) {
|
|
91
|
+
server.registerTool('capskip_status', {
|
|
92
|
+
title: 'Check CapSkip status',
|
|
93
|
+
description: 'Check whether the CapSkip desktop app is running and reachable. Call this '
|
|
94
|
+
+ 'first when a solve fails unexpectedly, to tell "CapSkip is not running" '
|
|
95
|
+
+ 'apart from "the sitekey was wrong". Takes no arguments.',
|
|
96
|
+
inputSchema: zod_1.z.strictObject({}),
|
|
97
|
+
outputSchema: schemas_js_1.statusOutput,
|
|
98
|
+
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
99
|
+
}, async () => {
|
|
100
|
+
const { host, port, apiKey } = ctx.config;
|
|
101
|
+
const result = await probe(host, port, apiKey);
|
|
102
|
+
// A rejected key still means CapSkip is there — the model should fix the
|
|
103
|
+
// key, not go looking for a process that is already running.
|
|
104
|
+
const reachable = result.outcome === 'ok' || result.outcome === 'key-rejected';
|
|
105
|
+
const detail = describe(result, host, port);
|
|
106
|
+
const structured = {
|
|
107
|
+
reachable,
|
|
108
|
+
host,
|
|
109
|
+
port,
|
|
110
|
+
...(result.latencyMs !== undefined ? { latencyMs: result.latencyMs } : {}),
|
|
111
|
+
detail,
|
|
112
|
+
};
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: 'text', text: detail }],
|
|
115
|
+
structuredContent: structured,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"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","sourcesContent":["import http from 'node:http';\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { statusOutput } from '../schemas.js';\nimport type { ToolContext } from '../solver.js';\n\nconst PROBE_TIMEOUT_MS = 3000;\n\n// A CapSkip res.php reply is a few dozen bytes. Anything else on this port could\n// stream indefinitely, so cap what we buffer and destroy the request once the\n// cap is hit. That matters for time, not just memory: `timeout` below only\n// fires on a gap in activity, so a peer that keeps sending data — resetting\n// that inactivity clock — would otherwise hold the probe open indefinitely.\nconst MAX_BODY_BYTES = 4096;\n\nconst KEY_REJECTED = /ERROR_(WRONG_USER_KEY|KEY_DOES_NOT_EXIST)/;\n\n/**\n * What the probe found. `reachable` alone is not enough: port 8080 is commonly\n * occupied, and a decoy that answers 404 to everything would otherwise be\n * reported as a healthy CapSkip moments before a solve fails against it.\n */\ntype ProbeOutcome =\n /** CapSkip answered normally. */\n | 'ok'\n /** CapSkip answered, but rejected the API key. It is running; the key is wrong. */\n | 'key-rejected'\n /** Something answered on this port, but not as CapSkip. */\n | 'not-capskip'\n /** Nothing answered at all. */\n | 'no-response';\n\ninterface Probe {\n outcome: ProbeOutcome;\n latencyMs?: number;\n statusCode?: number;\n error?: string;\n}\n\nfunction classify(statusCode: number | undefined, body: string): ProbeOutcome {\n if (statusCode !== 200) {\n return 'not-capskip';\n }\n return KEY_REJECTED.test(body) ? 'key-rejected' : 'ok';\n}\n\nfunction probe(host: string, port: number, apiKey: string): Promise<Probe> {\n return new Promise((resolve) => {\n const startedAt = Date.now();\n const path = `/res.php?key=${encodeURIComponent(apiKey)}&action=get&id=0`;\n\n // classify() only needs to see the first MAX_BODY_BYTES, so once a caller\n // settles the probe further events (a late 'error' from destroy(), a\n // stray 'end') must be ignored rather than resolving a second time.\n let settled = false;\n const settle = (result: Probe) => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n\n const req = http.request(\n { host, port, path, method: 'GET', timeout: PROBE_TIMEOUT_MS },\n (res) => {\n const chunks: Buffer[] = [];\n let size = 0;\n const finishWithBody = () => {\n const body = Buffer.concat(chunks).toString('utf-8');\n settle({\n outcome: classify(res.statusCode, body),\n latencyMs: Date.now() - startedAt,\n statusCode: res.statusCode,\n });\n };\n res.on('data', (chunk: Buffer) => {\n if (size >= MAX_BODY_BYTES) return;\n chunks.push(chunk);\n size += chunk.length;\n if (size >= MAX_BODY_BYTES) {\n // Enough to classify — stop a peer that streams continuously from\n // keeping the probe pending past PROBE_TIMEOUT_MS.\n req.destroy();\n finishWithBody();\n }\n });\n res.on('end', finishWithBody);\n },\n );\n\n req.on('timeout', () => {\n req.destroy();\n settle({ outcome: 'no-response', error: `no response within ${PROBE_TIMEOUT_MS}ms` });\n });\n req.on('error', (err: Error) => settle({ outcome: 'no-response', error: err.message }));\n req.end();\n });\n}\n\nfunction describe(result: Probe, host: string, port: number): string {\n switch (result.outcome) {\n case 'ok':\n return `CapSkip answered at ${host}:${port} in ${result.latencyMs}ms.`;\n case 'key-rejected':\n return (\n `CapSkip is running at ${host}:${port}, but it rejected the API key. Set `\n + 'CAPSKIP_API_KEY to the key shown in CapSkip settings, or disable key '\n + 'validation there. Solves will fail until this is fixed.'\n );\n case 'not-capskip':\n return (\n `Something is listening on ${host}:${port} but it did not answer as CapSkip `\n + `(HTTP ${result.statusCode}). Check the API port in CapSkip settings, and `\n + 'that nothing else has taken that port — override with CAPSKIP_HOST / '\n + 'CAPSKIP_PORT.'\n );\n default:\n return (\n `No response from ${host}:${port} (${result.error}). Start the CapSkip `\n + 'desktop app, then confirm its API port matches — override with '\n + 'CAPSKIP_HOST / CAPSKIP_PORT.'\n );\n }\n}\n\nexport function registerStatusTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_status',\n {\n title: 'Check CapSkip status',\n description:\n 'Check whether the CapSkip desktop app is running and reachable. Call this '\n + 'first when a solve fails unexpectedly, to tell \"CapSkip is not running\" '\n + 'apart from \"the sitekey was wrong\". Takes no arguments.',\n inputSchema: z.strictObject({}),\n outputSchema: statusOutput,\n annotations: { readOnlyHint: true, openWorldHint: true },\n },\n async () => {\n const { host, port, apiKey } = ctx.config;\n const result = await probe(host, port, apiKey);\n\n // A rejected key still means CapSkip is there — the model should fix the\n // key, not go looking for a process that is already running.\n const reachable = result.outcome === 'ok' || result.outcome === 'key-rejected';\n const detail = describe(result, host, port);\n\n const structured = {\n reachable,\n host,\n port,\n ...(result.latencyMs !== undefined ? { latencyMs: result.latencyMs } : {}),\n detail,\n };\n\n return {\n content: [{ type: 'text' as const, text: detail }],\n structuredContent: structured,\n };\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTurnstileTool = registerTurnstileTool;
|
|
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 Turnstile site key, from the widget's data-sitekey attribute."),
|
|
12
|
+
url: schemas_js_1.pageUrlSchema,
|
|
13
|
+
action: zod_1.z
|
|
14
|
+
.string()
|
|
15
|
+
.optional()
|
|
16
|
+
.describe('The action from data-action or turnstile.render().'),
|
|
17
|
+
cdata: zod_1.z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe('The cData value. Interstitial challenge pages only, not ordinary widgets.'),
|
|
21
|
+
pagedata: zod_1.z
|
|
22
|
+
.string()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe('The chlPageData value. Interstitial challenge pages only.'),
|
|
25
|
+
proxy: schemas_js_1.proxySchema
|
|
26
|
+
.optional()
|
|
27
|
+
.describe('Solve through this proxy so the token is issued against its IP.'),
|
|
28
|
+
timeout: schemas_js_1.timeoutSchema,
|
|
29
|
+
});
|
|
30
|
+
function registerTurnstileTool(server, ctx) {
|
|
31
|
+
server.registerTool('capskip_solve_turnstile', {
|
|
32
|
+
title: 'Solve Cloudflare Turnstile',
|
|
33
|
+
description: 'Solve a Cloudflare Turnstile widget or interstitial challenge page. Returns a '
|
|
34
|
+
+ 'token for the page\'s "cf-turnstile-response" field. IMPORTANT: submit the '
|
|
35
|
+
+ 'token using the returned userAgent — Cloudflare rejects a token replayed '
|
|
36
|
+
+ 'under a different User-Agent. For an interstitial challenge page, also pass '
|
|
37
|
+
+ 'cdata and pagedata read from the page.',
|
|
38
|
+
inputSchema,
|
|
39
|
+
outputSchema: schemas_js_1.turnstileOutput,
|
|
40
|
+
// destructiveHint defaults to *true* whenever readOnlyHint is false, so
|
|
41
|
+
// omitting it advertised this tool as potentially destructive and cost it
|
|
42
|
+
// auto-approval in clients that read the hint. Solving a captcha destroys
|
|
43
|
+
// nothing. idempotentHint is deliberately left at its false default: each
|
|
44
|
+
// call consumes a fresh, single-use challenge.
|
|
45
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
|
|
46
|
+
}, async (args, extra) => {
|
|
47
|
+
const timeout = args.timeout ?? ctx.config.recaptchaTimeout;
|
|
48
|
+
const options = {};
|
|
49
|
+
if (args.action !== undefined)
|
|
50
|
+
options.action = args.action;
|
|
51
|
+
if (args.cdata !== undefined)
|
|
52
|
+
options.data = args.cdata;
|
|
53
|
+
if (args.pagedata !== undefined)
|
|
54
|
+
options.pagedata = args.pagedata;
|
|
55
|
+
if (args.proxy !== undefined)
|
|
56
|
+
options.proxy = args.proxy;
|
|
57
|
+
return (0, solve_js_1.runSolve)(ctx, extra, { label: 'Solving Turnstile', timeoutSeconds: timeout }, (client) => client.turnstile(args.sitekey, args.url, options), (result, seconds) => {
|
|
58
|
+
const userAgent = result.userAgent;
|
|
59
|
+
const structured = {
|
|
60
|
+
captchaId: String(result.captchaId ?? ''),
|
|
61
|
+
code: String(result.code ?? ''),
|
|
62
|
+
solveSeconds: Number(seconds.toFixed(2)),
|
|
63
|
+
...(userAgent ? { userAgent } : {}),
|
|
64
|
+
};
|
|
65
|
+
const uaLine = userAgent
|
|
66
|
+
? `\nSend the token with this exact User-Agent: ${userAgent}`
|
|
67
|
+
: '';
|
|
68
|
+
return {
|
|
69
|
+
structured,
|
|
70
|
+
text: `Solved Turnstile in ${structured.solveSeconds}s.\n`
|
|
71
|
+
+ 'Put this token in the "cf-turnstile-response" field, then submit the form.\n'
|
|
72
|
+
+ `Token: ${structured.code}${uaLine}\n`
|
|
73
|
+
+ `(CapSkip captcha id ${structured.captchaId})`,
|
|
74
|
+
};
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=turnstile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turnstile.js","sourceRoot":"","sources":["../../src/tools/turnstile.ts"],"names":[],"mappings":"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","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { pageUrlSchema, proxySchema, timeoutSchema, turnstileOutput } 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 Turnstile site key, from the widget's data-sitekey attribute.\"),\n url: pageUrlSchema,\n action: z\n .string()\n .optional()\n .describe('The action from data-action or turnstile.render().'),\n cdata: z\n .string()\n .optional()\n .describe('The cData value. Interstitial challenge pages only, not ordinary widgets.'),\n pagedata: z\n .string()\n .optional()\n .describe('The chlPageData value. Interstitial challenge pages only.'),\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 registerTurnstileTool(server: McpServer, ctx: ToolContext): void {\n server.registerTool(\n 'capskip_solve_turnstile',\n {\n title: 'Solve Cloudflare Turnstile',\n description:\n 'Solve a Cloudflare Turnstile widget or interstitial challenge page. Returns a '\n + 'token for the page\\'s \"cf-turnstile-response\" field. IMPORTANT: submit the '\n + 'token using the returned userAgent — Cloudflare rejects a token replayed '\n + 'under a different User-Agent. For an interstitial challenge page, also pass '\n + 'cdata and pagedata read from the page.',\n inputSchema,\n outputSchema: turnstileOutput,\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.action !== undefined) options.action = args.action;\n if (args.cdata !== undefined) options.data = args.cdata;\n if (args.pagedata !== undefined) options.pagedata = args.pagedata;\n if (args.proxy !== undefined) options.proxy = args.proxy;\n\n return runSolve(\n ctx,\n extra,\n { label: 'Solving Turnstile', timeoutSeconds: timeout },\n (client) => client.turnstile(args.sitekey, args.url, options as never),\n (result, seconds) => {\n const userAgent = result.userAgent as string | undefined;\n const structured = {\n captchaId: String(result.captchaId ?? ''),\n code: String(result.code ?? ''),\n solveSeconds: Number(seconds.toFixed(2)),\n ...(userAgent ? { userAgent } : {}),\n };\n\n const uaLine = userAgent\n ? `\\nSend the token with this exact User-Agent: ${userAgent}`\n : '';\n\n return {\n structured,\n text:\n `Solved Turnstile in ${structured.solveSeconds}s.\\n`\n + 'Put this token in the \"cf-turnstile-response\" field, then submit the form.\\n'\n + `Token: ${structured.code}${uaLine}\\n`\n + `(CapSkip captcha id ${structured.captchaId})`,\n };\n },\n );\n },\n );\n}\n"]}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# CapSkip MCP Server — API Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for all five 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
|
+
|
|
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
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Tools at a glance
|
|
10
|
+
|
|
11
|
+
| Tool | Purpose | Proxy support |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| `capskip_status` | Check whether CapSkip is running and reachable | n/a |
|
|
14
|
+
| `capskip_solve_image_captcha` | Read the text out of a distorted-text captcha image | No |
|
|
15
|
+
| `capskip_solve_recaptcha` | Solve a Google reCAPTCHA v2 or v3 widget | Yes |
|
|
16
|
+
| `capskip_solve_turnstile` | Solve a Cloudflare Turnstile widget or challenge page | Yes |
|
|
17
|
+
| `capskip_solve_geetest` | Solve a GeeTest v3 slide-puzzle captcha | Yes |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## `capskip_status`
|
|
22
|
+
|
|
23
|
+
Check whether the CapSkip desktop app is running and reachable. Call this first when a solve fails unexpectedly, to tell "CapSkip is not running" apart from "the sitekey was wrong". Takes no arguments.
|
|
24
|
+
|
|
25
|
+
### Parameters
|
|
26
|
+
|
|
27
|
+
None. The tool rejects any argument it is passed, naming the key.
|
|
28
|
+
|
|
29
|
+
### Output shape
|
|
30
|
+
|
|
31
|
+
| Field | Type | Always present | Description |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| `reachable` | boolean | yes | Whether CapSkip itself answered |
|
|
34
|
+
| `host` | string | yes | Host that was probed |
|
|
35
|
+
| `port` | number | yes | Port that was probed |
|
|
36
|
+
| `latencyMs` | number | whenever anything answered | Round-trip time of the probe |
|
|
37
|
+
| `detail` | string | yes | Human-readable summary — also the tool's text output |
|
|
38
|
+
|
|
39
|
+
`capskip_status` never returns `isError: true` for an unreachable CapSkip; it reports the problem in `reachable` / `detail` instead, so the model can read the result rather than handle an error.
|
|
40
|
+
|
|
41
|
+
The probe reads the response, not merely the fact that one arrived. Four outcomes are distinguished:
|
|
42
|
+
|
|
43
|
+
| Situation | `reachable` | `detail` says |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| CapSkip answered normally | `true` | `CapSkip answered at <host>:<port> in <n>ms.` |
|
|
46
|
+
| CapSkip answered but rejected the API key | `true` | `CapSkip is running at <host>:<port>, but it rejected the API key. …` |
|
|
47
|
+
| Something else holds the port | `false` | `Something is listening on <host>:<port> but it did not answer as CapSkip (HTTP <code>). …` |
|
|
48
|
+
| Nothing answered | `false` | `No response from <host>:<port> (<error>). …` |
|
|
49
|
+
|
|
50
|
+
A rejected key counts as reachable on purpose: CapSkip *is* running, and the fix is the key, not the process.
|
|
51
|
+
|
|
52
|
+
### Example
|
|
53
|
+
|
|
54
|
+
Request: no arguments.
|
|
55
|
+
|
|
56
|
+
Response (`structuredContent`):
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"reachable": true,
|
|
61
|
+
"host": "127.0.0.1",
|
|
62
|
+
"port": 8080,
|
|
63
|
+
"latencyMs": 6,
|
|
64
|
+
"detail": "CapSkip answered at 127.0.0.1:8080 in 6ms."
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
When nothing is listening:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"reachable": false,
|
|
73
|
+
"host": "127.0.0.1",
|
|
74
|
+
"port": 8080,
|
|
75
|
+
"detail": "No response from 127.0.0.1:8080 (connect ECONNREFUSED 127.0.0.1:8080). Start the CapSkip desktop app, then confirm its API port matches — override with CAPSKIP_HOST / CAPSKIP_PORT."
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
When something else holds the port:
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"reachable": false,
|
|
84
|
+
"host": "127.0.0.1",
|
|
85
|
+
"port": 8080,
|
|
86
|
+
"latencyMs": 3,
|
|
87
|
+
"detail": "Something is listening on 127.0.0.1:8080 but it did not answer as CapSkip (HTTP 404). Check the API port in CapSkip settings, and that nothing else has taken that port — override with CAPSKIP_HOST / CAPSKIP_PORT."
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## `capskip_solve_image_captcha`
|
|
94
|
+
|
|
95
|
+
Read the text out of a distorted-text captcha image. Returns the recognized text, which you type into the page's captcha field. Proxies are not supported for image captchas.
|
|
96
|
+
|
|
97
|
+
### Parameters
|
|
98
|
+
|
|
99
|
+
| Name | Type | Required | Default | Description |
|
|
100
|
+
|---|---|---|---|---|
|
|
101
|
+
| `image` | string | Yes | — | "The captcha image: a local file path, an http(s) URL, a data: URI, or a raw base64 string." |
|
|
102
|
+
| `timeout` | integer | No | `CAPSKIP_TIMEOUT` (120) | "Seconds to wait before giving up. Maximum 600." |
|
|
103
|
+
|
|
104
|
+
There is no `proxy` field on this tool. Passing one is rejected as an unrecognized key — CapSkip does not support proxying image-captcha solves.
|
|
105
|
+
|
|
106
|
+
### Output shape
|
|
107
|
+
|
|
108
|
+
| Field | Type | Description |
|
|
109
|
+
|---|---|---|
|
|
110
|
+
| `captchaId` | string | CapSkip's internal id for this solve |
|
|
111
|
+
| `code` | string | The recognized text |
|
|
112
|
+
| `solveSeconds` | number | Wall-clock time the solve took |
|
|
113
|
+
|
|
114
|
+
### Example
|
|
115
|
+
|
|
116
|
+
Request:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"image": "https://example.com/captcha.png"
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Response (`structuredContent`):
|
|
125
|
+
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"captchaId": "48213",
|
|
129
|
+
"code": "8fx3k2",
|
|
130
|
+
"solveSeconds": 4.31
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Text output: `Solved the image captcha in 4.31s.\nText: 8fx3k2\n(CapSkip captcha id 48213)`
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## `capskip_solve_recaptcha`
|
|
139
|
+
|
|
140
|
+
Solve a Google reCAPTCHA v2 or v3 widget, including invisible and Enterprise variants. Returns a token to place in the page's `g-recaptcha-response` field before submitting the form. Read the sitekey from the page first — a guessed sitekey fails. Note that reCAPTCHA v3 returns a score assigned by Google; no solver can raise it.
|
|
141
|
+
|
|
142
|
+
### Parameters
|
|
143
|
+
|
|
144
|
+
| Name | Type | Required | Default | Description |
|
|
145
|
+
|---|---|---|---|---|
|
|
146
|
+
| `sitekey` | string | Yes | — | "The site key, from the widget's data-sitekey attribute or the grecaptcha config." |
|
|
147
|
+
| `url` | string (URL) | Yes | — | "Full URL of the page the captcha appears on, including scheme." |
|
|
148
|
+
| `version` | `"v2"` \| `"v3"` | No | `v2` | "Which reCAPTCHA generation the page uses. Defaults to v2." |
|
|
149
|
+
| `invisible` | boolean | No | `false` | "v2 only. True when the widget renders with size=invisible." |
|
|
150
|
+
| `enterprise` | boolean | No | `false` | "True for reCAPTCHA Enterprise. Works with both v2 and v3." |
|
|
151
|
+
| `action` | string | No | — | "v3 only. The action passed to grecaptcha.execute(), e.g. 'login'." |
|
|
152
|
+
| `data_s` | string | No | — | "v2 only. The data-s value, used by Google's own services. Rarely needed — CapSkip rejects it on a v3 submit." |
|
|
153
|
+
| `proxy` | object | No | — | "Solve through this proxy so the token is issued against its IP." |
|
|
154
|
+
| `timeout` | integer | No | `CAPSKIP_RECAPTCHA_TIMEOUT` (300) | "Seconds to wait before giving up. Maximum 600." |
|
|
155
|
+
|
|
156
|
+
`proxy` shape (shared with `capskip_solve_turnstile` and `capskip_solve_geetest`):
|
|
157
|
+
|
|
158
|
+
| Field | Type | Required | Description |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| `type` | `"HTTP"` \| `"HTTPS"` \| `"SOCKS5"` \| `"SOCKS5H"` | Yes | "Proxy scheme. CapSkip supports only these four." |
|
|
161
|
+
| `uri` | string | Yes | "Proxy address as `host:port` or `login:password@host:port`." |
|
|
162
|
+
|
|
163
|
+
**There is no `min_score` parameter.** CapSkip solves locally and returns whatever score Google assigns — it cannot re-solve to clear a threshold the way a cloud service can, so a `min_score` option would promise control that does not exist. Passing `min_score` is rejected as an unrecognized key (`Unrecognized key: "min_score"`), not silently dropped.
|
|
164
|
+
|
|
165
|
+
`action` applies only to `version: "v3"`; `invisible` applies only to `version: "v2"` (the default). Supplying either against the wrong version is rejected with a message naming the conflict — `'action' is only supported for reCAPTCHA v3.` and `invisible is only supported for reCAPTCHA v2.` respectively.
|
|
166
|
+
|
|
167
|
+
### Output shape
|
|
168
|
+
|
|
169
|
+
| Field | Type | Description |
|
|
170
|
+
|---|---|---|
|
|
171
|
+
| `captchaId` | string | CapSkip's internal id for this solve |
|
|
172
|
+
| `code` | string | The `g-recaptcha-response` token |
|
|
173
|
+
| `solveSeconds` | number | Wall-clock time the solve took |
|
|
174
|
+
|
|
175
|
+
### Example
|
|
176
|
+
|
|
177
|
+
Request (reCAPTCHA v3):
|
|
178
|
+
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"sitekey": "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-",
|
|
182
|
+
"url": "https://example.com/login",
|
|
183
|
+
"version": "v3",
|
|
184
|
+
"action": "submit"
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Response (`structuredContent`):
|
|
189
|
+
|
|
190
|
+
```json
|
|
191
|
+
{
|
|
192
|
+
"captchaId": "93841",
|
|
193
|
+
"code": "03AGdBq26f_score0.9token...",
|
|
194
|
+
"solveSeconds": 9.62
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Text output: `Solved reCAPTCHA v3 in 9.62s.\nPut this token in the "g-recaptcha-response" field, then submit the form.\nToken: 03AGdBq26f_score0.9token...\n(CapSkip captcha id 93841)`
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## `capskip_solve_turnstile`
|
|
203
|
+
|
|
204
|
+
Solve a Cloudflare Turnstile widget or interstitial challenge page. Returns a token for the page's `cf-turnstile-response` field. IMPORTANT: submit the token using the returned `userAgent` — Cloudflare rejects a token replayed under a different User-Agent. For an interstitial challenge page, also pass `cdata` and `pagedata` read from the page.
|
|
205
|
+
|
|
206
|
+
### Parameters
|
|
207
|
+
|
|
208
|
+
| Name | Type | Required | Default | Description |
|
|
209
|
+
|---|---|---|---|---|
|
|
210
|
+
| `sitekey` | string | Yes | — | "The Turnstile site key, from the widget's data-sitekey attribute." |
|
|
211
|
+
| `url` | string (URL) | Yes | — | "Full URL of the page the captcha appears on, including scheme." |
|
|
212
|
+
| `action` | string | No | — | "The action from data-action or turnstile.render()." |
|
|
213
|
+
| `cdata` | string | No | — | "The cData value. Interstitial challenge pages only, not ordinary widgets." |
|
|
214
|
+
| `pagedata` | string | No | — | "The chlPageData value. Interstitial challenge pages only." |
|
|
215
|
+
| `proxy` | object | No | — | "Solve through this proxy so the token is issued against its IP." |
|
|
216
|
+
| `timeout` | integer | No | `CAPSKIP_RECAPTCHA_TIMEOUT` (300) | "Seconds to wait before giving up. Maximum 600." |
|
|
217
|
+
|
|
218
|
+
`proxy` has the same `{ type, uri }` shape documented under `capskip_solve_recaptcha` above.
|
|
219
|
+
|
|
220
|
+
### Output shape
|
|
221
|
+
|
|
222
|
+
| Field | Type | Always present | Description |
|
|
223
|
+
|---|---|---|---|
|
|
224
|
+
| `captchaId` | string | yes | CapSkip's internal id for this solve |
|
|
225
|
+
| `code` | string | yes | The `cf-turnstile-response` token |
|
|
226
|
+
| `solveSeconds` | number | yes | Wall-clock time the solve took |
|
|
227
|
+
| `userAgent` | string | when CapSkip returns one | User-Agent the token must be submitted with |
|
|
228
|
+
|
|
229
|
+
### Example
|
|
230
|
+
|
|
231
|
+
Request:
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"sitekey": "0x4AAAAAAA...",
|
|
236
|
+
"url": "https://example.com"
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Response (`structuredContent`):
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"captchaId": "77120",
|
|
245
|
+
"code": "0.k9J3n...token",
|
|
246
|
+
"solveSeconds": 6.05,
|
|
247
|
+
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ..."
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Text output: `Solved Turnstile in 6.05s.\nPut this token in the "cf-turnstile-response" field, then submit the form.\nToken: 0.k9J3n...token\nSend the token with this exact User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...\n(CapSkip captcha id 77120)`
|
|
252
|
+
|
|
253
|
+
For a challenge page, add `cdata` and `pagedata` read from the page:
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"sitekey": "0x4AAAAAAA...",
|
|
258
|
+
"url": "https://example.com/challenge",
|
|
259
|
+
"action": "managed",
|
|
260
|
+
"cdata": "0=abc123...",
|
|
261
|
+
"pagedata": "3fH...pagedata"
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## `capskip_solve_geetest`
|
|
268
|
+
|
|
269
|
+
Solve a GeeTest v3 slide-puzzle captcha. Returns `geetest_challenge`, `geetest_validate`, and `geetest_seccode` to post back exactly as the site's own front-end would. IMPORTANT: the challenge value is single-use and expires in roughly a minute, so fetch `gt` and `challenge` from the page immediately before calling. A stale challenge is the most common failure.
|
|
270
|
+
|
|
271
|
+
### Parameters
|
|
272
|
+
|
|
273
|
+
| Name | Type | Required | Default | Description |
|
|
274
|
+
|---|---|---|---|---|
|
|
275
|
+
| `gt` | string | Yes | — | "The gt value. Static per site, so it can be reused." |
|
|
276
|
+
| `challenge` | string | Yes | — | "The challenge value. Single-use and expires in about a minute — fetch a fresh one immediately before calling this." |
|
|
277
|
+
| `url` | string (URL) | Yes | — | "Full URL of the page the captcha appears on, including scheme." |
|
|
278
|
+
| `api_server` | string | No | — | "A non-default GeeTest API domain, e.g. 'api-na.geetest.com'." |
|
|
279
|
+
| `proxy` | object | No | — | "Solve through this proxy so the answer is produced from its IP." |
|
|
280
|
+
| `timeout` | integer | No | `CAPSKIP_RECAPTCHA_TIMEOUT` (300) | "Seconds to wait before giving up. Maximum 600." |
|
|
281
|
+
|
|
282
|
+
`proxy` has the same `{ type, uri }` shape documented under `capskip_solve_recaptcha` above.
|
|
283
|
+
|
|
284
|
+
### Output shape
|
|
285
|
+
|
|
286
|
+
| Field | Type | Always present | Description |
|
|
287
|
+
|---|---|---|---|
|
|
288
|
+
| `captchaId` | string | yes | CapSkip's internal id for this solve |
|
|
289
|
+
| `code` | string | yes | The raw JSON string CapSkip returns |
|
|
290
|
+
| `solveSeconds` | number | yes | Wall-clock time the solve took |
|
|
291
|
+
| `challenge` | string | when parsed from the answer | `geetest_challenge` to post back |
|
|
292
|
+
| `validate` | string | when parsed from the answer | `geetest_validate` to post back |
|
|
293
|
+
| `seccode` | string | when parsed from the answer | `geetest_seccode` to post back |
|
|
294
|
+
|
|
295
|
+
### Example
|
|
296
|
+
|
|
297
|
+
Request:
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
{
|
|
301
|
+
"gt": "81388ea1fc187e0c335c0a8907ff2625",
|
|
302
|
+
"challenge": "7cf6a8b1a2c34d5e6f7089abcdef0123",
|
|
303
|
+
"url": "https://example.com/login"
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Response (`structuredContent`):
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"captchaId": "10432",
|
|
312
|
+
"code": "{\"geetest_challenge\":\"7cf6a8b1...\",\"geetest_validate\":\"a1b2c3...\",\"geetest_seccode\":\"a1b2c3...|jordan\"}",
|
|
313
|
+
"solveSeconds": 14.2,
|
|
314
|
+
"challenge": "7cf6a8b1a2c34d5e6f7089abcdef0123",
|
|
315
|
+
"validate": "a1b2c3d4e5f6...",
|
|
316
|
+
"seccode": "a1b2c3d4e5f6...|jordan"
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Text output: `Solved GeeTest in 14.2s.\nPost these back exactly as the site's own front-end would:\ngeetest_challenge: 7cf6a8b1a2c34d5e6f7089abcdef0123\ngeetest_validate: a1b2c3d4e5f6...\ngeetest_seccode: a1b2c3d4e5f6...|jordan\n(CapSkip captcha id 10432)`
|
|
321
|
+
|
|
322
|
+
---
|
|
323
|
+
|
|
324
|
+
## Validation errors
|
|
325
|
+
|
|
326
|
+
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:
|
|
327
|
+
|
|
328
|
+
| Situation | Message contains |
|
|
329
|
+
|---|---|
|
|
330
|
+
| Unknown key, e.g. `min_score` on `capskip_solve_recaptcha` | `Unrecognized key: "min_score"` |
|
|
331
|
+
| Missing required key, e.g. `image` omitted | `... at image` |
|
|
332
|
+
| `timeout` above 600 | `... expected number to be <=600 at timeout` |
|
|
333
|
+
| `url` without a scheme | `Invalid URL at url` |
|
|
334
|
+
| Unsupported `proxy.type`, e.g. `SOCKS4` | `Invalid option: expected one of "HTTP"\|"HTTPS"\|"SOCKS5"\|"SOCKS5H" at proxy.type` |
|
|
335
|
+
|
|
336
|
+
The 600-second cap applies only to the `timeout` argument on an individual tool call — it is rejected outright above 600, never silently reduced. `CAPSKIP_TIMEOUT` and `CAPSKIP_RECAPTCHA_TIMEOUT`, which supply the *default* when `timeout` is omitted, are a separate setting validated at startup with their own bound (1–3600 seconds); a default above 600 is accepted and simply is not capped by the per-call schema.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## See also
|
|
341
|
+
|
|
342
|
+
- [Tutorial](TUTORIAL.md) — recognizing each captcha type on a page and placing the answer
|
|
343
|
+
- [Getting Started](GETTING_STARTED.md) — installation and client setup
|
|
344
|
+
- [Troubleshooting](TROUBLESHOOTING.md) — fixes for common errors
|
|
345
|
+
- [CapSkip API docs](https://capskip.com/api-docs/) — the raw HTTP API `capskip-mcp` wraps
|