real-browser-mcp-server 1.1.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.
@@ -0,0 +1,59 @@
1
+ export const checkTurnstile = async ({ page }) => {
2
+ try {
3
+ const elements = await page.locator('[name="cf-turnstile-response"]').all();
4
+ if (elements.length <= 0) {
5
+ const coordinates = await page.evaluate(() => {
6
+ let coordinates = [];
7
+ document.querySelectorAll('div').forEach(item => {
8
+ try {
9
+ let itemCoordinates = item.getBoundingClientRect();
10
+ let itemCss = window.getComputedStyle(item);
11
+ if (itemCss.margin == "0px" && itemCss.padding == "0px" && itemCoordinates.width > 290 && itemCoordinates.width <= 310 && !item.querySelector('*')) {
12
+ coordinates.push({ x: itemCoordinates.x, y: item.getBoundingClientRect().y, w: item.getBoundingClientRect().width, h: item.getBoundingClientRect().height });
13
+ }
14
+ } catch (err) { }
15
+ });
16
+
17
+ if (coordinates.length <= 0) {
18
+ document.querySelectorAll('div').forEach(item => {
19
+ try {
20
+ let itemCoordinates = item.getBoundingClientRect();
21
+ if (itemCoordinates.width > 290 && itemCoordinates.width <= 310 && !item.querySelector('*')) {
22
+ coordinates.push({ x: itemCoordinates.x, y: item.getBoundingClientRect().y, w: item.getBoundingClientRect().width, h: item.getBoundingClientRect().height });
23
+ }
24
+ } catch (err) { }
25
+ });
26
+ }
27
+ return coordinates;
28
+ });
29
+
30
+ for (const item of coordinates) {
31
+ try {
32
+ let x = item.x + 30;
33
+ let y = item.y + item.h / 2;
34
+ await page.mouse.click(x, y);
35
+ } catch (err) { }
36
+ }
37
+ return true;
38
+ }
39
+
40
+ for (const element of elements) {
41
+ try {
42
+ const box = await element.evaluate(el => {
43
+ const parent = el.parentElement;
44
+ if (!parent) return null;
45
+ const rect = parent.getBoundingClientRect();
46
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
47
+ });
48
+ if (box) {
49
+ let x = box.x + 30;
50
+ let y = box.y + box.height / 2;
51
+ await page.mouse.click(x, y);
52
+ }
53
+ } catch (err) { }
54
+ }
55
+ return true;
56
+ } catch (err) {
57
+ return false;
58
+ }
59
+ }
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "real-browser-mcp-server",
3
+ "version": "1.1.1",
4
+ "description": "MCP Server for Real Browser - Patchright (undetected Playwright fork) with Stealth Mode, Ad Blocker, and Turnstile Auto-Solver for undetectable web automation.",
5
+ "main": "lib/cjs/index.js",
6
+ "module": "lib/esm/index.mjs",
7
+ "type": "commonjs",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./typings.d.ts",
11
+ "import": "./lib/esm/index.mjs",
12
+ "require": "./lib/cjs/index.js"
13
+ }
14
+ },
15
+ "typings": "typings.d.ts",
16
+ "engines": {
17
+ "node": ">=18.0.0"
18
+ },
19
+ "bin": {
20
+ "real-browser-mcp": "./src/index.js"
21
+ },
22
+ "scripts": {
23
+ "start": "node src/index.js",
24
+ "dev": "node src/index.js",
25
+ "mcp": "node src/index.js mcp",
26
+ "mcp:verbose": "node src/index.js mcp --verbose",
27
+ "list": "node src/index.js --list",
28
+ "test": "npm run cjs_test && npm run esm_test",
29
+ "esm_test": "node ./test/esm/test.js",
30
+ "cjs_test": "node ./test/cjs/test.js",
31
+ "build": "echo 'No build step required for this package.'"
32
+ },
33
+ "keywords": [
34
+ "mcp-server",
35
+ "model-context-protocol",
36
+ "patchright",
37
+ "playwright",
38
+ "stealth",
39
+ "cloudflare-bypass",
40
+ "turnstile-solver"
41
+ ],
42
+ "author": "codeiva4u",
43
+ "license": "ISC",
44
+ "dependencies": {
45
+ "@ghostery/adblocker-playwright": "latest",
46
+ "@modelcontextprotocol/sdk": "latest",
47
+ "patchright": "latest",
48
+ "playwright-ghost": "latest"
49
+ }
50
+ }
@@ -0,0 +1,274 @@
1
+ /**
2
+ * AI Action Parser - Parse natural language commands into actions
3
+ *
4
+ * Converts commands like:
5
+ * - "click the login button" -> { type: 'click', target: 'login button' }
6
+ * - "type hello in the search box" -> { type: 'type', target: 'search box', text: 'hello' }
7
+ * - "scroll down" -> { type: 'scroll', direction: 'down' }
8
+ */
9
+
10
+ class ActionParser {
11
+ constructor() {
12
+ // Action patterns
13
+ this.actionPatterns = [
14
+ // Click patterns
15
+ {
16
+ pattern: /^(click|tap|press|hit|select)\s+(?:on\s+)?(?:the\s+)?(.+)/i,
17
+ type: 'click',
18
+ extract: (match) => ({ target: match[2].trim() })
19
+ },
20
+ // Type patterns
21
+ {
22
+ pattern: /^(type|enter|write|input)\s+["']?([^"']+)["']?\s+(?:in|into|in the|into the)\s+(.+)/i,
23
+ type: 'type',
24
+ extract: (match) => ({ text: match[2].trim(), target: match[3].trim() })
25
+ },
26
+ {
27
+ pattern: /^(type|enter|write|input)\s+(.+)\s+(?:in|into)\s+["']?([^"']+)["']?/i,
28
+ type: 'type',
29
+ extract: (match) => ({ target: match[2].trim(), text: match[3].trim() })
30
+ },
31
+ {
32
+ pattern: /^(fill|fill in|complete)\s+(?:the\s+)?(.+)\s+(?:with|as)\s+["']?([^"']+)["']?/i,
33
+ type: 'type',
34
+ extract: (match) => ({ target: match[2].trim(), text: match[3].trim() })
35
+ },
36
+ // Navigate patterns
37
+ {
38
+ pattern: /^(go to|navigate to|open|visit)\s+(.+)/i,
39
+ type: 'navigate',
40
+ extract: (match) => {
41
+ let url = match[2].trim();
42
+ if (!url.startsWith('http')) {
43
+ url = 'https://' + url;
44
+ }
45
+ return { url };
46
+ }
47
+ },
48
+ // Scroll patterns
49
+ {
50
+ pattern: /^scroll\s+(up|down|left|right)(?:\s+(\d+)\s*(?:px|pixels)?)?/i,
51
+ type: 'scroll',
52
+ extract: (match) => ({
53
+ direction: match[1].toLowerCase(),
54
+ amount: parseInt(match[2]) || 300
55
+ })
56
+ },
57
+ {
58
+ pattern: /^scroll\s+to\s+(?:the\s+)?(top|bottom|footer|header)/i,
59
+ type: 'scroll',
60
+ extract: (match) => {
61
+ const target = match[1].toLowerCase();
62
+ return {
63
+ direction: target === 'top' || target === 'header' ? 'up' : 'down',
64
+ amount: 10000,
65
+ scrollTo: target
66
+ };
67
+ }
68
+ },
69
+ // Wait patterns
70
+ {
71
+ pattern: /^wait\s+(?:for\s+)?(\d+)\s*(?:ms|milliseconds?|s|seconds?)?/i,
72
+ type: 'wait',
73
+ extract: (match) => {
74
+ let duration = parseInt(match[1]);
75
+ const unit = match[0].toLowerCase();
76
+ if (unit.includes('s') && !unit.includes('ms')) {
77
+ duration *= 1000;
78
+ }
79
+ return { duration };
80
+ }
81
+ },
82
+ {
83
+ pattern: /^wait\s+(?:for\s+)?(?:the\s+)?(.+?)(?:\s+to\s+(?:appear|load|show))?$/i,
84
+ type: 'waitFor',
85
+ extract: (match) => ({ target: match[1].trim() })
86
+ },
87
+ // Find/Search patterns
88
+ {
89
+ pattern: /^(find|search|look for|locate)\s+(?:the\s+)?(.+)/i,
90
+ type: 'find',
91
+ extract: (match) => ({ query: match[2].trim() })
92
+ },
93
+ // Hover patterns
94
+ {
95
+ pattern: /^(hover|mouse over|move to)\s+(?:the\s+)?(.+)/i,
96
+ type: 'hover',
97
+ extract: (match) => ({ target: match[2].trim() })
98
+ },
99
+ // Clear patterns
100
+ {
101
+ pattern: /^(clear|empty|delete)\s+(?:the\s+)?(.+)/i,
102
+ type: 'clear',
103
+ extract: (match) => ({ target: match[2].trim() })
104
+ },
105
+ // Submit patterns
106
+ {
107
+ pattern: /^submit\s+(?:the\s+)?(?:form)?(.*)$/i,
108
+ type: 'submit',
109
+ extract: (match) => ({ target: match[1].trim() || 'form' })
110
+ },
111
+ // Screenshot patterns
112
+ {
113
+ pattern: /^(take|capture)\s+(?:a\s+)?screenshot/i,
114
+ type: 'screenshot',
115
+ extract: () => ({})
116
+ },
117
+ // Go back/forward patterns
118
+ {
119
+ pattern: /^go\s+(back|forward)/i,
120
+ type: 'navigation',
121
+ extract: (match) => ({ direction: match[1].toLowerCase() })
122
+ },
123
+ // Refresh patterns
124
+ {
125
+ pattern: /^(refresh|reload)\s*(?:the\s+)?(?:page)?/i,
126
+ type: 'refresh',
127
+ extract: () => ({})
128
+ }
129
+ ];
130
+
131
+ // Context variable patterns (for substitution)
132
+ this.variablePattern = /\{(\w+)\}/g;
133
+ }
134
+
135
+ /**
136
+ * Parse a natural language command
137
+ */
138
+ async parse(command, context = {}) {
139
+ // Substitute context variables
140
+ let processedCommand = command.replace(this.variablePattern, (match, varName) => {
141
+ return context[varName] !== undefined ? context[varName] : match;
142
+ });
143
+
144
+ // Trim and normalize
145
+ processedCommand = processedCommand.trim();
146
+
147
+ // Try each pattern
148
+ for (const pattern of this.actionPatterns) {
149
+ const match = processedCommand.match(pattern.pattern);
150
+ if (match) {
151
+ const extracted = pattern.extract(match);
152
+ return {
153
+ type: pattern.type,
154
+ ...extracted,
155
+ originalCommand: command,
156
+ confidence: 0.9
157
+ };
158
+ }
159
+ }
160
+
161
+ // Fallback: Try to guess action from keywords
162
+ const fallback = this.guessFallback(processedCommand);
163
+ if (fallback) {
164
+ return {
165
+ ...fallback,
166
+ originalCommand: command,
167
+ confidence: 0.5
168
+ };
169
+ }
170
+
171
+ // Could not parse
172
+ return {
173
+ type: 'unknown',
174
+ originalCommand: command,
175
+ confidence: 0,
176
+ error: 'Could not understand command'
177
+ };
178
+ }
179
+
180
+ /**
181
+ * Try to guess action from keywords
182
+ */
183
+ guessFallback(command) {
184
+ const lower = command.toLowerCase();
185
+
186
+ // Check for action keywords
187
+ if (lower.includes('button') || lower.includes('link') || lower.includes('click')) {
188
+ return { type: 'click', target: command };
189
+ }
190
+
191
+ if (lower.includes('type') || lower.includes('enter') || lower.includes('input')) {
192
+ return { type: 'find', query: command, suggestedAction: 'type' };
193
+ }
194
+
195
+ if (lower.includes('search') || lower.includes('find') || lower.includes('look')) {
196
+ return { type: 'find', query: command };
197
+ }
198
+
199
+ if (lower.includes('scroll')) {
200
+ return { type: 'scroll', direction: 'down', amount: 300 };
201
+ }
202
+
203
+ // Default to find
204
+ return { type: 'find', query: command };
205
+ }
206
+
207
+ /**
208
+ * Parse multiple commands (separated by 'then', 'and', or newlines)
209
+ */
210
+ async parseMultiple(commands, context = {}) {
211
+ // Split by separators
212
+ const parts = commands.split(/\s+(?:then|and)\s+|\n|;/i).filter(p => p.trim());
213
+
214
+ const results = [];
215
+ for (const part of parts) {
216
+ const parsed = await this.parse(part.trim(), context);
217
+ results.push(parsed);
218
+ }
219
+
220
+ return results;
221
+ }
222
+
223
+ /**
224
+ * Get suggestions for incomplete commands
225
+ */
226
+ getSuggestions(partialCommand) {
227
+ const lower = partialCommand.toLowerCase();
228
+ const suggestions = [];
229
+
230
+ if (lower.startsWith('click')) {
231
+ suggestions.push(
232
+ 'click the login button',
233
+ 'click the submit button',
234
+ 'click the link',
235
+ 'click the menu'
236
+ );
237
+ } else if (lower.startsWith('type')) {
238
+ suggestions.push(
239
+ 'type "text" in the search box',
240
+ 'type "username" in the login field',
241
+ 'type "hello" in the input'
242
+ );
243
+ } else if (lower.startsWith('go')) {
244
+ suggestions.push(
245
+ 'go to google.com',
246
+ 'go back',
247
+ 'go forward'
248
+ );
249
+ } else if (lower.startsWith('scroll')) {
250
+ suggestions.push(
251
+ 'scroll down',
252
+ 'scroll up',
253
+ 'scroll to the bottom',
254
+ 'scroll to the top'
255
+ );
256
+ } else if (lower.startsWith('wait')) {
257
+ suggestions.push(
258
+ 'wait 2 seconds',
259
+ 'wait for the button to appear',
260
+ 'wait 500ms'
261
+ );
262
+ } else if (lower.startsWith('find')) {
263
+ suggestions.push(
264
+ 'find the login button',
265
+ 'find the search input',
266
+ 'find all links'
267
+ );
268
+ }
269
+
270
+ return suggestions;
271
+ }
272
+ }
273
+
274
+ module.exports = ActionParser;