@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.
Files changed (3) hide show
  1. package/dist/index.js +186 -10
  2. package/package.json +11 -2
  3. 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.1.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. Payment is handled via x402 micropayments (USDC on Base network).", {
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, returns payment requirements."),
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
- const response = await fetch(`${GATESOLVE_API}/api/v1/solve`, {
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({ type, siteKey, pageUrl }),
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: `Payment required to solve this CAPTCHA.\n\nPayment details:\n- Network: ${data.accepts?.network || "base"}\n- Token: ${data.accepts?.token || "USDC"}\n- Amount: ${data.accepts?.amount || "0.02"} USDC\n- Receiver: ${data.accepts?.receiver}\n- Protocol: x402 v${data.x402Version || "1.0"}\n\nProvide the payment token in the 'paymentToken' parameter to complete the solve.`,
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 === 200) {
69
+ if (response.status !== 202) {
46
70
  return {
47
71
  content: [
48
72
  {
49
73
  type: "text",
50
- text: `CAPTCHA solved successfully!\n\nToken: ${data.token}\nType: ${data.type}\nSolved in: ${data.solvedIn}\nStatus: ${data.status}`,
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: `Error: ${data.message || "Unknown error"} (status: ${response.status})`,
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.1.1",
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.1.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. Payment is handled via x402 micropayments (USDC on Base network).",
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, returns payment requirements."
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
- const response = await fetch(`${GATESOLVE_API}/api/v1/solve`, {
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({ type, siteKey, pageUrl }),
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: `Payment required to solve this CAPTCHA.\n\nPayment details:\n- Network: ${data.accepts?.network || "base"}\n- Token: ${data.accepts?.token || "USDC"}\n- Amount: ${data.accepts?.amount || "0.02"} USDC\n- Receiver: ${data.accepts?.receiver}\n- Protocol: x402 v${data.x402Version || "1.0"}\n\nProvide the payment token in the 'paymentToken' parameter to complete the solve.`,
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 === 200) {
85
+ if (response.status !== 202) {
61
86
  return {
62
87
  content: [
63
88
  {
64
89
  type: "text" as const,
65
- text: `CAPTCHA solved successfully!\n\nToken: ${data.token}\nType: ${data.type}\nSolved in: ${data.solvedIn}\nStatus: ${data.status}`,
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: `Error: ${data.message || "Unknown error"} (status: ${response.status})`,
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",