real-browser-mcp-server 1.1.6 → 1.1.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.
Files changed (47) hide show
  1. package/README.md +4 -8
  2. package/dist/lib/cjs/index.js +384 -0
  3. package/{lib → dist/lib}/cjs/module/pageController.js +27 -29
  4. package/{lib → dist/lib}/cjs/module/turnstile.js +23 -12
  5. package/dist/src/ai/action-parser.js +229 -0
  6. package/dist/src/ai/core.js +367 -0
  7. package/dist/src/ai/element-finder.js +409 -0
  8. package/{src → dist/src}/ai/index.js +35 -50
  9. package/dist/src/ai/page-analyzer.js +264 -0
  10. package/dist/src/ai/selector-healer.js +215 -0
  11. package/dist/src/index.js +116 -0
  12. package/dist/src/mcp/handlers/browser.js +230 -0
  13. package/dist/src/mcp/handlers/dom.js +550 -0
  14. package/dist/src/mcp/handlers/extract.js +451 -0
  15. package/dist/src/mcp/handlers/helpers.js +514 -0
  16. package/dist/src/mcp/handlers/index.js +63 -0
  17. package/dist/src/mcp/handlers/misc.js +1224 -0
  18. package/dist/src/mcp/handlers/network.js +1134 -0
  19. package/dist/src/mcp/handlers/state.js +215 -0
  20. package/dist/src/mcp/handlers/vision.js +475 -0
  21. package/dist/src/mcp/index.js +166 -0
  22. package/dist/src/mcp/server.js +117 -0
  23. package/{src → dist/src}/mcp/tools.js +12 -11
  24. package/dist/src/shared/tools.js +598 -0
  25. package/{test → dist/test}/cjs/test.js +119 -169
  26. package/dist/test/mcp/smoke-test.js +131 -0
  27. package/lib/esm/module/pageController.mjs +21 -18
  28. package/lib/esm/module/turnstile.mjs +7 -0
  29. package/package.json +22 -11
  30. package/.github/ISSUE_TEMPLATE/general_issue.yaml +0 -58
  31. package/.github/SETUP.md +0 -111
  32. package/.github/workflows/publish.yml +0 -162
  33. package/Dockerfile +0 -78
  34. package/lib/cjs/adblocker.bin +0 -0
  35. package/lib/cjs/index.js +0 -396
  36. package/src/ai/action-parser.js +0 -274
  37. package/src/ai/core.js +0 -379
  38. package/src/ai/element-finder.js +0 -466
  39. package/src/ai/page-analyzer.js +0 -295
  40. package/src/ai/selector-healer.js +0 -236
  41. package/src/index.js +0 -128
  42. package/src/mcp/handlers.js +0 -5397
  43. package/src/mcp/index.js +0 -190
  44. package/src/mcp/server.js +0 -141
  45. package/src/shared/tools.js +0 -667
  46. package/test/esm/test.mjs +0 -299
  47. package/test/mcp/smoke-test.js +0 -141
