@gatesolve/mcp-server 0.1.1 → 0.2.1
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/dist/index.js +186 -10
- package/package.json +11 -2
- package/src/index.ts +209 -10
package/dist/index.js
CHANGED
|
@@ -5,31 +5,44 @@ import { z } from "zod";
|
|
|
5
5
|
const GATESOLVE_API = process.env.GATESOLVE_API_URL || "https://gatesolve.dev";
|
|
6
6
|
const server = new McpServer({
|
|
7
7
|
name: "gatesolve",
|
|
8
|
-
version: "0.
|
|
8
|
+
version: "0.2.0",
|
|
9
9
|
});
|
|
10
10
|
// Tool: solve_captcha
|
|
11
|
-
server.tool("solve_captcha", "Solve a CAPTCHA using GateSolve. Supports Cloudflare Turnstile, reCAPTCHA v2/v3, and hCaptcha.
|
|
11
|
+
server.tool("solve_captcha", "Solve a CAPTCHA using GateSolve. Supports Cloudflare Turnstile, reCAPTCHA v2/v3, and hCaptcha. Uses async submit-poll flow. Requires GATESOLVE_API_KEY env var (free tier: 100 solves) or paymentToken for MPP.", {
|
|
12
12
|
type: z
|
|
13
13
|
.enum(["cloudflare-turnstile", "recaptcha-v2", "recaptcha-v3", "hcaptcha"])
|
|
14
14
|
.describe("The type of CAPTCHA to solve"),
|
|
15
15
|
siteKey: z.string().describe("The site key / widget key for the CAPTCHA"),
|
|
16
16
|
pageUrl: z.string().url().describe("The URL of the page containing the CAPTCHA"),
|
|
17
|
+
callbackUrl: z
|
|
18
|
+
.string()
|
|
19
|
+
.url()
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Optional webhook URL to receive result instead of polling"),
|
|
17
22
|
paymentToken: z
|
|
18
23
|
.string()
|
|
19
24
|
.optional()
|
|
20
|
-
.describe("x402 payment token (USDC on Base). If omitted,
|
|
21
|
-
}, async ({ type, siteKey, pageUrl, paymentToken }) => {
|
|
25
|
+
.describe("x402 payment token (USDC on Base). If omitted, uses API key free tier."),
|
|
26
|
+
}, async ({ type, siteKey, pageUrl, callbackUrl, paymentToken }) => {
|
|
22
27
|
try {
|
|
28
|
+
const apiKey = process.env.GATESOLVE_API_KEY;
|
|
23
29
|
const headers = {
|
|
24
30
|
"Content-Type": "application/json",
|
|
25
31
|
};
|
|
32
|
+
if (apiKey) {
|
|
33
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
34
|
+
}
|
|
26
35
|
if (paymentToken) {
|
|
27
36
|
headers["X-402-Pay"] = paymentToken;
|
|
28
37
|
}
|
|
29
|
-
|
|
38
|
+
// Submit solve request
|
|
39
|
+
const body = { type, siteKey, pageUrl };
|
|
40
|
+
if (callbackUrl)
|
|
41
|
+
body.callbackUrl = callbackUrl;
|
|
42
|
+
const response = await fetch(`${GATESOLVE_API}/api/solve`, {
|
|
30
43
|
method: "POST",
|
|
31
44
|
headers,
|
|
32
|
-
body: JSON.stringify(
|
|
45
|
+
body: JSON.stringify(body),
|
|
33
46
|
});
|
|
34
47
|
const data = await response.json();
|
|
35
48
|
if (response.status === 402) {
|
|
@@ -37,26 +50,70 @@ server.tool("solve_captcha", "Solve a CAPTCHA using GateSolve. Supports Cloudfla
|
|
|
37
50
|
content: [
|
|
38
51
|
{
|
|
39
52
|
type: "text",
|
|
40
|
-
text: `
|
|
53
|
+
text: `Authentication required.\n\nSet GATESOLVE_API_KEY env var to your gs_xxx key (get one free at POST ${GATESOLVE_API}/api/waitlist).\n\nOr use MPP payment: ${data.mpp?.endpoint || `${GATESOLVE_API}/api/mpp/solve`}`,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (response.status === 401) {
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: "text",
|
|
63
|
+
text: `Invalid API key. Get a free one: POST ${GATESOLVE_API}/api/waitlist with {"email": "your@email.com"}`,
|
|
41
64
|
},
|
|
42
65
|
],
|
|
66
|
+
isError: true,
|
|
43
67
|
};
|
|
44
68
|
}
|
|
45
|
-
if (response.status
|
|
69
|
+
if (response.status !== 202) {
|
|
46
70
|
return {
|
|
47
71
|
content: [
|
|
48
72
|
{
|
|
49
73
|
type: "text",
|
|
50
|
-
text: `
|
|
74
|
+
text: `Error: ${data.message || data.error || "Unknown error"} (status: ${response.status})`,
|
|
51
75
|
},
|
|
52
76
|
],
|
|
77
|
+
isError: true,
|
|
53
78
|
};
|
|
54
79
|
}
|
|
80
|
+
// Poll for result (max 60 seconds)
|
|
81
|
+
const solveId = data.id;
|
|
82
|
+
const pollUrl = `${GATESOLVE_API}/api/solve?id=${solveId}`;
|
|
83
|
+
const maxAttempts = 20;
|
|
84
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
85
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
86
|
+
const pollResp = await fetch(pollUrl);
|
|
87
|
+
const pollData = await pollResp.json();
|
|
88
|
+
if (pollData.status === "solved") {
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `CAPTCHA solved successfully!\n\nToken: ${pollData.token}\nType: ${pollData.type}\nSolved in: ${pollData.solvedIn}\nSolve ID: ${solveId}`,
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (pollData.status === "failed") {
|
|
99
|
+
let failText = `CAPTCHA solve failed.\n\nSolve ID: ${solveId}\nType: ${type}`;
|
|
100
|
+
if (pollData.failure) {
|
|
101
|
+
failText += `\nReason: ${pollData.failure.reason}`;
|
|
102
|
+
failText += `\nSuggestion: ${pollData.failure.suggestion}`;
|
|
103
|
+
}
|
|
104
|
+
if (pollData.hint)
|
|
105
|
+
failText += `\nHint: ${pollData.hint}`;
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: "text", text: failText }],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
55
112
|
return {
|
|
56
113
|
content: [
|
|
57
114
|
{
|
|
58
115
|
type: "text",
|
|
59
|
-
text: `
|
|
116
|
+
text: `Solve timed out after 60 seconds. You can continue polling manually:\nGET ${pollUrl}`,
|
|
60
117
|
},
|
|
61
118
|
],
|
|
62
119
|
isError: true,
|
|
@@ -74,6 +131,125 @@ server.tool("solve_captcha", "Solve a CAPTCHA using GateSolve. Supports Cloudfla
|
|
|
74
131
|
};
|
|
75
132
|
}
|
|
76
133
|
});
|
|
134
|
+
// Tool: detect_block
|
|
135
|
+
server.tool("detect_block", "Check what kind of access block a URL has before attempting to solve. Returns classification (public-ok, js-challenge, captcha, auth-wall, blocked-silent, rate-limited, error), solvability, and recommendations. No API key required.", {
|
|
136
|
+
url: z.string().url().describe("The URL to check for access blocks"),
|
|
137
|
+
}, async ({ url }) => {
|
|
138
|
+
try {
|
|
139
|
+
const response = await fetch(`${GATESOLVE_API}/api/detect?url=${encodeURIComponent(url)}`);
|
|
140
|
+
const data = await response.json();
|
|
141
|
+
return {
|
|
142
|
+
content: [
|
|
143
|
+
{
|
|
144
|
+
type: "text",
|
|
145
|
+
text: `Block Detection for ${url}:\n\nClassification: ${data.classification}\nHTTP Status: ${data.httpStatus}\nSolvable: ${data.solvable ? "Yes" : "No"}${data.captchaType ? `\nCAPTCHA Type: ${data.captchaType}` : ""}${data.confidence !== undefined ? `\nConfidence: ${(data.confidence * 100).toFixed(0)}%` : ""}${data.recommendation ? `\n\nRecommendation: ${data.recommendation}` : ""}${data.recovery ? `\nMax Retries: ${data.recovery.maxRetries}, Backoff: ${data.recovery.backoffSeconds}s` : ""}`,
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{
|
|
154
|
+
type: "text",
|
|
155
|
+
text: `Failed to detect block: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
isError: true,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
// Tool: check_usage
|
|
163
|
+
server.tool("check_usage", "Check your API key quota — how many solves used, remaining, and limit. Requires GATESOLVE_API_KEY environment variable.", {}, async () => {
|
|
164
|
+
const apiKey = process.env.GATESOLVE_API_KEY;
|
|
165
|
+
if (!apiKey) {
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: "text",
|
|
170
|
+
text: "GATESOLVE_API_KEY environment variable not set. Set it to your gs_xxx API key to check usage.",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
isError: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const response = await fetch(`${GATESOLVE_API}/api/v1/usage`, {
|
|
178
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
179
|
+
});
|
|
180
|
+
const data = await response.json();
|
|
181
|
+
if (response.status === 401) {
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `Invalid API key. ${data.message || ""}`,
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
isError: true,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
content: [
|
|
194
|
+
{
|
|
195
|
+
type: "text",
|
|
196
|
+
text: `GateSolve Usage:\n\nUsed: ${data.used}/${data.limit} solves\nRemaining: ${data.remaining}\nPercent Used: ${data.percentUsed}\nPlan: ${data.plan}${data.upgradeInfo ? `\n\n⚠️ ${data.upgradeInfo}` : ""}`,
|
|
197
|
+
},
|
|
198
|
+
],
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: `Failed to check usage: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
isError: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
// Tool: dry_run_solve
|
|
214
|
+
server.tool("dry_run_solve", "Validate a CAPTCHA solve request without consuming credits or creating a job. Returns whether the request is valid, estimated time and cost, and type-specific warnings. No API key required.", {
|
|
215
|
+
type: z
|
|
216
|
+
.enum(["cloudflare-turnstile", "recaptcha-v2", "recaptcha-v3", "hcaptcha"])
|
|
217
|
+
.describe("The type of CAPTCHA to solve"),
|
|
218
|
+
siteKey: z.string().describe("The site key for the CAPTCHA"),
|
|
219
|
+
pageUrl: z.string().url().describe("The URL of the page containing the CAPTCHA"),
|
|
220
|
+
}, async ({ type, siteKey, pageUrl }) => {
|
|
221
|
+
try {
|
|
222
|
+
const response = await fetch(`${GATESOLVE_API}/api/v1/solve/dry-run`, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: { "Content-Type": "application/json" },
|
|
225
|
+
body: JSON.stringify({ type, siteKey, pageUrl }),
|
|
226
|
+
});
|
|
227
|
+
const data = await response.json();
|
|
228
|
+
let text = `Dry Run Result: ${data.valid ? "✅ Valid" : "❌ Invalid"}\n`;
|
|
229
|
+
if (data.issues?.length)
|
|
230
|
+
text += `\nIssues:\n${data.issues.map((i) => `- ${i}`).join("\n")}`;
|
|
231
|
+
if (data.warnings?.length)
|
|
232
|
+
text += `\nWarnings:\n${data.warnings.map((w) => `- ${w}`).join("\n")}`;
|
|
233
|
+
if (data.estimate)
|
|
234
|
+
text += `\n\nEstimate:\n- Solve time: ${data.estimate.solveTimeSeconds}s\n- Price: $${data.estimate.priceUsd} USDC\n- Method: ${data.estimate.method}`;
|
|
235
|
+
if (data.nextStep)
|
|
236
|
+
text += `\n\n${data.nextStep}`;
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: "text", text }],
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
return {
|
|
243
|
+
content: [
|
|
244
|
+
{
|
|
245
|
+
type: "text",
|
|
246
|
+
text: `Failed to dry run: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
isError: true,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
});
|
|
77
253
|
// Tool: list_captcha_types
|
|
78
254
|
server.tool("list_captcha_types", "List all supported CAPTCHA types with pricing, accuracy, and required fields.", {}, async () => {
|
|
79
255
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gatesolve/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"mcpName": "io.github.arsonx-dev/gatesolve-mcp",
|
|
5
5
|
"description": "MCP server for GateSolve CAPTCHA solving via x402 micropayments",
|
|
6
6
|
"type": "module",
|
|
@@ -29,5 +29,14 @@
|
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"typescript": "^5.0.0",
|
|
31
31
|
"@types/node": "^20.0.0"
|
|
32
|
-
}
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/arsonx-dev/gatesolve-mcp"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://gatesolve.dev",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/arsonx-dev/gatesolve-mcp/issues"
|
|
40
|
+
},
|
|
41
|
+
"author": "GateSolve <arson@agentmail.to> (https://gatesolve.dev)"
|
|
33
42
|
}
|
package/src/index.ts
CHANGED
|
@@ -8,40 +8,53 @@ const GATESOLVE_API = process.env.GATESOLVE_API_URL || "https://gatesolve.dev";
|
|
|
8
8
|
|
|
9
9
|
const server = new McpServer({
|
|
10
10
|
name: "gatesolve",
|
|
11
|
-
version: "0.
|
|
11
|
+
version: "0.2.0",
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
// Tool: solve_captcha
|
|
15
15
|
server.tool(
|
|
16
16
|
"solve_captcha",
|
|
17
|
-
"Solve a CAPTCHA using GateSolve. Supports Cloudflare Turnstile, reCAPTCHA v2/v3, and hCaptcha.
|
|
17
|
+
"Solve a CAPTCHA using GateSolve. Supports Cloudflare Turnstile, reCAPTCHA v2/v3, and hCaptcha. Uses async submit-poll flow. Requires GATESOLVE_API_KEY env var (free tier: 100 solves) or paymentToken for MPP.",
|
|
18
18
|
{
|
|
19
19
|
type: z
|
|
20
20
|
.enum(["cloudflare-turnstile", "recaptcha-v2", "recaptcha-v3", "hcaptcha"])
|
|
21
21
|
.describe("The type of CAPTCHA to solve"),
|
|
22
22
|
siteKey: z.string().describe("The site key / widget key for the CAPTCHA"),
|
|
23
23
|
pageUrl: z.string().url().describe("The URL of the page containing the CAPTCHA"),
|
|
24
|
+
callbackUrl: z
|
|
25
|
+
.string()
|
|
26
|
+
.url()
|
|
27
|
+
.optional()
|
|
28
|
+
.describe("Optional webhook URL to receive result instead of polling"),
|
|
24
29
|
paymentToken: z
|
|
25
30
|
.string()
|
|
26
31
|
.optional()
|
|
27
32
|
.describe(
|
|
28
|
-
"x402 payment token (USDC on Base). If omitted,
|
|
33
|
+
"x402 payment token (USDC on Base). If omitted, uses API key free tier."
|
|
29
34
|
),
|
|
30
35
|
},
|
|
31
|
-
async ({ type, siteKey, pageUrl, paymentToken }) => {
|
|
36
|
+
async ({ type, siteKey, pageUrl, callbackUrl, paymentToken }) => {
|
|
32
37
|
try {
|
|
38
|
+
const apiKey = process.env.GATESOLVE_API_KEY;
|
|
33
39
|
const headers: Record<string, string> = {
|
|
34
40
|
"Content-Type": "application/json",
|
|
35
41
|
};
|
|
36
42
|
|
|
43
|
+
if (apiKey) {
|
|
44
|
+
headers["Authorization"] = `Bearer ${apiKey}`;
|
|
45
|
+
}
|
|
37
46
|
if (paymentToken) {
|
|
38
47
|
headers["X-402-Pay"] = paymentToken;
|
|
39
48
|
}
|
|
40
49
|
|
|
41
|
-
|
|
50
|
+
// Submit solve request
|
|
51
|
+
const body: Record<string, string> = { type, siteKey, pageUrl };
|
|
52
|
+
if (callbackUrl) body.callbackUrl = callbackUrl;
|
|
53
|
+
|
|
54
|
+
const response = await fetch(`${GATESOLVE_API}/api/solve`, {
|
|
42
55
|
method: "POST",
|
|
43
56
|
headers,
|
|
44
|
-
body: JSON.stringify(
|
|
57
|
+
body: JSON.stringify(body),
|
|
45
58
|
});
|
|
46
59
|
|
|
47
60
|
const data = await response.json();
|
|
@@ -51,28 +64,76 @@ server.tool(
|
|
|
51
64
|
content: [
|
|
52
65
|
{
|
|
53
66
|
type: "text" as const,
|
|
54
|
-
text: `
|
|
67
|
+
text: `Authentication required.\n\nSet GATESOLVE_API_KEY env var to your gs_xxx key (get one free at POST ${GATESOLVE_API}/api/waitlist).\n\nOr use MPP payment: ${data.mpp?.endpoint || `${GATESOLVE_API}/api/mpp/solve`}`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (response.status === 401) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: "text" as const,
|
|
78
|
+
text: `Invalid API key. Get a free one: POST ${GATESOLVE_API}/api/waitlist with {"email": "your@email.com"}`,
|
|
55
79
|
},
|
|
56
80
|
],
|
|
81
|
+
isError: true,
|
|
57
82
|
};
|
|
58
83
|
}
|
|
59
84
|
|
|
60
|
-
if (response.status
|
|
85
|
+
if (response.status !== 202) {
|
|
61
86
|
return {
|
|
62
87
|
content: [
|
|
63
88
|
{
|
|
64
89
|
type: "text" as const,
|
|
65
|
-
text: `
|
|
90
|
+
text: `Error: ${data.message || data.error || "Unknown error"} (status: ${response.status})`,
|
|
66
91
|
},
|
|
67
92
|
],
|
|
93
|
+
isError: true,
|
|
68
94
|
};
|
|
69
95
|
}
|
|
70
96
|
|
|
97
|
+
// Poll for result (max 60 seconds)
|
|
98
|
+
const solveId = data.id;
|
|
99
|
+
const pollUrl = `${GATESOLVE_API}/api/solve?id=${solveId}`;
|
|
100
|
+
const maxAttempts = 20;
|
|
101
|
+
|
|
102
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
103
|
+
await new Promise((r) => setTimeout(r, 3000));
|
|
104
|
+
const pollResp = await fetch(pollUrl);
|
|
105
|
+
const pollData = await pollResp.json();
|
|
106
|
+
|
|
107
|
+
if (pollData.status === "solved") {
|
|
108
|
+
return {
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: "text" as const,
|
|
112
|
+
text: `CAPTCHA solved successfully!\n\nToken: ${pollData.token}\nType: ${pollData.type}\nSolved in: ${pollData.solvedIn}\nSolve ID: ${solveId}`,
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (pollData.status === "failed") {
|
|
119
|
+
let failText = `CAPTCHA solve failed.\n\nSolve ID: ${solveId}\nType: ${type}`;
|
|
120
|
+
if (pollData.failure) {
|
|
121
|
+
failText += `\nReason: ${pollData.failure.reason}`;
|
|
122
|
+
failText += `\nSuggestion: ${pollData.failure.suggestion}`;
|
|
123
|
+
}
|
|
124
|
+
if (pollData.hint) failText += `\nHint: ${pollData.hint}`;
|
|
125
|
+
return {
|
|
126
|
+
content: [{ type: "text" as const, text: failText }],
|
|
127
|
+
isError: true,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
71
132
|
return {
|
|
72
133
|
content: [
|
|
73
134
|
{
|
|
74
135
|
type: "text" as const,
|
|
75
|
-
text: `
|
|
136
|
+
text: `Solve timed out after 60 seconds. You can continue polling manually:\nGET ${pollUrl}`,
|
|
76
137
|
},
|
|
77
138
|
],
|
|
78
139
|
isError: true,
|
|
@@ -91,6 +152,144 @@ server.tool(
|
|
|
91
152
|
}
|
|
92
153
|
);
|
|
93
154
|
|
|
155
|
+
// Tool: detect_block
|
|
156
|
+
server.tool(
|
|
157
|
+
"detect_block",
|
|
158
|
+
"Check what kind of access block a URL has before attempting to solve. Returns classification (public-ok, js-challenge, captcha, auth-wall, blocked-silent, rate-limited, error), solvability, and recommendations. No API key required.",
|
|
159
|
+
{
|
|
160
|
+
url: z.string().url().describe("The URL to check for access blocks"),
|
|
161
|
+
},
|
|
162
|
+
async ({ url }) => {
|
|
163
|
+
try {
|
|
164
|
+
const response = await fetch(
|
|
165
|
+
`${GATESOLVE_API}/api/detect?url=${encodeURIComponent(url)}`
|
|
166
|
+
);
|
|
167
|
+
const data = await response.json();
|
|
168
|
+
|
|
169
|
+
return {
|
|
170
|
+
content: [
|
|
171
|
+
{
|
|
172
|
+
type: "text" as const,
|
|
173
|
+
text: `Block Detection for ${url}:\n\nClassification: ${data.classification}\nHTTP Status: ${data.httpStatus}\nSolvable: ${data.solvable ? "Yes" : "No"}${data.captchaType ? `\nCAPTCHA Type: ${data.captchaType}` : ""}${data.confidence !== undefined ? `\nConfidence: ${(data.confidence * 100).toFixed(0)}%` : ""}${data.recommendation ? `\n\nRecommendation: ${data.recommendation}` : ""}${data.recovery ? `\nMax Retries: ${data.recovery.maxRetries}, Backoff: ${data.recovery.backoffSeconds}s` : ""}`,
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
} catch (error) {
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: "text" as const,
|
|
182
|
+
text: `Failed to detect block: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
183
|
+
},
|
|
184
|
+
],
|
|
185
|
+
isError: true,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// Tool: check_usage
|
|
192
|
+
server.tool(
|
|
193
|
+
"check_usage",
|
|
194
|
+
"Check your API key quota — how many solves used, remaining, and limit. Requires GATESOLVE_API_KEY environment variable.",
|
|
195
|
+
{},
|
|
196
|
+
async () => {
|
|
197
|
+
const apiKey = process.env.GATESOLVE_API_KEY;
|
|
198
|
+
if (!apiKey) {
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: "text" as const,
|
|
203
|
+
text: "GATESOLVE_API_KEY environment variable not set. Set it to your gs_xxx API key to check usage.",
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
isError: true,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
try {
|
|
211
|
+
const response = await fetch(`${GATESOLVE_API}/api/v1/usage`, {
|
|
212
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
213
|
+
});
|
|
214
|
+
const data = await response.json();
|
|
215
|
+
|
|
216
|
+
if (response.status === 401) {
|
|
217
|
+
return {
|
|
218
|
+
content: [
|
|
219
|
+
{
|
|
220
|
+
type: "text" as const,
|
|
221
|
+
text: `Invalid API key. ${data.message || ""}`,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
isError: true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: "text" as const,
|
|
232
|
+
text: `GateSolve Usage:\n\nUsed: ${data.used}/${data.limit} solves\nRemaining: ${data.remaining}\nPercent Used: ${data.percentUsed}\nPlan: ${data.plan}${data.upgradeInfo ? `\n\n⚠️ ${data.upgradeInfo}` : ""}`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
content: [
|
|
239
|
+
{
|
|
240
|
+
type: "text" as const,
|
|
241
|
+
text: `Failed to check usage: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
242
|
+
},
|
|
243
|
+
],
|
|
244
|
+
isError: true,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Tool: dry_run_solve
|
|
251
|
+
server.tool(
|
|
252
|
+
"dry_run_solve",
|
|
253
|
+
"Validate a CAPTCHA solve request without consuming credits or creating a job. Returns whether the request is valid, estimated time and cost, and type-specific warnings. No API key required.",
|
|
254
|
+
{
|
|
255
|
+
type: z
|
|
256
|
+
.enum(["cloudflare-turnstile", "recaptcha-v2", "recaptcha-v3", "hcaptcha"])
|
|
257
|
+
.describe("The type of CAPTCHA to solve"),
|
|
258
|
+
siteKey: z.string().describe("The site key for the CAPTCHA"),
|
|
259
|
+
pageUrl: z.string().url().describe("The URL of the page containing the CAPTCHA"),
|
|
260
|
+
},
|
|
261
|
+
async ({ type, siteKey, pageUrl }) => {
|
|
262
|
+
try {
|
|
263
|
+
const response = await fetch(`${GATESOLVE_API}/api/v1/solve/dry-run`, {
|
|
264
|
+
method: "POST",
|
|
265
|
+
headers: { "Content-Type": "application/json" },
|
|
266
|
+
body: JSON.stringify({ type, siteKey, pageUrl }),
|
|
267
|
+
});
|
|
268
|
+
const data = await response.json();
|
|
269
|
+
|
|
270
|
+
let text = `Dry Run Result: ${data.valid ? "✅ Valid" : "❌ Invalid"}\n`;
|
|
271
|
+
if (data.issues?.length) text += `\nIssues:\n${data.issues.map((i: string) => `- ${i}`).join("\n")}`;
|
|
272
|
+
if (data.warnings?.length) text += `\nWarnings:\n${data.warnings.map((w: string) => `- ${w}`).join("\n")}`;
|
|
273
|
+
if (data.estimate) text += `\n\nEstimate:\n- Solve time: ${data.estimate.solveTimeSeconds}s\n- Price: $${data.estimate.priceUsd} USDC\n- Method: ${data.estimate.method}`;
|
|
274
|
+
if (data.nextStep) text += `\n\n${data.nextStep}`;
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
content: [{ type: "text" as const, text }],
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text" as const,
|
|
284
|
+
text: `Failed to dry run: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
isError: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
|
|
94
293
|
// Tool: list_captcha_types
|
|
95
294
|
server.tool(
|
|
96
295
|
"list_captcha_types",
|