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 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
- - ✅ **3 Protocol Modes** - MCP (STDIO), LSP, HTTP/WebSocket
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
- | **Others (HTTP)** | 3 min | Start HTTP server → Configure endpoint |
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
- # For MCP Editors (Claude, Cursor, Windsurf)
53
- npx -y brave-real-browser-mcp-server@latest
54
+ # Auto-detect environment
55
+ npx brave-real-browser-mcp-server@latest
54
56
 
55
- # For HTTP-based Editors (Qoder AI, Custom)
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 - 5 Steps
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:** HTTP/WebSocket
642
+ **Protocol:** MCP (STDIO) | **Setup Time:** 3 minutes | **Auto-Start:** ✅ Yes
531
643
 
532
- **⚠️ Important:** Qoder AI requires HTTP server to be running separately before configuration!
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
- Open a terminal and run:
648
+ **Step 1: Open Qoder Settings**
539
649
 
540
650
  ```bash
541
- # Windows (PowerShell)
542
- npx brave-real-browser-mcp-server@latest --mode http --port 3000
651
+ # Method 1: Using keyboard shortcut
652
+ # Windows: Ctrl + Shift + ,
653
+ # Mac: ⌘ + Shift + ,
543
654
 
544
- # Mac/Linux
545
- npx brave-real-browser-mcp-server@latest --mode http --port 3000
655
+ # Method 2: Click user icon in upper-right corner
656
+ # Then select "Qoder Settings"
546
657
  ```
547
658
 
548
- **Expected Output:**
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
- **Step 2: Test Server is Running**
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
- Open another terminal and verify:
665
+ **Step 3: Install Package Globally (Important for Qoder AI)**
558
666
 
559
667
  ```bash
560
- # Health check
561
- curl http://localhost:3000/health
668
+ # Install globally for faster startup
669
+ npm install -g brave-real-browser-mcp-server@latest
562
670
 
563
- # Expected response:
564
- # {"status":"ok","timestamp":"..."}
671
+ # Verify installation
672
+ where brave-real-browser-mcp-server # Windows
673
+ which brave-real-browser-mcp-server # Mac/Linux
674
+ ```
565
675
 
566
- # List all tools
567
- curl http://localhost:3000/tools
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
- **Step 3: Configure Qoder AI**
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
- Open Qoder AI settings and add:
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
- "type": "http",
580
- "endpoint": "http://localhost:3000",
581
- "enabled": true,
582
- "timeout": 30000
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
- **Option B - Extensions Configuration:**
741
+ **For Mac:**
742
+
589
743
  ```json
590
744
  {
591
- "extensions": {
745
+ "mcpServers": {
592
746
  "brave-real-browser": {
593
- "type": "http",
594
- "enabled": true,
595
- "endpoint": "http://localhost:3000",
596
- "timeout": 30000
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 4: Restart Qoder AI**
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
- Close and reopen Qoder AI for changes to take effect.
764
+ **Step 6: Verify Installation**
605
765
 
606
- **Step 5: Verify Connection**
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
- Check Qoder AI's output/console for connection confirmation.
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
- **Background Server Setup (Optional):**
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
- # Install pm2 globally
620
- npm install -g pm2
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
- # Start server in background
623
- pm2 start npx --name "brave-browser-server" -- brave-real-browser-mcp-server@latest --mode http --port 3000
801
+ **Troubleshooting:**
624
802
 
625
- # View logs
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 | 3 Protocols | Universal Support 🌟**
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
- // Main tool call handler
115
- console.error("🔍 [DEBUG] Registering tool call handler...");
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
- --host, -h <host> HTTP server host (default: 0.0.0.0)
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
- # All protocols (HTTP only, MCP/LSP need separate instances)
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, TOOL_NAMES } from '../tool-definitions.js';
5
- // Import all handlers
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, 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';
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
- 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
- }
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.5",
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",