@@ -0,0 +1,264 @@
1
+ "use strict";
2
+ // @ts-nocheck
3
+ class PageAnalyzer {
4
+ constructor() {
5
+ this.analysisTypes = ['full', 'interactive', 'forms', 'navigation', 'content', 'media'];
6
+ }
7
+ /**
8
+ * Analyze page structure
9
+ */
10
+ async analyze(page, options = {}) {
11
+ const { analysisType = 'full', includeSelectors = true, includeScreenshot = false, maxDepth = 10 } = options;
12
+ const analysis = await page.evaluate(({ analysisType, includeSelectors, maxDepth }) => {
13
+ const result = {
14
+ url: window.location.href,
15
+ title: document.title,
16
+ timestamp: new Date().toISOString(),
17
+ viewport: {
18
+ width: window.innerWidth,
19
+ height: window.innerHeight
20
+ }
21
+ };
22
+ // Helper to generate selector
23
+ const getSelector = (el) => {
24
+ if (!includeSelectors)
25
+ return null;
26
+ if (el.id)
27
+ return `#${el.id}`;
28
+ if (el.name)
29
+ return `[name="${el.name}"]`;
30
+ if (el.className && typeof el.className === 'string') {
31
+ const cls = el.className.split(' ').filter(c => c)[0];
32
+ if (cls)
33
+ return `${el.tagName.toLowerCase()}.${cls}`;
34
+ }
35
+ return el.tagName.toLowerCase();
36
+ };
37
+ // Analyze interactive elements
38
+ if (analysisType === 'full' || analysisType === 'interactive') {
39
+ result.interactive = {
40
+ buttons: [],
41
+ links: [],
42
+ inputs: []
43
+ };
44
+ // Buttons
45
+ document.querySelectorAll('button, [role="button"], input[type="button"], input[type="submit"]').forEach(el => {
46
+ const rect = el.getBoundingClientRect();
47
+ if (rect.width > 0 && rect.height > 0) {
48
+ result.interactive.buttons.push({
49
+ selector: getSelector(el),
50
+ text: el.textContent?.trim().substring(0, 50),
51
+ type: el.type || 'button',
52
+ disabled: el.disabled,
53
+ visible: true
54
+ });
55
+ }
56
+ });
57
+ // Links
58
+ document.querySelectorAll('a[href]').forEach(el => {
59
+ const rect = el.getBoundingClientRect();
60
+ if (rect.width > 0 && rect.height > 0) {
61
+ result.interactive.links.push({
62
+ selector: getSelector(el),
63
+ text: el.textContent?.trim().substring(0, 50),
64
+ href: el.href,
65
+ target: el.target || '_self',
66
+ visible: true
67
+ });
68
+ }
69
+ });
70
+ // Inputs
71
+ document.querySelectorAll('input, textarea, select').forEach(el => {
72
+ const rect = el.getBoundingClientRect();
73
+ if (rect.width > 0 && rect.height > 0) {
74
+ result.interactive.inputs.push({
75
+ selector: getSelector(el),
76
+ type: el.type || 'text',
77
+ name: el.name,
78
+ placeholder: el.placeholder,
79
+ required: el.required,
80
+ disabled: el.disabled,
81
+ value: el.type === 'password' ? '***' : el.value?.substring(0, 20),
82
+ visible: true
83
+ });
84
+ }
85
+ });
86
+ }
87
+ // Analyze forms
88
+ if (analysisType === 'full' || analysisType === 'forms') {
89
+ result.forms = [];
90
+ document.querySelectorAll('form').forEach(form => {
91
+ const formData = {
92
+ selector: getSelector(form),
93
+ action: form.action,
94
+ method: form.method,
95
+ fields: []
96
+ };
97
+ form.querySelectorAll('input, textarea, select').forEach(field => {
98
+ formData.fields.push({
99
+ selector: getSelector(field),
100
+ type: field.type || field.tagName.toLowerCase(),
101
+ name: field.name,
102
+ label: document.querySelector(`label[for="${field.id}"]`)?.textContent?.trim(),
103
+ required: field.required,
104
+ placeholder: field.placeholder
105
+ });
106
+ });
107
+ // Find submit button
108
+ const submitBtn = form.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
109
+ if (submitBtn) {
110
+ formData.submitButton = {
111
+ selector: getSelector(submitBtn),
112
+ text: submitBtn.textContent?.trim() || submitBtn.value
113
+ };
114
+ }
115
+ result.forms.push(formData);
116
+ });
117
+ }
118
+ // Analyze navigation
119
+ if (analysisType === 'full' || analysisType === 'navigation') {
120
+ result.navigation = {
121
+ menus: [],
122
+ breadcrumbs: null,
123
+ pagination: null
124
+ };
125
+ // Find menus
126
+ document.querySelectorAll('nav, [role="navigation"], .nav, .menu, header ul').forEach(nav => {
127
+ const links = [];
128
+ nav.querySelectorAll('a').forEach(a => {
129
+ links.push({
130
+ text: a.textContent?.trim().substring(0, 30),
131
+ href: a.href,
132
+ selector: getSelector(a)
133
+ });
134
+ });
135
+ if (links.length > 0) {
136
+ result.navigation.menus.push({
137
+ selector: getSelector(nav),
138
+ type: nav.tagName.toLowerCase(),
139
+ links: links.slice(0, 20)
140
+ });
141
+ }
142
+ });
143
+ // Find breadcrumbs
144
+ const breadcrumb = document.querySelector('[aria-label*="breadcrumb"], .breadcrumb, .breadcrumbs');
145
+ if (breadcrumb) {
146
+ result.navigation.breadcrumbs = {
147
+ selector: getSelector(breadcrumb),
148
+ items: Array.from(breadcrumb.querySelectorAll('a, span')).map(el => el.textContent?.trim()).filter(Boolean)
149
+ };
150
+ }
151
+ // Find pagination
152
+ const pagination = document.querySelector('.pagination, [aria-label*="pagination"], nav[role="navigation"] a[href*="page"]');
153
+ if (pagination) {
154
+ result.navigation.pagination = {
155
+ selector: getSelector(pagination.closest('nav, .pagination, div')),
156
+ found: true
157
+ };
158
+ }
159
+ }
160
+ // Analyze main content
161
+ if (analysisType === 'full' || analysisType === 'content') {
162
+ result.content = {
163
+ headings: [],
164
+ paragraphs: 0,
165
+ mainContent: null
166
+ };
167
+ // Headings
168
+ document.querySelectorAll('h1, h2, h3').forEach(h => {
169
+ result.content.headings.push({
170
+ level: parseInt(h.tagName[1]),
171
+ text: h.textContent?.trim().substring(0, 100),
172
+ selector: getSelector(h)
173
+ });
174
+ });
175
+ // Count paragraphs
176
+ result.content.paragraphs = document.querySelectorAll('p').length;
177
+ // Find main content area
178
+ const main = document.querySelector('main, [role="main"], article, .content, #content');
179
+ if (main) {
180
+ result.content.mainContent = {
181
+ selector: getSelector(main),
182
+ textLength: main.textContent?.length || 0
183
+ };
184
+ }
185
+ }
186
+ // Analyze media
187
+ if (analysisType === 'full' || analysisType === 'media') {
188
+ result.media = {
189
+ images: [],
190
+ videos: [],
191
+ iframes: []
192
+ };
193
+ // Images
194
+ document.querySelectorAll('img').forEach(img => {
195
+ const rect = img.getBoundingClientRect();
196
+ if (rect.width > 50 && rect.height > 50) {
197
+ result.media.images.push({
198
+ selector: getSelector(img),
199
+ src: img.src?.substring(0, 100),
200
+ alt: img.alt,
201
+ width: Math.round(rect.width),
202
+ height: Math.round(rect.height)
203
+ });
204
+ }
205
+ });
206
+ // Videos
207
+ document.querySelectorAll('video').forEach(video => {
208
+ result.media.videos.push({
209
+ selector: getSelector(video),
210
+ src: video.src || video.querySelector('source')?.src,
211
+ duration: video.duration,
212
+ controls: video.controls
213
+ });
214
+ });
215
+ // Iframes (potential embedded content)
216
+ document.querySelectorAll('iframe').forEach(iframe => {
217
+ result.media.iframes.push({
218
+ selector: getSelector(iframe),
219
+ src: iframe.src?.substring(0, 100),
220
+ title: iframe.title
221
+ });
222
+ });
223
+ }
224
+ // Summary stats
225
+ result.summary = {
226
+ totalButtons: result.interactive?.buttons?.length || 0,
227
+ totalLinks: result.interactive?.links?.length || 0,
228
+ totalInputs: result.interactive?.inputs?.length || 0,
229
+ totalForms: result.forms?.length || 0,
230
+ totalImages: result.media?.images?.length || 0,
231
+ hasNavigation: (result.navigation?.menus?.length || 0) > 0,
232
+ isLoginPage: !!(result.interactive?.inputs?.find(i => i.type === 'password')),
233
+ isSearchPage: !!(result.interactive?.inputs?.find(i => i.type === 'search' || i.name?.includes('search') || i.placeholder?.toLowerCase().includes('search')))
234
+ };
235
+ return result;
236
+ }, { analysisType, includeSelectors, maxDepth });
237
+ // Add screenshot if requested
238
+ if (includeScreenshot) {
239
+ // Playwright/Patchright returns a Buffer; convert to base64 ourselves
240
+ const buf = await page.screenshot({ type: 'jpeg', quality: 50 });
241
+ analysis.screenshot = Buffer.from(buf).toString('base64');
242
+ }
243
+ return analysis;
244
+ }
245
+ /**
246
+ * Get quick summary of page
247
+ */
248
+ async quickSummary(page) {
249
+ return await page.evaluate(() => {
250
+ return {
251
+ url: window.location.href,
252
+ title: document.title,
253
+ buttons: document.querySelectorAll('button, [role="button"]').length,
254
+ links: document.querySelectorAll('a[href]').length,
255
+ inputs: document.querySelectorAll('input, textarea').length,
256
+ forms: document.querySelectorAll('form').length,
257
+ images: document.querySelectorAll('img').length,
258
+ hasLogin: !!document.querySelector('input[type="password"]'),
259
+ hasSearch: !!document.querySelector('input[type="search"], [name*="search"], [placeholder*="earch"]')
260
+ };
261
+ });
262
+ }
263
+ }
264
+ module.exports = PageAnalyzer;
@@ -0,0 +1,215 @@
1
+ "use strict";
2
+ // @ts-nocheck
3
+ /**
4
+ * AI Selector Healer - Auto-fix broken CSS selectors
5
+ *
6
+ * When a selector stops working (page updated, dynamic content, etc.),
7
+ * this module tries to find alternative selectors that match the same element.
8
+ */
9
+ class SelectorHealer {
10
+ constructor() {
11
+ this.healingStrategies = [
12
+ 'byId',
13
+ 'byName',
14
+ 'byAriaLabel',
15
+ 'byText',
16
+ 'byClasses',
17
+ 'byAttributes',
18
+ 'byStructure',
19
+ 'byPosition'
20
+ ];
21
+ }
22
+ /**
23
+ * Heal a broken selector by finding alternatives
24
+ */
25
+ async heal(page, brokenSelector, options = {}) {
26
+ const { lastKnownText = null, lastKnownAttributes = {}, elementType = null, maxAlternatives = 5 } = options;
27
+ const context = {
28
+ brokenSelector,
29
+ lastKnownText,
30
+ lastKnownAttributes,
31
+ elementType
32
+ };
33
+ // Try to extract info from broken selector
34
+ const selectorInfo = this.parseSelector(brokenSelector);
35
+ // Find alternatives using multiple strategies
36
+ const alternatives = await page.evaluate(({ context, selectorInfo }) => {
37
+ const results = [];
38
+ // Strategy 1: Find by similar ID
39
+ if (selectorInfo.id) {
40
+ const elements = document.querySelectorAll(`[id*="${selectorInfo.id}"]`);
41
+ for (const el of elements) {
42
+ results.push({
43
+ selector: `#${el.id}`,
44
+ confidence: 0.9,
45
+ strategy: 'byId',
46
+ reason: 'Similar ID found'
47
+ });
48
+ }
49
+ }
50
+ // Strategy 2: Find by name attribute
51
+ if (selectorInfo.name) {
52
+ const elements = document.querySelectorAll(`[name*="${selectorInfo.name}"]`);
53
+ for (const el of elements) {
54
+ results.push({
55
+ selector: `[name="${el.name}"]`,
56
+ confidence: 0.85,
57
+ strategy: 'byName',
58
+ reason: 'Similar name attribute found'
59
+ });
60
+ }
61
+ }
62
+ // Strategy 3: Find by last known text
63
+ if (context.lastKnownText) {
64
+ const allElements = document.querySelectorAll('button, a, input, label, span, div, [role]');
65
+ for (const el of allElements) {
66
+ const text = el.textContent?.trim();
67
+ if (text && text.toLowerCase().includes(context.lastKnownText.toLowerCase())) {
68
+ let selector = '';
69
+ if (el.id) {
70
+ selector = `#${el.id}`;
71
+ }
72
+ else if (el.className) {
73
+ selector = `${el.tagName.toLowerCase()}.${el.className.split(' ')[0]}`;
74
+ }
75
+ else {
76
+ selector = `${el.tagName.toLowerCase()}`;
77
+ }
78
+ results.push({
79
+ selector,
80
+ confidence: 0.8,
81
+ strategy: 'byText',
82
+ reason: `Contains text: "${text.substring(0, 30)}"`
83
+ });
84
+ }
85
+ }
86
+ }
87
+ // Strategy 4: Find by similar classes
88
+ if (selectorInfo.classes.length > 0) {
89
+ for (const cls of selectorInfo.classes) {
90
+ const elements = document.querySelectorAll(`[class*="${cls}"]`);
91
+ for (const el of elements) {
92
+ const rect = el.getBoundingClientRect();
93
+ if (rect.width > 0 && rect.height > 0) {
94
+ results.push({
95
+ selector: `.${cls}`,
96
+ confidence: 0.7,
97
+ strategy: 'byClasses',
98
+ reason: `Similar class: ${cls}`
99
+ });
100
+ }
101
+ }
102
+ }
103
+ }
104
+ // Strategy 5: Find by tag and type
105
+ if (selectorInfo.tag) {
106
+ let selector = selectorInfo.tag;
107
+ if (selectorInfo.type) {
108
+ selector += `[type="${selectorInfo.type}"]`;
109
+ }
110
+ const elements = document.querySelectorAll(selector);
111
+ for (const el of elements) {
112
+ const rect = el.getBoundingClientRect();
113
+ if (rect.width > 0 && rect.height > 0) {
114
+ let uniqueSelector = selector;
115
+ if (el.placeholder) {
116
+ uniqueSelector += `[placeholder="${el.placeholder}"]`;
117
+ }
118
+ else if (el.value) {
119
+ uniqueSelector += `[value="${el.value}"]`;
120
+ }
121
+ results.push({
122
+ selector: uniqueSelector,
123
+ confidence: 0.6,
124
+ strategy: 'byAttributes',
125
+ reason: `Similar element structure`
126
+ });
127
+ }
128
+ }
129
+ }
130
+ // Strategy 6: Find by aria-label
131
+ if (context.lastKnownAttributes?.['aria-label']) {
132
+ const label = context.lastKnownAttributes['aria-label'];
133
+ const elements = document.querySelectorAll(`[aria-label*="${label}"]`);
134
+ for (const el of elements) {
135
+ results.push({
136
+ selector: `[aria-label="${el.getAttribute('aria-label')}"]`,
137
+ confidence: 0.85,
138
+ strategy: 'byAriaLabel',
139
+ reason: 'Similar aria-label found'
140
+ });
141
+ }
142
+ }
143
+ // Deduplicate
144
+ const seen = new Set();
145
+ return results.filter(r => {
146
+ if (seen.has(r.selector))
147
+ return false;
148
+ seen.add(r.selector);
149
+ return true;
150
+ });
151
+ }, { context, selectorInfo });
152
+ // Sort by confidence and return top alternatives
153
+ return alternatives
154
+ .sort((a, b) => b.confidence - a.confidence)
155
+ .slice(0, maxAlternatives);
156
+ }
157
+ /**
158
+ * Parse a CSS selector to extract components
159
+ */
160
+ parseSelector(selector) {
161
+ const info = {
162
+ tag: null,
163
+ id: null,
164
+ classes: [],
165
+ name: null,
166
+ type: null,
167
+ attributes: {}
168
+ };
169
+ // Extract ID
170
+ const idMatch = selector.match(/#([a-zA-Z0-9_-]+)/);
171
+ if (idMatch)
172
+ info.id = idMatch[1];
173
+ // Extract classes
174
+ const classMatches = selector.matchAll(/\.([a-zA-Z0-9_-]+)/g);
175
+ for (const match of classMatches) {
176
+ info.classes.push(match[1]);
177
+ }
178
+ // Extract tag
179
+ const tagMatch = selector.match(/^([a-zA-Z]+)/);
180
+ if (tagMatch)
181
+ info.tag = tagMatch[1];
182
+ // Extract attributes
183
+ const attrMatches = selector.matchAll(/\[([a-zA-Z-]+)(?:=["']?([^"'\]]+)["']?)?\]/g);
184
+ for (const match of attrMatches) {
185
+ const key = match[1];
186
+ const value = match[2] || true;
187
+ info.attributes[key] = value;
188
+ if (key === 'name')
189
+ info.name = value;
190
+ if (key === 'type')
191
+ info.type = value;
192
+ }
193
+ return info;
194
+ }
195
+ /**
196
+ * Test if a selector works
197
+ */
198
+ async testSelector(page, selector) {
199
+ try {
200
+ const element = await page.$(selector);
201
+ if (!element)
202
+ return { valid: false };
203
+ const info = await element.evaluate(el => ({
204
+ visible: el.offsetWidth > 0 && el.offsetHeight > 0,
205
+ tag: el.tagName.toLowerCase(),
206
+ text: el.textContent?.substring(0, 50)
207
+ }));
208
+ return { valid: true, ...info };
209
+ }
210
+ catch {
211
+ return { valid: false };
212
+ }
213
+ }
214
+ }
215
+ module.exports = SelectorHealer;
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ // @ts-nocheck
4
+ /**
5
+ * Real Browser MCP Server
6
+ *
7
+ * Usage:
8
+ * node src/index.js - Start MCP Server (default)
9
+ * node src/index.js --help - Show help
10
+ * node src/index.js --list - List all available tools
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ const { TOOLS, TOOL_DISPLAY, CATEGORIES } = require('./shared/tools');
14
+ // ANSI colors for terminal
15
+ const colors = {
16
+ reset: '\x1b[0m',
17
+ bright: '\x1b[1m',
18
+ dim: '\x1b[2m',
19
+ green: '\x1b[32m',
20
+ yellow: '\x1b[33m',
21
+ blue: '\x1b[34m',
22
+ magenta: '\x1b[35m',
23
+ cyan: '\x1b[36m',
24
+ red: '\x1b[31m',
25
+ };
26
+ /**
27
+ * Display help message
28
+ */
29
+ function showHelp() {
30
+ console.log(`
31
+ ${colors.bright}${colors.cyan}🦁 Real Browser MCP Server${colors.reset}
32
+
33
+ ${colors.bright}USAGE:${colors.reset}
34
+ node src/index.js [options]
35
+
36
+ ${colors.bright}OPTIONS:${colors.reset}
37
+ ${colors.yellow}--help, -h${colors.reset} Show this help message
38
+ ${colors.yellow}--verbose, -v${colors.reset} Show detailed tool information
39
+ ${colors.yellow}--list${colors.reset} List all available tools
40
+
41
+ ${colors.bright}EXAMPLES:${colors.reset}
42
+ node src/index.js # Start MCP server
43
+ node src/index.js --list # List all tools
44
+
45
+ ${colors.bright}NPM SCRIPTS:${colors.reset}
46
+ npm run dev # Start MCP server
47
+ npm run mcp # Start MCP server only
48
+
49
+ ${colors.bright}ARCHITECTURE:${colors.reset}
50
+ ${colors.cyan}MCP Server${colors.reset} → STDIO transport → AI Agents (Claude, Cursor, Copilot)
51
+
52
+ ${colors.bright}TOOL CATEGORIES (${TOOLS.length} tools):${colors.reset}
53
+ ${Object.entries(CATEGORIES).map(([key, cat]) => {
54
+ const count = TOOLS.filter(t => t.category === key).length;
55
+ return ` ${cat.emoji} ${colors.yellow}${cat.name.padEnd(15)}${colors.reset} ${colors.dim}(${count} tools)${colors.reset}`;
56
+ }).join('\n')}
57
+ `);
58
+ }
59
+ /**
60
+ * List all available tools
61
+ */
62
+ function listTools() {
63
+ console.log(`\n${colors.bright}${colors.cyan}🦁 Available Tools (${TOOLS.length}):${colors.reset}\n`);
64
+ for (const [key, cat] of Object.entries(CATEGORIES)) {
65
+ const tools = TOOLS.filter(t => t.category === key);
66
+ if (tools.length === 0)
67
+ continue;
68
+ console.log(`${colors.bright}${cat.emoji} ${cat.name}${colors.reset} ${colors.dim}(${tools.length})${colors.reset}`);
69
+ console.log(`${colors.dim}${'─'.repeat(50)}${colors.reset}`);
70
+ for (const tool of tools) {
71
+ console.log(` ${tool.emoji} ${colors.yellow}${tool.name.padEnd(25)}${colors.reset} ${colors.dim}${tool.description.substring(0, 40)}${colors.reset}`);
72
+ }
73
+ console.log('');
74
+ }
75
+ }
76
+ /**
77
+ * Main entry point
78
+ */
79
+ async function main() {
80
+ const args = process.argv.slice(2);
81
+ // Parse arguments
82
+ const hasHelp = args.includes('--help') || args.includes('-h');
83
+ const hasList = args.includes('--list');
84
+ if (hasHelp) {
85
+ showHelp();
86
+ process.exit(0);
87
+ }
88
+ if (hasList) {
89
+ listTools();
90
+ process.exit(0);
91
+ }
92
+ console.error('');
93
+ console.error(`${colors.bright}${colors.cyan}╔════════════════════════════════════════════════════════════╗${colors.reset}`);
94
+ console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.bright}${colors.magenta}🦁 Real Browser MCP Server${colors.reset} ${colors.cyan}║${colors.reset}`);
95
+ console.error(`${colors.bright}${colors.cyan}║${colors.reset} ${colors.dim}MCP (AI Agents) Server running on STDIO${colors.reset} ${colors.cyan}║${colors.reset}`);
96
+ console.error(`${colors.bright}${colors.cyan}╚════════════════════════════════════════════════════════════╝${colors.reset}`);
97
+ console.error('');
98
+ console.error(`${colors.bright}${colors.blue}🚀 Starting MCP Server...${colors.reset}`);
99
+ // Import and run MCP server
100
+ require('./mcp/index');
101
+ }
102
+ // Export for programmatic use
103
+ module.exports = {
104
+ TOOLS,
105
+ TOOL_DISPLAY,
106
+ CATEGORIES,
107
+ startMCP: () => require('./mcp/index'),
108
+ startBoth: () => require('./mcp/index'),
109
+ };
110
+ // Run if called directly
111
+ if (require.main === module) {
112
+ main().catch(error => {
113
+ console.error(`${colors.red}❌ Fatal error:${colors.reset}`, error.message);
114
+ process.exit(1);
115
+ });
116
+ }