brave-real-browser-mcp-server 2.0.6 → 2.0.8
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 +67 -0
- package/dist/browser-manager.js +24 -20
- package/dist/constants.js +127 -0
- package/dist/errors/index.js +385 -0
- package/dist/errors/workflow-wrapper.js +57 -0
- package/dist/handlers/browser-handlers.js +2 -30
- package/dist/handlers/interaction-handlers-temp.js +294 -0
- package/dist/index.js +67 -87
- package/dist/utils/logger.js +116 -0
- package/package.json +12 -11
package/README.md
CHANGED
|
@@ -184,6 +184,73 @@ complex web automation tasks with human-like behavior.
|
|
|
184
184
|
```
|
|
185
185
|
- Install xvfb for headless operation: `sudo apt-get install -y xvfb`
|
|
186
186
|
|
|
187
|
+
## 🚀 Auto-Update Feature
|
|
188
|
+
|
|
189
|
+
This package **automatically updates all dependencies to their latest versions** whenever you run `npm install` during development. This ensures you always have the most recent bug fixes, security patches, and feature improvements from the underlying Brave browser packages.
|
|
190
|
+
|
|
191
|
+
### How it works:
|
|
192
|
+
- ✅ Automatically checks for outdated dependencies on every `npm install`
|
|
193
|
+
- ✅ Updates core Brave packages (`brave-real-browser`, `brave-real-launcher`, `brave-real-puppeteer-core`) with `--force`
|
|
194
|
+
- ✅ Updates other dependencies (`@modelcontextprotocol/sdk`, `turndown`) separately
|
|
195
|
+
- ✅ Works in CI/CD environments (GitHub Actions workflow)
|
|
196
|
+
- ✅ Can be disabled if needed via environment variable
|
|
197
|
+
- ✅ Smart recursion prevention to avoid loops
|
|
198
|
+
|
|
199
|
+
### For End Users (Using NPX):
|
|
200
|
+
If you're using this package via `npx` (as recommended in Claude Desktop config), you don't need to worry about updates - `npx` with `@latest` always fetches the newest version automatically. This auto-update feature is primarily for developers.
|
|
201
|
+
|
|
202
|
+
### For Developers:
|
|
203
|
+
|
|
204
|
+
**Auto-update is enabled by default:**
|
|
205
|
+
```bash
|
|
206
|
+
npm install
|
|
207
|
+
```
|
|
208
|
+
This will automatically update all dependencies to their latest versions.
|
|
209
|
+
|
|
210
|
+
**To disable auto-update temporarily:**
|
|
211
|
+
```bash
|
|
212
|
+
# On Windows (PowerShell)
|
|
213
|
+
$env:SKIP_AUTO_UPDATE="true"; npm install
|
|
214
|
+
|
|
215
|
+
# On Windows (CMD)
|
|
216
|
+
set SKIP_AUTO_UPDATE=true && npm install
|
|
217
|
+
|
|
218
|
+
# On macOS/Linux
|
|
219
|
+
SKIP_AUTO_UPDATE=true npm install
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Manual update commands:**
|
|
223
|
+
```bash
|
|
224
|
+
# Check for outdated dependencies
|
|
225
|
+
npm run check-updates
|
|
226
|
+
|
|
227
|
+
# Update all core Brave packages
|
|
228
|
+
npm run update-brave-packages
|
|
229
|
+
|
|
230
|
+
# Update using ensure-latest script (with verification)
|
|
231
|
+
npm run ensure-latest-packages
|
|
232
|
+
|
|
233
|
+
# Run auto-update script manually
|
|
234
|
+
npm run upgrade-all
|
|
235
|
+
|
|
236
|
+
# Complete fresh install with latest packages
|
|
237
|
+
npm run fresh-install
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**In CI/CD:**
|
|
241
|
+
The GitHub Actions workflow automatically updates dependencies during the build process using the `ensure-latest-packages` script.
|
|
242
|
+
|
|
243
|
+
### Why Auto-Update?
|
|
244
|
+
|
|
245
|
+
The underlying `brave-real-browser` ecosystem is actively maintained with frequent updates for:
|
|
246
|
+
- 🐛 Bug fixes
|
|
247
|
+
- 🔒 Security patches
|
|
248
|
+
- 🎯 Anti-detection improvements
|
|
249
|
+
- 🚀 New features
|
|
250
|
+
- 🌐 Browser compatibility updates
|
|
251
|
+
|
|
252
|
+
Auto-updates ensure you're always running the most stable and secure version.
|
|
253
|
+
|
|
187
254
|
## Platform-Specific Installation
|
|
188
255
|
|
|
189
256
|
> **🎯 Choose Your Platform:** Select the installation method that matches your operating system. Each section includes complete setup commands for both Brave Browser and MCP server configuration.
|
package/dist/browser-manager.js
CHANGED
|
@@ -2,6 +2,7 @@ import { connect } from 'brave-real-browser';
|
|
|
2
2
|
import * as fs from 'fs';
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import * as net from 'net';
|
|
5
|
+
import { categorizeError as categorizeErrorMCP, ErrorCategory as MCPErrorCategory } from './errors/index.js';
|
|
5
6
|
// Browser error categorization
|
|
6
7
|
export var BrowserErrorType;
|
|
7
8
|
(function (BrowserErrorType) {
|
|
@@ -52,28 +53,31 @@ export async function initializeBrowserForTesting(options) {
|
|
|
52
53
|
return await initializeBrowser(options);
|
|
53
54
|
}
|
|
54
55
|
let sessionValidationInProgress = false;
|
|
55
|
-
// Error handling functions
|
|
56
|
+
// Error handling functions - now uses centralized error system
|
|
57
|
+
// Legacy function kept for backward compatibility
|
|
56
58
|
export function categorizeError(error) {
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return BrowserErrorType.ELEMENT_NOT_FOUND;
|
|
59
|
+
const mcpError = categorizeErrorMCP(error);
|
|
60
|
+
// Map MCPError categories to BrowserErrorType for backward compatibility
|
|
61
|
+
switch (mcpError.category) {
|
|
62
|
+
case MCPErrorCategory.FRAME_DETACHED:
|
|
63
|
+
return BrowserErrorType.FRAME_DETACHED;
|
|
64
|
+
case MCPErrorCategory.SESSION_CLOSED:
|
|
65
|
+
return BrowserErrorType.SESSION_CLOSED;
|
|
66
|
+
case MCPErrorCategory.TARGET_CLOSED:
|
|
67
|
+
return BrowserErrorType.TARGET_CLOSED;
|
|
68
|
+
case MCPErrorCategory.PROTOCOL_ERROR:
|
|
69
|
+
return BrowserErrorType.PROTOCOL_ERROR;
|
|
70
|
+
case MCPErrorCategory.NAVIGATION_TIMEOUT:
|
|
71
|
+
return BrowserErrorType.NAVIGATION_TIMEOUT;
|
|
72
|
+
case MCPErrorCategory.ELEMENT_NOT_FOUND:
|
|
73
|
+
return BrowserErrorType.ELEMENT_NOT_FOUND;
|
|
74
|
+
default:
|
|
75
|
+
return BrowserErrorType.UNKNOWN;
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
+
}
|
|
78
|
+
// New function that returns MCPError for better error handling
|
|
79
|
+
export function categorizeBrowserError(error) {
|
|
80
|
+
return categorizeErrorMCP(error);
|
|
77
81
|
}
|
|
78
82
|
// Timeout wrapper for operations that may hang
|
|
79
83
|
export async function withTimeout(operation, timeoutMs, context = 'unknown') {
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants for Brave Real Browser MCP Server
|
|
3
|
+
* Centralizes all magic numbers and configuration values
|
|
4
|
+
*/
|
|
5
|
+
// Timeout configurations (in milliseconds)
|
|
6
|
+
export const TIMEOUTS = {
|
|
7
|
+
NAVIGATION: 30000, // 30 seconds for page navigation
|
|
8
|
+
ELEMENT_WAIT: 10000, // 10 seconds to wait for elements
|
|
9
|
+
BROWSER_LAUNCH: 60000, // 60 seconds for browser launch
|
|
10
|
+
CAPTCHA_SOLVE: 300000, // 5 minutes for captcha solving
|
|
11
|
+
SCRIPT_EXECUTION: 30000, // 30 seconds for script execution
|
|
12
|
+
PAGE_LOAD: 30000, // 30 seconds for page load
|
|
13
|
+
};
|
|
14
|
+
// Retry configurations
|
|
15
|
+
export const RETRIES = {
|
|
16
|
+
MAX_ATTEMPTS: 3,
|
|
17
|
+
BACKOFF_MULTIPLIER: 2,
|
|
18
|
+
INITIAL_DELAY: 1000, // 1 second initial delay
|
|
19
|
+
MAX_DELAY: 10000, // 10 seconds max delay
|
|
20
|
+
};
|
|
21
|
+
// Browser configurations
|
|
22
|
+
export const BROWSER = {
|
|
23
|
+
DEFAULT_VIEWPORT: {
|
|
24
|
+
width: 1920,
|
|
25
|
+
height: 1080,
|
|
26
|
+
},
|
|
27
|
+
USER_AGENT: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
|
|
28
|
+
DEFAULT_ARGS: [
|
|
29
|
+
'--no-sandbox',
|
|
30
|
+
'--disable-setuid-sandbox',
|
|
31
|
+
'--disable-dev-shm-usage',
|
|
32
|
+
'--disable-blink-features=AutomationControlled',
|
|
33
|
+
],
|
|
34
|
+
};
|
|
35
|
+
// Content extraction limits
|
|
36
|
+
export const CONTENT = {
|
|
37
|
+
MAX_TEXT_LENGTH: 1000000, // 1MB max text content
|
|
38
|
+
MAX_HTML_LENGTH: 5000000, // 5MB max HTML content
|
|
39
|
+
MAX_SCREENSHOT_SIZE: 10000000, // 10MB max screenshot
|
|
40
|
+
MARKDOWN_MAX_LENGTH: 2000000, // 2MB max markdown
|
|
41
|
+
};
|
|
42
|
+
// Selector timeouts
|
|
43
|
+
export const SELECTORS = {
|
|
44
|
+
WAIT_TIMEOUT: 10000, // 10 seconds
|
|
45
|
+
POLL_INTERVAL: 100, // 100ms polling
|
|
46
|
+
MAX_RETRIES: 3,
|
|
47
|
+
};
|
|
48
|
+
// File operation limits
|
|
49
|
+
export const FILES = {
|
|
50
|
+
MAX_FILE_SIZE: 50000000, // 50MB max file size
|
|
51
|
+
ALLOWED_EXTENSIONS: ['.md', '.txt', '.html', '.json'],
|
|
52
|
+
TEMP_DIR: '.temp',
|
|
53
|
+
};
|
|
54
|
+
// Error codes
|
|
55
|
+
export const ERROR_CODES = {
|
|
56
|
+
// Browser errors (1xxx)
|
|
57
|
+
BROWSER_NOT_INITIALIZED: 'MCP_1001',
|
|
58
|
+
BROWSER_LAUNCH_FAILED: 'MCP_1002',
|
|
59
|
+
BROWSER_CONNECTION_LOST: 'MCP_1003',
|
|
60
|
+
// Navigation errors (2xxx)
|
|
61
|
+
NAVIGATION_FAILED: 'MCP_2001',
|
|
62
|
+
NAVIGATION_TIMEOUT: 'MCP_2002',
|
|
63
|
+
PAGE_NOT_FOUND: 'MCP_2003',
|
|
64
|
+
// Element errors (3xxx)
|
|
65
|
+
ELEMENT_NOT_FOUND: 'MCP_3001',
|
|
66
|
+
ELEMENT_NOT_VISIBLE: 'MCP_3002',
|
|
67
|
+
ELEMENT_NOT_CLICKABLE: 'MCP_3003',
|
|
68
|
+
// Content errors (4xxx)
|
|
69
|
+
CONTENT_TOO_LARGE: 'MCP_4001',
|
|
70
|
+
CONTENT_EXTRACTION_FAILED: 'MCP_4002',
|
|
71
|
+
INVALID_SELECTOR: 'MCP_4003',
|
|
72
|
+
// File errors (5xxx)
|
|
73
|
+
FILE_NOT_FOUND: 'MCP_5001',
|
|
74
|
+
FILE_TOO_LARGE: 'MCP_5002',
|
|
75
|
+
FILE_WRITE_FAILED: 'MCP_5003',
|
|
76
|
+
INVALID_FILE_TYPE: 'MCP_5004',
|
|
77
|
+
// General errors (9xxx)
|
|
78
|
+
UNKNOWN_ERROR: 'MCP_9001',
|
|
79
|
+
INVALID_ARGUMENTS: 'MCP_9002',
|
|
80
|
+
OPERATION_TIMEOUT: 'MCP_9003',
|
|
81
|
+
};
|
|
82
|
+
// HTTP status codes for common scenarios
|
|
83
|
+
export const HTTP_STATUS = {
|
|
84
|
+
OK: 200,
|
|
85
|
+
MOVED_PERMANENTLY: 301,
|
|
86
|
+
FOUND: 302,
|
|
87
|
+
BAD_REQUEST: 400,
|
|
88
|
+
UNAUTHORIZED: 401,
|
|
89
|
+
FORBIDDEN: 403,
|
|
90
|
+
NOT_FOUND: 404,
|
|
91
|
+
TIMEOUT: 408,
|
|
92
|
+
INTERNAL_ERROR: 500,
|
|
93
|
+
BAD_GATEWAY: 502,
|
|
94
|
+
SERVICE_UNAVAILABLE: 503,
|
|
95
|
+
};
|
|
96
|
+
// Logging levels
|
|
97
|
+
export const LOG_LEVELS = {
|
|
98
|
+
DEBUG: 0,
|
|
99
|
+
INFO: 1,
|
|
100
|
+
WARN: 2,
|
|
101
|
+
ERROR: 3,
|
|
102
|
+
SILENT: 4,
|
|
103
|
+
};
|
|
104
|
+
// MCP Server configuration
|
|
105
|
+
export const MCP_CONFIG = {
|
|
106
|
+
SERVER_NAME: 'brave-real-browser-mcp-server',
|
|
107
|
+
SERVER_VERSION: '2.0.0',
|
|
108
|
+
PROTOCOL_VERSION: '2024-11-05',
|
|
109
|
+
MAX_TOOLS: 50,
|
|
110
|
+
MAX_RESOURCES: 100,
|
|
111
|
+
};
|
|
112
|
+
// Performance monitoring thresholds
|
|
113
|
+
export const PERFORMANCE = {
|
|
114
|
+
SLOW_OPERATION_THRESHOLD: 5000, // 5 seconds
|
|
115
|
+
MEMORY_WARNING_THRESHOLD: 500 * 1024 * 1024, // 500MB
|
|
116
|
+
CPU_WARNING_THRESHOLD: 80, // 80%
|
|
117
|
+
};
|
|
118
|
+
// Type guards for constants
|
|
119
|
+
export function isValidErrorCode(code) {
|
|
120
|
+
return code in ERROR_CODES;
|
|
121
|
+
}
|
|
122
|
+
export function isValidTimeout(timeout) {
|
|
123
|
+
return timeout > 0 && timeout <= 300000; // Max 5 minutes
|
|
124
|
+
}
|
|
125
|
+
export function isValidRetryCount(count) {
|
|
126
|
+
return count >= 0 && count <= 10;
|
|
127
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Error Handling System for MCP Server
|
|
3
|
+
*
|
|
4
|
+
* This module provides a comprehensive error handling framework with:
|
|
5
|
+
* - Custom error classes for different error categories
|
|
6
|
+
* - Error factory functions for consistent error creation
|
|
7
|
+
* - Error recovery strategies
|
|
8
|
+
* - Error serialization for MCP protocol
|
|
9
|
+
*/
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// ERROR CATEGORIES
|
|
12
|
+
// ============================================================================
|
|
13
|
+
export var ErrorCategory;
|
|
14
|
+
(function (ErrorCategory) {
|
|
15
|
+
// Browser-related errors
|
|
16
|
+
ErrorCategory["BROWSER_NOT_INITIALIZED"] = "BROWSER_NOT_INITIALIZED";
|
|
17
|
+
ErrorCategory["BROWSER_INITIALIZATION_FAILED"] = "BROWSER_INITIALIZATION_FAILED";
|
|
18
|
+
ErrorCategory["BROWSER_CONNECTION_FAILED"] = "BROWSER_CONNECTION_FAILED";
|
|
19
|
+
ErrorCategory["BROWSER_CLOSED"] = "BROWSER_CLOSED";
|
|
20
|
+
// Navigation errors
|
|
21
|
+
ErrorCategory["NAVIGATION_FAILED"] = "NAVIGATION_FAILED";
|
|
22
|
+
ErrorCategory["NAVIGATION_TIMEOUT"] = "NAVIGATION_TIMEOUT";
|
|
23
|
+
ErrorCategory["PAGE_LOAD_FAILED"] = "PAGE_LOAD_FAILED";
|
|
24
|
+
// Element interaction errors
|
|
25
|
+
ErrorCategory["ELEMENT_NOT_FOUND"] = "ELEMENT_NOT_FOUND";
|
|
26
|
+
ErrorCategory["ELEMENT_NOT_INTERACTABLE"] = "ELEMENT_NOT_INTERACTABLE";
|
|
27
|
+
ErrorCategory["SELECTOR_INVALID"] = "SELECTOR_INVALID";
|
|
28
|
+
ErrorCategory["CLICK_FAILED"] = "CLICK_FAILED";
|
|
29
|
+
ErrorCategory["TYPE_FAILED"] = "TYPE_FAILED";
|
|
30
|
+
// Content retrieval errors
|
|
31
|
+
ErrorCategory["CONTENT_RETRIEVAL_FAILED"] = "CONTENT_RETRIEVAL_FAILED";
|
|
32
|
+
ErrorCategory["SCREENSHOT_FAILED"] = "SCREENSHOT_FAILED";
|
|
33
|
+
// File operation errors
|
|
34
|
+
ErrorCategory["FILE_NOT_FOUND"] = "FILE_NOT_FOUND";
|
|
35
|
+
ErrorCategory["FILE_READ_ERROR"] = "FILE_READ_ERROR";
|
|
36
|
+
ErrorCategory["FILE_WRITE_ERROR"] = "FILE_WRITE_ERROR";
|
|
37
|
+
ErrorCategory["INVALID_FILE_PATH"] = "INVALID_FILE_PATH";
|
|
38
|
+
// Validation errors
|
|
39
|
+
ErrorCategory["INVALID_INPUT"] = "INVALID_INPUT";
|
|
40
|
+
ErrorCategory["VALIDATION_FAILED"] = "VALIDATION_FAILED";
|
|
41
|
+
ErrorCategory["WORKFLOW_VIOLATION"] = "WORKFLOW_VIOLATION";
|
|
42
|
+
// Protocol errors
|
|
43
|
+
ErrorCategory["FRAME_DETACHED"] = "FRAME_DETACHED";
|
|
44
|
+
ErrorCategory["SESSION_CLOSED"] = "SESSION_CLOSED";
|
|
45
|
+
ErrorCategory["TARGET_CLOSED"] = "TARGET_CLOSED";
|
|
46
|
+
ErrorCategory["PROTOCOL_ERROR"] = "PROTOCOL_ERROR";
|
|
47
|
+
// System errors
|
|
48
|
+
ErrorCategory["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
49
|
+
ErrorCategory["TIMEOUT_ERROR"] = "TIMEOUT_ERROR";
|
|
50
|
+
ErrorCategory["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
51
|
+
})(ErrorCategory || (ErrorCategory = {}));
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// ERROR SEVERITY LEVELS
|
|
54
|
+
// ============================================================================
|
|
55
|
+
export var ErrorSeverity;
|
|
56
|
+
(function (ErrorSeverity) {
|
|
57
|
+
ErrorSeverity["LOW"] = "LOW";
|
|
58
|
+
ErrorSeverity["MEDIUM"] = "MEDIUM";
|
|
59
|
+
ErrorSeverity["HIGH"] = "HIGH";
|
|
60
|
+
ErrorSeverity["CRITICAL"] = "CRITICAL";
|
|
61
|
+
})(ErrorSeverity || (ErrorSeverity = {}));
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// CUSTOM ERROR CLASSES
|
|
64
|
+
// ============================================================================
|
|
65
|
+
/**
|
|
66
|
+
* Base error class for all MCP Server errors
|
|
67
|
+
*/
|
|
68
|
+
export class MCPError extends Error {
|
|
69
|
+
category;
|
|
70
|
+
severity;
|
|
71
|
+
isRecoverable;
|
|
72
|
+
timestamp;
|
|
73
|
+
context;
|
|
74
|
+
suggestedAction;
|
|
75
|
+
constructor(message, category, severity, isRecoverable = false, context, suggestedAction) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.name = 'MCPError';
|
|
78
|
+
this.category = category;
|
|
79
|
+
this.severity = severity;
|
|
80
|
+
this.isRecoverable = isRecoverable;
|
|
81
|
+
this.timestamp = new Date();
|
|
82
|
+
this.context = context;
|
|
83
|
+
this.suggestedAction = suggestedAction;
|
|
84
|
+
// Maintains proper stack trace for where our error was thrown (only available on V8)
|
|
85
|
+
if (Error.captureStackTrace) {
|
|
86
|
+
Error.captureStackTrace(this, this.constructor);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Serialize error for MCP protocol response
|
|
91
|
+
*/
|
|
92
|
+
toMCPResponse() {
|
|
93
|
+
let errorText = `❌ Error: ${this.message}\n\n`;
|
|
94
|
+
errorText += `📋 Category: ${this.category}\n`;
|
|
95
|
+
errorText += `⚠️ Severity: ${this.severity}\n`;
|
|
96
|
+
if (this.suggestedAction) {
|
|
97
|
+
errorText += `\n💡 Suggested Action:\n${this.suggestedAction}\n`;
|
|
98
|
+
}
|
|
99
|
+
if (this.context && Object.keys(this.context).length > 0) {
|
|
100
|
+
errorText += `\n🔍 Context:\n${JSON.stringify(this.context, null, 2)}\n`;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: 'text',
|
|
106
|
+
text: errorText,
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Browser initialization and lifecycle errors
|
|
114
|
+
*/
|
|
115
|
+
export class BrowserError extends MCPError {
|
|
116
|
+
constructor(message, category = ErrorCategory.BROWSER_INITIALIZATION_FAILED, context, suggestedAction) {
|
|
117
|
+
const severity = category === ErrorCategory.BROWSER_NOT_INITIALIZED
|
|
118
|
+
? ErrorSeverity.HIGH
|
|
119
|
+
: ErrorSeverity.CRITICAL;
|
|
120
|
+
const isRecoverable = category === ErrorCategory.BROWSER_NOT_INITIALIZED;
|
|
121
|
+
super(message, category, severity, isRecoverable, context, suggestedAction);
|
|
122
|
+
this.name = 'BrowserError';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Navigation-related errors
|
|
127
|
+
*/
|
|
128
|
+
export class NavigationError extends MCPError {
|
|
129
|
+
constructor(message, category = ErrorCategory.NAVIGATION_FAILED, context, suggestedAction) {
|
|
130
|
+
const severity = category === ErrorCategory.NAVIGATION_TIMEOUT
|
|
131
|
+
? ErrorSeverity.MEDIUM
|
|
132
|
+
: ErrorSeverity.HIGH;
|
|
133
|
+
super(message, category, severity, true, context, suggestedAction);
|
|
134
|
+
this.name = 'NavigationError';
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Element interaction errors
|
|
139
|
+
*/
|
|
140
|
+
export class InteractionError extends MCPError {
|
|
141
|
+
constructor(message, category = ErrorCategory.ELEMENT_NOT_FOUND, context, suggestedAction) {
|
|
142
|
+
super(message, category, ErrorSeverity.MEDIUM, true, context, suggestedAction);
|
|
143
|
+
this.name = 'InteractionError';
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Content retrieval errors
|
|
148
|
+
*/
|
|
149
|
+
export class ContentError extends MCPError {
|
|
150
|
+
constructor(message, category = ErrorCategory.CONTENT_RETRIEVAL_FAILED, context, suggestedAction) {
|
|
151
|
+
super(message, category, ErrorSeverity.MEDIUM, true, context, suggestedAction);
|
|
152
|
+
this.name = 'ContentError';
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* File operation errors
|
|
157
|
+
*/
|
|
158
|
+
export class FileError extends MCPError {
|
|
159
|
+
constructor(message, category = ErrorCategory.FILE_WRITE_ERROR, context, suggestedAction) {
|
|
160
|
+
const severity = category === ErrorCategory.INVALID_FILE_PATH
|
|
161
|
+
? ErrorSeverity.HIGH
|
|
162
|
+
: ErrorSeverity.MEDIUM;
|
|
163
|
+
super(message, category, severity, false, context, suggestedAction);
|
|
164
|
+
this.name = 'FileError';
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Validation errors
|
|
169
|
+
*/
|
|
170
|
+
export class ValidationError extends MCPError {
|
|
171
|
+
constructor(message, category = ErrorCategory.VALIDATION_FAILED, context, suggestedAction) {
|
|
172
|
+
super(message, category, ErrorSeverity.HIGH, false, context, suggestedAction);
|
|
173
|
+
this.name = 'ValidationError';
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Protocol-level errors
|
|
178
|
+
*/
|
|
179
|
+
export class ProtocolError extends MCPError {
|
|
180
|
+
constructor(message, category = ErrorCategory.PROTOCOL_ERROR, context, suggestedAction) {
|
|
181
|
+
const severity = [
|
|
182
|
+
ErrorCategory.FRAME_DETACHED,
|
|
183
|
+
ErrorCategory.SESSION_CLOSED,
|
|
184
|
+
ErrorCategory.TARGET_CLOSED
|
|
185
|
+
].includes(category) ? ErrorSeverity.CRITICAL : ErrorSeverity.HIGH;
|
|
186
|
+
const isRecoverable = category === ErrorCategory.FRAME_DETACHED;
|
|
187
|
+
super(message, category, severity, isRecoverable, context, suggestedAction);
|
|
188
|
+
this.name = 'ProtocolError';
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// ============================================================================
|
|
192
|
+
// ERROR FACTORY FUNCTIONS
|
|
193
|
+
// ============================================================================
|
|
194
|
+
/**
|
|
195
|
+
* Create browser not initialized error
|
|
196
|
+
*/
|
|
197
|
+
export function createBrowserNotInitializedError() {
|
|
198
|
+
return new BrowserError('Browser is not initialized', ErrorCategory.BROWSER_NOT_INITIALIZED, {}, 'Please call browser_init first to initialize the browser instance.');
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Create browser initialization failed error
|
|
202
|
+
*/
|
|
203
|
+
export function createBrowserInitializationError(originalError, context) {
|
|
204
|
+
return new BrowserError(`Failed to initialize browser: ${originalError.message}`, ErrorCategory.BROWSER_INITIALIZATION_FAILED, { originalError: originalError.message, ...context }, 'Check browser installation and ensure all dependencies are installed. Try running with different configuration options.');
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Create navigation error
|
|
208
|
+
*/
|
|
209
|
+
export function createNavigationError(url, originalError) {
|
|
210
|
+
return new NavigationError(`Failed to navigate to ${url}: ${originalError.message}`, ErrorCategory.NAVIGATION_FAILED, { url, originalError: originalError.message }, 'Verify the URL is correct and accessible. Check network connectivity.');
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Create navigation timeout error
|
|
214
|
+
*/
|
|
215
|
+
export function createNavigationTimeoutError(url, timeout) {
|
|
216
|
+
return new NavigationError(`Navigation to ${url} timed out after ${timeout}ms`, ErrorCategory.NAVIGATION_TIMEOUT, { url, timeout }, 'Try increasing the timeout value or use a different waitUntil option.');
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Create element not found error
|
|
220
|
+
*/
|
|
221
|
+
export function createElementNotFoundError(selector, context) {
|
|
222
|
+
return new InteractionError(`Element not found: ${selector}`, ErrorCategory.ELEMENT_NOT_FOUND, { selector, ...context }, 'Use get_content to analyze the page first, then use find_selector to locate elements by text content.');
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Create element not interactable error
|
|
226
|
+
*/
|
|
227
|
+
export function createElementNotInteractableError(selector, reason) {
|
|
228
|
+
return new InteractionError(`Element not interactable: ${selector}. Reason: ${reason}`, ErrorCategory.ELEMENT_NOT_INTERACTABLE, { selector, reason }, 'Wait for the element to become visible and enabled using the wait tool.');
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Create invalid selector error
|
|
232
|
+
*/
|
|
233
|
+
export function createInvalidSelectorError(selector, originalError) {
|
|
234
|
+
return new InteractionError(`Invalid CSS selector: ${selector}. ${originalError.message}`, ErrorCategory.SELECTOR_INVALID, { selector, originalError: originalError.message }, 'Ensure the selector follows valid CSS syntax. Use find_selector to generate selectors from text content.');
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Create content retrieval error
|
|
238
|
+
*/
|
|
239
|
+
export function createContentRetrievalError(originalError, context) {
|
|
240
|
+
return new ContentError(`Failed to retrieve content: ${originalError.message}`, ErrorCategory.CONTENT_RETRIEVAL_FAILED, { originalError: originalError.message, ...context }, 'Ensure the page is fully loaded. Try waiting for specific elements before retrieving content.');
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Create file operation error
|
|
244
|
+
*/
|
|
245
|
+
export function createFileWriteError(filePath, originalError) {
|
|
246
|
+
return new FileError(`Failed to write file ${filePath}: ${originalError.message}`, ErrorCategory.FILE_WRITE_ERROR, { filePath, originalError: originalError.message }, 'Check file permissions and ensure the directory exists.');
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Create invalid file path error
|
|
250
|
+
*/
|
|
251
|
+
export function createInvalidFilePathError(filePath, reason) {
|
|
252
|
+
return new FileError(`Invalid file path: ${filePath}. ${reason}`, ErrorCategory.INVALID_FILE_PATH, { filePath, reason }, 'Ensure the file path is absolute and uses the correct format for your operating system.');
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Create validation error
|
|
256
|
+
*/
|
|
257
|
+
export function createValidationError(fieldName, value, expectedType) {
|
|
258
|
+
return new ValidationError(`Validation failed for field '${fieldName}': expected ${expectedType}, got ${typeof value}`, ErrorCategory.INVALID_INPUT, { fieldName, value, expectedType }, `Provide a valid ${expectedType} value for ${fieldName}.`);
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Create workflow violation error
|
|
262
|
+
*/
|
|
263
|
+
export function createWorkflowViolationError(toolName, currentState, requiredState) {
|
|
264
|
+
return new ValidationError(`Workflow violation: Cannot execute '${toolName}' in current state '${currentState}'`, ErrorCategory.WORKFLOW_VIOLATION, { toolName, currentState, requiredState }, `Execute the following steps first: ${requiredState}`);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Create protocol error from puppeteer error
|
|
268
|
+
*/
|
|
269
|
+
export function createProtocolErrorFromPuppeteer(originalError) {
|
|
270
|
+
const message = originalError.message.toLowerCase();
|
|
271
|
+
let category = ErrorCategory.PROTOCOL_ERROR;
|
|
272
|
+
let suggestedAction = 'Try refreshing the page or reinitializing the browser.';
|
|
273
|
+
if (message.includes('frame') && message.includes('detached')) {
|
|
274
|
+
category = ErrorCategory.FRAME_DETACHED;
|
|
275
|
+
suggestedAction = 'The page frame was detached. Navigate to a new page or refresh.';
|
|
276
|
+
}
|
|
277
|
+
else if (message.includes('session closed')) {
|
|
278
|
+
category = ErrorCategory.SESSION_CLOSED;
|
|
279
|
+
suggestedAction = 'Browser session was closed. Reinitialize the browser using browser_init.';
|
|
280
|
+
}
|
|
281
|
+
else if (message.includes('target closed')) {
|
|
282
|
+
category = ErrorCategory.TARGET_CLOSED;
|
|
283
|
+
suggestedAction = 'Browser target was closed. Reinitialize the browser using browser_init.';
|
|
284
|
+
}
|
|
285
|
+
return new ProtocolError(originalError.message, category, { originalError: originalError.message }, suggestedAction);
|
|
286
|
+
}
|
|
287
|
+
// ============================================================================
|
|
288
|
+
// ERROR CATEGORIZATION UTILITIES
|
|
289
|
+
// ============================================================================
|
|
290
|
+
/**
|
|
291
|
+
* Categorize a generic error into appropriate MCP error
|
|
292
|
+
*/
|
|
293
|
+
export function categorizeError(error) {
|
|
294
|
+
// Already an MCP error
|
|
295
|
+
if (error instanceof MCPError) {
|
|
296
|
+
return error;
|
|
297
|
+
}
|
|
298
|
+
// Convert to Error if not already
|
|
299
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
300
|
+
const message = err.message.toLowerCase();
|
|
301
|
+
// Browser errors
|
|
302
|
+
if (message.includes('browser not initialized')) {
|
|
303
|
+
return createBrowserNotInitializedError();
|
|
304
|
+
}
|
|
305
|
+
// Navigation errors
|
|
306
|
+
if (message.includes('navigation') || message.includes('navigate')) {
|
|
307
|
+
if (message.includes('timeout')) {
|
|
308
|
+
return new NavigationError(err.message, ErrorCategory.NAVIGATION_TIMEOUT);
|
|
309
|
+
}
|
|
310
|
+
return new NavigationError(err.message, ErrorCategory.NAVIGATION_FAILED);
|
|
311
|
+
}
|
|
312
|
+
// Protocol errors
|
|
313
|
+
if (message.includes('frame') && message.includes('detached')) {
|
|
314
|
+
return createProtocolErrorFromPuppeteer(err);
|
|
315
|
+
}
|
|
316
|
+
if (message.includes('session closed') || message.includes('target closed')) {
|
|
317
|
+
return createProtocolErrorFromPuppeteer(err);
|
|
318
|
+
}
|
|
319
|
+
// Element errors
|
|
320
|
+
if (message.includes('element') || message.includes('selector')) {
|
|
321
|
+
if (message.includes('not found') || message.includes('no node')) {
|
|
322
|
+
return new InteractionError(err.message, ErrorCategory.ELEMENT_NOT_FOUND);
|
|
323
|
+
}
|
|
324
|
+
if (message.includes('not visible') || message.includes('not interactable')) {
|
|
325
|
+
return new InteractionError(err.message, ErrorCategory.ELEMENT_NOT_INTERACTABLE);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// File errors
|
|
329
|
+
if (message.includes('enoent') || message.includes('file not found')) {
|
|
330
|
+
return new FileError(err.message, ErrorCategory.FILE_NOT_FOUND);
|
|
331
|
+
}
|
|
332
|
+
if (message.includes('eacces') || message.includes('permission')) {
|
|
333
|
+
return new FileError(err.message, ErrorCategory.FILE_WRITE_ERROR);
|
|
334
|
+
}
|
|
335
|
+
// Network errors
|
|
336
|
+
if (message.includes('network') || message.includes('econnrefused') || message.includes('enotfound')) {
|
|
337
|
+
return new MCPError(err.message, ErrorCategory.NETWORK_ERROR, ErrorSeverity.HIGH, true, { originalError: err.message }, 'Check network connectivity and firewall settings.');
|
|
338
|
+
}
|
|
339
|
+
// Timeout errors
|
|
340
|
+
if (message.includes('timeout')) {
|
|
341
|
+
return new MCPError(err.message, ErrorCategory.TIMEOUT_ERROR, ErrorSeverity.MEDIUM, true, { originalError: err.message }, 'Try increasing timeout values or check if the operation is blocking.');
|
|
342
|
+
}
|
|
343
|
+
// Default unknown error
|
|
344
|
+
return new MCPError(err.message, ErrorCategory.UNKNOWN_ERROR, ErrorSeverity.MEDIUM, false, { originalError: err.message, stack: err.stack });
|
|
345
|
+
}
|
|
346
|
+
// ============================================================================
|
|
347
|
+
// ERROR RECOVERY UTILITIES
|
|
348
|
+
// ============================================================================
|
|
349
|
+
/**
|
|
350
|
+
* Determine if an error is recoverable
|
|
351
|
+
*/
|
|
352
|
+
export function isRecoverableError(error) {
|
|
353
|
+
if (error instanceof MCPError) {
|
|
354
|
+
return error.isRecoverable;
|
|
355
|
+
}
|
|
356
|
+
const categorized = categorizeError(error);
|
|
357
|
+
return categorized.isRecoverable;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Get recovery strategy for an error
|
|
361
|
+
*/
|
|
362
|
+
export function getRecoveryStrategy(error) {
|
|
363
|
+
if (!error.isRecoverable) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
switch (error.category) {
|
|
367
|
+
case ErrorCategory.BROWSER_NOT_INITIALIZED:
|
|
368
|
+
return 'initialize_browser';
|
|
369
|
+
case ErrorCategory.NAVIGATION_TIMEOUT:
|
|
370
|
+
case ErrorCategory.NAVIGATION_FAILED:
|
|
371
|
+
return 'retry_navigation';
|
|
372
|
+
case ErrorCategory.ELEMENT_NOT_FOUND:
|
|
373
|
+
return 'use_self_healing_locators';
|
|
374
|
+
case ErrorCategory.FRAME_DETACHED:
|
|
375
|
+
return 'refresh_page';
|
|
376
|
+
case ErrorCategory.TIMEOUT_ERROR:
|
|
377
|
+
return 'retry_with_increased_timeout';
|
|
378
|
+
default:
|
|
379
|
+
return 'retry_operation';
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ============================================================================
|
|
383
|
+
// EXPORTS
|
|
384
|
+
// ============================================================================
|
|
385
|
+
export { MCPError as default, };
|