brave-real-browser-mcp-server 2.29.0 โ 2.30.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/README.md +127 -20
- package/dist/handlers/advanced-tools.js +318 -0
- package/dist/index.js +7 -1
- package/dist/tool-definitions.js +67 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -107,6 +107,115 @@ npm run dev
|
|
|
107
107
|
|
|
108
108
|
---
|
|
109
109
|
|
|
110
|
+
## ๐ New in v2.28.1 - Advanced Enhancements
|
|
111
|
+
|
|
112
|
+
### ๐ Advanced Navigation
|
|
113
|
+
| Option | Description |
|
|
114
|
+
|--------|-------------|
|
|
115
|
+
| `blockResources` | Block images, fonts, CSS for faster loading |
|
|
116
|
+
| `customHeaders` | Set custom HTTP headers |
|
|
117
|
+
| `referrer` | Custom referrer URL |
|
|
118
|
+
| `waitForSelector` | Wait for specific element after load |
|
|
119
|
+
| `waitForContent` | Wait for specific text content |
|
|
120
|
+
| `scrollToBottom` | Auto-scroll for lazy loading |
|
|
121
|
+
| `randomDelay` | Human-like delay (100-500ms) |
|
|
122
|
+
| `bypassCSP` | Bypass Content Security Policy |
|
|
123
|
+
|
|
124
|
+
**Example:**
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"url": "https://example.com",
|
|
128
|
+
"blockResources": ["image", "font"],
|
|
129
|
+
"waitForSelector": ".main-content",
|
|
130
|
+
"scrollToBottom": true,
|
|
131
|
+
"randomDelay": true
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### ๐ Parallel Scraping (js_scrape)
|
|
136
|
+
| Option | Description |
|
|
137
|
+
|--------|-------------|
|
|
138
|
+
| `urls` | Array of multiple URLs to scrape |
|
|
139
|
+
| `concurrency` | Max concurrent scrapes (1-10) |
|
|
140
|
+
| `continueOnError` | Continue even if some URLs fail |
|
|
141
|
+
| `delayBetween` | Delay between scrapes (ms) |
|
|
142
|
+
|
|
143
|
+
**Example:**
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"urls": ["https://site1.com", "https://site2.com", "https://site3.com"],
|
|
147
|
+
"concurrency": 3,
|
|
148
|
+
"extractSelector": "article"
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### ๐ Auto-Pagination (link_harvester)
|
|
153
|
+
| Option | Description |
|
|
154
|
+
|--------|-------------|
|
|
155
|
+
| `followPagination` | Auto-follow pagination links |
|
|
156
|
+
| `maxPages` | Maximum pages to scrape (1-20) |
|
|
157
|
+
| `paginationSelector` | Custom next page selector |
|
|
158
|
+
| `delayBetweenPages` | Delay between pages (ms) |
|
|
159
|
+
|
|
160
|
+
**Example:**
|
|
161
|
+
```json
|
|
162
|
+
{
|
|
163
|
+
"followPagination": true,
|
|
164
|
+
"maxPages": 10,
|
|
165
|
+
"filter": "movie"
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### ๐ API Interceptor (network_recorder)
|
|
170
|
+
| Option | Description |
|
|
171
|
+
|--------|-------------|
|
|
172
|
+
| `interceptMode` | `record`, `intercept`, or `mock` |
|
|
173
|
+
| `blockPatterns` | URL patterns to block |
|
|
174
|
+
| `mockResponses` | Fake responses for URLs |
|
|
175
|
+
| `modifyHeaders` | Modify request headers |
|
|
176
|
+
| `capturePayloads` | Capture POST/PUT bodies |
|
|
177
|
+
|
|
178
|
+
**Example:**
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"interceptMode": "intercept",
|
|
182
|
+
"blockPatterns": ["ads", "tracking"],
|
|
183
|
+
"capturePayloads": true
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### ๐ก๏ธ Anti-Detection Enhancements
|
|
188
|
+
|
|
189
|
+
| Feature | Description |
|
|
190
|
+
|---------|-------------|
|
|
191
|
+
| **Fingerprint Randomizer** | Canvas, Audio, Hardware randomization |
|
|
192
|
+
| **Human Behavior Simulation** | Natural mouse/scroll patterns |
|
|
193
|
+
| **WebGL Spoofing** | Random GPU from 8 configs (Intel, NVIDIA, AMD) |
|
|
194
|
+
| **Proxy Rotation** | round-robin, random, least-used, on-error strategies |
|
|
195
|
+
| **Rate Limiter** | Per-second/minute limits, domain-specific |
|
|
196
|
+
| **Error Auto-Recovery** | Smart retry with different strategies |
|
|
197
|
+
|
|
198
|
+
### ๐ Internal Systems
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
// Rate Limiter
|
|
202
|
+
initRateLimiter({ requestsPerSecond: 5, requestsPerMinute: 100 });
|
|
203
|
+
setDomainRateLimit('api.example.com', 2); // 2 req/sec for this domain
|
|
204
|
+
|
|
205
|
+
// Proxy Rotation
|
|
206
|
+
initProxyRotation(['proxy1:8080', 'proxy2:8080'], 'round-robin');
|
|
207
|
+
rotateProxy('error'); // Rotate on error
|
|
208
|
+
|
|
209
|
+
// Error Recovery
|
|
210
|
+
executeWithRecovery(operation, {
|
|
211
|
+
toolName: 'navigate',
|
|
212
|
+
page: pageInstance,
|
|
213
|
+
restartBrowser: () => browser.restart()
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
110
219
|
## ๐ Unified MCP+LSP+SSE Ecosystem
|
|
111
220
|
|
|
112
221
|
**เคเค command เคธเฅ เคธเคฌ active:**
|
|
@@ -117,7 +226,7 @@ npm run dev
|
|
|
117
226
|
|
|
118
227
|
### Output:
|
|
119
228
|
```
|
|
120
|
-
๐ฆ Brave Real Browser - Unified Server v2.
|
|
229
|
+
๐ฆ Brave Real Browser - Unified Server v2.28.1
|
|
121
230
|
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
122
231
|
๐ก HTTP Server: http://localhost:3000
|
|
123
232
|
|
|
@@ -185,14 +294,14 @@ brave-real-launcher
|
|
|
185
294
|
### Navigation
|
|
186
295
|
| Tool | Description |
|
|
187
296
|
|------|-------------|
|
|
188
|
-
| `navigate` | Navigate to URL |
|
|
297
|
+
| `navigate` | Navigate to URL with advanced options (resource blocking, custom headers, auto-scroll) |
|
|
189
298
|
| `wait` | Wait for conditions |
|
|
190
299
|
|
|
191
300
|
### Content
|
|
192
301
|
| Tool | Description |
|
|
193
302
|
|------|-------------|
|
|
194
303
|
| `get_content` | Get page HTML/text |
|
|
195
|
-
| `find_element` | Find by selector/text/AI |
|
|
304
|
+
| `find_element` | Find by selector/text/AI with batch mode |
|
|
196
305
|
| `save_content_as_markdown` | Save as markdown file |
|
|
197
306
|
|
|
198
307
|
### Interaction
|
|
@@ -204,34 +313,32 @@ brave-real-launcher
|
|
|
204
313
|
| `random_scroll` | Natural scrolling |
|
|
205
314
|
| `solve_captcha` | Solve CAPTCHAs |
|
|
206
315
|
|
|
207
|
-
### Media
|
|
316
|
+
### Media & Streaming
|
|
317
|
+
| Tool | Description |
|
|
318
|
+
|------|-------------|
|
|
319
|
+
| `stream_extractor` | Extract streams with multi-quality selector, VidSrc/Filemoon/StreamWish support |
|
|
320
|
+
| `player_api_hook` | Hook into JWPlayer, Video.js, HLS.js, Plyr, Vidstack, DASH.js |
|
|
321
|
+
| `iframe_handler` | Deep iframe scraping with video source extraction |
|
|
322
|
+
|
|
323
|
+
### Scraping
|
|
208
324
|
| Tool | Description |
|
|
209
325
|
|------|-------------|
|
|
210
|
-
| `
|
|
211
|
-
| `
|
|
212
|
-
| `
|
|
326
|
+
| `js_scrape` | JavaScript-rendered scraping with parallel URL support |
|
|
327
|
+
| `link_harvester` | Harvest links with auto-pagination |
|
|
328
|
+
| `network_recorder` | Record traffic with API interception |
|
|
329
|
+
| `extract_json` | Extract embedded JSON with AES decryption |
|
|
330
|
+
| `search_regex` | Regex search like regex101.com |
|
|
213
331
|
|
|
214
332
|
### Advanced
|
|
215
333
|
| Tool | Description |
|
|
216
334
|
|------|-------------|
|
|
217
|
-
| `search_content` | Search patterns |
|
|
218
|
-
| `extract_json` | Extract embedded JSON |
|
|
219
335
|
| `scrape_meta_tags` | Meta/OG tags |
|
|
220
|
-
| `deep_analysis` | Full page analysis |
|
|
221
|
-
| `network_recorder` | Record traffic |
|
|
222
|
-
| `api_finder` | Discover APIs |
|
|
223
|
-
| `ajax_content_waiter` | Wait for AJAX |
|
|
224
|
-
| `link_harvester` | Harvest links |
|
|
225
|
-
| `batch_element_scraper` | Batch scrape |
|
|
226
|
-
| `extract_schema` | Schema.org data |
|
|
227
|
-
| `element_screenshot` | Screenshot element |
|
|
228
|
-
| `breadcrumb_navigator` | Navigate breadcrumbs |
|
|
336
|
+
| `deep_analysis` | Full page analysis with screenshot |
|
|
229
337
|
| `redirect_tracer` | Trace redirects |
|
|
230
338
|
| `progress_tracker` | Track progress |
|
|
231
339
|
| `cookie_manager` | Manage cookies |
|
|
232
340
|
| `file_downloader` | Download files |
|
|
233
|
-
| `
|
|
234
|
-
| `popup_handler` | Handle popups |
|
|
341
|
+
| `execute_js` | Run custom JavaScript |
|
|
235
342
|
|
|
236
343
|
---
|
|
237
344
|
|
|
@@ -5962,3 +5962,321 @@ export async function handlePlayerApiHook(page, args) {
|
|
|
5962
5962
|
};
|
|
5963
5963
|
}
|
|
5964
5964
|
}
|
|
5965
|
+
// Field type detection patterns
|
|
5966
|
+
const FIELD_PATTERNS = {
|
|
5967
|
+
email: [/email/i, /e-mail/i, /mail/i],
|
|
5968
|
+
password: [/password/i, /pass/i, /pwd/i],
|
|
5969
|
+
username: [/username/i, /user/i, /login/i, /uname/i],
|
|
5970
|
+
firstName: [/first.?name/i, /fname/i, /given.?name/i],
|
|
5971
|
+
lastName: [/last.?name/i, /lname/i, /surname/i, /family.?name/i],
|
|
5972
|
+
fullName: [/full.?name/i, /name/i],
|
|
5973
|
+
phone: [/phone/i, /tel/i, /mobile/i, /cell/i],
|
|
5974
|
+
address: [/address/i, /street/i, /addr/i],
|
|
5975
|
+
city: [/city/i, /town/i],
|
|
5976
|
+
state: [/state/i, /province/i, /region/i],
|
|
5977
|
+
zip: [/zip/i, /postal/i, /postcode/i],
|
|
5978
|
+
country: [/country/i, /nation/i],
|
|
5979
|
+
cardNumber: [/card.?number/i, /cc.?num/i, /credit.?card/i],
|
|
5980
|
+
cvv: [/cvv/i, /cvc/i, /security.?code/i],
|
|
5981
|
+
expiry: [/expir/i, /exp.?date/i],
|
|
5982
|
+
search: [/search/i, /query/i, /q/i],
|
|
5983
|
+
message: [/message/i, /comment/i, /feedback/i, /note/i],
|
|
5984
|
+
company: [/company/i, /organization/i, /org/i],
|
|
5985
|
+
website: [/website/i, /url/i, /web/i],
|
|
5986
|
+
age: [/age/i, /birth/i, /dob/i],
|
|
5987
|
+
};
|
|
5988
|
+
// Detect field type from attributes
|
|
5989
|
+
function detectFieldType(attributes) {
|
|
5990
|
+
const { name = '', id = '', type = '', placeholder = '', label = '', autocomplete = '' } = attributes;
|
|
5991
|
+
const combined = `${name} ${id} ${type} ${placeholder} ${label} ${autocomplete}`.toLowerCase();
|
|
5992
|
+
for (const [fieldType, patterns] of Object.entries(FIELD_PATTERNS)) {
|
|
5993
|
+
for (const pattern of patterns) {
|
|
5994
|
+
if (pattern.test(combined)) {
|
|
5995
|
+
return fieldType;
|
|
5996
|
+
}
|
|
5997
|
+
}
|
|
5998
|
+
}
|
|
5999
|
+
return 'unknown';
|
|
6000
|
+
}
|
|
6001
|
+
export async function handleFormAutomator(page, args) {
|
|
6002
|
+
const action = args.action || 'fill';
|
|
6003
|
+
const formSelector = args.formSelector || 'form';
|
|
6004
|
+
const humanLike = args.humanLike !== false;
|
|
6005
|
+
const clearFields = args.clearFields !== false;
|
|
6006
|
+
const delayBetweenFields = args.delayBetweenFields || 500;
|
|
6007
|
+
try {
|
|
6008
|
+
// Get form fields
|
|
6009
|
+
const getFormFields = async () => {
|
|
6010
|
+
return await page.evaluate((selector) => {
|
|
6011
|
+
const form = document.querySelector(selector);
|
|
6012
|
+
if (!form)
|
|
6013
|
+
return [];
|
|
6014
|
+
const inputs = form.querySelectorAll('input, textarea, select');
|
|
6015
|
+
const fields = [];
|
|
6016
|
+
inputs.forEach((input, index) => {
|
|
6017
|
+
// Find associated label
|
|
6018
|
+
let label = '';
|
|
6019
|
+
if (input.id) {
|
|
6020
|
+
const labelEl = document.querySelector(`label[for="${input.id}"]`);
|
|
6021
|
+
if (labelEl)
|
|
6022
|
+
label = labelEl.textContent?.trim() || '';
|
|
6023
|
+
}
|
|
6024
|
+
if (!label) {
|
|
6025
|
+
const parent = input.closest('label');
|
|
6026
|
+
if (parent)
|
|
6027
|
+
label = parent.textContent?.replace(input.value || '', '').trim() || '';
|
|
6028
|
+
}
|
|
6029
|
+
fields.push({
|
|
6030
|
+
index,
|
|
6031
|
+
tagName: input.tagName.toLowerCase(),
|
|
6032
|
+
type: input.type || 'text',
|
|
6033
|
+
name: input.name || '',
|
|
6034
|
+
id: input.id || '',
|
|
6035
|
+
placeholder: input.placeholder || '',
|
|
6036
|
+
label,
|
|
6037
|
+
autocomplete: input.autocomplete || '',
|
|
6038
|
+
required: input.required,
|
|
6039
|
+
value: input.value || '',
|
|
6040
|
+
visible: input.offsetWidth > 0 && input.offsetHeight > 0,
|
|
6041
|
+
selector: input.id ? `#${input.id}` :
|
|
6042
|
+
input.name ? `[name="${input.name}"]` :
|
|
6043
|
+
`${selector} ${input.tagName.toLowerCase()}:nth-of-type(${index + 1})`
|
|
6044
|
+
});
|
|
6045
|
+
});
|
|
6046
|
+
return fields;
|
|
6047
|
+
}, formSelector);
|
|
6048
|
+
};
|
|
6049
|
+
// Human-like typing function
|
|
6050
|
+
const humanType = async (selector, text) => {
|
|
6051
|
+
const element = await page.$(selector);
|
|
6052
|
+
if (!element)
|
|
6053
|
+
throw new Error(`Element not found: ${selector}`);
|
|
6054
|
+
// Focus and clear field
|
|
6055
|
+
await element.click();
|
|
6056
|
+
await page.keyboard.down('Control');
|
|
6057
|
+
await page.keyboard.press('a');
|
|
6058
|
+
await page.keyboard.up('Control');
|
|
6059
|
+
await page.keyboard.press('Backspace');
|
|
6060
|
+
const minDelay = 50;
|
|
6061
|
+
const maxDelay = 150;
|
|
6062
|
+
const errorRate = 0.02;
|
|
6063
|
+
for (let i = 0; i < text.length; i++) {
|
|
6064
|
+
const char = text[i];
|
|
6065
|
+
// Random delay between keystrokes
|
|
6066
|
+
const delay = minDelay + Math.random() * (maxDelay - minDelay);
|
|
6067
|
+
await new Promise(r => setTimeout(r, delay));
|
|
6068
|
+
// Simulate occasional typo
|
|
6069
|
+
if (Math.random() < errorRate && i > 0 && i < text.length - 1) {
|
|
6070
|
+
const wrongChar = String.fromCharCode(char.charCodeAt(0) + (Math.random() > 0.5 ? 1 : -1));
|
|
6071
|
+
await page.keyboard.type(wrongChar);
|
|
6072
|
+
await new Promise(r => setTimeout(r, 200));
|
|
6073
|
+
await page.keyboard.press('Backspace');
|
|
6074
|
+
await new Promise(r => setTimeout(r, 50 + Math.random() * 50));
|
|
6075
|
+
}
|
|
6076
|
+
await page.keyboard.type(char);
|
|
6077
|
+
}
|
|
6078
|
+
};
|
|
6079
|
+
// Handle different actions
|
|
6080
|
+
switch (action) {
|
|
6081
|
+
case 'getFields': {
|
|
6082
|
+
const fields = await getFormFields();
|
|
6083
|
+
const detectedFields = fields.map(f => ({
|
|
6084
|
+
...f,
|
|
6085
|
+
detectedType: detectFieldType(f)
|
|
6086
|
+
}));
|
|
6087
|
+
return {
|
|
6088
|
+
success: true,
|
|
6089
|
+
formFound: fields.length > 0,
|
|
6090
|
+
totalFields: fields.length,
|
|
6091
|
+
visibleFields: fields.filter(f => f.visible).length,
|
|
6092
|
+
fields: detectedFields,
|
|
6093
|
+
message: `Found ${fields.length} fields in form`
|
|
6094
|
+
};
|
|
6095
|
+
}
|
|
6096
|
+
case 'fillField': {
|
|
6097
|
+
if (!args.fieldSelector || !args.fieldValue) {
|
|
6098
|
+
throw new Error('fieldSelector and fieldValue are required for fillField action');
|
|
6099
|
+
}
|
|
6100
|
+
if (humanLike) {
|
|
6101
|
+
await humanType(args.fieldSelector, args.fieldValue);
|
|
6102
|
+
}
|
|
6103
|
+
else {
|
|
6104
|
+
if (clearFields) {
|
|
6105
|
+
await page.$eval(args.fieldSelector, (el) => el.value = '');
|
|
6106
|
+
}
|
|
6107
|
+
await page.type(args.fieldSelector, args.fieldValue);
|
|
6108
|
+
}
|
|
6109
|
+
return {
|
|
6110
|
+
success: true,
|
|
6111
|
+
field: args.fieldSelector,
|
|
6112
|
+
value: args.fieldValue,
|
|
6113
|
+
message: `Field filled successfully`
|
|
6114
|
+
};
|
|
6115
|
+
}
|
|
6116
|
+
case 'submit': {
|
|
6117
|
+
const submitSelector = args.submitSelector || 'button[type="submit"], input[type="submit"]';
|
|
6118
|
+
const waitForNav = args.waitForNavigation !== false;
|
|
6119
|
+
const submitButton = await page.$(submitSelector);
|
|
6120
|
+
if (waitForNav) {
|
|
6121
|
+
const navigationPromise = page.waitForNavigation({ timeout: 30000 }).catch(() => null);
|
|
6122
|
+
if (submitButton) {
|
|
6123
|
+
await submitButton.click();
|
|
6124
|
+
}
|
|
6125
|
+
else {
|
|
6126
|
+
await page.$eval(formSelector, (f) => f.submit());
|
|
6127
|
+
}
|
|
6128
|
+
await navigationPromise;
|
|
6129
|
+
}
|
|
6130
|
+
else {
|
|
6131
|
+
if (submitButton) {
|
|
6132
|
+
await submitButton.click();
|
|
6133
|
+
}
|
|
6134
|
+
else {
|
|
6135
|
+
await page.$eval(formSelector, (f) => f.submit());
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
return {
|
|
6139
|
+
success: true,
|
|
6140
|
+
submitted: true,
|
|
6141
|
+
currentUrl: page.url(),
|
|
6142
|
+
message: 'Form submitted successfully'
|
|
6143
|
+
};
|
|
6144
|
+
}
|
|
6145
|
+
case 'autoFill': {
|
|
6146
|
+
// Default test data
|
|
6147
|
+
const defaultData = {
|
|
6148
|
+
email: 'test@example.com',
|
|
6149
|
+
password: 'SecurePass123!',
|
|
6150
|
+
username: 'testuser',
|
|
6151
|
+
firstName: 'John',
|
|
6152
|
+
lastName: 'Doe',
|
|
6153
|
+
fullName: 'John Doe',
|
|
6154
|
+
phone: '+1-555-123-4567',
|
|
6155
|
+
address: '123 Main Street',
|
|
6156
|
+
city: 'New York',
|
|
6157
|
+
state: 'NY',
|
|
6158
|
+
zip: '10001',
|
|
6159
|
+
country: 'United States',
|
|
6160
|
+
company: 'Acme Corp',
|
|
6161
|
+
website: 'https://example.com',
|
|
6162
|
+
message: 'This is a test message.',
|
|
6163
|
+
...args.data
|
|
6164
|
+
};
|
|
6165
|
+
args.data = defaultData;
|
|
6166
|
+
// Fall through to fill action
|
|
6167
|
+
}
|
|
6168
|
+
case 'fill':
|
|
6169
|
+
default: {
|
|
6170
|
+
const data = args.data || {};
|
|
6171
|
+
const fields = await getFormFields();
|
|
6172
|
+
const filledFields = [];
|
|
6173
|
+
const errors = [];
|
|
6174
|
+
for (const field of fields) {
|
|
6175
|
+
if (!field.visible)
|
|
6176
|
+
continue;
|
|
6177
|
+
// Detect field type
|
|
6178
|
+
const fieldType = detectFieldType(field);
|
|
6179
|
+
// Find matching data
|
|
6180
|
+
let value = null;
|
|
6181
|
+
if (data[field.name])
|
|
6182
|
+
value = data[field.name];
|
|
6183
|
+
else if (data[field.id])
|
|
6184
|
+
value = data[field.id];
|
|
6185
|
+
else if (data[fieldType])
|
|
6186
|
+
value = data[fieldType];
|
|
6187
|
+
if (!value)
|
|
6188
|
+
continue;
|
|
6189
|
+
try {
|
|
6190
|
+
if (field.tagName === 'select') {
|
|
6191
|
+
await page.select(field.selector, value);
|
|
6192
|
+
}
|
|
6193
|
+
else if (field.type === 'checkbox') {
|
|
6194
|
+
const isChecked = await page.$eval(field.selector, (el) => el.checked);
|
|
6195
|
+
if ((value === true || value === 'true' || value === '1') && !isChecked) {
|
|
6196
|
+
await page.click(field.selector);
|
|
6197
|
+
}
|
|
6198
|
+
else if ((value === false || value === 'false' || value === '0') && isChecked) {
|
|
6199
|
+
await page.click(field.selector);
|
|
6200
|
+
}
|
|
6201
|
+
}
|
|
6202
|
+
else if (field.type === 'radio') {
|
|
6203
|
+
const radioSelector = `${field.selector}[value="${value}"]`;
|
|
6204
|
+
await page.click(radioSelector);
|
|
6205
|
+
}
|
|
6206
|
+
else {
|
|
6207
|
+
if (humanLike) {
|
|
6208
|
+
await humanType(field.selector, String(value));
|
|
6209
|
+
}
|
|
6210
|
+
else {
|
|
6211
|
+
if (clearFields) {
|
|
6212
|
+
await page.$eval(field.selector, (el) => el.value = '');
|
|
6213
|
+
}
|
|
6214
|
+
await page.type(field.selector, String(value));
|
|
6215
|
+
}
|
|
6216
|
+
}
|
|
6217
|
+
filledFields.push({
|
|
6218
|
+
field: field.name || field.id || field.selector,
|
|
6219
|
+
type: fieldType,
|
|
6220
|
+
success: true
|
|
6221
|
+
});
|
|
6222
|
+
// Delay between fields
|
|
6223
|
+
if (humanLike) {
|
|
6224
|
+
await new Promise(r => setTimeout(r, delayBetweenFields * (0.5 + Math.random())));
|
|
6225
|
+
}
|
|
6226
|
+
}
|
|
6227
|
+
catch (err) {
|
|
6228
|
+
errors.push({
|
|
6229
|
+
field: field.name || field.id || field.selector,
|
|
6230
|
+
error: err.message
|
|
6231
|
+
});
|
|
6232
|
+
}
|
|
6233
|
+
}
|
|
6234
|
+
// Submit if requested
|
|
6235
|
+
let submitted = false;
|
|
6236
|
+
if (args.submitAfter) {
|
|
6237
|
+
try {
|
|
6238
|
+
const submitSelector = args.submitSelector || 'button[type="submit"], input[type="submit"]';
|
|
6239
|
+
const submitButton = await page.$(submitSelector);
|
|
6240
|
+
if (args.waitForNavigation !== false) {
|
|
6241
|
+
const navigationPromise = page.waitForNavigation({ timeout: 30000 }).catch(() => null);
|
|
6242
|
+
if (submitButton) {
|
|
6243
|
+
await submitButton.click();
|
|
6244
|
+
}
|
|
6245
|
+
else {
|
|
6246
|
+
await page.$eval(formSelector, (f) => f.submit());
|
|
6247
|
+
}
|
|
6248
|
+
await navigationPromise;
|
|
6249
|
+
}
|
|
6250
|
+
else {
|
|
6251
|
+
if (submitButton) {
|
|
6252
|
+
await submitButton.click();
|
|
6253
|
+
}
|
|
6254
|
+
else {
|
|
6255
|
+
await page.$eval(formSelector, (f) => f.submit());
|
|
6256
|
+
}
|
|
6257
|
+
}
|
|
6258
|
+
submitted = true;
|
|
6259
|
+
}
|
|
6260
|
+
catch (err) {
|
|
6261
|
+
errors.push({ field: 'submit', error: err.message });
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
return {
|
|
6265
|
+
success: errors.length === 0,
|
|
6266
|
+
filledFields,
|
|
6267
|
+
errors,
|
|
6268
|
+
submitted,
|
|
6269
|
+
totalFields: fields.filter(f => f.visible).length,
|
|
6270
|
+
currentUrl: page.url(),
|
|
6271
|
+
message: `Filled ${filledFields.length} fields${submitted ? ' and submitted form' : ''}`
|
|
6272
|
+
};
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
catch (error) {
|
|
6277
|
+
return {
|
|
6278
|
+
success: false,
|
|
6279
|
+
message: `Form automator error: ${error instanceof Error ? error.message : String(error)}`
|
|
6280
|
+
};
|
|
6281
|
+
}
|
|
6282
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -67,7 +67,9 @@ handleFileDownloader,
|
|
|
67
67
|
// Enhanced streaming/download tools
|
|
68
68
|
handleIframeHandler, handleStreamExtractor, handleJsScrape,
|
|
69
69
|
// New JS extraction tools
|
|
70
|
-
handleExecuteJs, handlePlayerApiHook,
|
|
70
|
+
handleExecuteJs, handlePlayerApiHook,
|
|
71
|
+
// Form automation
|
|
72
|
+
handleFormAutomator, } from './handlers/advanced-tools.js';
|
|
71
73
|
// State for video recording
|
|
72
74
|
const recorderState = new Map();
|
|
73
75
|
debug('All modules loaded successfully');
|
|
@@ -256,6 +258,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
256
258
|
if (!page)
|
|
257
259
|
throw new Error('Browser not initialized. Call browser_init first.');
|
|
258
260
|
return { content: [{ type: 'text', text: JSON.stringify(await handlePlayerApiHook(page, args)) }] };
|
|
261
|
+
case TOOL_NAMES.FORM_AUTOMATOR:
|
|
262
|
+
if (!page)
|
|
263
|
+
throw new Error('Browser not initialized. Call browser_init first.');
|
|
264
|
+
return { content: [{ type: 'text', text: JSON.stringify(await handleFormAutomator(page, args)) }] };
|
|
259
265
|
default:
|
|
260
266
|
throw new Error(`Unknown tool: ${name}`);
|
|
261
267
|
}
|
package/dist/tool-definitions.js
CHANGED
|
@@ -914,6 +914,71 @@ export const TOOLS = [
|
|
|
914
914
|
},
|
|
915
915
|
},
|
|
916
916
|
},
|
|
917
|
+
// Form Automator Tool
|
|
918
|
+
{
|
|
919
|
+
name: 'form_automator',
|
|
920
|
+
description: 'Auto-fill forms with human-like typing, field detection, and smart form submission. Supports auto-detection of field types (email, password, name, phone, address, etc.)',
|
|
921
|
+
inputSchema: {
|
|
922
|
+
type: 'object',
|
|
923
|
+
additionalProperties: false,
|
|
924
|
+
properties: {
|
|
925
|
+
action: {
|
|
926
|
+
type: 'string',
|
|
927
|
+
enum: ['fill', 'autoFill', 'getFields', 'submit', 'fillField'],
|
|
928
|
+
description: 'Action to perform: fill (with custom data), autoFill (with test data), getFields (detect fields), submit, fillField (single field)',
|
|
929
|
+
default: 'fill'
|
|
930
|
+
},
|
|
931
|
+
formSelector: {
|
|
932
|
+
type: 'string',
|
|
933
|
+
description: 'CSS selector for the form (default: "form")',
|
|
934
|
+
default: 'form'
|
|
935
|
+
},
|
|
936
|
+
data: {
|
|
937
|
+
type: 'object',
|
|
938
|
+
description: 'Data to fill in form. Keys can be field names, IDs, or types (email, password, username, firstName, lastName, phone, address, etc.)',
|
|
939
|
+
additionalProperties: true
|
|
940
|
+
},
|
|
941
|
+
fieldSelector: {
|
|
942
|
+
type: 'string',
|
|
943
|
+
description: 'CSS selector for specific field (used with fillField action)'
|
|
944
|
+
},
|
|
945
|
+
fieldValue: {
|
|
946
|
+
type: 'string',
|
|
947
|
+
description: 'Value to fill in specific field (used with fillField action)'
|
|
948
|
+
},
|
|
949
|
+
humanLike: {
|
|
950
|
+
type: 'boolean',
|
|
951
|
+
description: 'Use human-like typing with variable delays and occasional typos',
|
|
952
|
+
default: true
|
|
953
|
+
},
|
|
954
|
+
clearFields: {
|
|
955
|
+
type: 'boolean',
|
|
956
|
+
description: 'Clear existing field values before filling',
|
|
957
|
+
default: true
|
|
958
|
+
},
|
|
959
|
+
submitAfter: {
|
|
960
|
+
type: 'boolean',
|
|
961
|
+
description: 'Submit form after filling',
|
|
962
|
+
default: false
|
|
963
|
+
},
|
|
964
|
+
submitSelector: {
|
|
965
|
+
type: 'string',
|
|
966
|
+
description: 'CSS selector for submit button',
|
|
967
|
+
default: 'button[type="submit"], input[type="submit"]'
|
|
968
|
+
},
|
|
969
|
+
waitForNavigation: {
|
|
970
|
+
type: 'boolean',
|
|
971
|
+
description: 'Wait for page navigation after submit',
|
|
972
|
+
default: true
|
|
973
|
+
},
|
|
974
|
+
delayBetweenFields: {
|
|
975
|
+
type: 'number',
|
|
976
|
+
description: 'Delay in ms between filling fields (for human-like behavior)',
|
|
977
|
+
default: 500
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
},
|
|
981
|
+
},
|
|
917
982
|
];
|
|
918
983
|
// Tool name constants for type safety
|
|
919
984
|
export const TOOL_NAMES = {
|
|
@@ -956,6 +1021,8 @@ export const TOOL_NAMES = {
|
|
|
956
1021
|
// New JS extraction tools
|
|
957
1022
|
EXECUTE_JS: 'execute_js',
|
|
958
1023
|
PLAYER_API_HOOK: 'player_api_hook',
|
|
1024
|
+
// Form automation
|
|
1025
|
+
FORM_AUTOMATOR: 'form_automator',
|
|
959
1026
|
};
|
|
960
1027
|
// Tool categories for organization
|
|
961
1028
|
export const TOOL_CATEGORIES = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.30.0",
|
|
4
4
|
"description": "๐ฆ MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features, SSE streaming, and LSP compatibility",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"@modelcontextprotocol/sdk": "latest",
|
|
52
52
|
"@types/turndown": "latest",
|
|
53
|
-
"brave-real-browser": "^2.
|
|
53
|
+
"brave-real-browser": "^2.10.0",
|
|
54
54
|
"puppeteer-core": "^24.35.0",
|
|
55
55
|
"turndown": "latest",
|
|
56
56
|
"vscode-languageserver": "^9.0.1",
|