brave-real-browser-mcp-server 2.11.6 → 2.11.7
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 +268 -18
- package/dist/index.js +22 -34
- package/dist/launcher.js +164 -0
- package/dist/mcp-server.js +141 -0
- package/dist/transports/http-transport.js +222 -0
- package/dist/transports/lsp-transport.js +165 -0
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -14,19 +14,28 @@ Provides AI assistants with powerful, detection-resistant browser automation cap
|
|
|
14
14
|
3. [Features](#features)
|
|
15
15
|
4. [Prerequisites](#prerequisites)
|
|
16
16
|
5. [Installation](#installation)
|
|
17
|
-
6. [
|
|
18
|
-
|
|
17
|
+
6. [Multi-Protocol Support](#-multi-protocol-support)
|
|
18
|
+
7. [Usage](#usage)
|
|
19
|
+
- [Quick Configuration Reference](#-quick-configuration-reference)
|
|
20
|
+
- [With Claude Desktop](#with-claude-desktop-mcp-protocol)
|
|
19
21
|
- [With Claude Code CLI](#with-claude-code-cli)
|
|
20
22
|
- [With Cursor IDE](#with-cursor-ide)
|
|
21
|
-
- [With
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
23
|
+
- [With Warp AI Terminal](#with-warp-ai-terminal)
|
|
24
|
+
- [With Windsurf IDE](#with-windsurf-ide-codeium)
|
|
25
|
+
- [With Continue.dev](#with-continuedev-vscode-extension)
|
|
26
|
+
- [With Cody AI](#with-cody-ai-sourcegraph)
|
|
27
|
+
- [With Zed Editor](#with-zed-editor-lsp-mode)
|
|
28
|
+
- [With VSCode](#with-vscode-via-lsp)
|
|
29
|
+
- [HTTP/WebSocket Mode](#httpwebsocket-mode-for-any-language)
|
|
30
|
+
- [Complete MCP IDE Compatibility Matrix](#-complete-mcp-ide-compatibility-matrix)
|
|
31
|
+
8. [Available Tools](#available-tools)
|
|
32
|
+
9. [Advanced Features](#advanced-features)
|
|
33
|
+
10. [Configuration](#configuration)
|
|
34
|
+
11. [Troubleshooting](#troubleshooting)
|
|
35
|
+
12. [Development](#development)
|
|
36
|
+
13. [Testing](#testing)
|
|
37
|
+
14. [Contributing](#contributing)
|
|
38
|
+
15. [License](#license)
|
|
30
39
|
|
|
31
40
|
## Quick Start for Beginners
|
|
32
41
|
|
|
@@ -193,9 +202,65 @@ npm run build
|
|
|
193
202
|
npm run dev
|
|
194
203
|
```
|
|
195
204
|
|
|
205
|
+
## 🚀 Multi-Protocol Support
|
|
206
|
+
|
|
207
|
+
This server now supports **three protocols** for maximum compatibility:
|
|
208
|
+
|
|
209
|
+
| Protocol | Best For | Setup Difficulty |
|
|
210
|
+
|----------|----------|-----------------|
|
|
211
|
+
| **MCP** | Claude Desktop, Cursor, Warp AI | Easy |
|
|
212
|
+
| **HTTP/WebSocket** | Any programming language | Easy |
|
|
213
|
+
| **LSP** | Zed, VSCode, Neovim | Medium |
|
|
214
|
+
|
|
215
|
+
### Quick Protocol Selection
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# MCP mode (default) - For Claude Desktop, Cursor, Warp
|
|
219
|
+
npx brave-real-browser-mcp-server
|
|
220
|
+
|
|
221
|
+
# HTTP mode - For REST API / Any language
|
|
222
|
+
npx brave-real-browser-mcp-server --mode http --port 3000
|
|
223
|
+
|
|
224
|
+
# LSP mode - For Zed AI IDE, VSCode, Neovim
|
|
225
|
+
npx brave-real-browser-mcp-server --mode lsp
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
📖 **Complete Guide:** See [MULTI_PROTOCOL_GUIDE.md](./MULTI_PROTOCOL_GUIDE.md) for detailed setup instructions.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
196
232
|
## Usage
|
|
197
233
|
|
|
198
|
-
###
|
|
234
|
+
### 📝 Quick Configuration Reference
|
|
235
|
+
|
|
236
|
+
| Your IDE/Tool | Jump To Section |
|
|
237
|
+
|---------------|----------------|
|
|
238
|
+
| Claude Desktop | [Configuration](#with-claude-desktop-mcp-protocol) |
|
|
239
|
+
| Claude Code CLI | [Configuration](#with-claude-code-cli) |
|
|
240
|
+
| Cursor IDE | [Configuration](#with-cursor-ide) |
|
|
241
|
+
| Warp Terminal | [Configuration](#with-warp-ai-terminal) |
|
|
242
|
+
| Windsurf IDE | [Configuration](#with-windsurf-ide-codeium) |
|
|
243
|
+
| Continue.dev | [Configuration](#with-continuedev-vscode-extension) |
|
|
244
|
+
| Cody AI | [Configuration](#with-cody-ai-sourcegraph) |
|
|
245
|
+
| Zed Editor | [Configuration](#with-zed-editor-lsp-mode) |
|
|
246
|
+
| VSCode | [Configuration](#with-vscode-via-lsp) |
|
|
247
|
+
| Python/Node.js/Any Language | [Configuration](#httpwebsocket-mode-for-any-language) |
|
|
248
|
+
|
|
249
|
+
**All platforms use the same simple pattern:**
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"mcpServers": {
|
|
253
|
+
"brave-real-browser": {
|
|
254
|
+
"command": "npx",
|
|
255
|
+
"args": ["brave-real-browser-mcp-server@latest"]
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
### With Claude Desktop (MCP Protocol)
|
|
199
264
|
|
|
200
265
|
The configuration below uses `npx` to automatically download and run the latest version. No installation required!
|
|
201
266
|
|
|
@@ -436,22 +501,207 @@ If successful, you should see:
|
|
|
436
501
|
- Check file is in correct directory
|
|
437
502
|
- Restart Cursor IDE after changes
|
|
438
503
|
|
|
439
|
-
### With
|
|
504
|
+
### With Warp AI Terminal
|
|
440
505
|
|
|
441
|
-
|
|
506
|
+
Warp Terminal has native MCP support built-in.
|
|
442
507
|
|
|
443
|
-
|
|
444
|
-
|
|
508
|
+
**Configuration Location:**
|
|
509
|
+
- macOS: `~/.warp/mcp_config.json`
|
|
510
|
+
- Linux: `~/.warp/mcp_config.json`
|
|
511
|
+
- Windows: `%USERPROFILE%\.warp\mcp_config.json`
|
|
512
|
+
|
|
513
|
+
**Configuration:**
|
|
514
|
+
```json
|
|
515
|
+
{
|
|
516
|
+
"mcpServers": {
|
|
517
|
+
"brave-real-browser": {
|
|
518
|
+
"command": "npx",
|
|
519
|
+
"args": ["brave-real-browser-mcp-server@latest"]
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
**Testing:**
|
|
526
|
+
1. Restart Warp Terminal
|
|
527
|
+
2. Type: `/mcp` to see available servers
|
|
528
|
+
3. Test: "Initialize browser and go to google.com"
|
|
529
|
+
|
|
530
|
+
### With Windsurf IDE (Codeium)
|
|
531
|
+
|
|
532
|
+
Windsurf IDE by Codeium supports MCP servers.
|
|
533
|
+
|
|
534
|
+
**Configuration Location:**
|
|
535
|
+
- Create `.windsurf/mcp.json` in your project
|
|
536
|
+
- Or `~/.windsurf/mcp.json` for global configuration
|
|
537
|
+
|
|
538
|
+
**Configuration:**
|
|
539
|
+
```json
|
|
540
|
+
{
|
|
541
|
+
"mcpServers": {
|
|
542
|
+
"brave-real-browser": {
|
|
543
|
+
"command": "npx",
|
|
544
|
+
"args": ["brave-real-browser-mcp-server@latest"],
|
|
545
|
+
"disabled": false
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### With Continue.dev (VSCode Extension)
|
|
552
|
+
|
|
553
|
+
Continue.dev supports MCP through its VSCode/JetBrains extension.
|
|
554
|
+
|
|
555
|
+
**Configuration Location:**
|
|
556
|
+
- `~/.continue/config.json` (Global)
|
|
557
|
+
- Or `.continue/config.json` in your project
|
|
558
|
+
|
|
559
|
+
**Configuration:**
|
|
560
|
+
```json
|
|
561
|
+
{
|
|
562
|
+
"mcpServers": [
|
|
563
|
+
{
|
|
564
|
+
"name": "brave-real-browser",
|
|
565
|
+
"command": "npx",
|
|
566
|
+
"args": ["brave-real-browser-mcp-server@latest"]
|
|
567
|
+
}
|
|
568
|
+
]
|
|
569
|
+
}
|
|
445
570
|
```
|
|
446
571
|
|
|
447
|
-
|
|
572
|
+
### With Cody AI (Sourcegraph)
|
|
448
573
|
|
|
574
|
+
Cody AI in VS Code supports MCP servers.
|
|
575
|
+
|
|
576
|
+
**Configuration Location:**
|
|
577
|
+
- VS Code Settings: `settings.json`
|
|
578
|
+
|
|
579
|
+
**Configuration:**
|
|
580
|
+
```json
|
|
581
|
+
{
|
|
582
|
+
"cody.mcp.servers": {
|
|
583
|
+
"brave-real-browser": {
|
|
584
|
+
"command": "npx",
|
|
585
|
+
"args": ["brave-real-browser-mcp-server@latest"]
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
### With Zed Editor (LSP Mode)
|
|
592
|
+
|
|
593
|
+
Zed uses LSP protocol instead of MCP.
|
|
594
|
+
|
|
595
|
+
**Configuration Location:**
|
|
596
|
+
- macOS/Linux: `~/.config/zed/settings.json`
|
|
597
|
+
- Windows: `%APPDATA%\Zed\settings.json`
|
|
598
|
+
|
|
599
|
+
**Configuration:**
|
|
600
|
+
```json
|
|
601
|
+
{
|
|
602
|
+
"lsp": {
|
|
603
|
+
"brave-browser-automation": {
|
|
604
|
+
"command": "npx",
|
|
605
|
+
"args": ["brave-real-browser-mcp-server@latest", "--mode", "lsp"],
|
|
606
|
+
"settings": {}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
### With VSCode (via LSP)
|
|
613
|
+
|
|
614
|
+
VSCode can use LSP mode for browser automation.
|
|
615
|
+
|
|
616
|
+
**Create:** `.vscode/settings.json` in your project
|
|
617
|
+
|
|
618
|
+
**Configuration:**
|
|
619
|
+
```json
|
|
620
|
+
{
|
|
621
|
+
"brave-browser-automation.serverPath": "npx",
|
|
622
|
+
"brave-browser-automation.serverArgs": [
|
|
623
|
+
"brave-real-browser-mcp-server@latest",
|
|
624
|
+
"--mode",
|
|
625
|
+
"lsp"
|
|
626
|
+
]
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### HTTP/WebSocket Mode (For Any Language)
|
|
631
|
+
|
|
632
|
+
Use REST API mode for maximum compatibility with any programming language.
|
|
633
|
+
|
|
634
|
+
**Start Server:**
|
|
449
635
|
```bash
|
|
450
|
-
|
|
636
|
+
npx brave-real-browser-mcp-server --mode http --port 3000
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
**Example - Python:**
|
|
640
|
+
```python
|
|
641
|
+
import requests
|
|
642
|
+
|
|
643
|
+
base_url = "http://localhost:3000"
|
|
644
|
+
|
|
645
|
+
# Initialize browser
|
|
646
|
+
requests.post(f"{base_url}/browser/init", json={"headless": False})
|
|
647
|
+
|
|
648
|
+
# Navigate
|
|
649
|
+
requests.post(f"{base_url}/browser/navigate", json={"url": "https://google.com"})
|
|
650
|
+
|
|
651
|
+
# Get content
|
|
652
|
+
response = requests.post(f"{base_url}/browser/get-content", json={"type": "text"})
|
|
653
|
+
print(response.json())
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
**Example - Node.js:**
|
|
657
|
+
```javascript
|
|
658
|
+
const axios = require('axios');
|
|
659
|
+
|
|
660
|
+
const baseUrl = 'http://localhost:3000';
|
|
661
|
+
|
|
662
|
+
// Initialize and automate
|
|
663
|
+
await axios.post(`${baseUrl}/browser/init`, {headless: false});
|
|
664
|
+
await axios.post(`${baseUrl}/browser/navigate`, {url: 'https://google.com'});
|
|
665
|
+
const content = await axios.post(`${baseUrl}/browser/get-content`, {type: 'text'});
|
|
666
|
+
console.log(content.data);
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### With Other MCP-Compatible Tools
|
|
670
|
+
|
|
671
|
+
For any other MCP-compatible IDE or tool, use the standard MCP configuration:
|
|
672
|
+
|
|
673
|
+
```json
|
|
674
|
+
{
|
|
675
|
+
"mcpServers": {
|
|
676
|
+
"brave-real-browser": {
|
|
677
|
+
"command": "npx",
|
|
678
|
+
"args": ["brave-real-browser-mcp-server@latest"]
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
}
|
|
451
682
|
```
|
|
452
683
|
|
|
453
684
|
The server communicates via stdin/stdout using the MCP protocol.
|
|
454
685
|
|
|
686
|
+
---
|
|
687
|
+
|
|
688
|
+
## 📊 Complete MCP IDE Compatibility Matrix
|
|
689
|
+
|
|
690
|
+
| IDE/Tool | Protocol | Config File | Status |
|
|
691
|
+
|----------|----------|-------------|--------|
|
|
692
|
+
| **Claude Desktop** | MCP | `claude_desktop_config.json` | ✅ Fully Tested |
|
|
693
|
+
| **Claude Code CLI** | MCP | `.mcp.json` | ✅ Fully Tested |
|
|
694
|
+
| **Cursor IDE** | MCP | `.cursor/mcp.json` | ✅ Fully Tested |
|
|
695
|
+
| **Warp Terminal** | MCP | `mcp_config.json` | ✅ Supported |
|
|
696
|
+
| **Windsurf IDE** | MCP | `.windsurf/mcp.json` | ✅ Supported |
|
|
697
|
+
| **Continue.dev** | MCP | `.continue/config.json` | ✅ Supported |
|
|
698
|
+
| **Cody AI** | MCP | `settings.json` | ✅ Supported |
|
|
699
|
+
| **Zed Editor** | LSP | `settings.json` | ✅ LSP Mode |
|
|
700
|
+
| **VSCode** | LSP | `.vscode/settings.json` | ✅ LSP Mode |
|
|
701
|
+
| **Any HTTP Client** | HTTP/WebSocket | REST API | ✅ Fully Supported |
|
|
702
|
+
|
|
703
|
+
**Total Supported Platforms:** 10+
|
|
704
|
+
|
|
455
705
|
### Example Interactions
|
|
456
706
|
|
|
457
707
|
#### Basic Web Browsing
|
package/dist/index.js
CHANGED
|
@@ -483,42 +483,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
483
483
|
};
|
|
484
484
|
}
|
|
485
485
|
});
|
|
486
|
-
// Main function
|
|
486
|
+
// Main function - now using multi-protocol launcher
|
|
487
|
+
import { main as launcherMain } from './launcher.js';
|
|
487
488
|
async function main() {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
console.error('🔍 [DEBUG] Attempting to connect server to transport...');
|
|
502
|
-
await server.connect(transport);
|
|
503
|
-
console.error('🔍 [DEBUG] Server connected to transport successfully');
|
|
504
|
-
console.error('🚀 Brave Real Browser MCP Server started successfully');
|
|
505
|
-
console.error('📋 Available tools:', TOOLS.map(t => t.name).join(', '));
|
|
506
|
-
console.error('🔧 Workflow validation: Active');
|
|
507
|
-
console.error('💡 Content priority mode: Enabled (use get_content for better reliability)');
|
|
508
|
-
console.error('🔍 [DEBUG] Server is now ready and waiting for requests...');
|
|
509
|
-
// Keep the process alive by maintaining the connection
|
|
510
|
-
console.error('🔍 [DEBUG] Maintaining process alive - server will wait for requests');
|
|
511
|
-
// Add a heartbeat to confirm the process is still running
|
|
512
|
-
const heartbeat = setInterval(() => {
|
|
513
|
-
console.error(`🔍 [DEBUG] Heartbeat - Server alive at ${new Date().toISOString()}`);
|
|
514
|
-
}, 30000); // Every 30 seconds
|
|
515
|
-
// Cleanup heartbeat on process exit
|
|
516
|
-
process.on('exit', () => {
|
|
517
|
-
console.error('🔍 [DEBUG] Process exiting - clearing heartbeat');
|
|
518
|
-
clearInterval(heartbeat);
|
|
489
|
+
// Check if user wants multi-protocol mode
|
|
490
|
+
const hasProtocolArg = process.argv.some(arg => arg === '--mode' || arg === '-m' || arg === '--http' || arg === '--lsp');
|
|
491
|
+
if (hasProtocolArg) {
|
|
492
|
+
// Use multi-protocol launcher
|
|
493
|
+
await launcherMain();
|
|
494
|
+
}
|
|
495
|
+
else {
|
|
496
|
+
// Default: MCP mode (backward compatibility)
|
|
497
|
+
console.error('🔍 [DEBUG] Starting in MCP mode (default)...');
|
|
498
|
+
console.error('💡 Tip: Use --mode http or --mode lsp for other protocols');
|
|
499
|
+
setupProcessCleanup(async () => {
|
|
500
|
+
await closeBrowser();
|
|
501
|
+
await forceKillAllBraveProcesses();
|
|
519
502
|
});
|
|
520
|
-
|
|
521
|
-
|
|
503
|
+
const transport = new StdioServerTransport();
|
|
504
|
+
await withErrorHandling(async () => {
|
|
505
|
+
await server.connect(transport);
|
|
506
|
+
console.error('🚀 Brave Real Browser MCP Server started successfully');
|
|
507
|
+
console.error('📋 Available tools:', TOOLS.map(t => t.name).join(', '));
|
|
508
|
+
}, 'Failed to start MCP server');
|
|
509
|
+
}
|
|
522
510
|
}
|
|
523
511
|
// Enhanced error handling with debug info
|
|
524
512
|
console.error('🔍 [DEBUG] Setting up error handlers...');
|
package/dist/launcher.js
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
+
import { HttpTransport } from './transports/http-transport.js';
|
|
4
|
+
import { LspTransport } from './transports/lsp-transport.js';
|
|
5
|
+
import { closeBrowser, forceKillAllBraveProcesses } from './browser-manager.js';
|
|
6
|
+
import { setupProcessCleanup } from './core-infrastructure.js';
|
|
7
|
+
export class MultiProtocolLauncher {
|
|
8
|
+
config;
|
|
9
|
+
httpTransport;
|
|
10
|
+
lspTransport;
|
|
11
|
+
mcpServer;
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
mode: config.mode || 'mcp',
|
|
15
|
+
httpPort: config.httpPort || 3000,
|
|
16
|
+
httpHost: config.httpHost || '0.0.0.0',
|
|
17
|
+
enableWebSocket: config.enableWebSocket !== false,
|
|
18
|
+
enableCors: config.enableCors !== false,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
async start() {
|
|
22
|
+
console.error('🚀 Multi-Protocol Brave Browser Server Starting...');
|
|
23
|
+
console.error(`📡 Mode: ${this.config.mode.toUpperCase()}`);
|
|
24
|
+
// Setup cleanup handlers
|
|
25
|
+
setupProcessCleanup(async () => {
|
|
26
|
+
await this.stop();
|
|
27
|
+
});
|
|
28
|
+
switch (this.config.mode) {
|
|
29
|
+
case 'mcp':
|
|
30
|
+
await this.startMcp();
|
|
31
|
+
break;
|
|
32
|
+
case 'http':
|
|
33
|
+
await this.startHttp();
|
|
34
|
+
break;
|
|
35
|
+
case 'lsp':
|
|
36
|
+
await this.startLsp();
|
|
37
|
+
break;
|
|
38
|
+
case 'all':
|
|
39
|
+
await this.startAll();
|
|
40
|
+
break;
|
|
41
|
+
default:
|
|
42
|
+
throw new Error(`Unknown mode: ${this.config.mode}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async startMcp() {
|
|
46
|
+
console.error('🔵 [MCP] Starting MCP server...');
|
|
47
|
+
// Import MCP server setup from index.ts
|
|
48
|
+
const { createMcpServer } = await import('./mcp-server.js');
|
|
49
|
+
this.mcpServer = await createMcpServer();
|
|
50
|
+
const transport = new StdioServerTransport();
|
|
51
|
+
await this.mcpServer.connect(transport);
|
|
52
|
+
console.error('✅ [MCP] Server started successfully');
|
|
53
|
+
}
|
|
54
|
+
async startHttp() {
|
|
55
|
+
console.error('🟢 [HTTP] Starting HTTP/WebSocket server...');
|
|
56
|
+
this.httpTransport = new HttpTransport({
|
|
57
|
+
port: this.config.httpPort,
|
|
58
|
+
host: this.config.httpHost,
|
|
59
|
+
enableWebSocket: this.config.enableWebSocket,
|
|
60
|
+
});
|
|
61
|
+
await this.httpTransport.start();
|
|
62
|
+
}
|
|
63
|
+
async startLsp() {
|
|
64
|
+
console.error('🟣 [LSP] Starting Language Server...');
|
|
65
|
+
this.lspTransport = new LspTransport();
|
|
66
|
+
await this.lspTransport.start();
|
|
67
|
+
}
|
|
68
|
+
async startAll() {
|
|
69
|
+
console.error('🌈 Starting all protocols...');
|
|
70
|
+
// Start HTTP in background
|
|
71
|
+
if (!this.httpTransport) {
|
|
72
|
+
this.httpTransport = new HttpTransport({
|
|
73
|
+
port: this.config.httpPort,
|
|
74
|
+
host: this.config.httpHost,
|
|
75
|
+
enableWebSocket: this.config.enableWebSocket,
|
|
76
|
+
});
|
|
77
|
+
await this.httpTransport.start();
|
|
78
|
+
}
|
|
79
|
+
// Note: LSP and MCP use stdio/IPC, so they can't run simultaneously
|
|
80
|
+
// In 'all' mode, we prioritize HTTP/WebSocket for universal access
|
|
81
|
+
console.error('⚠️ [Note] MCP and LSP use stdio, so only HTTP/WebSocket is active in "all" mode');
|
|
82
|
+
console.error('💡 Use separate instances for MCP or LSP: --mode=mcp or --mode=lsp');
|
|
83
|
+
}
|
|
84
|
+
async stop() {
|
|
85
|
+
console.error('🛑 Stopping servers...');
|
|
86
|
+
await closeBrowser();
|
|
87
|
+
await forceKillAllBraveProcesses();
|
|
88
|
+
if (this.httpTransport) {
|
|
89
|
+
await this.httpTransport.stop();
|
|
90
|
+
}
|
|
91
|
+
if (this.lspTransport) {
|
|
92
|
+
await this.lspTransport.stop();
|
|
93
|
+
}
|
|
94
|
+
console.error('✅ All servers stopped');
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Parse CLI arguments
|
|
98
|
+
export function parseArgs() {
|
|
99
|
+
const args = process.argv.slice(2);
|
|
100
|
+
const config = {};
|
|
101
|
+
for (let i = 0; i < args.length; i++) {
|
|
102
|
+
const arg = args[i];
|
|
103
|
+
if (arg === '--mode' || arg === '-m') {
|
|
104
|
+
const mode = args[++i];
|
|
105
|
+
if (['mcp', 'http', 'lsp', 'all'].includes(mode)) {
|
|
106
|
+
config.mode = mode;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.error(`❌ Invalid mode: ${mode}. Use: mcp, http, lsp, or all`);
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else if (arg === '--port' || arg === '-p') {
|
|
114
|
+
config.httpPort = parseInt(args[++i]);
|
|
115
|
+
}
|
|
116
|
+
else if (arg === '--host' || arg === '-h') {
|
|
117
|
+
config.httpHost = args[++i];
|
|
118
|
+
}
|
|
119
|
+
else if (arg === '--no-websocket') {
|
|
120
|
+
config.enableWebSocket = false;
|
|
121
|
+
}
|
|
122
|
+
else if (arg === '--help') {
|
|
123
|
+
console.log(`
|
|
124
|
+
Brave Real Browser MCP Server - Multi-Protocol Support
|
|
125
|
+
|
|
126
|
+
Usage: brave-real-browser-mcp-server [options]
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
--mode, -m <mode> Protocol mode: mcp, http, lsp, or all (default: mcp)
|
|
130
|
+
--port, -p <port> HTTP server port (default: 3000)
|
|
131
|
+
--host, -h <host> HTTP server host (default: 0.0.0.0)
|
|
132
|
+
--no-websocket Disable WebSocket support in HTTP mode
|
|
133
|
+
--help Show this help message
|
|
134
|
+
|
|
135
|
+
Examples:
|
|
136
|
+
# MCP mode (for Claude Desktop, Cursor, Warp)
|
|
137
|
+
brave-real-browser-mcp-server --mode mcp
|
|
138
|
+
|
|
139
|
+
# HTTP/WebSocket mode (for any HTTP client)
|
|
140
|
+
brave-real-browser-mcp-server --mode http --port 3000
|
|
141
|
+
|
|
142
|
+
# LSP mode (for Zed, VSCode, etc.)
|
|
143
|
+
brave-real-browser-mcp-server --mode lsp
|
|
144
|
+
|
|
145
|
+
# All protocols (HTTP only, MCP/LSP need separate instances)
|
|
146
|
+
brave-real-browser-mcp-server --mode all
|
|
147
|
+
`);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return config;
|
|
152
|
+
}
|
|
153
|
+
// Main entry point
|
|
154
|
+
export async function main() {
|
|
155
|
+
const config = parseArgs();
|
|
156
|
+
const launcher = new MultiProtocolLauncher(config);
|
|
157
|
+
try {
|
|
158
|
+
await launcher.start();
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
console.error('❌ Failed to start server:', error);
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ListPromptsRequestSchema, InitializeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import { TOOLS, SERVER_INFO, CAPABILITIES, TOOL_NAMES } from './tool-definitions.js';
|
|
4
|
+
import { validateMCPResponse } from './mcp-response-validator.js';
|
|
5
|
+
// Import handlers
|
|
6
|
+
import { handleBrowserInit, handleBrowserClose } from './handlers/browser-handlers.js';
|
|
7
|
+
import { handleNavigate, handleWait } from './handlers/navigation-handlers.js';
|
|
8
|
+
import { handleClick, handleType, handleSolveCaptcha, handleRandomScroll } from './handlers/interaction-handlers.js';
|
|
9
|
+
import { handleGetContent, handleFindSelector } from './handlers/content-handlers.js';
|
|
10
|
+
import { handleSaveContentAsMarkdown } from './handlers/file-handlers.js';
|
|
11
|
+
import { handleScrapeTable, handleExtractList, handleExtractJSON, handleScrapeMetaTags, handleExtractSchema } from './handlers/data-extraction-handlers.js';
|
|
12
|
+
import { handleBatchElementScraper, handleNestedDataExtraction, handleAttributeHarvester, handleImageScraper, handleLinkHarvester, handleMediaExtractor, handlePDFLinkFinder } from './handlers/multi-element-handlers.js';
|
|
13
|
+
export async function createMcpServer() {
|
|
14
|
+
const server = new Server(SERVER_INFO, { capabilities: CAPABILITIES });
|
|
15
|
+
// Register initialize handler
|
|
16
|
+
server.setRequestHandler(InitializeRequestSchema, async (request) => {
|
|
17
|
+
const clientProtocolVersion = request.params.protocolVersion;
|
|
18
|
+
return {
|
|
19
|
+
protocolVersion: clientProtocolVersion,
|
|
20
|
+
capabilities: CAPABILITIES,
|
|
21
|
+
serverInfo: SERVER_INFO,
|
|
22
|
+
};
|
|
23
|
+
});
|
|
24
|
+
// Register tool handlers
|
|
25
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
26
|
+
return { tools: TOOLS };
|
|
27
|
+
});
|
|
28
|
+
// Register resource handlers (placeholder)
|
|
29
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
30
|
+
return { resources: [] };
|
|
31
|
+
});
|
|
32
|
+
// Register prompt handlers (placeholder)
|
|
33
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
34
|
+
return { prompts: [] };
|
|
35
|
+
});
|
|
36
|
+
// Main tool call handler
|
|
37
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
38
|
+
const { name, arguments: args } = request.params;
|
|
39
|
+
try {
|
|
40
|
+
let result;
|
|
41
|
+
switch (name) {
|
|
42
|
+
case TOOL_NAMES.BROWSER_INIT:
|
|
43
|
+
result = await handleBrowserInit(args || {});
|
|
44
|
+
break;
|
|
45
|
+
case TOOL_NAMES.NAVIGATE:
|
|
46
|
+
result = await handleNavigate(args);
|
|
47
|
+
break;
|
|
48
|
+
case TOOL_NAMES.GET_CONTENT:
|
|
49
|
+
result = await handleGetContent(args || {});
|
|
50
|
+
break;
|
|
51
|
+
case TOOL_NAMES.CLICK:
|
|
52
|
+
result = await handleClick(args);
|
|
53
|
+
break;
|
|
54
|
+
case TOOL_NAMES.TYPE:
|
|
55
|
+
result = await handleType(args);
|
|
56
|
+
break;
|
|
57
|
+
case TOOL_NAMES.WAIT:
|
|
58
|
+
result = await handleWait(args);
|
|
59
|
+
break;
|
|
60
|
+
case TOOL_NAMES.BROWSER_CLOSE:
|
|
61
|
+
result = await handleBrowserClose();
|
|
62
|
+
break;
|
|
63
|
+
case TOOL_NAMES.SOLVE_CAPTCHA:
|
|
64
|
+
result = await handleSolveCaptcha(args);
|
|
65
|
+
break;
|
|
66
|
+
case TOOL_NAMES.RANDOM_SCROLL:
|
|
67
|
+
result = await handleRandomScroll();
|
|
68
|
+
break;
|
|
69
|
+
case TOOL_NAMES.FIND_SELECTOR:
|
|
70
|
+
result = await handleFindSelector(args);
|
|
71
|
+
break;
|
|
72
|
+
case TOOL_NAMES.SAVE_CONTENT_AS_MARKDOWN:
|
|
73
|
+
result = await handleSaveContentAsMarkdown(args);
|
|
74
|
+
break;
|
|
75
|
+
// Smart Data Extractors
|
|
76
|
+
case TOOL_NAMES.SCRAPE_TABLE:
|
|
77
|
+
result = await handleScrapeTable(args || {});
|
|
78
|
+
break;
|
|
79
|
+
case TOOL_NAMES.EXTRACT_LIST:
|
|
80
|
+
result = await handleExtractList(args || {});
|
|
81
|
+
break;
|
|
82
|
+
case TOOL_NAMES.EXTRACT_JSON:
|
|
83
|
+
result = await handleExtractJSON(args || {});
|
|
84
|
+
break;
|
|
85
|
+
case TOOL_NAMES.SCRAPE_META_TAGS:
|
|
86
|
+
result = await handleScrapeMetaTags(args || {});
|
|
87
|
+
break;
|
|
88
|
+
case TOOL_NAMES.EXTRACT_SCHEMA:
|
|
89
|
+
result = await handleExtractSchema(args || {});
|
|
90
|
+
break;
|
|
91
|
+
// Multi-Element Extractors
|
|
92
|
+
case TOOL_NAMES.BATCH_ELEMENT_SCRAPER:
|
|
93
|
+
result = await handleBatchElementScraper(args);
|
|
94
|
+
break;
|
|
95
|
+
case TOOL_NAMES.NESTED_DATA_EXTRACTION:
|
|
96
|
+
result = await handleNestedDataExtraction(args);
|
|
97
|
+
break;
|
|
98
|
+
case TOOL_NAMES.ATTRIBUTE_HARVESTER:
|
|
99
|
+
result = await handleAttributeHarvester(args);
|
|
100
|
+
break;
|
|
101
|
+
// Content Type Specific
|
|
102
|
+
case TOOL_NAMES.IMAGE_SCRAPER:
|
|
103
|
+
result = await handleImageScraper(args || {});
|
|
104
|
+
break;
|
|
105
|
+
case TOOL_NAMES.LINK_HARVESTER:
|
|
106
|
+
result = await handleLinkHarvester(args || {});
|
|
107
|
+
break;
|
|
108
|
+
case TOOL_NAMES.MEDIA_EXTRACTOR:
|
|
109
|
+
result = await handleMediaExtractor(args || {});
|
|
110
|
+
break;
|
|
111
|
+
case TOOL_NAMES.PDF_LINK_FINDER:
|
|
112
|
+
result = await handlePDFLinkFinder(args || {});
|
|
113
|
+
break;
|
|
114
|
+
default:
|
|
115
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
116
|
+
}
|
|
117
|
+
// Validate MCP response format universally
|
|
118
|
+
return validateMCPResponse(result, name);
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
122
|
+
// For workflow validation errors, throw them so MCP SDK handles them properly
|
|
123
|
+
if (errorMessage.includes('cannot be executed in current state') ||
|
|
124
|
+
errorMessage.includes('Cannot search for selectors') ||
|
|
125
|
+
errorMessage.includes('Next Steps:')) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
// For other errors, return formatted response
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `❌ Tool execution failed: ${errorMessage}`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
isError: true,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
return server;
|
|
141
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { WebSocketServer } from 'ws';
|
|
4
|
+
import { TOOLS, TOOL_NAMES } from '../tool-definitions.js';
|
|
5
|
+
// Import all handlers
|
|
6
|
+
import { handleBrowserInit, handleBrowserClose } from '../handlers/browser-handlers.js';
|
|
7
|
+
import { handleNavigate, handleWait } from '../handlers/navigation-handlers.js';
|
|
8
|
+
import { handleClick, handleType, handleSolveCaptcha, handleRandomScroll } from '../handlers/interaction-handlers.js';
|
|
9
|
+
import { handleGetContent, handleFindSelector } from '../handlers/content-handlers.js';
|
|
10
|
+
import { handleSaveContentAsMarkdown } from '../handlers/file-handlers.js';
|
|
11
|
+
export class HttpTransport {
|
|
12
|
+
app;
|
|
13
|
+
server = null;
|
|
14
|
+
wss = null;
|
|
15
|
+
config;
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
this.config = {
|
|
18
|
+
port: config.port || 3000,
|
|
19
|
+
host: config.host || '0.0.0.0',
|
|
20
|
+
enableWebSocket: config.enableWebSocket !== false,
|
|
21
|
+
corsOrigins: config.corsOrigins || ['*'],
|
|
22
|
+
};
|
|
23
|
+
this.app = express();
|
|
24
|
+
this.setupMiddleware();
|
|
25
|
+
this.setupRoutes();
|
|
26
|
+
}
|
|
27
|
+
setupMiddleware() {
|
|
28
|
+
// Body parsing
|
|
29
|
+
this.app.use(express.json());
|
|
30
|
+
this.app.use(express.urlencoded({ extended: true }));
|
|
31
|
+
// CORS
|
|
32
|
+
this.app.use((req, res, next) => {
|
|
33
|
+
res.header('Access-Control-Allow-Origin', this.config.corsOrigins?.[0] || '*');
|
|
34
|
+
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
35
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
|
36
|
+
if (req.method === 'OPTIONS') {
|
|
37
|
+
return res.sendStatus(200);
|
|
38
|
+
}
|
|
39
|
+
next();
|
|
40
|
+
});
|
|
41
|
+
// Request logging
|
|
42
|
+
this.app.use((req, res, next) => {
|
|
43
|
+
console.error(`📡 [HTTP] ${req.method} ${req.path} - ${new Date().toISOString()}`);
|
|
44
|
+
next();
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
setupRoutes() {
|
|
48
|
+
// Health check
|
|
49
|
+
this.app.get('/health', (req, res) => {
|
|
50
|
+
res.json({ status: 'ok', timestamp: new Date().toISOString() });
|
|
51
|
+
});
|
|
52
|
+
// List all available tools
|
|
53
|
+
this.app.get('/tools', (req, res) => {
|
|
54
|
+
res.json({ tools: TOOLS });
|
|
55
|
+
});
|
|
56
|
+
// Execute tool - Generic endpoint
|
|
57
|
+
this.app.post('/tools/:toolName', async (req, res) => {
|
|
58
|
+
const { toolName } = req.params;
|
|
59
|
+
const args = req.body;
|
|
60
|
+
try {
|
|
61
|
+
const result = await this.executeTool(toolName, args);
|
|
62
|
+
res.json({ success: true, result });
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
+
res.status(500).json({ success: false, error: errorMessage });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
// Browser automation endpoints
|
|
70
|
+
this.app.post('/browser/init', async (req, res) => {
|
|
71
|
+
try {
|
|
72
|
+
const result = await handleBrowserInit(req.body);
|
|
73
|
+
res.json({ success: true, result });
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
res.status(500).json({ success: false, error: error.message });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
this.app.post('/browser/navigate', async (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const result = await handleNavigate(req.body);
|
|
82
|
+
res.json({ success: true, result });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
res.status(500).json({ success: false, error: error.message });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
this.app.post('/browser/click', async (req, res) => {
|
|
89
|
+
try {
|
|
90
|
+
const result = await handleClick(req.body);
|
|
91
|
+
res.json({ success: true, result });
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
res.status(500).json({ success: false, error: error.message });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
this.app.post('/browser/type', async (req, res) => {
|
|
98
|
+
try {
|
|
99
|
+
const result = await handleType(req.body);
|
|
100
|
+
res.json({ success: true, result });
|
|
101
|
+
}
|
|
102
|
+
catch (error) {
|
|
103
|
+
res.status(500).json({ success: false, error: error.message });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
this.app.post('/browser/get-content', async (req, res) => {
|
|
107
|
+
try {
|
|
108
|
+
const result = await handleGetContent(req.body);
|
|
109
|
+
res.json({ success: true, result });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
res.status(500).json({ success: false, error: error.message });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
this.app.post('/browser/close', async (req, res) => {
|
|
116
|
+
try {
|
|
117
|
+
const result = await handleBrowserClose();
|
|
118
|
+
res.json({ success: true, result });
|
|
119
|
+
}
|
|
120
|
+
catch (error) {
|
|
121
|
+
res.status(500).json({ success: false, error: error.message });
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
// Error handling
|
|
125
|
+
this.app.use((err, req, res, next) => {
|
|
126
|
+
console.error('❌ [HTTP] Error:', err);
|
|
127
|
+
res.status(500).json({ success: false, error: err.message });
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
async executeTool(toolName, args) {
|
|
131
|
+
switch (toolName) {
|
|
132
|
+
case TOOL_NAMES.BROWSER_INIT:
|
|
133
|
+
return await handleBrowserInit(args || {});
|
|
134
|
+
case TOOL_NAMES.NAVIGATE:
|
|
135
|
+
return await handleNavigate(args);
|
|
136
|
+
case TOOL_NAMES.GET_CONTENT:
|
|
137
|
+
return await handleGetContent(args || {});
|
|
138
|
+
case TOOL_NAMES.CLICK:
|
|
139
|
+
return await handleClick(args);
|
|
140
|
+
case TOOL_NAMES.TYPE:
|
|
141
|
+
return await handleType(args);
|
|
142
|
+
case TOOL_NAMES.WAIT:
|
|
143
|
+
return await handleWait(args);
|
|
144
|
+
case TOOL_NAMES.BROWSER_CLOSE:
|
|
145
|
+
return await handleBrowserClose();
|
|
146
|
+
case TOOL_NAMES.SOLVE_CAPTCHA:
|
|
147
|
+
return await handleSolveCaptcha(args);
|
|
148
|
+
case TOOL_NAMES.RANDOM_SCROLL:
|
|
149
|
+
return await handleRandomScroll();
|
|
150
|
+
case TOOL_NAMES.FIND_SELECTOR:
|
|
151
|
+
return await handleFindSelector(args);
|
|
152
|
+
case TOOL_NAMES.SAVE_CONTENT_AS_MARKDOWN:
|
|
153
|
+
return await handleSaveContentAsMarkdown(args);
|
|
154
|
+
default:
|
|
155
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
setupWebSocket() {
|
|
159
|
+
if (!this.server || !this.config.enableWebSocket)
|
|
160
|
+
return;
|
|
161
|
+
this.wss = new WebSocketServer({ server: this.server });
|
|
162
|
+
this.wss.on('connection', (ws) => {
|
|
163
|
+
console.error('🔌 [WebSocket] Client connected');
|
|
164
|
+
ws.on('message', async (message) => {
|
|
165
|
+
try {
|
|
166
|
+
const data = JSON.parse(message.toString());
|
|
167
|
+
const { id, tool, args } = data;
|
|
168
|
+
const result = await this.executeTool(tool, args);
|
|
169
|
+
ws.send(JSON.stringify({
|
|
170
|
+
id,
|
|
171
|
+
success: true,
|
|
172
|
+
result,
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
177
|
+
ws.send(JSON.stringify({
|
|
178
|
+
success: false,
|
|
179
|
+
error: errorMessage,
|
|
180
|
+
}));
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
ws.on('close', () => {
|
|
184
|
+
console.error('🔌 [WebSocket] Client disconnected');
|
|
185
|
+
});
|
|
186
|
+
ws.on('error', (error) => {
|
|
187
|
+
console.error('❌ [WebSocket] Error:', error);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async start() {
|
|
192
|
+
return new Promise((resolve) => {
|
|
193
|
+
this.server = createServer(this.app);
|
|
194
|
+
this.setupWebSocket();
|
|
195
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
196
|
+
console.error(`✅ [HTTP] Server running on http://${this.config.host}:${this.config.port}`);
|
|
197
|
+
if (this.config.enableWebSocket) {
|
|
198
|
+
console.error(`✅ [WebSocket] Server running on ws://${this.config.host}:${this.config.port}`);
|
|
199
|
+
}
|
|
200
|
+
resolve();
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
async stop() {
|
|
205
|
+
return new Promise((resolve, reject) => {
|
|
206
|
+
if (this.wss) {
|
|
207
|
+
this.wss.close();
|
|
208
|
+
}
|
|
209
|
+
if (this.server) {
|
|
210
|
+
this.server.close((err) => {
|
|
211
|
+
if (err)
|
|
212
|
+
reject(err);
|
|
213
|
+
else
|
|
214
|
+
resolve();
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
resolve();
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { createConnection, TextDocuments, ProposedFeatures, CompletionItemKind, TextDocumentSyncKind, } from 'vscode-languageserver/node.js';
|
|
2
|
+
import { TextDocument } from 'vscode-languageserver-textdocument';
|
|
3
|
+
import { TOOLS, TOOL_NAMES } from '../tool-definitions.js';
|
|
4
|
+
// Import all handlers
|
|
5
|
+
import { handleBrowserInit, handleBrowserClose } from '../handlers/browser-handlers.js';
|
|
6
|
+
import { handleNavigate, handleWait } from '../handlers/navigation-handlers.js';
|
|
7
|
+
import { handleClick, handleType, handleSolveCaptcha, handleRandomScroll } from '../handlers/interaction-handlers.js';
|
|
8
|
+
import { handleGetContent, handleFindSelector } from '../handlers/content-handlers.js';
|
|
9
|
+
import { handleSaveContentAsMarkdown } from '../handlers/file-handlers.js';
|
|
10
|
+
export class LspTransport {
|
|
11
|
+
connection;
|
|
12
|
+
documents;
|
|
13
|
+
hasWorkspaceFolderCapability = false;
|
|
14
|
+
constructor() {
|
|
15
|
+
// Create a connection for the server using Node's IPC as a transport
|
|
16
|
+
this.connection = createConnection(ProposedFeatures.all);
|
|
17
|
+
// Create a simple text document manager
|
|
18
|
+
this.documents = new TextDocuments(TextDocument);
|
|
19
|
+
this.setupHandlers();
|
|
20
|
+
// Make the text document manager listen on the connection
|
|
21
|
+
this.documents.listen(this.connection);
|
|
22
|
+
}
|
|
23
|
+
setupHandlers() {
|
|
24
|
+
// Initialize
|
|
25
|
+
this.connection.onInitialize((params) => {
|
|
26
|
+
const capabilities = params.capabilities;
|
|
27
|
+
this.hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders);
|
|
28
|
+
const result = {
|
|
29
|
+
capabilities: {
|
|
30
|
+
textDocumentSync: TextDocumentSyncKind.Incremental,
|
|
31
|
+
completionProvider: {
|
|
32
|
+
resolveProvider: true,
|
|
33
|
+
triggerCharacters: ['.', ':'],
|
|
34
|
+
},
|
|
35
|
+
executeCommandProvider: {
|
|
36
|
+
commands: TOOLS.map(tool => `browser.${tool.name}`),
|
|
37
|
+
},
|
|
38
|
+
hoverProvider: true,
|
|
39
|
+
definitionProvider: true,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
if (this.hasWorkspaceFolderCapability) {
|
|
43
|
+
result.capabilities.workspace = {
|
|
44
|
+
workspaceFolders: {
|
|
45
|
+
supported: true,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
});
|
|
51
|
+
this.connection.onInitialized(() => {
|
|
52
|
+
console.error('✅ [LSP] Server initialized');
|
|
53
|
+
if (this.hasWorkspaceFolderCapability) {
|
|
54
|
+
this.connection.workspace.onDidChangeWorkspaceFolders((_event) => {
|
|
55
|
+
console.error('[LSP] Workspace folder change event received');
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
// Completion
|
|
60
|
+
this.connection.onCompletion((_textDocumentPosition) => {
|
|
61
|
+
return TOOLS.map(tool => ({
|
|
62
|
+
label: tool.name,
|
|
63
|
+
kind: CompletionItemKind.Function,
|
|
64
|
+
data: tool.name,
|
|
65
|
+
detail: tool.description,
|
|
66
|
+
documentation: JSON.stringify(tool.inputSchema, null, 2),
|
|
67
|
+
}));
|
|
68
|
+
});
|
|
69
|
+
this.connection.onCompletionResolve((item) => {
|
|
70
|
+
return item;
|
|
71
|
+
});
|
|
72
|
+
// Execute command (actual browser automation)
|
|
73
|
+
this.connection.onExecuteCommand(async (params) => {
|
|
74
|
+
const commandName = params.command.replace('browser.', '');
|
|
75
|
+
const args = params.arguments?.[0] || {};
|
|
76
|
+
console.error(`[LSP] Executing command: ${commandName}`);
|
|
77
|
+
try {
|
|
78
|
+
const result = await this.executeTool(commandName, args);
|
|
79
|
+
this.connection.window.showInformationMessage(`✅ Command ${commandName} executed successfully`);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
84
|
+
this.connection.window.showErrorMessage(`❌ Command ${commandName} failed: ${errorMessage}`);
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// Hover information
|
|
89
|
+
this.connection.onHover((params) => {
|
|
90
|
+
const document = this.documents.get(params.textDocument.uri);
|
|
91
|
+
if (!document)
|
|
92
|
+
return null;
|
|
93
|
+
const position = params.position;
|
|
94
|
+
const word = this.getWordAtPosition(document, position);
|
|
95
|
+
const tool = TOOLS.find(t => t.name === word);
|
|
96
|
+
if (tool) {
|
|
97
|
+
return {
|
|
98
|
+
contents: {
|
|
99
|
+
kind: 'markdown',
|
|
100
|
+
value: [
|
|
101
|
+
`**${tool.name}**`,
|
|
102
|
+
'',
|
|
103
|
+
tool.description,
|
|
104
|
+
'',
|
|
105
|
+
'**Parameters:**',
|
|
106
|
+
'```json',
|
|
107
|
+
JSON.stringify(tool.inputSchema, null, 2),
|
|
108
|
+
'```',
|
|
109
|
+
].join('\n'),
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return null;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
getWordAtPosition(document, position) {
|
|
117
|
+
const text = document.getText();
|
|
118
|
+
const offset = document.offsetAt(position);
|
|
119
|
+
let start = offset;
|
|
120
|
+
let end = offset;
|
|
121
|
+
// Find word boundaries
|
|
122
|
+
while (start > 0 && /\w/.test(text[start - 1]))
|
|
123
|
+
start--;
|
|
124
|
+
while (end < text.length && /\w/.test(text[end]))
|
|
125
|
+
end++;
|
|
126
|
+
return text.substring(start, end);
|
|
127
|
+
}
|
|
128
|
+
async executeTool(toolName, args) {
|
|
129
|
+
switch (toolName) {
|
|
130
|
+
case TOOL_NAMES.BROWSER_INIT:
|
|
131
|
+
return await handleBrowserInit(args || {});
|
|
132
|
+
case TOOL_NAMES.NAVIGATE:
|
|
133
|
+
return await handleNavigate(args);
|
|
134
|
+
case TOOL_NAMES.GET_CONTENT:
|
|
135
|
+
return await handleGetContent(args || {});
|
|
136
|
+
case TOOL_NAMES.CLICK:
|
|
137
|
+
return await handleClick(args);
|
|
138
|
+
case TOOL_NAMES.TYPE:
|
|
139
|
+
return await handleType(args);
|
|
140
|
+
case TOOL_NAMES.WAIT:
|
|
141
|
+
return await handleWait(args);
|
|
142
|
+
case TOOL_NAMES.BROWSER_CLOSE:
|
|
143
|
+
return await handleBrowserClose();
|
|
144
|
+
case TOOL_NAMES.SOLVE_CAPTCHA:
|
|
145
|
+
return await handleSolveCaptcha(args);
|
|
146
|
+
case TOOL_NAMES.RANDOM_SCROLL:
|
|
147
|
+
return await handleRandomScroll();
|
|
148
|
+
case TOOL_NAMES.FIND_SELECTOR:
|
|
149
|
+
return await handleFindSelector(args);
|
|
150
|
+
case TOOL_NAMES.SAVE_CONTENT_AS_MARKDOWN:
|
|
151
|
+
return await handleSaveContentAsMarkdown(args);
|
|
152
|
+
default:
|
|
153
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async start() {
|
|
157
|
+
console.error('🔍 [LSP] Starting Language Server...');
|
|
158
|
+
this.connection.listen();
|
|
159
|
+
console.error('✅ [LSP] Language Server started and listening');
|
|
160
|
+
}
|
|
161
|
+
async stop() {
|
|
162
|
+
console.error('[LSP] Stopping server...');
|
|
163
|
+
this.connection.dispose();
|
|
164
|
+
}
|
|
165
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.7",
|
|
4
4
|
"description": "MCP server for brave-real-browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,7 +18,12 @@
|
|
|
18
18
|
"build": "tsc",
|
|
19
19
|
"rebuild": "npm run clean && npm run build",
|
|
20
20
|
"start": "node dist/index.js",
|
|
21
|
+
"start:http": "node dist/index.js --mode http",
|
|
22
|
+
"start:lsp": "node dist/index.js --mode lsp",
|
|
23
|
+
"start:all": "node dist/index.js --mode all",
|
|
21
24
|
"dev": "tsx src/index.ts",
|
|
25
|
+
"dev:http": "tsx src/index.ts --mode http",
|
|
26
|
+
"dev:lsp": "tsx src/index.ts --mode lsp",
|
|
22
27
|
"test": "vitest",
|
|
23
28
|
"test:watch": "vitest --watch",
|
|
24
29
|
"test:ui": "vitest --ui",
|
|
@@ -37,11 +42,12 @@
|
|
|
37
42
|
"axios": "^1.6.5",
|
|
38
43
|
"brave-real-browser": "^1.5.102",
|
|
39
44
|
"brave-real-launcher": "^1.2.17",
|
|
40
|
-
"brave-real-puppeteer-core": "^24.
|
|
45
|
+
"brave-real-puppeteer-core": "^24.25.0",
|
|
41
46
|
"cheerio": "^1.0.0-rc.12",
|
|
42
47
|
"chrono-node": "^2.7.0",
|
|
43
48
|
"compromise": "^14.13.0",
|
|
44
49
|
"dotenv": "^17.2.3",
|
|
50
|
+
"express": "^4.21.2",
|
|
45
51
|
"franc": "^6.2.0",
|
|
46
52
|
"libphonenumber-js": "^1.10.51",
|
|
47
53
|
"natural": "^6.12.0",
|
|
@@ -51,11 +57,16 @@
|
|
|
51
57
|
"sentiment": "^5.0.2",
|
|
52
58
|
"tesseract.js": "^5.0.5",
|
|
53
59
|
"turndown": "^7.2.1",
|
|
60
|
+
"vscode-languageserver": "^9.0.1",
|
|
61
|
+
"vscode-languageserver-textdocument": "^1.0.12",
|
|
62
|
+
"ws": "^8.18.3",
|
|
54
63
|
"xml2js": "^0.6.2"
|
|
55
64
|
},
|
|
56
65
|
"devDependencies": {
|
|
57
66
|
"@types/cheerio": "^0.22.35",
|
|
67
|
+
"@types/express": "^4.17.23",
|
|
58
68
|
"@types/node": "latest",
|
|
69
|
+
"@types/ws": "^8.18.1",
|
|
59
70
|
"@types/xml2js": "^0.4.14",
|
|
60
71
|
"@vitest/coverage-v8": "^3.2.4",
|
|
61
72
|
"@vitest/ui": "^3.2.4",
|
|
@@ -100,6 +111,6 @@
|
|
|
100
111
|
"puppeteer": "npm:brave-real-puppeteer-core@latest",
|
|
101
112
|
"puppeteer-core": "npm:brave-real-puppeteer-core@latest",
|
|
102
113
|
"tar-fs": "^3.0.0",
|
|
103
|
-
"ws": "^8.18.
|
|
114
|
+
"ws": "^8.18.3"
|
|
104
115
|
}
|
|
105
116
|
}
|