@ricardodeazambuja/browser-mcp-server 1.0.0
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/LICENSE +21 -0
- package/README.md +544 -0
- package/browser-mcp-server-playwright.js +684 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ricardo de Azambuja
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# Browser MCP Server
|
|
2
|
+
|
|
3
|
+
A universal browser automation MCP server using Playwright. Control Chrome programmatically through the Model Context Protocol.
|
|
4
|
+
|
|
5
|
+
**16 powerful browser automation tools** including navigation, clicking, typing, screenshots, console capture, and session recording.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- ✅ **Universal**: Works with Antigravity, Claude Desktop, and any MCP client
|
|
10
|
+
- ✅ **Hybrid Mode**: Connects to existing Chrome OR launches its own
|
|
11
|
+
- ✅ **Safe**: Isolated browser profile (won't touch your personal Chrome)
|
|
12
|
+
- ✅ **16 Tools**: Navigate, click, type, screenshot, console logs, and more
|
|
13
|
+
- ✅ **Console Capture**: Debug JavaScript errors in real-time
|
|
14
|
+
- ✅ **Session Recording**: Playwright traces with screenshots, DOM, and network activity
|
|
15
|
+
- ✅ **Portable**: One codebase works everywhere
|
|
16
|
+
|
|
17
|
+
## Quick Reference
|
|
18
|
+
|
|
19
|
+
| Installation Method | Best For | Setup Time |
|
|
20
|
+
|-------------------|----------|------------|
|
|
21
|
+
| **Clone Repository** | Development, contributing | 2 minutes |
|
|
22
|
+
| **Direct Download** | Quick testing, minimal setup | 1 minute |
|
|
23
|
+
| **NPM Package** (coming soon) | Production use, easy updates | 30 seconds |
|
|
24
|
+
|
|
25
|
+
| MCP Client | Config File Location |
|
|
26
|
+
|------------|---------------------|
|
|
27
|
+
| **Claude Desktop** | `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)<br>`%APPDATA%/Claude/claude_desktop_config.json` (Windows) |
|
|
28
|
+
| **Antigravity** | `~/.gemini/antigravity/mcp_config.json` |
|
|
29
|
+
| **Claude Code** | Use `claude mcp add` command |
|
|
30
|
+
| **Gemini CLI** | Use `gemini mcp add` command |
|
|
31
|
+
|
|
32
|
+
**Key Points:**
|
|
33
|
+
- ✅ Requires Node.js >= 16.0.0
|
|
34
|
+
- ✅ Must install Playwright separately
|
|
35
|
+
- ✅ Uses absolute paths in config files
|
|
36
|
+
- ✅ Isolated browser profile (won't touch personal Chrome)
|
|
37
|
+
- ✅ Restart MCP client after config changes
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
#### Method 1: Clone Repository (Recommended)
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Clone the repository
|
|
47
|
+
git clone https://github.com/ricardodeazambuja/browser-mcp-server.git
|
|
48
|
+
cd browser-mcp-server
|
|
49
|
+
|
|
50
|
+
# Install Playwright (one-time setup)
|
|
51
|
+
npm install playwright
|
|
52
|
+
npx playwright install chromium
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### Method 2: Direct Download (Single File)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Download the main file directly (no git required)
|
|
59
|
+
curl -o browser-mcp-server-playwright.js \
|
|
60
|
+
https://raw.githubusercontent.com/ricardodeazambuja/browser-mcp-server/master/browser-mcp-server-playwright.js
|
|
61
|
+
|
|
62
|
+
# Install Playwright
|
|
63
|
+
npm install playwright
|
|
64
|
+
npx playwright install chromium
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Method 3: NPM Package (Coming Soon)
|
|
68
|
+
|
|
69
|
+
Once published to npm:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Install globally
|
|
73
|
+
npm install -g @ricardodeazambuja/browser-mcp-server
|
|
74
|
+
|
|
75
|
+
# Or use directly with npx (no installation needed)
|
|
76
|
+
npx @ricardodeazambuja/browser-mcp-server
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Usage with Claude Desktop
|
|
80
|
+
|
|
81
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
82
|
+
|
|
83
|
+
**Using local installation:**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"browser-tools": {
|
|
88
|
+
"command": "node",
|
|
89
|
+
"args": ["/absolute/path/to/browser-mcp-server-playwright.js"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Using NPM (when published):**
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"browser-tools": {
|
|
100
|
+
"command": "npx",
|
|
101
|
+
"args": ["-y", "@ricardodeazambuja/browser-mcp-server"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Note:** Replace `/absolute/path/to/` with the actual path where you installed the file.
|
|
108
|
+
|
|
109
|
+
### Usage with Antigravity
|
|
110
|
+
|
|
111
|
+
Add to `~/.gemini/antigravity/mcp_config.json`:
|
|
112
|
+
|
|
113
|
+
**Using local installation:**
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"mcpServers": {
|
|
117
|
+
"browser-tools": {
|
|
118
|
+
"command": "node",
|
|
119
|
+
"args": ["/home/username/.gemini/antigravity/browser-mcp-server-playwright.js"]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**Using NPM (when published):**
|
|
126
|
+
```json
|
|
127
|
+
{
|
|
128
|
+
"mcpServers": {
|
|
129
|
+
"browser-tools": {
|
|
130
|
+
"command": "npx",
|
|
131
|
+
"args": ["-y", "@ricardodeazambuja/browser-mcp-server"]
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Then refresh MCP servers in Antigravity.
|
|
138
|
+
|
|
139
|
+
### Usage with Claude Code
|
|
140
|
+
|
|
141
|
+
Add the browser-mcp-server using the Claude CLI:
|
|
142
|
+
|
|
143
|
+
**Using local installation:**
|
|
144
|
+
```bash
|
|
145
|
+
# Install the MCP server with default isolated profile
|
|
146
|
+
claude mcp add --transport stdio browser \
|
|
147
|
+
-- node /absolute/path/to/browser-mcp-server-playwright.js
|
|
148
|
+
|
|
149
|
+
# Or with custom browser profile for more control
|
|
150
|
+
claude mcp add --transport stdio browser \
|
|
151
|
+
--env MCP_BROWSER_PROFILE=/path/to/custom/profile \
|
|
152
|
+
-- node /absolute/path/to/browser-mcp-server-playwright.js
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
**Using NPM (when published):**
|
|
156
|
+
```bash
|
|
157
|
+
# Install using npx (no local installation needed)
|
|
158
|
+
claude mcp add --transport stdio browser \
|
|
159
|
+
-- npx -y @ricardodeazambuja/browser-mcp-server
|
|
160
|
+
|
|
161
|
+
# With custom browser profile
|
|
162
|
+
claude mcp add --transport stdio browser \
|
|
163
|
+
--env MCP_BROWSER_PROFILE=/path/to/custom/profile \
|
|
164
|
+
-- npx -y @ricardodeazambuja/browser-mcp-server
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Verify installation:**
|
|
168
|
+
```bash
|
|
169
|
+
# List all MCP servers
|
|
170
|
+
claude mcp list
|
|
171
|
+
|
|
172
|
+
# Check server status
|
|
173
|
+
claude mcp get browser
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**Example usage in Claude Code:**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Natural language commands
|
|
180
|
+
> Navigate to https://example.com and take a screenshot
|
|
181
|
+
> Click the login button and fill in the username field
|
|
182
|
+
> What's the text in the .main-content selector?
|
|
183
|
+
|
|
184
|
+
# Direct tool invocation via slash commands
|
|
185
|
+
> /mcp__browser__browser_navigate https://example.com
|
|
186
|
+
> /mcp__browser__browser_screenshot
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**Note:** The server uses an isolated browser profile at `/tmp/chrome-mcp-profile` by default, ensuring it won't access your personal Chrome cookies or data.
|
|
190
|
+
|
|
191
|
+
### Usage with Gemini CLI
|
|
192
|
+
|
|
193
|
+
Add the browser-mcp-server using the Gemini CLI commands:
|
|
194
|
+
|
|
195
|
+
**Using local installation:**
|
|
196
|
+
```bash
|
|
197
|
+
# Install the MCP server with default isolated profile
|
|
198
|
+
gemini mcp add browser node /absolute/path/to/browser-mcp-server-playwright.js
|
|
199
|
+
|
|
200
|
+
# Or with custom browser profile
|
|
201
|
+
gemini mcp add -e MCP_BROWSER_PROFILE=/path/to/custom/profile browser \
|
|
202
|
+
node /absolute/path/to/browser-mcp-server-playwright.js
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Using NPM (when published):**
|
|
206
|
+
```bash
|
|
207
|
+
# Install using npx (no local installation needed)
|
|
208
|
+
gemini mcp add browser npx -y @ricardodeazambuja/browser-mcp-server
|
|
209
|
+
|
|
210
|
+
# With custom browser profile
|
|
211
|
+
gemini mcp add -e MCP_BROWSER_PROFILE=/path/to/custom/profile browser \
|
|
212
|
+
npx -y @ricardodeazambuja/browser-mcp-server
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
**Management commands:**
|
|
216
|
+
```bash
|
|
217
|
+
# List all configured MCP servers
|
|
218
|
+
gemini mcp list
|
|
219
|
+
|
|
220
|
+
# Remove the server if needed
|
|
221
|
+
gemini mcp remove browser
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Example usage in Gemini CLI:**
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# Natural language commands
|
|
228
|
+
> Navigate to https://github.com and take a screenshot
|
|
229
|
+
> Click the search button and type "MCP servers"
|
|
230
|
+
> Get the text from the .repository-content selector
|
|
231
|
+
|
|
232
|
+
# The CLI will use the browser automation tools automatically
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
**Advanced options:**
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
# Add with specific scope (user vs project)
|
|
239
|
+
gemini mcp add -s user browser node /path/to/browser-mcp-server-playwright.js
|
|
240
|
+
|
|
241
|
+
# Add with timeout configuration
|
|
242
|
+
gemini mcp add --timeout 30000 browser node /path/to/browser-mcp-server-playwright.js
|
|
243
|
+
|
|
244
|
+
# Skip tool confirmation prompts (use with caution)
|
|
245
|
+
gemini mcp add --trust browser node /path/to/browser-mcp-server-playwright.js
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Available Tools (16)
|
|
249
|
+
|
|
250
|
+
### Navigation & Interaction
|
|
251
|
+
1. **browser_navigate(url)** - Navigate to a URL
|
|
252
|
+
2. **browser_click(selector)** - Click an element
|
|
253
|
+
3. **browser_type(selector, text)** - Type text into an input
|
|
254
|
+
4. **browser_scroll(x?, y?)** - Scroll the page
|
|
255
|
+
|
|
256
|
+
### Information Gathering
|
|
257
|
+
5. **browser_screenshot(fullPage?)** - Capture screenshot
|
|
258
|
+
6. **browser_get_text(selector)** - Get text from element
|
|
259
|
+
7. **browser_get_dom(selector?)** - Get DOM structure
|
|
260
|
+
8. **browser_evaluate(code)** - Execute JavaScript
|
|
261
|
+
|
|
262
|
+
### Console Debugging ⭐ NEW
|
|
263
|
+
9. **browser_console_start(level?)** - Start capturing console logs
|
|
264
|
+
10. **browser_console_get(filter?)** - Get captured logs
|
|
265
|
+
11. **browser_console_clear()** - Clear logs and stop
|
|
266
|
+
|
|
267
|
+
### Advanced
|
|
268
|
+
12. **browser_wait_for_selector(selector, timeout?)** - Wait for element
|
|
269
|
+
13. **browser_resize_window(width, height)** - Resize browser window
|
|
270
|
+
14. **browser_start_video_recording(path?)** - Start recording session (Playwright traces)
|
|
271
|
+
15. **browser_stop_video_recording()** - Stop and save recording
|
|
272
|
+
16. **browser_health_check()** - Verify browser connection
|
|
273
|
+
|
|
274
|
+
## Examples
|
|
275
|
+
|
|
276
|
+
### Navigate and Screenshot
|
|
277
|
+
```javascript
|
|
278
|
+
// Agent uses:
|
|
279
|
+
browser_navigate("https://example.com")
|
|
280
|
+
browser_screenshot(fullPage: true)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Debug JavaScript Errors
|
|
284
|
+
```javascript
|
|
285
|
+
// Agent uses:
|
|
286
|
+
browser_console_start()
|
|
287
|
+
browser_navigate("https://myapp.com")
|
|
288
|
+
browser_click("#submit-button")
|
|
289
|
+
browser_console_get(filter: "error")
|
|
290
|
+
// Shows: ❌ [ERROR] Uncaught TypeError: ...
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Automate Form Submission
|
|
294
|
+
```javascript
|
|
295
|
+
// Agent uses:
|
|
296
|
+
browser_navigate("https://example.com/login")
|
|
297
|
+
browser_type("#username", "user@example.com")
|
|
298
|
+
browser_type("#password", "secret")
|
|
299
|
+
browser_click("#login-button")
|
|
300
|
+
browser_wait_for_selector(".dashboard")
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## How It Works
|
|
304
|
+
|
|
305
|
+
### Hybrid Mode (Automatic)
|
|
306
|
+
|
|
307
|
+
The server automatically detects your environment:
|
|
308
|
+
|
|
309
|
+
**Antigravity Mode:**
|
|
310
|
+
- Detects Chrome on port 9222
|
|
311
|
+
- Connects to existing browser
|
|
312
|
+
- Uses Antigravity's browser profile
|
|
313
|
+
- No new browser window
|
|
314
|
+
|
|
315
|
+
**Standalone Mode:**
|
|
316
|
+
- No Chrome detected on port 9222
|
|
317
|
+
- Launches new Chrome instance
|
|
318
|
+
- Uses isolated profile (`/tmp/chrome-mcp-profile`)
|
|
319
|
+
- New browser window appears
|
|
320
|
+
|
|
321
|
+
### Safety Features
|
|
322
|
+
|
|
323
|
+
- **Isolated Profile**: Uses `/tmp/chrome-mcp-profile` (not your personal Chrome!)
|
|
324
|
+
- **No Setup Dialogs**: Silent startup with `--no-first-run` flags
|
|
325
|
+
- **Clean Environment**: No extensions, sync, or background updates
|
|
326
|
+
- **Reproducible**: Same behavior across systems
|
|
327
|
+
|
|
328
|
+
## Security
|
|
329
|
+
|
|
330
|
+
This MCP server provides powerful browser automation capabilities. Please review these security considerations:
|
|
331
|
+
|
|
332
|
+
### Isolated Browser Profile
|
|
333
|
+
- Uses `/tmp/chrome-mcp-profile` by default (configurable via `MCP_BROWSER_PROFILE`)
|
|
334
|
+
- **Does NOT access your personal Chrome data** (cookies, passwords, history)
|
|
335
|
+
- Each instance runs in a clean, isolated environment
|
|
336
|
+
|
|
337
|
+
### Tool Safety
|
|
338
|
+
|
|
339
|
+
**browser_evaluate**: Executes arbitrary JavaScript in the browser context
|
|
340
|
+
- Code runs in browser sandbox (no access to your host system)
|
|
341
|
+
- Only executes when explicitly called by MCP client
|
|
342
|
+
- Requires user approval in most MCP clients
|
|
343
|
+
- **Recommendation**: Only use with trusted MCP clients and review code when possible
|
|
344
|
+
|
|
345
|
+
**browser_navigate**: Navigates to any URL
|
|
346
|
+
- Can visit any website the browser can access
|
|
347
|
+
- Uses isolated profile to prevent cookie/session theft
|
|
348
|
+
- **Recommendation**: Be cautious with URLs from untrusted sources
|
|
349
|
+
|
|
350
|
+
### Debug Logs
|
|
351
|
+
- Server logs to `/tmp/mcp-browser-server.log`
|
|
352
|
+
- Logs may contain visited URLs and error messages
|
|
353
|
+
- Log file is cleared on system reboot (stored in `/tmp`)
|
|
354
|
+
- **Does NOT log** page content or sensitive data
|
|
355
|
+
|
|
356
|
+
### Best Practices
|
|
357
|
+
- ✅ Only use with trusted MCP clients (Claude Desktop, Antigravity, etc.)
|
|
358
|
+
- ✅ Review automation scripts before execution when possible
|
|
359
|
+
- ✅ Use the default isolated profile (don't point to your personal Chrome)
|
|
360
|
+
- ✅ Report security issues via [GitHub Issues](https://github.com/ricardodeazambuja/browser-mcp-server/issues)
|
|
361
|
+
|
|
362
|
+
## Configuration
|
|
363
|
+
|
|
364
|
+
### Environment Variables
|
|
365
|
+
|
|
366
|
+
```bash
|
|
367
|
+
# Custom browser profile location (optional)
|
|
368
|
+
export MCP_BROWSER_PROFILE="$HOME/.mcp-browser-profile"
|
|
369
|
+
|
|
370
|
+
# Then run the server
|
|
371
|
+
node browser-mcp-server-playwright.js
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### MCP Config with Environment Variables
|
|
375
|
+
|
|
376
|
+
```json
|
|
377
|
+
{
|
|
378
|
+
"mcpServers": {
|
|
379
|
+
"browser-tools": {
|
|
380
|
+
"command": "node",
|
|
381
|
+
"args": ["/path/to/browser-mcp-server-playwright.js"],
|
|
382
|
+
"env": {
|
|
383
|
+
"MCP_BROWSER_PROFILE": "/tmp/my-custom-profile"
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Troubleshooting
|
|
391
|
+
|
|
392
|
+
### "Playwright is not installed"
|
|
393
|
+
|
|
394
|
+
```bash
|
|
395
|
+
npm install playwright
|
|
396
|
+
npx playwright install chromium
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### "Cannot connect to Chrome"
|
|
400
|
+
|
|
401
|
+
**For Antigravity:**
|
|
402
|
+
- Click the Chrome logo (top right) to launch browser
|
|
403
|
+
|
|
404
|
+
**For Standalone:**
|
|
405
|
+
- The server will auto-launch Chrome
|
|
406
|
+
- Ensure Playwright is installed (see above)
|
|
407
|
+
|
|
408
|
+
### Check Server Status
|
|
409
|
+
|
|
410
|
+
Use the `browser_health_check` tool to verify:
|
|
411
|
+
- Connection mode (Antigravity vs Standalone)
|
|
412
|
+
- Playwright source
|
|
413
|
+
- Browser profile location
|
|
414
|
+
- Current page URL
|
|
415
|
+
|
|
416
|
+
## Development
|
|
417
|
+
|
|
418
|
+
### Project Structure
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
browser-mcp-server/
|
|
422
|
+
├── browser-mcp-server-playwright.js # Main server
|
|
423
|
+
├── package.json # npm package config
|
|
424
|
+
├── README.md # This file
|
|
425
|
+
└── LICENSE # MIT license
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### Testing
|
|
429
|
+
|
|
430
|
+
```bash
|
|
431
|
+
# Test server initialization
|
|
432
|
+
npm test
|
|
433
|
+
|
|
434
|
+
# Manual test
|
|
435
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | node browser-mcp-server-playwright.js
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
### Debug Logging
|
|
439
|
+
|
|
440
|
+
Check `/tmp/mcp-browser-server.log` for detailed logs:
|
|
441
|
+
- Playwright loading attempts
|
|
442
|
+
- Browser connection/launch status
|
|
443
|
+
- Console capture events
|
|
444
|
+
- Tool execution
|
|
445
|
+
|
|
446
|
+
## Technical Details
|
|
447
|
+
|
|
448
|
+
### MCP Protocol
|
|
449
|
+
- Implements MCP 2024-11-05 protocol
|
|
450
|
+
- JSON-RPC 2.0 over stdio
|
|
451
|
+
- Supports `initialize`, `notifications/initialized`, `tools/list`, `tools/call`
|
|
452
|
+
|
|
453
|
+
### Browser Control
|
|
454
|
+
- Uses Playwright for automation
|
|
455
|
+
- Connects via Chrome DevTools Protocol (CDP)
|
|
456
|
+
- Port 9222 for remote debugging
|
|
457
|
+
|
|
458
|
+
### Chrome Launch Flags
|
|
459
|
+
```bash
|
|
460
|
+
--remote-debugging-port=9222 # Enable CDP
|
|
461
|
+
--user-data-dir=/tmp/chrome-mcp-profile # Isolated profile
|
|
462
|
+
--no-first-run # Skip setup
|
|
463
|
+
--no-default-browser-check # No popups
|
|
464
|
+
--disable-fre # No first-run experience
|
|
465
|
+
--disable-sync # No Google sync
|
|
466
|
+
--disable-component-update # No auto-updates
|
|
467
|
+
# + more stability flags
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
## Compatibility
|
|
471
|
+
|
|
472
|
+
### Tested With
|
|
473
|
+
- ✅ Antigravity
|
|
474
|
+
- ✅ Claude Desktop (macOS, Windows, Linux)
|
|
475
|
+
- ✅ Other MCP clients via stdio
|
|
476
|
+
|
|
477
|
+
### Requirements
|
|
478
|
+
- Node.js >= 16.0.0
|
|
479
|
+
- Playwright (peer dependency)
|
|
480
|
+
- Chrome/Chromium browser
|
|
481
|
+
|
|
482
|
+
### Platforms
|
|
483
|
+
- ✅ Linux
|
|
484
|
+
- ✅ macOS
|
|
485
|
+
- ✅ Windows
|
|
486
|
+
|
|
487
|
+
## Comparison with Other Tools
|
|
488
|
+
|
|
489
|
+
### vs. Puppeteer MCP Servers
|
|
490
|
+
- ✅ More tools (16 vs typical 8-10)
|
|
491
|
+
- ✅ Console capture built-in
|
|
492
|
+
- ✅ Better error messages
|
|
493
|
+
- ✅ Hybrid mode (connect OR launch)
|
|
494
|
+
|
|
495
|
+
### vs. Selenium Grid
|
|
496
|
+
- ✅ Simpler setup (no grid needed)
|
|
497
|
+
- ✅ MCP protocol integration
|
|
498
|
+
- ✅ Built for AI agents
|
|
499
|
+
- ✅ Lightweight (single process)
|
|
500
|
+
|
|
501
|
+
### vs. Browser Extensions
|
|
502
|
+
- ✅ Works headlessly if needed
|
|
503
|
+
- ✅ No extension installation
|
|
504
|
+
- ✅ Programmable via MCP
|
|
505
|
+
- ✅ Works with any MCP client
|
|
506
|
+
|
|
507
|
+
## Contributing
|
|
508
|
+
|
|
509
|
+
Contributions welcome! Please:
|
|
510
|
+
1. Fork the repository
|
|
511
|
+
2. Create a feature branch
|
|
512
|
+
3. Make your changes
|
|
513
|
+
4. Submit a pull request
|
|
514
|
+
|
|
515
|
+
## License
|
|
516
|
+
|
|
517
|
+
MIT License - see LICENSE file
|
|
518
|
+
|
|
519
|
+
## Credits
|
|
520
|
+
|
|
521
|
+
- Built with [Playwright](https://playwright.dev/)
|
|
522
|
+
- Implements [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
523
|
+
- Originally developed for [Antigravity](https://antigravity.google/)
|
|
524
|
+
|
|
525
|
+
## Support
|
|
526
|
+
|
|
527
|
+
- 🐛 [Report Issues](https://github.com/ricardodeazambuja/browser-mcp-server/issues)
|
|
528
|
+
- 💬 [Discussions](https://github.com/ricardodeazambuja/browser-mcp-server/discussions)
|
|
529
|
+
- 📧 Contact: Via GitHub Issues
|
|
530
|
+
|
|
531
|
+
## Changelog
|
|
532
|
+
|
|
533
|
+
### v1.0.0 (2025-12-26)
|
|
534
|
+
- ✅ Initial release
|
|
535
|
+
- ✅ 16 browser automation tools
|
|
536
|
+
- ✅ Console capture (start/get/clear)
|
|
537
|
+
- ✅ Hybrid mode (connect OR launch)
|
|
538
|
+
- ✅ Safe Chrome launch with isolated profile
|
|
539
|
+
- ✅ Multi-source Playwright loading
|
|
540
|
+
- ✅ Universal compatibility (Antigravity + Claude Desktop + more)
|
|
541
|
+
|
|
542
|
+
---
|
|
543
|
+
|
|
544
|
+
**Made with ❤️ for the MCP community**
|
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Browser Automation MCP Server for Antigravity (Playwright Edition)
|
|
5
|
+
*
|
|
6
|
+
* This MCP server provides 13 browser automation tools by connecting to
|
|
7
|
+
* Antigravity's Chrome instance that runs with remote debugging on port 9222.
|
|
8
|
+
*
|
|
9
|
+
* Antigravity launches Chrome with:
|
|
10
|
+
* --remote-debugging-port=9222
|
|
11
|
+
* --user-data-dir=~/.gemini/antigravity-browser-profile
|
|
12
|
+
*
|
|
13
|
+
* This server uses the same Playwright installation as browser_subagent,
|
|
14
|
+
* allowing seamless integration with Antigravity's browser ecosystem.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const logFile = `${os.tmpdir()}/mcp-browser-server.log`;
|
|
20
|
+
|
|
21
|
+
// Helper to log debug info
|
|
22
|
+
function debugLog(msg) {
|
|
23
|
+
const timestamp = new Date().toISOString();
|
|
24
|
+
fs.appendFileSync(logFile, `${timestamp} - ${msg}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
debugLog('Server starting...');
|
|
28
|
+
debugLog(`HOME: ${process.env.HOME}`);
|
|
29
|
+
debugLog(`CWD: ${process.cwd()}`);
|
|
30
|
+
|
|
31
|
+
let playwright = null;
|
|
32
|
+
let playwrightError = null;
|
|
33
|
+
let playwrightPath = null;
|
|
34
|
+
|
|
35
|
+
// Try to load Playwright from multiple sources
|
|
36
|
+
function loadPlaywright() {
|
|
37
|
+
if (playwright) return playwright;
|
|
38
|
+
if (playwrightError) throw playwrightError;
|
|
39
|
+
|
|
40
|
+
const sources = [
|
|
41
|
+
// 1. Antigravity's Go-based Playwright
|
|
42
|
+
{ path: `${process.env.HOME}/.cache/ms-playwright-go/1.50.1/package`, name: 'Antigravity Go Playwright' },
|
|
43
|
+
// 2. Standard npm Playwright (local)
|
|
44
|
+
{ path: 'playwright', name: 'npm Playwright (local)' },
|
|
45
|
+
// 3. Global npm Playwright
|
|
46
|
+
{ path: `${process.env.HOME}/.npm-global/lib/node_modules/playwright`, name: 'npm Playwright (global)' }
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
for (const source of sources) {
|
|
50
|
+
try {
|
|
51
|
+
debugLog(`Trying to load Playwright from: ${source.path}`);
|
|
52
|
+
playwright = require(source.path);
|
|
53
|
+
playwrightPath = source.path;
|
|
54
|
+
debugLog(`✅ Playwright loaded successfully: ${source.name}`);
|
|
55
|
+
return playwright;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
debugLog(`❌ Could not load from ${source.path}: ${error.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// None worked
|
|
62
|
+
playwrightError = new Error(
|
|
63
|
+
'❌ Playwright is not installed.\n\n' +
|
|
64
|
+
'To install Playwright:\n' +
|
|
65
|
+
'1. In Antigravity: Click the Chrome logo (top right) to "Open Browser" - this installs Playwright automatically\n' +
|
|
66
|
+
'2. Standalone mode: Run:\n' +
|
|
67
|
+
' npm install playwright\n' +
|
|
68
|
+
' npx playwright install chromium\n\n' +
|
|
69
|
+
`Tried locations:\n${sources.map(s => ` - ${s.path}`).join('\n')}`
|
|
70
|
+
);
|
|
71
|
+
throw playwrightError;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const readline = require('readline');
|
|
75
|
+
|
|
76
|
+
const rl = readline.createInterface({
|
|
77
|
+
input: process.stdin,
|
|
78
|
+
output: process.stdout,
|
|
79
|
+
terminal: false
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
let browser = null;
|
|
83
|
+
let context = null;
|
|
84
|
+
let page = null;
|
|
85
|
+
|
|
86
|
+
// Console log capture
|
|
87
|
+
let consoleLogs = [];
|
|
88
|
+
let consoleListening = false;
|
|
89
|
+
|
|
90
|
+
// Connect to existing Chrome OR launch new instance (hybrid mode)
|
|
91
|
+
async function connectToBrowser() {
|
|
92
|
+
if (!browser) {
|
|
93
|
+
try {
|
|
94
|
+
// Load Playwright (will throw if not installed)
|
|
95
|
+
const pw = loadPlaywright();
|
|
96
|
+
|
|
97
|
+
// STRATEGY 1: Try to connect to existing Chrome (Antigravity mode)
|
|
98
|
+
try {
|
|
99
|
+
debugLog('Attempting to connect to Chrome on port 9222...');
|
|
100
|
+
browser = await pw.chromium.connectOverCDP('http://localhost:9222');
|
|
101
|
+
debugLog('✅ Connected to existing Chrome (Antigravity mode)');
|
|
102
|
+
|
|
103
|
+
const contexts = browser.contexts();
|
|
104
|
+
context = contexts.length > 0 ? contexts[0] : await browser.newContext();
|
|
105
|
+
const pages = context.pages();
|
|
106
|
+
page = pages.length > 0 ? pages[0] : await context.newPage();
|
|
107
|
+
|
|
108
|
+
debugLog('Successfully connected to Chrome');
|
|
109
|
+
return { browser, context, page };
|
|
110
|
+
} catch (connectError) {
|
|
111
|
+
debugLog(`Could not connect to existing Chrome: ${connectError.message}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// STRATEGY 2: Launch our own Chrome (Standalone mode)
|
|
115
|
+
debugLog('No existing Chrome found. Launching new instance...');
|
|
116
|
+
|
|
117
|
+
const profileDir = process.env.MCP_BROWSER_PROFILE ||
|
|
118
|
+
`${os.tmpdir()}/chrome-mcp-profile`;
|
|
119
|
+
|
|
120
|
+
debugLog(`Browser profile: ${profileDir}`);
|
|
121
|
+
|
|
122
|
+
browser = await pw.chromium.launch({
|
|
123
|
+
headless: false,
|
|
124
|
+
args: [
|
|
125
|
+
// CRITICAL: Remote debugging
|
|
126
|
+
'--remote-debugging-port=9222',
|
|
127
|
+
|
|
128
|
+
// CRITICAL: Isolated profile (don't touch user's personal Chrome!)
|
|
129
|
+
`--user-data-dir=${profileDir}`,
|
|
130
|
+
|
|
131
|
+
// IMPORTANT: Skip first-run experience
|
|
132
|
+
'--no-first-run',
|
|
133
|
+
'--no-default-browser-check',
|
|
134
|
+
'--disable-fre',
|
|
135
|
+
|
|
136
|
+
// STABILITY: Reduce popups and background activity
|
|
137
|
+
'--disable-features=TranslateUI,OptGuideOnDeviceModel',
|
|
138
|
+
'--disable-sync',
|
|
139
|
+
'--disable-component-update',
|
|
140
|
+
'--disable-background-networking',
|
|
141
|
+
'--disable-breakpad',
|
|
142
|
+
'--disable-background-timer-throttling',
|
|
143
|
+
'--disable-backgrounding-occluded-windows',
|
|
144
|
+
'--disable-renderer-backgrounding'
|
|
145
|
+
]
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
context = await browser.newContext();
|
|
149
|
+
page = await context.newPage();
|
|
150
|
+
|
|
151
|
+
debugLog('✅ Successfully launched new Chrome instance (Standalone mode)');
|
|
152
|
+
|
|
153
|
+
} catch (error) {
|
|
154
|
+
debugLog(`Failed to connect/launch Chrome: ${error.message}`);
|
|
155
|
+
const errorMsg =
|
|
156
|
+
'❌ Cannot start browser.\n\n' +
|
|
157
|
+
'To fix this:\n' +
|
|
158
|
+
'1. In Antigravity: Click the Chrome logo (top right) to "Open Browser"\n' +
|
|
159
|
+
'2. Standalone mode: Ensure Playwright is installed:\n' +
|
|
160
|
+
' npm install playwright\n' +
|
|
161
|
+
' npx playwright install chromium\n\n' +
|
|
162
|
+
`Error: ${error.message}`;
|
|
163
|
+
throw new Error(errorMsg);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return { browser, context, page };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// MCP Tool definitions
|
|
170
|
+
const tools = [
|
|
171
|
+
{
|
|
172
|
+
name: 'browser_navigate',
|
|
173
|
+
description: 'Navigate to a URL in the browser',
|
|
174
|
+
inputSchema: {
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
url: { type: 'string', description: 'The URL to navigate to' }
|
|
178
|
+
},
|
|
179
|
+
required: ['url'],
|
|
180
|
+
additionalProperties: false,
|
|
181
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'browser_click',
|
|
186
|
+
description: 'Click an element on the page using Playwright selector',
|
|
187
|
+
inputSchema: {
|
|
188
|
+
type: 'object',
|
|
189
|
+
properties: {
|
|
190
|
+
selector: { type: 'string', description: 'Playwright selector for the element' }
|
|
191
|
+
},
|
|
192
|
+
required: ['selector'],
|
|
193
|
+
additionalProperties: false,
|
|
194
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: 'browser_screenshot',
|
|
199
|
+
description: 'Take a screenshot of the current page',
|
|
200
|
+
inputSchema: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
properties: {
|
|
203
|
+
fullPage: { type: 'boolean', description: 'Capture full page', default: false }
|
|
204
|
+
},
|
|
205
|
+
additionalProperties: false,
|
|
206
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
name: 'browser_get_text',
|
|
211
|
+
description: 'Get text content from an element',
|
|
212
|
+
inputSchema: {
|
|
213
|
+
type: 'object',
|
|
214
|
+
properties: {
|
|
215
|
+
selector: { type: 'string', description: 'Playwright selector for the element' }
|
|
216
|
+
},
|
|
217
|
+
required: ['selector'],
|
|
218
|
+
additionalProperties: false,
|
|
219
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'browser_type',
|
|
224
|
+
description: 'Type text into an input field',
|
|
225
|
+
inputSchema: {
|
|
226
|
+
type: 'object',
|
|
227
|
+
properties: {
|
|
228
|
+
selector: { type: 'string', description: 'Playwright selector for the input' },
|
|
229
|
+
text: { type: 'string', description: 'Text to type' }
|
|
230
|
+
},
|
|
231
|
+
required: ['selector', 'text'],
|
|
232
|
+
additionalProperties: false,
|
|
233
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'browser_evaluate',
|
|
238
|
+
description: 'Execute JavaScript in the browser context',
|
|
239
|
+
inputSchema: {
|
|
240
|
+
type: 'object',
|
|
241
|
+
properties: {
|
|
242
|
+
code: { type: 'string', description: 'JavaScript code to execute' }
|
|
243
|
+
},
|
|
244
|
+
required: ['code'],
|
|
245
|
+
additionalProperties: false,
|
|
246
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
name: 'browser_wait_for_selector',
|
|
251
|
+
description: 'Wait for an element to appear on the page',
|
|
252
|
+
inputSchema: {
|
|
253
|
+
type: 'object',
|
|
254
|
+
properties: {
|
|
255
|
+
selector: { type: 'string', description: 'Playwright selector to wait for' },
|
|
256
|
+
timeout: { type: 'number', description: 'Timeout in milliseconds', default: 30000 }
|
|
257
|
+
},
|
|
258
|
+
required: ['selector'],
|
|
259
|
+
additionalProperties: false,
|
|
260
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
name: 'browser_scroll',
|
|
265
|
+
description: 'Scroll the page',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
type: 'object',
|
|
268
|
+
properties: {
|
|
269
|
+
x: { type: 'number', description: 'Horizontal scroll position' },
|
|
270
|
+
y: { type: 'number', description: 'Vertical scroll position' }
|
|
271
|
+
},
|
|
272
|
+
additionalProperties: false,
|
|
273
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
name: 'browser_resize_window',
|
|
278
|
+
description: 'Resize the browser window (useful for testing responsiveness)',
|
|
279
|
+
inputSchema: {
|
|
280
|
+
type: 'object',
|
|
281
|
+
properties: {
|
|
282
|
+
width: { type: 'number', description: 'Window width in pixels' },
|
|
283
|
+
height: { type: 'number', description: 'Window height in pixels' }
|
|
284
|
+
},
|
|
285
|
+
required: ['width', 'height'],
|
|
286
|
+
additionalProperties: false,
|
|
287
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
name: 'browser_get_dom',
|
|
292
|
+
description: 'Get the full DOM structure or specific element data',
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: 'object',
|
|
295
|
+
properties: {
|
|
296
|
+
selector: { type: 'string', description: 'Optional selector to get DOM of specific element' }
|
|
297
|
+
},
|
|
298
|
+
additionalProperties: false,
|
|
299
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
name: 'browser_start_video_recording',
|
|
304
|
+
description: 'Start recording browser session as video',
|
|
305
|
+
inputSchema: {
|
|
306
|
+
type: 'object',
|
|
307
|
+
properties: {
|
|
308
|
+
path: { type: 'string', description: 'Path to save the video file' }
|
|
309
|
+
},
|
|
310
|
+
additionalProperties: false,
|
|
311
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
312
|
+
}
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
name: 'browser_stop_video_recording',
|
|
316
|
+
description: 'Stop video recording and save the file',
|
|
317
|
+
inputSchema: {
|
|
318
|
+
type: 'object',
|
|
319
|
+
properties: {},
|
|
320
|
+
additionalProperties: false,
|
|
321
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: 'browser_health_check',
|
|
326
|
+
description: 'Check if the browser is running and accessible on port 9222',
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: 'object',
|
|
329
|
+
properties: {},
|
|
330
|
+
additionalProperties: false,
|
|
331
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'browser_console_start',
|
|
336
|
+
description: 'Start capturing browser console logs (console.log, console.error, console.warn, etc.)',
|
|
337
|
+
inputSchema: {
|
|
338
|
+
type: 'object',
|
|
339
|
+
properties: {
|
|
340
|
+
level: {
|
|
341
|
+
type: 'string',
|
|
342
|
+
description: 'Optional filter for log level: "log", "error", "warn", "info", "debug", or "all"',
|
|
343
|
+
enum: ['log', 'error', 'warn', 'info', 'debug', 'all']
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
additionalProperties: false,
|
|
347
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: 'browser_console_get',
|
|
352
|
+
description: 'Get all captured console logs since browser_console_start was called',
|
|
353
|
+
inputSchema: {
|
|
354
|
+
type: 'object',
|
|
355
|
+
properties: {
|
|
356
|
+
filter: {
|
|
357
|
+
type: 'string',
|
|
358
|
+
description: 'Optional filter by log level: "log", "error", "warn", "info", "debug", or "all"',
|
|
359
|
+
enum: ['log', 'error', 'warn', 'info', 'debug', 'all']
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
additionalProperties: false,
|
|
363
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: 'browser_console_clear',
|
|
368
|
+
description: 'Clear all captured console logs and stop listening',
|
|
369
|
+
inputSchema: {
|
|
370
|
+
type: 'object',
|
|
371
|
+
properties: {},
|
|
372
|
+
additionalProperties: false,
|
|
373
|
+
$schema: 'http://json-schema.org/draft-07/schema#'
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
];
|
|
377
|
+
|
|
378
|
+
// Tool execution handlers
|
|
379
|
+
async function executeTool(name, args) {
|
|
380
|
+
try {
|
|
381
|
+
const { page } = await connectToBrowser();
|
|
382
|
+
|
|
383
|
+
switch (name) {
|
|
384
|
+
case 'browser_navigate':
|
|
385
|
+
await page.goto(args.url, { waitUntil: 'domcontentloaded' });
|
|
386
|
+
return { content: [{ type: 'text', text: `Navigated to ${args.url}` }] };
|
|
387
|
+
|
|
388
|
+
case 'browser_click':
|
|
389
|
+
await page.click(args.selector);
|
|
390
|
+
return { content: [{ type: 'text', text: `Clicked ${args.selector}` }] };
|
|
391
|
+
|
|
392
|
+
case 'browser_screenshot':
|
|
393
|
+
const screenshot = await page.screenshot({
|
|
394
|
+
fullPage: args.fullPage || false,
|
|
395
|
+
type: 'png'
|
|
396
|
+
});
|
|
397
|
+
return {
|
|
398
|
+
content: [{
|
|
399
|
+
type: 'image',
|
|
400
|
+
data: screenshot.toString('base64'),
|
|
401
|
+
mimeType: 'image/png'
|
|
402
|
+
}]
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
case 'browser_get_text':
|
|
406
|
+
const text = await page.textContent(args.selector);
|
|
407
|
+
return { content: [{ type: 'text', text }] };
|
|
408
|
+
|
|
409
|
+
case 'browser_type':
|
|
410
|
+
await page.fill(args.selector, args.text);
|
|
411
|
+
return { content: [{ type: 'text', text: `Typed into ${args.selector}` }] };
|
|
412
|
+
|
|
413
|
+
case 'browser_evaluate':
|
|
414
|
+
const result = await page.evaluate(args.code);
|
|
415
|
+
return {
|
|
416
|
+
content: [{
|
|
417
|
+
type: 'text',
|
|
418
|
+
text: JSON.stringify(result, null, 2)
|
|
419
|
+
}]
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
case 'browser_wait_for_selector':
|
|
423
|
+
await page.waitForSelector(args.selector, {
|
|
424
|
+
timeout: args.timeout || 30000
|
|
425
|
+
});
|
|
426
|
+
return {
|
|
427
|
+
content: [{
|
|
428
|
+
type: 'text',
|
|
429
|
+
text: `Element ${args.selector} appeared`
|
|
430
|
+
}]
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
case 'browser_scroll':
|
|
434
|
+
await page.evaluate(({ x, y }) => {
|
|
435
|
+
window.scrollTo(x || 0, y || 0);
|
|
436
|
+
}, args);
|
|
437
|
+
return {
|
|
438
|
+
content: [{
|
|
439
|
+
type: 'text',
|
|
440
|
+
text: `Scrolled to (${args.x || 0}, ${args.y || 0})`
|
|
441
|
+
}]
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
case 'browser_resize_window':
|
|
445
|
+
await page.setViewportSize({
|
|
446
|
+
width: args.width,
|
|
447
|
+
height: args.height
|
|
448
|
+
});
|
|
449
|
+
return {
|
|
450
|
+
content: [{
|
|
451
|
+
type: 'text',
|
|
452
|
+
text: `Resized window to ${args.width}x${args.height}`
|
|
453
|
+
}]
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
case 'browser_get_dom':
|
|
457
|
+
const domContent = await page.evaluate((sel) => {
|
|
458
|
+
const element = sel ? document.querySelector(sel) : document.documentElement;
|
|
459
|
+
if (!element) return null;
|
|
460
|
+
return {
|
|
461
|
+
outerHTML: element.outerHTML,
|
|
462
|
+
textContent: element.textContent,
|
|
463
|
+
attributes: Array.from(element.attributes || []).map(attr => ({
|
|
464
|
+
name: attr.name,
|
|
465
|
+
value: attr.value
|
|
466
|
+
})),
|
|
467
|
+
children: element.children.length
|
|
468
|
+
};
|
|
469
|
+
}, args.selector);
|
|
470
|
+
return {
|
|
471
|
+
content: [{
|
|
472
|
+
type: 'text',
|
|
473
|
+
text: JSON.stringify(domContent, null, 2)
|
|
474
|
+
}]
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
case 'browser_start_video_recording':
|
|
478
|
+
const videoPath = args.path || `${os.tmpdir()}/browser-recording-${Date.now()}.webm`;
|
|
479
|
+
await context.tracing.start({
|
|
480
|
+
screenshots: true,
|
|
481
|
+
snapshots: true
|
|
482
|
+
});
|
|
483
|
+
// Start video recording using Playwright's video feature
|
|
484
|
+
if (!context._options.recordVideo) {
|
|
485
|
+
// Note: Video recording needs to be set when creating context
|
|
486
|
+
// For existing context, we'll use screenshots as fallback
|
|
487
|
+
return {
|
|
488
|
+
content: [{
|
|
489
|
+
type: 'text',
|
|
490
|
+
text: 'Started session tracing (screenshots). For full video, context needs recordVideo option at creation.'
|
|
491
|
+
}]
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
content: [{
|
|
496
|
+
type: 'text',
|
|
497
|
+
text: `Started video recording to ${videoPath}`
|
|
498
|
+
}]
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
case 'browser_stop_video_recording':
|
|
502
|
+
const tracePath = `${os.tmpdir()}/trace-${Date.now()}.zip`;
|
|
503
|
+
await context.tracing.stop({ path: tracePath });
|
|
504
|
+
return {
|
|
505
|
+
content: [{
|
|
506
|
+
type: 'text',
|
|
507
|
+
text: `Stopped recording. Trace saved to ${tracePath}. Use 'playwright show-trace ${tracePath}' to view.`
|
|
508
|
+
}]
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
case 'browser_health_check':
|
|
512
|
+
// Connection already succeeded if we got here
|
|
513
|
+
const url = await page.url();
|
|
514
|
+
|
|
515
|
+
// Detect mode: connected to existing Chrome or launched our own
|
|
516
|
+
const isConnected = browser.isConnected && browser.isConnected();
|
|
517
|
+
const mode = isConnected ? 'Connected to existing Chrome (Antigravity)' : 'Launched standalone Chrome';
|
|
518
|
+
|
|
519
|
+
// Determine profile path based on mode
|
|
520
|
+
let browserProfile;
|
|
521
|
+
if (isConnected) {
|
|
522
|
+
browserProfile = `${process.env.HOME}/.gemini/antigravity-browser-profile`;
|
|
523
|
+
} else {
|
|
524
|
+
browserProfile = process.env.MCP_BROWSER_PROFILE || `${os.tmpdir()}/chrome-mcp-profile`;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
content: [{
|
|
529
|
+
type: 'text',
|
|
530
|
+
text: `✅ Browser automation is fully functional!\n\n` +
|
|
531
|
+
`Mode: ${mode}\n` +
|
|
532
|
+
`✅ Playwright: ${playwrightPath || 'loaded'}\n` +
|
|
533
|
+
`✅ Chrome: Port 9222\n` +
|
|
534
|
+
`✅ Profile: ${browserProfile}\n` +
|
|
535
|
+
`✅ Current page: ${url}\n\n` +
|
|
536
|
+
`All 16 browser tools are ready to use!`
|
|
537
|
+
}]
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
case 'browser_console_start':
|
|
541
|
+
if (!consoleListening) {
|
|
542
|
+
page.on('console', msg => {
|
|
543
|
+
const logEntry = {
|
|
544
|
+
type: msg.type(),
|
|
545
|
+
text: msg.text(),
|
|
546
|
+
timestamp: new Date().toISOString(),
|
|
547
|
+
location: msg.location()
|
|
548
|
+
};
|
|
549
|
+
consoleLogs.push(logEntry);
|
|
550
|
+
debugLog(`Console [${logEntry.type}]: ${logEntry.text}`);
|
|
551
|
+
});
|
|
552
|
+
consoleListening = true;
|
|
553
|
+
debugLog('Console logging started');
|
|
554
|
+
}
|
|
555
|
+
return {
|
|
556
|
+
content: [{
|
|
557
|
+
type: 'text',
|
|
558
|
+
text: `✅ Console logging started.\n\nCapturing: console.log, console.error, console.warn, console.info, console.debug\n\nUse browser_console_get to retrieve captured logs.`
|
|
559
|
+
}]
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
case 'browser_console_get':
|
|
563
|
+
const filter = args.filter;
|
|
564
|
+
const filtered = filter && filter !== 'all'
|
|
565
|
+
? consoleLogs.filter(log => log.type === filter)
|
|
566
|
+
: consoleLogs;
|
|
567
|
+
|
|
568
|
+
if (filtered.length === 0) {
|
|
569
|
+
return {
|
|
570
|
+
content: [{
|
|
571
|
+
type: 'text',
|
|
572
|
+
text: consoleListening
|
|
573
|
+
? `No console logs captured yet.\n\n${filter && filter !== 'all' ? `Filter: ${filter}\n` : ''}Console logging is active - logs will appear as the page executes JavaScript.`
|
|
574
|
+
: `Console logging is not active.\n\nUse browser_console_start to begin capturing logs.`
|
|
575
|
+
}]
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
const logSummary = `📋 Captured ${filtered.length} console log${filtered.length === 1 ? '' : 's'}${filter && filter !== 'all' ? ` (filtered by: ${filter})` : ''}:\n\n`;
|
|
580
|
+
const formattedLogs = filtered.map((log, i) => {
|
|
581
|
+
const icon = {
|
|
582
|
+
'error': '❌',
|
|
583
|
+
'warn': '⚠️',
|
|
584
|
+
'log': '📝',
|
|
585
|
+
'info': 'ℹ️',
|
|
586
|
+
'debug': '🔍'
|
|
587
|
+
}[log.type] || '📄';
|
|
588
|
+
|
|
589
|
+
return `${i + 1}. ${icon} [${log.type.toUpperCase()}] ${log.timestamp}\n ${log.text}${log.location.url ? `\n Location: ${log.location.url}:${log.location.lineNumber}` : ''}`;
|
|
590
|
+
}).join('\n\n');
|
|
591
|
+
|
|
592
|
+
return {
|
|
593
|
+
content: [{
|
|
594
|
+
type: 'text',
|
|
595
|
+
text: logSummary + formattedLogs
|
|
596
|
+
}]
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
case 'browser_console_clear':
|
|
600
|
+
const count = consoleLogs.length;
|
|
601
|
+
consoleLogs = [];
|
|
602
|
+
if (consoleListening) {
|
|
603
|
+
page.removeAllListeners('console');
|
|
604
|
+
consoleListening = false;
|
|
605
|
+
}
|
|
606
|
+
debugLog(`Cleared ${count} console logs and stopped listening`);
|
|
607
|
+
return {
|
|
608
|
+
content: [{
|
|
609
|
+
type: 'text',
|
|
610
|
+
text: `✅ Cleared ${count} console log${count === 1 ? '' : 's'} and stopped listening.\n\nUse browser_console_start to resume capturing.`
|
|
611
|
+
}]
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
default:
|
|
615
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
616
|
+
}
|
|
617
|
+
} catch (error) {
|
|
618
|
+
debugLog(`Tool execution error (${name}): ${error.message}`);
|
|
619
|
+
return {
|
|
620
|
+
content: [{
|
|
621
|
+
type: 'text',
|
|
622
|
+
text: `❌ Error executing ${name}: ${error.message}`
|
|
623
|
+
}],
|
|
624
|
+
isError: true
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// MCP Protocol handler
|
|
630
|
+
rl.on('line', async (line) => {
|
|
631
|
+
let request;
|
|
632
|
+
try {
|
|
633
|
+
debugLog(`Received: ${line.substring(0, 200)}`);
|
|
634
|
+
request = JSON.parse(line);
|
|
635
|
+
|
|
636
|
+
if (request.method === 'initialize') {
|
|
637
|
+
debugLog(`Initialize with protocol: ${request.params.protocolVersion}`);
|
|
638
|
+
respond(request.id, {
|
|
639
|
+
protocolVersion: request.params.protocolVersion || '2024-11-05',
|
|
640
|
+
capabilities: { tools: {} },
|
|
641
|
+
serverInfo: {
|
|
642
|
+
name: 'browser-automation-playwright',
|
|
643
|
+
version: '1.0.0'
|
|
644
|
+
}
|
|
645
|
+
});
|
|
646
|
+
} else if (request.method === 'notifications/initialized') {
|
|
647
|
+
// This is a notification - no response needed
|
|
648
|
+
debugLog('Received initialized notification');
|
|
649
|
+
} else if (request.method === 'tools/list') {
|
|
650
|
+
debugLog('Sending tools list');
|
|
651
|
+
respond(request.id, { tools });
|
|
652
|
+
} else if (request.method === 'tools/call') {
|
|
653
|
+
debugLog(`Calling tool: ${request.params.name}`);
|
|
654
|
+
const result = await executeTool(request.params.name, request.params.arguments || {});
|
|
655
|
+
respond(request.id, result);
|
|
656
|
+
} else {
|
|
657
|
+
debugLog(`Unknown method: ${request.method}`);
|
|
658
|
+
respond(request.id, null, { code: -32601, message: 'Method not found' });
|
|
659
|
+
}
|
|
660
|
+
} catch (error) {
|
|
661
|
+
debugLog(`Error processing request: ${error.message}`);
|
|
662
|
+
console.error('Error processing request:', error.message, 'Request:', line);
|
|
663
|
+
const id = request?.id || null;
|
|
664
|
+
respond(id, null, { code: -32603, message: error.message });
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
function respond(id, result, error = null) {
|
|
669
|
+
const response = { jsonrpc: '2.0', id };
|
|
670
|
+
if (error) response.error = error;
|
|
671
|
+
else response.result = result;
|
|
672
|
+
console.log(JSON.stringify(response));
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Cleanup on exit
|
|
676
|
+
process.on('SIGTERM', async () => {
|
|
677
|
+
if (browser) await browser.close();
|
|
678
|
+
process.exit(0);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
process.on('SIGINT', async () => {
|
|
682
|
+
if (browser) await browser.close();
|
|
683
|
+
process.exit(0);
|
|
684
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ricardodeazambuja/browser-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Universal browser automation MCP server using Playwright. Works with Antigravity, Claude Desktop, and any MCP client.",
|
|
5
|
+
"main": "browser-mcp-server-playwright.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"browser-mcp-server": "./browser-mcp-server-playwright.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node browser-mcp-server-playwright.js",
|
|
11
|
+
"install-browsers": "npx playwright install chromium",
|
|
12
|
+
"test": "echo 'Testing MCP server...' && echo '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{},\"clientInfo\":{\"name\":\"test\",\"version\":\"1.0.0\"}}}' | node browser-mcp-server-playwright.js"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"mcp",
|
|
16
|
+
"mcp-server",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"browser-automation",
|
|
19
|
+
"playwright",
|
|
20
|
+
"claude",
|
|
21
|
+
"antigravity",
|
|
22
|
+
"web-automation",
|
|
23
|
+
"testing",
|
|
24
|
+
"scraping"
|
|
25
|
+
],
|
|
26
|
+
"author": "Ricardo de Azambuja (https://ricardodeazambuja.com)",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/ricardodeazambuja/browser-mcp-server.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/ricardodeazambuja/browser-mcp-server/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/ricardodeazambuja/browser-mcp-server#readme",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=16.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"playwright": "^1.40.0"
|
|
41
|
+
},
|
|
42
|
+
"peerDependenciesMeta": {
|
|
43
|
+
"playwright": {
|
|
44
|
+
"optional": true
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"browser-mcp-server-playwright.js",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
]
|
|
52
|
+
}
|