brave-real-browser-mcp-server 2.12.5 → 2.12.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 +347 -68
- package/dist/index.js +9 -5
- package/dist/launcher.js +35 -5
- package/dist/transports/http-transport.js +8 -32
- package/dist/transports/sse-transport.js +302 -0
- package/dist/transports/sse-transport.test.js +242 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
**सभी AI IDEs के लिए Universal MCP Server | 111+ Tools | Browser Automation | Web Scraping | CAPTCHA Solving**
|
|
14
14
|
|
|
15
|
+
[📖 **All 5 Protocols Complete Guide**](./ALL-PROTOCOLS.md) 👈 **NEW! Step-by-step setup for all protocols**
|
|
16
|
+
|
|
15
17
|
[Installation](#-installation) | [Quick Start](#-quick-start) | [Tools](#-available-tools-111) | [HTTP/WebSocket](#-httpwebsocket-setup) | [Configuration](#-ide-configurations) | [Troubleshooting](#-troubleshooting)
|
|
16
18
|
|
|
17
19
|
</div>
|
|
@@ -24,7 +26,7 @@
|
|
|
24
26
|
|
|
25
27
|
- ✅ **15+ AI IDEs में काम करता है** (Claude, Cursor, Windsurf, Cline, Zed, VSCode, Qoder AI, etc.)
|
|
26
28
|
- ✅ **111+ Automation Tools** - Browser control, scraping, CAPTCHA solving, video extraction
|
|
27
|
-
- ✅ **
|
|
29
|
+
- ✅ **5 Protocol Modes** - MCP (STDIO), LSP, HTTP/WebSocket, SSE
|
|
28
30
|
- ✅ **Auto-Detection** - Automatically detects your IDE
|
|
29
31
|
- ✅ **Real Brave Browser** - Anti-detection features, bypass Cloudflare
|
|
30
32
|
- ✅ **Universal API** - Works with any programming language (JS, Python, PHP, Go, etc.)
|
|
@@ -37,26 +39,36 @@
|
|
|
37
39
|
|
|
38
40
|
**Choose your setup based on your AI Editor:**
|
|
39
41
|
|
|
40
|
-
| Editor | Setup Time | Method |
|
|
41
|
-
|
|
42
|
-
| **Claude Desktop** | 2 min | Add config → Restart |
|
|
43
|
-
| **Cursor AI** | 2 min | Add config → Restart |
|
|
44
|
-
| **Windsurf** | 2 min | Add config → Restart |
|
|
45
|
-
| **Zed Editor** | 3 min | Add to `context_servers` → Restart |
|
|
46
|
-
| **Qoder AI** | 4 min | Start HTTP server → Add config → Restart |
|
|
47
|
-
| **
|
|
42
|
+
| Editor | Setup Time | Protocol | Method |
|
|
43
|
+
|--------|-----------|----------|--------|
|
|
44
|
+
| **Claude Desktop** | 2 min | MCP | Add config → Restart |
|
|
45
|
+
| **Cursor AI** | 2 min | MCP | Add config → Restart |
|
|
46
|
+
| **Windsurf** | 2 min | MCP | Add config → Restart |
|
|
47
|
+
| **Zed Editor** | 3 min | LSP | Add to `context_servers` → Restart |
|
|
48
|
+
| **Qoder AI** | 4 min | HTTP | Start HTTP server → Add config → Restart |
|
|
49
|
+
| **Custom Apps** | 1 min | HTTP/WebSocket/SSE | Start server → Use API |
|
|
48
50
|
|
|
49
51
|
**Quick Commands:**
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
#
|
|
53
|
-
npx
|
|
54
|
+
# Auto-detect environment
|
|
55
|
+
npx brave-real-browser-mcp-server@latest
|
|
54
56
|
|
|
55
|
-
#
|
|
57
|
+
# MCP mode (Claude, Cursor, Windsurf)
|
|
58
|
+
npx brave-real-browser-mcp-server@latest --mode mcp
|
|
59
|
+
|
|
60
|
+
# LSP mode (Zed, VSCode, Neovim)
|
|
61
|
+
npx brave-real-browser-mcp-server@latest --mode lsp
|
|
62
|
+
|
|
63
|
+
# HTTP mode (Universal API + WebSocket)
|
|
56
64
|
npx brave-real-browser-mcp-server@latest --mode http --port 3000
|
|
57
65
|
|
|
66
|
+
# SSE mode (Real-time monitoring)
|
|
67
|
+
npx brave-real-browser-mcp-server@latest --mode sse --sse-port 3001
|
|
68
|
+
|
|
58
69
|
# Check if working
|
|
59
70
|
curl http://localhost:3000/health # For HTTP mode
|
|
71
|
+
curl http://localhost:3001/health # For SSE mode
|
|
60
72
|
```
|
|
61
73
|
|
|
62
74
|
---
|
|
@@ -155,7 +167,7 @@ curl http://localhost:3000/tools
|
|
|
155
167
|
|
|
156
168
|
---
|
|
157
169
|
|
|
158
|
-
### WebSocket Protocol -
|
|
170
|
+
### WebSocket Protocol - Complete Setup Guide
|
|
159
171
|
|
|
160
172
|
WebSocket provides **real-time, bidirectional communication** for modern applications.
|
|
161
173
|
|
|
@@ -168,6 +180,106 @@ npx brave-real-browser-mcp-server@latest --mode http --port 3000
|
|
|
168
180
|
# WebSocket will be available at: ws://localhost:3000
|
|
169
181
|
```
|
|
170
182
|
|
|
183
|
+
**Server will start and show:**
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
🟢 [HTTP] Starting HTTP/WebSocket server...
|
|
187
|
+
✅ [HTTP] Server ready at http://localhost:3000
|
|
188
|
+
✅ [WebSocket] Server running on ws://localhost:3000
|
|
189
|
+
💡 [HTTP] Universal mode - works with ALL AI IDEs
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Step 6: WebSocket Advanced Features
|
|
193
|
+
|
|
194
|
+
**Connection Options:**
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
const ws = new WebSocket('ws://localhost:3000', {
|
|
198
|
+
headers: {
|
|
199
|
+
'Authorization': 'Bearer your-token',
|
|
200
|
+
'X-Custom-Header': 'value'
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Reconnection Logic:**
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
function connectWebSocket() {
|
|
209
|
+
const ws = new WebSocket('ws://localhost:3000');
|
|
210
|
+
|
|
211
|
+
ws.on('close', () => {
|
|
212
|
+
console.log('Connection closed, reconnecting in 5s...');
|
|
213
|
+
setTimeout(connectWebSocket, 5000);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
ws.on('error', (error) => {
|
|
217
|
+
console.error('WebSocket error:', error);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return ws;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const ws = connectWebSocket();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Heartbeat/Ping-Pong:**
|
|
227
|
+
|
|
228
|
+
```javascript
|
|
229
|
+
const ws = new WebSocket('ws://localhost:3000');
|
|
230
|
+
|
|
231
|
+
setInterval(() => {
|
|
232
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
233
|
+
ws.ping();
|
|
234
|
+
}
|
|
235
|
+
}, 30000); // Ping every 30 seconds
|
|
236
|
+
|
|
237
|
+
ws.on('pong', () => {
|
|
238
|
+
console.log('Pong received - connection alive');
|
|
239
|
+
});
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### Troubleshooting WebSocket
|
|
243
|
+
|
|
244
|
+
**Issue: Connection Refused**
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
# Check if HTTP server is running
|
|
248
|
+
curl http://localhost:3000/health
|
|
249
|
+
|
|
250
|
+
# If not running, start it:
|
|
251
|
+
npx brave-real-browser-mcp-server@latest --mode http --port 3000
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Issue: WebSocket Disabled**
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
# Start server with WebSocket explicitly enabled
|
|
258
|
+
npx brave-real-browser-mcp-server@latest --mode http --port 3000
|
|
259
|
+
|
|
260
|
+
# Note: WebSocket is enabled by default
|
|
261
|
+
# To disable: use --no-websocket flag
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
**Issue: Connection Timeout**
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// Increase connection timeout
|
|
268
|
+
const ws = new WebSocket('ws://localhost:3000', {
|
|
269
|
+
handshakeTimeout: 10000 // 10 seconds
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Issue: Firewall Blocking**
|
|
274
|
+
|
|
275
|
+
```bash
|
|
276
|
+
# Windows - Allow Node.js through firewall
|
|
277
|
+
netsh advfirewall firewall add rule name="Node.js WebSocket" dir=in action=allow program="C:\Program Files\nodejs\node.exe" enable=yes
|
|
278
|
+
|
|
279
|
+
# Linux - Allow port 3000
|
|
280
|
+
sudo ufw allow 3000
|
|
281
|
+
```
|
|
282
|
+
|
|
171
283
|
## 🎨 IDE Configurations
|
|
172
284
|
|
|
173
285
|
### Claude Desktop
|
|
@@ -527,107 +639,273 @@ rm -rf ~/Library/Application\ Support/Cursor/Cache
|
|
|
527
639
|
|
|
528
640
|
### Qoder AI Editor
|
|
529
641
|
|
|
530
|
-
**Protocol:**
|
|
642
|
+
**Protocol:** MCP (STDIO) | **Setup Time:** 3 minutes | **Auto-Start:** ✅ Yes
|
|
531
643
|
|
|
532
|
-
|
|
644
|
+
**✅ Good News:** Qoder AI supports standard STDIO-based MCP servers (just like Claude, Cursor, Windsurf)!
|
|
533
645
|
|
|
534
|
-
#### Step-by-Step Setup Guide:
|
|
535
|
-
|
|
536
|
-
**Step 1: Start HTTP Server First**
|
|
646
|
+
#### 📋 Step-by-Step Setup Guide:
|
|
537
647
|
|
|
538
|
-
|
|
648
|
+
**Step 1: Open Qoder Settings**
|
|
539
649
|
|
|
540
650
|
```bash
|
|
541
|
-
#
|
|
542
|
-
|
|
651
|
+
# Method 1: Using keyboard shortcut
|
|
652
|
+
# Windows: Ctrl + Shift + ,
|
|
653
|
+
# Mac: ⌘ + Shift + ,
|
|
543
654
|
|
|
544
|
-
#
|
|
545
|
-
|
|
655
|
+
# Method 2: Click user icon in upper-right corner
|
|
656
|
+
# Then select "Qoder Settings"
|
|
546
657
|
```
|
|
547
658
|
|
|
548
|
-
**
|
|
549
|
-
```
|
|
550
|
-
🟢 [HTTP] Starting HTTP/WebSocket server...
|
|
551
|
-
✅ [HTTP] Server ready at http://localhost:3000
|
|
552
|
-
💡 [HTTP] Universal mode - works with ALL AI IDEs
|
|
553
|
-
```
|
|
659
|
+
**Step 2: Navigate to MCP Section**
|
|
554
660
|
|
|
555
|
-
|
|
661
|
+
1. In left-side navigation pane, click **MCP**
|
|
662
|
+
2. Click on **My Servers** tab
|
|
663
|
+
3. Click **+ Add** button in upper-right corner
|
|
556
664
|
|
|
557
|
-
|
|
665
|
+
**Step 3: Install Package Globally (Important for Qoder AI)**
|
|
558
666
|
|
|
559
667
|
```bash
|
|
560
|
-
#
|
|
561
|
-
|
|
668
|
+
# Install globally for faster startup
|
|
669
|
+
npm install -g brave-real-browser-mcp-server@latest
|
|
562
670
|
|
|
563
|
-
#
|
|
564
|
-
#
|
|
671
|
+
# Verify installation
|
|
672
|
+
where brave-real-browser-mcp-server # Windows
|
|
673
|
+
which brave-real-browser-mcp-server # Mac/Linux
|
|
674
|
+
```
|
|
565
675
|
|
|
566
|
-
|
|
567
|
-
|
|
676
|
+
**Why global install?** Qoder AI has a short timeout for MCP server initialization. Using `npx` can be slow on first run. Global installation ensures fast startup.
|
|
677
|
+
|
|
678
|
+
**Step 4: Add Configuration**
|
|
679
|
+
|
|
680
|
+
A JSON file will appear. Add this configuration:
|
|
681
|
+
|
|
682
|
+
**Option A - Using Global Install (Recommended):**
|
|
683
|
+
```json
|
|
684
|
+
{
|
|
685
|
+
"mcpServers": {
|
|
686
|
+
"brave-real-browser": {
|
|
687
|
+
"command": "brave-real-browser-mcp-server",
|
|
688
|
+
"args": []
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
**Option B - Using NPX (May timeout on first run):**
|
|
695
|
+
```json
|
|
696
|
+
{
|
|
697
|
+
"mcpServers": {
|
|
698
|
+
"brave-real-browser": {
|
|
699
|
+
"command": "npx",
|
|
700
|
+
"args": ["-y", "brave-real-browser-mcp-server@latest"]
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
**Option C - Using Node directly:**
|
|
707
|
+
```json
|
|
708
|
+
{
|
|
709
|
+
"mcpServers": {
|
|
710
|
+
"brave-real-browser": {
|
|
711
|
+
"command": "node",
|
|
712
|
+
"args": [
|
|
713
|
+
"C:\\Users\\Admin\\AppData\\Roaming\\npm\\node_modules\\brave-real-browser-mcp-server\\dist\\index.js"
|
|
714
|
+
]
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
568
718
|
```
|
|
569
719
|
|
|
570
|
-
**
|
|
720
|
+
**Note:** Replace path in Option C with your actual global npm modules path:
|
|
721
|
+
- Windows: `%APPDATA%\npm\node_modules\brave-real-browser-mcp-server\dist\index.js`
|
|
722
|
+
- Mac/Linux: `/usr/local/lib/node_modules/brave-real-browser-mcp-server/dist/index.js`
|
|
571
723
|
|
|
572
|
-
|
|
724
|
+
**Advanced Configuration (with environment variables):**
|
|
573
725
|
|
|
574
|
-
**Option A - MCP Configuration (Recommended):**
|
|
575
726
|
```json
|
|
576
727
|
{
|
|
577
728
|
"mcpServers": {
|
|
578
729
|
"brave-real-browser": {
|
|
579
|
-
"
|
|
580
|
-
"
|
|
581
|
-
"
|
|
582
|
-
|
|
730
|
+
"command": "npx",
|
|
731
|
+
"args": ["-y", "brave-real-browser-mcp-server@latest"],
|
|
732
|
+
"env": {
|
|
733
|
+
"BRAVE_PATH": "C:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe",
|
|
734
|
+
"HEADLESS": "false"
|
|
735
|
+
}
|
|
583
736
|
}
|
|
584
737
|
}
|
|
585
738
|
}
|
|
586
739
|
```
|
|
587
740
|
|
|
588
|
-
**
|
|
741
|
+
**For Mac:**
|
|
742
|
+
|
|
589
743
|
```json
|
|
590
744
|
{
|
|
591
|
-
"
|
|
745
|
+
"mcpServers": {
|
|
592
746
|
"brave-real-browser": {
|
|
593
|
-
"
|
|
594
|
-
"
|
|
595
|
-
"
|
|
596
|
-
|
|
747
|
+
"command": "npx",
|
|
748
|
+
"args": ["-y", "brave-real-browser-mcp-server@latest"],
|
|
749
|
+
"env": {
|
|
750
|
+
"BRAVE_PATH": "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
|
|
751
|
+
}
|
|
597
752
|
}
|
|
598
753
|
}
|
|
599
754
|
}
|
|
600
755
|
```
|
|
601
756
|
|
|
602
|
-
**Step
|
|
757
|
+
**Step 5: Save Configuration**
|
|
758
|
+
|
|
759
|
+
1. Close the JSON file
|
|
760
|
+
2. Click **Save** when prompted
|
|
761
|
+
3. The new server will appear in your list
|
|
762
|
+
4. A **link icon** (🔗) means the connection is successful
|
|
603
763
|
|
|
604
|
-
|
|
764
|
+
**Step 6: Verify Installation**
|
|
605
765
|
|
|
606
|
-
|
|
766
|
+
1. Expand the **brave-real-browser** entry
|
|
767
|
+
2. You should see list of 111 available tools
|
|
768
|
+
3. If server fails to start, click **Quick Fix** button
|
|
769
|
+
4. If issue persists, check troubleshooting section below
|
|
607
770
|
|
|
608
|
-
|
|
771
|
+
**Step 7: Using Tools in Qoder AI**
|
|
772
|
+
|
|
773
|
+
1. Switch to **Agent mode** in AI Chat panel
|
|
774
|
+
2. Ask Qoder to use browser automation:
|
|
775
|
+
```
|
|
776
|
+
Use brave-real-browser to navigate to https://example.com and extract the main content
|
|
777
|
+
```
|
|
778
|
+
3. Qoder will prompt for confirmation before using MCP tools
|
|
779
|
+
4. Press `Ctrl+Enter` (Windows) or `⌘+Enter` (Mac) to execute
|
|
609
780
|
|
|
610
781
|
**Important Notes:**
|
|
611
|
-
- 🔴 **Server must be running BEFORE starting Qoder AI**
|
|
612
|
-
- ✅ Keep the HTTP server terminal window open while using Qoder AI
|
|
613
|
-
- ✅ If server stops, restart it before using browser automation
|
|
614
|
-
- 💡 You can run server in background with `pm2` or as a service
|
|
615
782
|
|
|
616
|
-
**
|
|
783
|
+
- ⚠️ **Maximum 10 MCP servers** can be used simultaneously
|
|
784
|
+
- ✅ **Only works in Agent mode** (not in Ask mode)
|
|
785
|
+
- ✅ **Server auto-starts** when Qoder launches
|
|
786
|
+
- ✅ **Node.js V18+ required** (includes NPM V8+)
|
|
787
|
+
|
|
788
|
+
**Prerequisites Check:**
|
|
617
789
|
|
|
618
790
|
```bash
|
|
619
|
-
#
|
|
620
|
-
|
|
791
|
+
# Verify Node.js installation
|
|
792
|
+
node -v # Should show v18.0.0 or higher
|
|
793
|
+
npx -v # Should show version number
|
|
794
|
+
|
|
795
|
+
# If not installed:
|
|
796
|
+
# Windows: Download from https://nodejs.org/
|
|
797
|
+
# Mac: brew install node
|
|
798
|
+
# Linux: Use package manager (apt, yum, etc.)
|
|
799
|
+
```
|
|
621
800
|
|
|
622
|
-
|
|
623
|
-
pm2 start npx --name "brave-browser-server" -- brave-real-browser-mcp-server@latest --mode http --port 3000
|
|
801
|
+
**Troubleshooting:**
|
|
624
802
|
|
|
625
|
-
|
|
626
|
-
pm2 logs brave-browser-server
|
|
803
|
+
**Issue: "context deadline exceeded" or Timeout Error**
|
|
627
804
|
|
|
628
|
-
# Stop server
|
|
629
|
-
pm2 stop brave-browser-server
|
|
630
805
|
```
|
|
806
|
+
failed to initialize MCP client: context deadline exceeded
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
**Cause:** Qoder AI has a short initialization timeout. Using `npx` can be slow on first run because it needs to download and cache the package.
|
|
810
|
+
|
|
811
|
+
**Solution 1 - Install Globally (Recommended):**
|
|
812
|
+
```bash
|
|
813
|
+
# Install package globally for instant startup
|
|
814
|
+
npm install -g brave-real-browser-mcp-server@latest
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
Then update your configuration to:
|
|
818
|
+
```json
|
|
819
|
+
{
|
|
820
|
+
"mcpServers": {
|
|
821
|
+
"brave-real-browser": {
|
|
822
|
+
"command": "brave-real-browser-mcp-server",
|
|
823
|
+
"args": []
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
**Solution 2 - Pre-cache npx package:**
|
|
830
|
+
```bash
|
|
831
|
+
# Run once to cache the package
|
|
832
|
+
npx -y brave-real-browser-mcp-server@latest
|
|
833
|
+
# Press Ctrl+C after server starts
|
|
834
|
+
|
|
835
|
+
# Now npx will be fast on subsequent runs
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
**Solution 3 - Use Direct Node Path:**
|
|
839
|
+
|
|
840
|
+
First, find the global package location:
|
|
841
|
+
```bash
|
|
842
|
+
# Windows
|
|
843
|
+
npm root -g
|
|
844
|
+
# Usually: C:\Users\<USERNAME>\AppData\Roaming\npm\node_modules
|
|
845
|
+
|
|
846
|
+
# Mac/Linux
|
|
847
|
+
npm root -g
|
|
848
|
+
# Usually: /usr/local/lib/node_modules
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
Then use full path:
|
|
852
|
+
```json
|
|
853
|
+
{
|
|
854
|
+
"mcpServers": {
|
|
855
|
+
"brave-real-browser": {
|
|
856
|
+
"command": "node",
|
|
857
|
+
"args": ["<npm-root>\\brave-real-browser-mcp-server\\dist\\index.js"]
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
**Issue: "exec: npx: executable file not found"**
|
|
864
|
+
|
|
865
|
+
```bash
|
|
866
|
+
# Solution: Install Node.js V18 or later
|
|
867
|
+
# Windows
|
|
868
|
+
nvm install 22.14.0
|
|
869
|
+
nvm use 22.14.0
|
|
870
|
+
|
|
871
|
+
# Mac
|
|
872
|
+
brew install node
|
|
873
|
+
|
|
874
|
+
# Verify
|
|
875
|
+
node -v
|
|
876
|
+
npx -v
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
**Issue: "failed to initialize MCP client: context deadline exceeded"**
|
|
880
|
+
|
|
881
|
+
1. Click **Copy complete command** in Qoder UI
|
|
882
|
+
2. Run command in terminal to see detailed error
|
|
883
|
+
3. Check if Node.js is blocked by security software
|
|
884
|
+
4. Add Node.js to security software whitelist
|
|
885
|
+
|
|
886
|
+
**Issue: Server fails to connect**
|
|
887
|
+
|
|
888
|
+
1. Click **Retry** icon in Qoder interface
|
|
889
|
+
2. Qoder will attempt to restart MCP server automatically
|
|
890
|
+
3. Check **My Servers** tab for connection status
|
|
891
|
+
4. Expand server details to see tools list
|
|
892
|
+
|
|
893
|
+
**Issue: Tools not being called by LLM**
|
|
894
|
+
|
|
895
|
+
1. Make sure you're in **Agent mode** (not Ask mode)
|
|
896
|
+
2. Open a project directory in Qoder
|
|
897
|
+
3. Ensure MCP server shows **link icon** (connected)
|
|
898
|
+
4. Try explicit prompt: "Use brave-real-browser to..."
|
|
899
|
+
|
|
900
|
+
**Configuration Locations:**
|
|
901
|
+
|
|
902
|
+
- Windows: Qoder Settings → MCP → My Servers
|
|
903
|
+
- Mac: Qoder Settings → MCP → My Servers
|
|
904
|
+
- Linux: Qoder Settings → MCP → My Servers
|
|
905
|
+
|
|
906
|
+
**Official Documentation:**
|
|
907
|
+
- Qoder MCP Guide: https://docs.qoder.com/user-guide/chat/model-context-protocol
|
|
908
|
+
- MCP Common Issues: https://docs.qoder.com/support/mcp-common-issues
|
|
631
909
|
|
|
632
910
|
### Other HTTP-based IDEs (Gemini CLI, Qwen Code CLI, Custom Tools)
|
|
633
911
|
|
|
@@ -1402,6 +1680,7 @@ DEBUG=* npx brave-real-browser-mcp-server@latest --mode http
|
|
|
1402
1680
|
|| **LSP** | Zed Editor, VSCode, Neovim | ✅ | 🟢 Working |
|
|
1403
1681
|
|| **HTTP/REST** | Any IDE/Tool | ✅ | 🟢 Working |
|
|
1404
1682
|
|| **WebSocket** | Modern Web Apps, Real-time Tools | ✅ | 🟢 Working |
|
|
1683
|
+
|| **SSE** | Real-time Streaming, Web Apps | ✅ | 🟢 Working |
|
|
1405
1684
|
|
|
1406
1685
|
---
|
|
1407
1686
|
|
|
@@ -1459,7 +1738,7 @@ MIT License - See LICENSE file for details.
|
|
|
1459
1738
|
|
|
1460
1739
|
<div align="center">
|
|
1461
1740
|
|
|
1462
|
-
**🌟 111 Tools | 15+ AI IDEs |
|
|
1741
|
+
**🌟 111 Tools | 15+ AI IDEs | 5 Protocols | Universal Support 🌟**
|
|
1463
1742
|
|
|
1464
1743
|
**Made with ❤️ for the AI Development Community**
|
|
1465
1744
|
|
package/dist/index.js
CHANGED
|
@@ -111,11 +111,8 @@ server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
|
111
111
|
console.error("🔍 [DEBUG] Prompts list requested");
|
|
112
112
|
return { prompts: [] };
|
|
113
113
|
});
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
117
|
-
const { name, arguments: args } = request.params;
|
|
118
|
-
console.error(`🔍 [DEBUG] Tool call received: ${name} with args: ${JSON.stringify(args)}`);
|
|
114
|
+
// Tool execution function - exported for use in transports
|
|
115
|
+
export async function executeToolByName(name, args) {
|
|
119
116
|
try {
|
|
120
117
|
let result;
|
|
121
118
|
switch (name) {
|
|
@@ -492,6 +489,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
492
489
|
isError: true,
|
|
493
490
|
};
|
|
494
491
|
}
|
|
492
|
+
}
|
|
493
|
+
// Main tool call handler
|
|
494
|
+
console.error("🔍 [DEBUG] Registering tool call handler...");
|
|
495
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
496
|
+
const { name, arguments: args } = request.params;
|
|
497
|
+
console.error(`🔍 [DEBUG] Tool call received: ${name} with args: ${JSON.stringify(args)}`);
|
|
498
|
+
return await executeToolByName(name, args);
|
|
495
499
|
});
|
|
496
500
|
// Main function - now using multi-protocol launcher
|
|
497
501
|
import { main as launcherMain } from "./launcher.js";
|
package/dist/launcher.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
3
|
import { HttpTransport } from "./transports/http-transport.js";
|
|
4
4
|
import { LspTransport } from "./transports/lsp-transport.js";
|
|
5
|
+
import { SseTransport } from "./transports/sse-transport.js";
|
|
5
6
|
import { closeBrowser, forceKillAllBraveProcesses } from "./browser-manager.js";
|
|
6
7
|
import { setupProcessCleanup } from "./core-infrastructure.js";
|
|
7
8
|
import { UniversalIDEAdapter, ProtocolType, } from "./universal-ide-adapter.js";
|
|
@@ -9,6 +10,7 @@ export class MultiProtocolLauncher {
|
|
|
9
10
|
config;
|
|
10
11
|
httpTransport;
|
|
11
12
|
lspTransport;
|
|
13
|
+
sseTransport;
|
|
12
14
|
mcpServer;
|
|
13
15
|
universalAdapter;
|
|
14
16
|
constructor(config = {}) {
|
|
@@ -16,6 +18,8 @@ export class MultiProtocolLauncher {
|
|
|
16
18
|
mode: config.mode || "auto",
|
|
17
19
|
httpPort: config.httpPort || 3000,
|
|
18
20
|
httpHost: config.httpHost || "0.0.0.0",
|
|
21
|
+
ssePort: config.ssePort || 3001,
|
|
22
|
+
sseHost: config.sseHost || "0.0.0.0",
|
|
19
23
|
enableWebSocket: config.enableWebSocket !== false,
|
|
20
24
|
enableCors: config.enableCors !== false,
|
|
21
25
|
enableAutoDetection: config.enableAutoDetection !== false,
|
|
@@ -66,6 +70,9 @@ export class MultiProtocolLauncher {
|
|
|
66
70
|
case "lsp":
|
|
67
71
|
await this.startLsp();
|
|
68
72
|
break;
|
|
73
|
+
case "sse":
|
|
74
|
+
await this.startSse();
|
|
75
|
+
break;
|
|
69
76
|
case "all":
|
|
70
77
|
await this.startAll();
|
|
71
78
|
break;
|
|
@@ -111,6 +118,19 @@ export class MultiProtocolLauncher {
|
|
|
111
118
|
await this.lspTransport.start();
|
|
112
119
|
console.error("💡 [LSP] Compatible with: Zed Editor, VSCode, Neovim, Emacs, Sublime Text");
|
|
113
120
|
}
|
|
121
|
+
async startSse() {
|
|
122
|
+
console.error("🟠 [SSE] Starting Server-Sent Events transport...");
|
|
123
|
+
if (this.universalAdapter?.getDetectionResult()) {
|
|
124
|
+
const result = this.universalAdapter.getDetectionResult();
|
|
125
|
+
console.error(`🎯 [SSE] Optimized for: ${result.capabilities.name}`);
|
|
126
|
+
}
|
|
127
|
+
this.sseTransport = new SseTransport({
|
|
128
|
+
port: this.config.ssePort,
|
|
129
|
+
host: this.config.sseHost,
|
|
130
|
+
});
|
|
131
|
+
await this.sseTransport.start();
|
|
132
|
+
console.error("💡 [SSE] Real-time streaming - works with web apps and modern clients");
|
|
133
|
+
}
|
|
114
134
|
async startAll() {
|
|
115
135
|
console.error("🌈 Starting all protocols...");
|
|
116
136
|
// Start HTTP in background
|
|
@@ -137,6 +157,9 @@ export class MultiProtocolLauncher {
|
|
|
137
157
|
if (this.lspTransport) {
|
|
138
158
|
await this.lspTransport.stop();
|
|
139
159
|
}
|
|
160
|
+
if (this.sseTransport) {
|
|
161
|
+
await this.sseTransport.stop();
|
|
162
|
+
}
|
|
140
163
|
console.error("✅ All servers stopped");
|
|
141
164
|
}
|
|
142
165
|
}
|
|
@@ -148,17 +171,20 @@ export function parseArgs() {
|
|
|
148
171
|
const arg = args[i];
|
|
149
172
|
if (arg === "--mode" || arg === "-m") {
|
|
150
173
|
const mode = args[++i];
|
|
151
|
-
if (["auto", "mcp", "http", "lsp", "all"].includes(mode)) {
|
|
174
|
+
if (["auto", "mcp", "http", "lsp", "sse", "all"].includes(mode)) {
|
|
152
175
|
config.mode = mode;
|
|
153
176
|
}
|
|
154
177
|
else {
|
|
155
|
-
console.error(`❌ Invalid mode: ${mode}. Use: auto, mcp, http, lsp, or all`);
|
|
178
|
+
console.error(`❌ Invalid mode: ${mode}. Use: auto, mcp, http, lsp, sse, or all`);
|
|
156
179
|
process.exit(1);
|
|
157
180
|
}
|
|
158
181
|
}
|
|
159
182
|
else if (arg === "--port" || arg === "-p") {
|
|
160
183
|
config.httpPort = parseInt(args[++i]);
|
|
161
184
|
}
|
|
185
|
+
else if (arg === "--sse-port") {
|
|
186
|
+
config.ssePort = parseInt(args[++i]);
|
|
187
|
+
}
|
|
162
188
|
else if (arg === "--host" || arg === "-h") {
|
|
163
189
|
config.httpHost = args[++i];
|
|
164
190
|
}
|
|
@@ -179,9 +205,10 @@ Brave Real Browser MCP Server - Universal AI IDE Support
|
|
|
179
205
|
Usage: brave-real-browser-mcp-server [options]
|
|
180
206
|
|
|
181
207
|
Options:
|
|
182
|
-
--mode, -m <mode> Protocol mode: auto, mcp, http, lsp, or all (default: auto)
|
|
208
|
+
--mode, -m <mode> Protocol mode: auto, mcp, http, lsp, sse, or all (default: auto)
|
|
183
209
|
--port, -p <port> HTTP server port (default: 3000)
|
|
184
|
-
--
|
|
210
|
+
--sse-port <port> SSE server port (default: 3001)
|
|
211
|
+
--host, -h <host> HTTP/SSE server host (default: 0.0.0.0)
|
|
185
212
|
--no-websocket Disable WebSocket support in HTTP mode
|
|
186
213
|
--no-auto-detect Disable automatic IDE detection
|
|
187
214
|
--list-ides Show list of all supported AI IDEs
|
|
@@ -211,7 +238,10 @@ Examples:
|
|
|
211
238
|
# LSP mode (for Zed, VSCode, Neovim, Emacs, Sublime Text)
|
|
212
239
|
brave-real-browser-mcp-server --mode lsp
|
|
213
240
|
|
|
214
|
-
#
|
|
241
|
+
# SSE mode (for real-time streaming, web apps)
|
|
242
|
+
brave-real-browser-mcp-server --mode sse --sse-port 3001
|
|
243
|
+
|
|
244
|
+
# All protocols (HTTP only, MCP/LSP/SSE need separate instances)
|
|
215
245
|
brave-real-browser-mcp-server --mode all
|
|
216
246
|
|
|
217
247
|
# Show all supported IDEs
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import express from 'express';
|
|
2
2
|
import { createServer } from 'http';
|
|
3
3
|
import { WebSocketServer } from 'ws';
|
|
4
|
-
import { TOOLS
|
|
5
|
-
|
|
4
|
+
import { TOOLS } from '../tool-definitions.js';
|
|
5
|
+
import { executeToolByName } from '../index.js';
|
|
6
|
+
// Import specific handlers for direct endpoints (for backward compatibility)
|
|
6
7
|
import { handleBrowserInit, handleBrowserClose } from '../handlers/browser-handlers.js';
|
|
7
|
-
import { handleNavigate
|
|
8
|
-
import { handleClick, handleType
|
|
9
|
-
import { handleGetContent
|
|
10
|
-
import { handleSaveContentAsMarkdown } from '../handlers/file-handlers.js';
|
|
8
|
+
import { handleNavigate } from '../handlers/navigation-handlers.js';
|
|
9
|
+
import { handleClick, handleType } from '../handlers/interaction-handlers.js';
|
|
10
|
+
import { handleGetContent } from '../handlers/content-handlers.js';
|
|
11
11
|
export class HttpTransport {
|
|
12
12
|
app;
|
|
13
13
|
server = null;
|
|
@@ -128,32 +128,8 @@ export class HttpTransport {
|
|
|
128
128
|
});
|
|
129
129
|
}
|
|
130
130
|
async executeTool(toolName, args) {
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
}
|
|
131
|
+
// Use universal tool executor from index.ts (supports all 110 tools)
|
|
132
|
+
return await executeToolByName(toolName, args);
|
|
157
133
|
}
|
|
158
134
|
setupWebSocket() {
|
|
159
135
|
if (!this.server || !this.config.enableWebSocket)
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { createServer } from 'http';
|
|
3
|
+
import { TOOLS } from '../tool-definitions.js';
|
|
4
|
+
import { executeToolByName } from '../index.js';
|
|
5
|
+
// Import specific handlers for direct endpoints (for backward compatibility)
|
|
6
|
+
import { handleBrowserInit, handleBrowserClose } from '../handlers/browser-handlers.js';
|
|
7
|
+
import { handleNavigate } from '../handlers/navigation-handlers.js';
|
|
8
|
+
import { handleClick, handleType } from '../handlers/interaction-handlers.js';
|
|
9
|
+
import { handleGetContent } from '../handlers/content-handlers.js';
|
|
10
|
+
export class SseTransport {
|
|
11
|
+
app;
|
|
12
|
+
server = null;
|
|
13
|
+
config;
|
|
14
|
+
clients = new Map();
|
|
15
|
+
heartbeatTimer = null;
|
|
16
|
+
constructor(config = {}) {
|
|
17
|
+
this.config = {
|
|
18
|
+
port: config.port || 3001,
|
|
19
|
+
host: config.host || '0.0.0.0',
|
|
20
|
+
corsOrigins: config.corsOrigins || ['*'],
|
|
21
|
+
heartbeatInterval: config.heartbeatInterval || 30000, // 30 seconds
|
|
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 with SSE-specific headers
|
|
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, OPTIONS');
|
|
35
|
+
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, Cache-Control');
|
|
36
|
+
res.header('Access-Control-Expose-Headers', 'Content-Type, Cache-Control');
|
|
37
|
+
if (req.method === 'OPTIONS') {
|
|
38
|
+
return res.sendStatus(200);
|
|
39
|
+
}
|
|
40
|
+
next();
|
|
41
|
+
});
|
|
42
|
+
// Request logging
|
|
43
|
+
this.app.use((req, res, next) => {
|
|
44
|
+
console.error(`📡 [SSE] ${req.method} ${req.path} - ${new Date().toISOString()}`);
|
|
45
|
+
next();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
setupRoutes() {
|
|
49
|
+
// Health check
|
|
50
|
+
this.app.get('/health', (req, res) => {
|
|
51
|
+
res.json({
|
|
52
|
+
status: 'ok',
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
protocol: 'SSE',
|
|
55
|
+
clients: this.clients.size,
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// List all available tools
|
|
59
|
+
this.app.get('/tools', (req, res) => {
|
|
60
|
+
res.json({ tools: TOOLS });
|
|
61
|
+
});
|
|
62
|
+
// SSE event stream endpoint
|
|
63
|
+
this.app.get('/events', (req, res) => {
|
|
64
|
+
// Set SSE headers
|
|
65
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
66
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
67
|
+
res.setHeader('Connection', 'keep-alive');
|
|
68
|
+
res.setHeader('X-Accel-Buffering', 'no'); // Disable nginx buffering
|
|
69
|
+
const clientId = `client_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
70
|
+
// Add client to active connections
|
|
71
|
+
this.clients.set(clientId, {
|
|
72
|
+
id: clientId,
|
|
73
|
+
response: res,
|
|
74
|
+
lastActivity: Date.now(),
|
|
75
|
+
});
|
|
76
|
+
console.error(`📡 [SSE] Client connected: ${clientId} (Total: ${this.clients.size})`);
|
|
77
|
+
// Send initial connection message
|
|
78
|
+
this.sendEvent(res, 'connected', {
|
|
79
|
+
clientId,
|
|
80
|
+
message: 'Connected to Brave Browser MCP Server',
|
|
81
|
+
timestamp: new Date().toISOString(),
|
|
82
|
+
});
|
|
83
|
+
// Handle client disconnect
|
|
84
|
+
req.on('close', () => {
|
|
85
|
+
this.clients.delete(clientId);
|
|
86
|
+
console.error(`📡 [SSE] Client disconnected: ${clientId} (Remaining: ${this.clients.size})`);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// Execute tool via POST with SSE response
|
|
90
|
+
this.app.post('/tools/:toolName', async (req, res) => {
|
|
91
|
+
const { toolName } = req.params;
|
|
92
|
+
const args = req.body;
|
|
93
|
+
const clientId = req.headers['x-client-id'];
|
|
94
|
+
try {
|
|
95
|
+
// Send start event to SSE clients
|
|
96
|
+
this.broadcastEvent('tool_start', {
|
|
97
|
+
tool: toolName,
|
|
98
|
+
timestamp: new Date().toISOString(),
|
|
99
|
+
});
|
|
100
|
+
const result = await this.executeTool(toolName, args);
|
|
101
|
+
// Send success event to SSE clients
|
|
102
|
+
this.broadcastEvent('tool_success', {
|
|
103
|
+
tool: toolName,
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
});
|
|
106
|
+
res.json({ success: true, result });
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
110
|
+
// Send error event to SSE clients
|
|
111
|
+
this.broadcastEvent('tool_error', {
|
|
112
|
+
tool: toolName,
|
|
113
|
+
error: errorMessage,
|
|
114
|
+
timestamp: new Date().toISOString(),
|
|
115
|
+
});
|
|
116
|
+
res.status(500).json({ success: false, error: errorMessage });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
// Browser automation endpoints with SSE notifications
|
|
120
|
+
this.app.post('/browser/init', async (req, res) => {
|
|
121
|
+
try {
|
|
122
|
+
this.broadcastEvent('browser_init_start', { timestamp: new Date().toISOString() });
|
|
123
|
+
const result = await handleBrowserInit(req.body);
|
|
124
|
+
this.broadcastEvent('browser_init_success', { timestamp: new Date().toISOString() });
|
|
125
|
+
res.json({ success: true, result });
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
this.broadcastEvent('browser_init_error', {
|
|
129
|
+
error: error.message,
|
|
130
|
+
timestamp: new Date().toISOString()
|
|
131
|
+
});
|
|
132
|
+
res.status(500).json({ success: false, error: error.message });
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
this.app.post('/browser/navigate', async (req, res) => {
|
|
136
|
+
try {
|
|
137
|
+
this.broadcastEvent('browser_navigate_start', {
|
|
138
|
+
url: req.body.url,
|
|
139
|
+
timestamp: new Date().toISOString()
|
|
140
|
+
});
|
|
141
|
+
const result = await handleNavigate(req.body);
|
|
142
|
+
this.broadcastEvent('browser_navigate_success', {
|
|
143
|
+
url: req.body.url,
|
|
144
|
+
timestamp: new Date().toISOString()
|
|
145
|
+
});
|
|
146
|
+
res.json({ success: true, result });
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
this.broadcastEvent('browser_navigate_error', {
|
|
150
|
+
error: error.message,
|
|
151
|
+
timestamp: new Date().toISOString()
|
|
152
|
+
});
|
|
153
|
+
res.status(500).json({ success: false, error: error.message });
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
this.app.post('/browser/click', async (req, res) => {
|
|
157
|
+
try {
|
|
158
|
+
const result = await handleClick(req.body);
|
|
159
|
+
this.broadcastEvent('browser_action', {
|
|
160
|
+
action: 'click',
|
|
161
|
+
selector: req.body.selector,
|
|
162
|
+
timestamp: new Date().toISOString()
|
|
163
|
+
});
|
|
164
|
+
res.json({ success: true, result });
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
res.status(500).json({ success: false, error: error.message });
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
this.app.post('/browser/type', async (req, res) => {
|
|
171
|
+
try {
|
|
172
|
+
const result = await handleType(req.body);
|
|
173
|
+
this.broadcastEvent('browser_action', {
|
|
174
|
+
action: 'type',
|
|
175
|
+
selector: req.body.selector,
|
|
176
|
+
timestamp: new Date().toISOString()
|
|
177
|
+
});
|
|
178
|
+
res.json({ success: true, result });
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
res.status(500).json({ success: false, error: error.message });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
this.app.post('/browser/get-content', async (req, res) => {
|
|
185
|
+
try {
|
|
186
|
+
const result = await handleGetContent(req.body);
|
|
187
|
+
res.json({ success: true, result });
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
res.status(500).json({ success: false, error: error.message });
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
this.app.post('/browser/close', async (req, res) => {
|
|
194
|
+
try {
|
|
195
|
+
this.broadcastEvent('browser_close', { timestamp: new Date().toISOString() });
|
|
196
|
+
const result = await handleBrowserClose();
|
|
197
|
+
res.json({ success: true, result });
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
res.status(500).json({ success: false, error: error.message });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
// Error handling
|
|
204
|
+
this.app.use((err, req, res, next) => {
|
|
205
|
+
console.error('❌ [SSE] Error:', err);
|
|
206
|
+
res.status(500).json({ success: false, error: err.message });
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
async executeTool(toolName, args) {
|
|
210
|
+
// Use universal tool executor from index.ts (supports all 110 tools)
|
|
211
|
+
return await executeToolByName(toolName, args);
|
|
212
|
+
}
|
|
213
|
+
sendEvent(res, eventType, data) {
|
|
214
|
+
const eventData = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
215
|
+
res.write(eventData);
|
|
216
|
+
}
|
|
217
|
+
broadcastEvent(eventType, data) {
|
|
218
|
+
const eventData = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
219
|
+
this.clients.forEach((client, clientId) => {
|
|
220
|
+
try {
|
|
221
|
+
client.response.write(eventData);
|
|
222
|
+
client.lastActivity = Date.now();
|
|
223
|
+
}
|
|
224
|
+
catch (error) {
|
|
225
|
+
console.error(`❌ [SSE] Failed to send to client ${clientId}:`, error);
|
|
226
|
+
this.clients.delete(clientId);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
startHeartbeat() {
|
|
231
|
+
this.heartbeatTimer = setInterval(() => {
|
|
232
|
+
const now = Date.now();
|
|
233
|
+
this.clients.forEach((client, clientId) => {
|
|
234
|
+
try {
|
|
235
|
+
// Send heartbeat
|
|
236
|
+
client.response.write(': heartbeat\n\n');
|
|
237
|
+
// Check for stale connections (no activity for 2 minutes)
|
|
238
|
+
if (now - client.lastActivity > 120000) {
|
|
239
|
+
console.error(`📡 [SSE] Removing stale client: ${clientId}`);
|
|
240
|
+
client.response.end();
|
|
241
|
+
this.clients.delete(clientId);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
console.error(`❌ [SSE] Heartbeat failed for client ${clientId}:`, error);
|
|
246
|
+
this.clients.delete(clientId);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
}, this.config.heartbeatInterval);
|
|
250
|
+
}
|
|
251
|
+
async start() {
|
|
252
|
+
return new Promise((resolve) => {
|
|
253
|
+
this.server = createServer(this.app);
|
|
254
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
255
|
+
console.error(`✅ [SSE] Server running on http://${this.config.host}:${this.config.port}`);
|
|
256
|
+
console.error(`📡 [SSE] Event stream available at http://${this.config.host}:${this.config.port}/events`);
|
|
257
|
+
console.error(`💡 [SSE] Real-time browser automation events enabled`);
|
|
258
|
+
this.startHeartbeat();
|
|
259
|
+
resolve();
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async stop() {
|
|
264
|
+
return new Promise((resolve, reject) => {
|
|
265
|
+
// Stop heartbeat
|
|
266
|
+
if (this.heartbeatTimer) {
|
|
267
|
+
clearInterval(this.heartbeatTimer);
|
|
268
|
+
this.heartbeatTimer = null;
|
|
269
|
+
}
|
|
270
|
+
// Close all SSE connections
|
|
271
|
+
this.clients.forEach((client, clientId) => {
|
|
272
|
+
try {
|
|
273
|
+
client.response.end();
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
console.error(`❌ [SSE] Error closing client ${clientId}:`, error);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
this.clients.clear();
|
|
280
|
+
// Close server
|
|
281
|
+
if (this.server) {
|
|
282
|
+
this.server.close((err) => {
|
|
283
|
+
if (err)
|
|
284
|
+
reject(err);
|
|
285
|
+
else
|
|
286
|
+
resolve();
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
resolve();
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
// Public method to broadcast custom events
|
|
295
|
+
broadcast(eventType, data) {
|
|
296
|
+
this.broadcastEvent(eventType, data);
|
|
297
|
+
}
|
|
298
|
+
// Get connected clients count
|
|
299
|
+
getClientsCount() {
|
|
300
|
+
return this.clients.size;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { SseTransport } from './sse-transport.js';
|
|
3
|
+
describe('SSE Transport', () => {
|
|
4
|
+
let transport;
|
|
5
|
+
const TEST_PORT = 3099; // Use different port to avoid conflicts
|
|
6
|
+
const TEST_HOST = 'localhost';
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
transport = new SseTransport({
|
|
9
|
+
port: TEST_PORT,
|
|
10
|
+
host: TEST_HOST,
|
|
11
|
+
heartbeatInterval: 5000,
|
|
12
|
+
});
|
|
13
|
+
await transport.start();
|
|
14
|
+
// Give server time to fully start
|
|
15
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
16
|
+
});
|
|
17
|
+
afterAll(async () => {
|
|
18
|
+
await transport.stop();
|
|
19
|
+
});
|
|
20
|
+
describe('Server Initialization', () => {
|
|
21
|
+
it('should start SSE server successfully', async () => {
|
|
22
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/health`);
|
|
23
|
+
const data = await response.json();
|
|
24
|
+
expect(response.status).toBe(200);
|
|
25
|
+
expect(data.status).toBe('ok');
|
|
26
|
+
expect(data.protocol).toBe('SSE');
|
|
27
|
+
});
|
|
28
|
+
it('should return tools list', async () => {
|
|
29
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/tools`);
|
|
30
|
+
const data = await response.json();
|
|
31
|
+
expect(response.status).toBe(200);
|
|
32
|
+
expect(data.tools).toBeDefined();
|
|
33
|
+
expect(Array.isArray(data.tools)).toBe(true);
|
|
34
|
+
expect(data.tools.length).toBeGreaterThan(0);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
describe('SSE Event Stream', () => {
|
|
38
|
+
it('should establish SSE connection and receive connected event', async () => {
|
|
39
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/events`, {
|
|
40
|
+
headers: {
|
|
41
|
+
'Accept': 'text/event-stream',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
expect(response.status).toBe(200);
|
|
45
|
+
expect(response.headers.get('content-type')).toBe('text/event-stream');
|
|
46
|
+
expect(response.headers.get('cache-control')).toBe('no-cache');
|
|
47
|
+
expect(response.headers.get('connection')).toBe('keep-alive');
|
|
48
|
+
});
|
|
49
|
+
it('should track connected clients', async () => {
|
|
50
|
+
const initialClientsCount = transport.getClientsCount();
|
|
51
|
+
// Establish SSE connection
|
|
52
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/events`, {
|
|
53
|
+
headers: {
|
|
54
|
+
'Accept': 'text/event-stream',
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
// Wait a bit for connection to be registered
|
|
58
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
59
|
+
const currentClientsCount = transport.getClientsCount();
|
|
60
|
+
expect(currentClientsCount).toBeGreaterThan(initialClientsCount);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe('Browser Tool Execution', () => {
|
|
64
|
+
it('should handle browser init failure gracefully', async () => {
|
|
65
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/browser/init`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
headers: {
|
|
68
|
+
'Content-Type': 'application/json',
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({}),
|
|
71
|
+
});
|
|
72
|
+
const data = await response.json();
|
|
73
|
+
expect([200, 500]).toContain(response.status);
|
|
74
|
+
expect(data).toHaveProperty('success');
|
|
75
|
+
expect(data).toHaveProperty('result');
|
|
76
|
+
});
|
|
77
|
+
it('should handle navigation without browser', async () => {
|
|
78
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/browser/navigate`, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
},
|
|
83
|
+
body: JSON.stringify({ url: 'https://example.com' }),
|
|
84
|
+
});
|
|
85
|
+
const data = await response.json();
|
|
86
|
+
expect(response.status).toBe(500);
|
|
87
|
+
expect(data.success).toBe(false);
|
|
88
|
+
expect(data.error).toBeTruthy();
|
|
89
|
+
});
|
|
90
|
+
it('should handle get-content without browser', async () => {
|
|
91
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/browser/get-content`, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: {
|
|
94
|
+
'Content-Type': 'application/json',
|
|
95
|
+
},
|
|
96
|
+
body: JSON.stringify({ type: 'text' }),
|
|
97
|
+
});
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
expect(response.status).toBe(500);
|
|
100
|
+
expect(data.success).toBe(false);
|
|
101
|
+
expect(data.error).toBeTruthy();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
describe('Generic Tool Execution', () => {
|
|
105
|
+
it('should reject unknown tool', async () => {
|
|
106
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/tools/unknown_tool`, {
|
|
107
|
+
method: 'POST',
|
|
108
|
+
headers: {
|
|
109
|
+
'Content-Type': 'application/json',
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({}),
|
|
112
|
+
});
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
// MCP format: errors are returned in content with isError flag
|
|
115
|
+
expect(response.status).toBe(200);
|
|
116
|
+
expect(data.success).toBe(true);
|
|
117
|
+
expect(data.result.isError).toBe(true);
|
|
118
|
+
expect(data.result.content[0].text).toContain('Unknown tool');
|
|
119
|
+
});
|
|
120
|
+
it('should validate tool arguments', async () => {
|
|
121
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/tools/navigate`, {
|
|
122
|
+
method: 'POST',
|
|
123
|
+
headers: {
|
|
124
|
+
'Content-Type': 'application/json',
|
|
125
|
+
},
|
|
126
|
+
body: JSON.stringify({ /* missing url */}),
|
|
127
|
+
});
|
|
128
|
+
const data = await response.json();
|
|
129
|
+
expect(response.status).toBe(500);
|
|
130
|
+
expect(data.success).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
describe('CORS Headers', () => {
|
|
134
|
+
it('should include CORS headers', async () => {
|
|
135
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/health`);
|
|
136
|
+
expect(response.headers.get('access-control-allow-origin')).toBeTruthy();
|
|
137
|
+
expect(response.headers.get('access-control-allow-methods')).toBeTruthy();
|
|
138
|
+
});
|
|
139
|
+
it('should handle OPTIONS preflight', async () => {
|
|
140
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/tools`, {
|
|
141
|
+
method: 'OPTIONS',
|
|
142
|
+
});
|
|
143
|
+
expect(response.status).toBe(200);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('Event Broadcasting', () => {
|
|
147
|
+
it('should broadcast custom events', () => {
|
|
148
|
+
// This is a unit test for the broadcast method
|
|
149
|
+
expect(typeof transport.broadcast).toBe('function');
|
|
150
|
+
// Call broadcast (won't fail even with no clients)
|
|
151
|
+
expect(() => {
|
|
152
|
+
transport.broadcast('test_event', { test: 'data' });
|
|
153
|
+
}).not.toThrow();
|
|
154
|
+
});
|
|
155
|
+
it('should handle broadcast to disconnected clients', () => {
|
|
156
|
+
// Broadcast should handle errors gracefully
|
|
157
|
+
expect(() => {
|
|
158
|
+
transport.broadcast('error_test', { data: 'test' });
|
|
159
|
+
}).not.toThrow();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('Error Handling', () => {
|
|
163
|
+
it('should handle malformed JSON', async () => {
|
|
164
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/tools/navigate`, {
|
|
165
|
+
method: 'POST',
|
|
166
|
+
headers: {
|
|
167
|
+
'Content-Type': 'application/json',
|
|
168
|
+
},
|
|
169
|
+
body: 'invalid json',
|
|
170
|
+
});
|
|
171
|
+
expect(response.status).toBe(500); // Express body-parser returns 500 for bad JSON
|
|
172
|
+
});
|
|
173
|
+
it('should handle missing request body', async () => {
|
|
174
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/browser/navigate`, {
|
|
175
|
+
method: 'POST',
|
|
176
|
+
headers: {
|
|
177
|
+
'Content-Type': 'application/json',
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
const data = await response.json();
|
|
181
|
+
expect(response.status).toBe(500);
|
|
182
|
+
expect(data.success).toBe(false);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
describe('Server Lifecycle', () => {
|
|
186
|
+
it('should stop gracefully', async () => {
|
|
187
|
+
const tempTransport = new SseTransport({
|
|
188
|
+
port: TEST_PORT + 1,
|
|
189
|
+
host: TEST_HOST,
|
|
190
|
+
});
|
|
191
|
+
await tempTransport.start();
|
|
192
|
+
expect(tempTransport.getClientsCount()).toBe(0);
|
|
193
|
+
await tempTransport.stop();
|
|
194
|
+
// Verify server is stopped by trying to connect
|
|
195
|
+
await expect(fetch(`http://${TEST_HOST}:${TEST_PORT + 1}/health`)).rejects.toThrow();
|
|
196
|
+
});
|
|
197
|
+
it('should handle stop when no clients connected', async () => {
|
|
198
|
+
const tempTransport = new SseTransport({
|
|
199
|
+
port: TEST_PORT + 2,
|
|
200
|
+
host: TEST_HOST,
|
|
201
|
+
});
|
|
202
|
+
await tempTransport.start();
|
|
203
|
+
await tempTransport.stop();
|
|
204
|
+
expect(tempTransport.getClientsCount()).toBe(0);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe('Configuration', () => {
|
|
208
|
+
it('should use default configuration', () => {
|
|
209
|
+
const defaultTransport = new SseTransport();
|
|
210
|
+
expect(defaultTransport).toBeDefined();
|
|
211
|
+
// Don't start it to avoid port conflicts
|
|
212
|
+
});
|
|
213
|
+
it('should use custom port and host', async () => {
|
|
214
|
+
const customTransport = new SseTransport({
|
|
215
|
+
port: TEST_PORT + 3,
|
|
216
|
+
host: '127.0.0.1',
|
|
217
|
+
heartbeatInterval: 10000,
|
|
218
|
+
});
|
|
219
|
+
await customTransport.start();
|
|
220
|
+
const response = await fetch(`http://127.0.0.1:${TEST_PORT + 3}/health`);
|
|
221
|
+
const data = await response.json();
|
|
222
|
+
expect(response.status).toBe(200);
|
|
223
|
+
expect(data.status).toBe('ok');
|
|
224
|
+
await customTransport.stop();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
describe('Health Check Details', () => {
|
|
228
|
+
it('should include client count in health check', async () => {
|
|
229
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/health`);
|
|
230
|
+
const data = await response.json();
|
|
231
|
+
expect(data).toHaveProperty('clients');
|
|
232
|
+
expect(typeof data.clients).toBe('number');
|
|
233
|
+
expect(data.clients).toBeGreaterThanOrEqual(0);
|
|
234
|
+
});
|
|
235
|
+
it('should include timestamp in health check', async () => {
|
|
236
|
+
const response = await fetch(`http://${TEST_HOST}:${TEST_PORT}/health`);
|
|
237
|
+
const data = await response.json();
|
|
238
|
+
expect(data).toHaveProperty('timestamp');
|
|
239
|
+
expect(new Date(data.timestamp).getTime()).toBeGreaterThan(0);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brave-real-browser-mcp-server",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.7",
|
|
4
4
|
"description": "Universal AI IDE MCP Server - Auto-detects and supports all AI IDEs (Claude Desktop, Cursor, Windsurf, Cline, Zed, VSCode, Qoder AI, etc.) with Brave browser automation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|