mcpbrowser 0.3.17 → 0.3.19
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 +130 -26
- package/package.json +2 -2
- package/src/actions/click-element.js +48 -21
- package/src/actions/close-tab.js +3 -3
- package/src/actions/fetch-page.js +38 -11
- package/src/actions/get-current-html.js +38 -15
- package/src/actions/scroll-page.js +318 -0
- package/src/actions/take-screenshot.js +213 -0
- package/src/actions/type-text.js +163 -54
- package/src/browsers/brave.js +66 -0
- package/src/core/browser.js +57 -4
- package/src/core/page.js +90 -28
- package/src/core/responses.js +193 -0
- package/src/mcp-browser.js +17 -3
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data.
|
|
9
9
|
|
|
10
|
-
**MCPBrowser is an MCP browser server that gives AI assistants the ability to browse web pages using a real Chrome or
|
|
10
|
+
**MCPBrowser is an MCP browser server that gives AI assistants the ability to browse web pages using a real Chrome, Edge, or Brave browser.** This browser-based MCP server fetches any web page — especially those protected by authentication, CAPTCHAs, anti-bot protection, or requiring JavaScript rendering. Uses your real browser for web automation so you can log in normally, then automatically extracts content. Works with corporate SSO, login forms, Cloudflare, and JavaScript-heavy sites (SPAs, dashboards).
|
|
11
11
|
|
|
12
12
|
This is an [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server using [stdio transport](https://modelcontextprotocol.io/docs/concepts/transports#stdio). Your AI assistant uses this web browser MCP server when standard HTTP requests fail — pages requiring authentication, CAPTCHA protection, or heavy JavaScript (SPAs). Once connected, the browser MCP server can navigate through websites, interact with elements, and send HTML back to the AI assistant. This gives your AI the ability to browse the web just like you do.
|
|
13
13
|
|
|
@@ -15,10 +15,9 @@ Example workflow for AI assistant to use MCPBrowser
|
|
|
15
15
|
|
|
16
16
|
```
|
|
17
17
|
1. fetch_webpage → Load the login page
|
|
18
|
-
2. type_text → Enter username
|
|
19
|
-
3.
|
|
20
|
-
4.
|
|
21
|
-
5. get_current_html → Extract the content after login
|
|
18
|
+
2. type_text → Enter username and password (multiple fields at once)
|
|
19
|
+
3. click_element → Click "Sign In"
|
|
20
|
+
4. get_current_html → Extract the content after login
|
|
22
21
|
```
|
|
23
22
|
|
|
24
23
|
|
|
@@ -35,6 +34,8 @@ Example workflow for AI assistant to use MCPBrowser
|
|
|
35
34
|
- [click_element](#click_element)
|
|
36
35
|
- [type_text](#type_text)
|
|
37
36
|
- [get_current_html](#get_current_html)
|
|
37
|
+
- [scroll_page](#scroll_page)
|
|
38
|
+
- [take_screenshot](#take_screenshot)
|
|
38
39
|
- [close_tab](#close_tab)
|
|
39
40
|
- [Configuration](#configuration-optional)
|
|
40
41
|
- [Troubleshooting](#troubleshooting)
|
|
@@ -42,7 +43,7 @@ Example workflow for AI assistant to use MCPBrowser
|
|
|
42
43
|
|
|
43
44
|
## Requirements
|
|
44
45
|
|
|
45
|
-
- Chrome or
|
|
46
|
+
- Chrome, Edge, or Brave browser
|
|
46
47
|
- [Node.js 18+](https://nodejs.org/) (includes npm)
|
|
47
48
|
|
|
48
49
|
> **Note:** Node.js must be installed on your system. The VS Code extension and npm package both require Node.js to run the MCP server. Download from [nodejs.org](https://nodejs.org/) if not already installed.
|
|
@@ -53,8 +54,9 @@ Example workflow for AI assistant to use MCPBrowser
|
|
|
53
54
|
|---|----------|------------|
|
|
54
55
|
| 1 | [VS Code Extension](#option-1-vs-code-extension) | One Click |
|
|
55
56
|
| 2 | [Claude Code](#option-2-claude-code) | One Command |
|
|
56
|
-
| 3 | [
|
|
57
|
-
| 4 | [
|
|
57
|
+
| 3 | [OpenClaw](#option-3-openclaw) | One Command |
|
|
58
|
+
| 4 | [Claude Desktop](#option-4-claude-desktop) | Manual |
|
|
59
|
+
| 5 | [npm Package](#option-5-npm-package) | Manual |
|
|
58
60
|
|
|
59
61
|
### Option 1: VS Code Extension
|
|
60
62
|
|
|
@@ -84,7 +86,22 @@ mcpbrowser: npx -y mcpbrowser@latest - ✓ Connected
|
|
|
84
86
|
That's it! Ask Claude to fetch any protected page:
|
|
85
87
|
> "Fetch https://portal.azure.com using mcpbrowser"
|
|
86
88
|
|
|
87
|
-
### Option 3:
|
|
89
|
+
### Option 3: OpenClaw
|
|
90
|
+
|
|
91
|
+
[OpenClaw](https://openclaw.ai/) is a personal AI assistant that runs on your devices. Add MCPBrowser to give it browser automation capabilities:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
openclaw mcp add mcpbrowser -- npx -y mcpbrowser@latest
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Verify it's working:
|
|
98
|
+
```bash
|
|
99
|
+
openclaw mcp list
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Now OpenClaw can browse authenticated pages, fill forms, and interact with web apps using your existing browser sessions.
|
|
103
|
+
|
|
104
|
+
### Option 4: Claude Desktop
|
|
88
105
|
|
|
89
106
|
Add to your config file:
|
|
90
107
|
|
|
@@ -104,7 +121,7 @@ Add to your config file:
|
|
|
104
121
|
|
|
105
122
|
Restart Claude Desktop after saving.
|
|
106
123
|
|
|
107
|
-
### Option
|
|
124
|
+
### Option 5: npm Package
|
|
108
125
|
|
|
109
126
|
For VS Code (GitHub Copilot) manual setup, add to your `mcp.json`:
|
|
110
127
|
|
|
@@ -182,36 +199,55 @@ Clicks on any clickable element (buttons, links, divs with onclick handlers, etc
|
|
|
182
199
|
|
|
183
200
|
### `type_text`
|
|
184
201
|
|
|
185
|
-
Types text into
|
|
202
|
+
Types text into one or more input fields in a single call. Supports filling entire forms at once for efficient automation. Automatically clears existing text by default.
|
|
186
203
|
|
|
187
204
|
**⚠️ Note:** Page must be already loaded via `fetch_webpage` first.
|
|
188
205
|
|
|
189
206
|
**Parameters:**
|
|
190
207
|
- `url` (string, required) - The URL of the page (must match a previously fetched page)
|
|
191
|
-
- `
|
|
192
|
-
- `
|
|
193
|
-
- `
|
|
194
|
-
- `
|
|
208
|
+
- `fields` (array, required) - Array of fields to fill. Each field object contains:
|
|
209
|
+
- `selector` (string, required) - CSS selector for the input element (e.g., `#username`, `input[name="email"]`)
|
|
210
|
+
- `text` (string, required) - Text to type into the field
|
|
211
|
+
- `clear` (boolean, optional, default: `true`) - Whether to clear existing text first
|
|
212
|
+
- `waitForElementTimeout` (number, optional, default: `5000`) - Maximum time to wait for element in milliseconds
|
|
195
213
|
- `returnHtml` (boolean, optional, default: `true`) - Whether to wait for stability and return HTML after typing
|
|
196
214
|
- `removeUnnecessaryHTML` (boolean, optional, default: `true`) - Remove unnecessary HTML for size reduction. Only used when `returnHtml` is `true`
|
|
197
215
|
- `postTypeWait` (number, optional, default: `1000`) - Milliseconds to wait after typing for SPAs to render dynamic content
|
|
198
|
-
- `waitForElementTimeout` (number, optional, default: `5000`) - Maximum time to wait for element in milliseconds
|
|
199
216
|
|
|
200
217
|
**Examples:**
|
|
201
218
|
```javascript
|
|
202
|
-
//
|
|
203
|
-
{
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
{
|
|
219
|
+
// Fill multiple fields at once (login form)
|
|
220
|
+
{
|
|
221
|
+
url: "https://example.com/login",
|
|
222
|
+
fields: [
|
|
223
|
+
{ selector: "#username", text: "john@example.com" },
|
|
224
|
+
{ selector: "#password", text: "secretpass123" }
|
|
225
|
+
]
|
|
226
|
+
}
|
|
207
227
|
|
|
208
|
-
//
|
|
209
|
-
{ url: "https://example.com", selector: "#
|
|
228
|
+
// Single field input
|
|
229
|
+
{ url: "https://example.com", fields: [{ selector: "#search", text: "query" }] }
|
|
210
230
|
|
|
211
|
-
//
|
|
212
|
-
{ url: "https://example.com", selector: "#
|
|
231
|
+
// Append text without clearing
|
|
232
|
+
{ url: "https://example.com", fields: [{ selector: "#notes", text: " additional text", clear: false }] }
|
|
233
|
+
|
|
234
|
+
// Fast form fill without HTML return
|
|
235
|
+
{
|
|
236
|
+
url: "https://example.com/signup",
|
|
237
|
+
fields: [
|
|
238
|
+
{ selector: "#firstName", text: "John" },
|
|
239
|
+
{ selector: "#lastName", text: "Doe" },
|
|
240
|
+
{ selector: "#email", text: "john@example.com" }
|
|
241
|
+
],
|
|
242
|
+
returnHtml: false
|
|
243
|
+
}
|
|
213
244
|
```
|
|
214
245
|
|
|
246
|
+
**Error handling:** If a field fails, the response indicates:
|
|
247
|
+
- Which field number failed (e.g., "Failed on field 2 of 3")
|
|
248
|
+
- Which fields were successfully filled
|
|
249
|
+
- Clear guidance to NOT re-type already filled fields
|
|
250
|
+
|
|
215
251
|
---
|
|
216
252
|
|
|
217
253
|
### `get_current_html`
|
|
@@ -239,6 +275,74 @@ Gets the current HTML from an already-loaded page **WITHOUT** navigating or relo
|
|
|
239
275
|
|
|
240
276
|
---
|
|
241
277
|
|
|
278
|
+
### `scroll_page`
|
|
279
|
+
|
|
280
|
+
Scrolls within an already-loaded page. Use before `take_screenshot` to capture different parts of the page, or to bring elements into view before interaction. Supports multiple scroll modes:
|
|
281
|
+
|
|
282
|
+
- **By direction**: Scroll up/down/left/right by pixel amount
|
|
283
|
+
- **To element**: Scroll until a specific element is visible
|
|
284
|
+
- **To position**: Scroll to absolute coordinates
|
|
285
|
+
|
|
286
|
+
**⚠️ Note:** Page must be already loaded via `fetch_webpage` first.
|
|
287
|
+
|
|
288
|
+
**Parameters:**
|
|
289
|
+
- `url` (string, required) - The URL of the page (must match a previously fetched page)
|
|
290
|
+
- `direction` (string, optional) - Direction to scroll: `up`, `down`, `left`, `right`. Use with `amount`.
|
|
291
|
+
- `amount` (number, optional, default: `500`) - Pixels to scroll in the specified direction (~half a viewport)
|
|
292
|
+
- `selector` (string, optional) - CSS selector of element to scroll into view. Ignores direction/amount.
|
|
293
|
+
- `x` (number, optional) - Absolute horizontal scroll position. Use with `y`.
|
|
294
|
+
- `y` (number, optional) - Absolute vertical scroll position. Use with `x`.
|
|
295
|
+
|
|
296
|
+
**Examples:**
|
|
297
|
+
```javascript
|
|
298
|
+
// Scroll down by 500px (default)
|
|
299
|
+
{ url: "https://example.com", direction: "down" }
|
|
300
|
+
|
|
301
|
+
// Scroll down by 1000px
|
|
302
|
+
{ url: "https://example.com", direction: "down", amount: 1000 }
|
|
303
|
+
|
|
304
|
+
// Scroll an element into view
|
|
305
|
+
{ url: "https://example.com", selector: "#footer" }
|
|
306
|
+
|
|
307
|
+
// Scroll to specific position
|
|
308
|
+
{ url: "https://example.com", x: 0, y: 2000 }
|
|
309
|
+
|
|
310
|
+
// Scroll to top of page
|
|
311
|
+
{ url: "https://example.com", x: 0, y: 0 }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
**Returns:** Current scroll position, page dimensions, and viewport size — useful for understanding where you are on the page.
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
### `take_screenshot`
|
|
319
|
+
|
|
320
|
+
Takes a screenshot of an already-loaded page for visual analysis. **Useful when HTML parsing is insufficient** — for example, pages with charts, images, complex layouts, popups, or visual content that's hard to understand from HTML alone. Returns a PNG image.
|
|
321
|
+
|
|
322
|
+
**⚠️ Note:** Page must be already loaded via `fetch_webpage` first.
|
|
323
|
+
|
|
324
|
+
**Parameters:**
|
|
325
|
+
- `url` (string, required) - The URL of the page (must match a previously fetched page)
|
|
326
|
+
- `fullPage` (boolean, optional, default: `false`) - Capture the full scrollable page instead of just the viewport
|
|
327
|
+
|
|
328
|
+
**Examples:**
|
|
329
|
+
```javascript
|
|
330
|
+
// Capture viewport screenshot (default)
|
|
331
|
+
{ url: "https://example.com" }
|
|
332
|
+
|
|
333
|
+
// Capture full scrollable page
|
|
334
|
+
{ url: "https://dashboard.example.com", fullPage: true }
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Use cases:**
|
|
338
|
+
- Visualize page layout when HTML is hard to parse
|
|
339
|
+
- Capture charts, graphs, or data visualizations
|
|
340
|
+
- Debug popups, modals, or overlays
|
|
341
|
+
- Understand visual feedback (highlights, animations)
|
|
342
|
+
- See what's blocking an element click
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
242
346
|
### `close_tab`
|
|
243
347
|
|
|
244
348
|
Closes the browser tab for the given URL's hostname. Removes the page from the tab pool and forces a fresh session on the next visit to that hostname. Useful for clearing authentication state, managing memory, or starting fresh with a domain.
|
|
@@ -299,7 +403,7 @@ Logs go to `stderr` so they don't interfere with MCP protocol on `stdout`.
|
|
|
299
403
|
## Troubleshooting
|
|
300
404
|
|
|
301
405
|
**Browser doesn't open?**
|
|
302
|
-
- Make sure Chrome or
|
|
406
|
+
- Make sure Chrome, Edge, or Brave is installed
|
|
303
407
|
- Try setting `CHROME_PATH` explicitly
|
|
304
408
|
|
|
305
409
|
**Can't connect to browser?**
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcpbrowser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.19",
|
|
4
4
|
"mcpName": "io.github.cherchyk/mcpbrowser",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "MCP browser server - fetch web pages using real Chrome/Edge browser. Handles authentication, SSO, CAPTCHAs, and anti-bot protection. Browser automation for AI assistants.",
|
|
6
|
+
"description": "MCP browser server - fetch web pages using real Chrome/Edge/Brave browser. Handles authentication, SSO, CAPTCHAs, and anti-bot protection. Browser automation for AI assistants.",
|
|
7
7
|
"main": "src/mcp-browser.js",
|
|
8
8
|
"bin": {
|
|
9
9
|
"mcpbrowser": "src/mcp-browser.js"
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
* - Flexible: Can disable waiting for fast form interactions
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
|
-
import { getBrowser,
|
|
27
|
+
import { getBrowser, getValidatedPage } from '../core/browser.js';
|
|
28
28
|
import { extractAndProcessHtml, waitForPageStability } from '../core/page.js';
|
|
29
|
-
import { MCPResponse,
|
|
29
|
+
import { MCPResponse, InformationalResponse } from '../core/responses.js';
|
|
30
30
|
import logger from '../core/logger.js';
|
|
31
31
|
|
|
32
32
|
/**
|
|
@@ -171,15 +171,36 @@ export async function clickElement({ url, selector, text, waitForElementTimeout
|
|
|
171
171
|
throw new Error(`Invalid URL: ${url}`);
|
|
172
172
|
}
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
// Ensure browser connection (triggers domain map rebuild on reconnect)
|
|
175
|
+
try {
|
|
176
|
+
await getBrowser();
|
|
177
|
+
} catch (err) {
|
|
178
|
+
logger.error(`click_element: Failed to connect to browser: ${err.message}`);
|
|
179
|
+
return new InformationalResponse(
|
|
180
|
+
`Browser connection failed: ${err.message}`,
|
|
181
|
+
'The browser must be running with remote debugging enabled.',
|
|
182
|
+
[
|
|
183
|
+
'Ensure the browser is installed and running',
|
|
184
|
+
'Check that remote debugging is enabled (--remote-debugging-port)',
|
|
185
|
+
'Try restarting the MCP server'
|
|
186
|
+
]
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Validate page exists and is usable
|
|
191
|
+
const { page, error: pageError } = await getValidatedPage(hostname);
|
|
176
192
|
|
|
177
|
-
if (!page
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
193
|
+
if (!page) {
|
|
194
|
+
const isConnectionLost = pageError && pageError.includes('connection');
|
|
195
|
+
logger.info(`click_element: ${pageError || 'No page found for ' + hostname}`);
|
|
196
|
+
return new InformationalResponse(
|
|
197
|
+
isConnectionLost ? `Page connection lost for ${hostname}` : `No open page found for ${hostname}`,
|
|
198
|
+
isConnectionLost
|
|
199
|
+
? 'The browser tab was closed or the connection was lost. The page needs to be reloaded.'
|
|
200
|
+
: 'The page must be loaded before you can interact with elements on it',
|
|
181
201
|
[
|
|
182
|
-
"Use fetch_webpage to load the page first"
|
|
202
|
+
"Use MCPBrowser's fetch_webpage tool to load the page first",
|
|
203
|
+
"Then retry MCPBrowser's click_element with the same URL"
|
|
183
204
|
]
|
|
184
205
|
);
|
|
185
206
|
}
|
|
@@ -218,10 +239,12 @@ export async function clickElement({ url, selector, text, waitForElementTimeout
|
|
|
218
239
|
}
|
|
219
240
|
|
|
220
241
|
if (!elementHandle || !elementHandle.asElement()) {
|
|
221
|
-
return new
|
|
242
|
+
return new InformationalResponse(
|
|
222
243
|
selector ? `Element not found: ${selector}` : `Element with text "${text}" not found`,
|
|
244
|
+
'The element could not be located on the page. It may be hidden, dynamically loaded, or the selector/text may be incorrect.',
|
|
223
245
|
[
|
|
224
|
-
"Use get_current_html to verify page content",
|
|
246
|
+
"Use MCPBrowser's get_current_html to verify page content",
|
|
247
|
+
"Use MCPBrowser's take_screenshot to see the visual layout if HTML is unclear",
|
|
225
248
|
"Try a different selector or text",
|
|
226
249
|
"Check if the element is visible on the page"
|
|
227
250
|
]
|
|
@@ -260,10 +283,11 @@ export async function clickElement({ url, selector, text, waitForElementTimeout
|
|
|
260
283
|
selector ? `Clicked element: ${selector}` : `Clicked element with text: "${text}"`,
|
|
261
284
|
html,
|
|
262
285
|
[
|
|
263
|
-
"Use click_element again to navigate further",
|
|
264
|
-
"Use type_text to fill forms if needed",
|
|
265
|
-
"Use get_current_html to refresh page state",
|
|
266
|
-
"Use
|
|
286
|
+
"Use MCPBrowser's click_element again to navigate further",
|
|
287
|
+
"Use MCPBrowser's type_text to fill forms if needed",
|
|
288
|
+
"Use MCPBrowser's get_current_html to refresh page state",
|
|
289
|
+
"Use MCPBrowser's take_screenshot if page has popups or visual content that's hard to parse from HTML",
|
|
290
|
+
"Use MCPBrowser's close_tab when finished"
|
|
267
291
|
]
|
|
268
292
|
);
|
|
269
293
|
} else {
|
|
@@ -285,20 +309,23 @@ export async function clickElement({ url, selector, text, waitForElementTimeout
|
|
|
285
309
|
selector ? `Clicked element: ${selector}` : `Clicked element with text: "${text}"`,
|
|
286
310
|
null,
|
|
287
311
|
[
|
|
288
|
-
"Use get_current_html to see updated page state",
|
|
289
|
-
"Use
|
|
290
|
-
"Use
|
|
312
|
+
"Use MCPBrowser's get_current_html to see updated page state",
|
|
313
|
+
"Use MCPBrowser's take_screenshot if the page has popups, modals, or visual content",
|
|
314
|
+
"Use MCPBrowser's click_element or MCPBrowser's type_text for more interactions",
|
|
315
|
+
"Use MCPBrowser's close_tab when finished"
|
|
291
316
|
]
|
|
292
317
|
);
|
|
293
318
|
}
|
|
294
319
|
} catch (err) {
|
|
295
320
|
logger.error(`click_element failed: ${err.message}`);
|
|
296
|
-
return new
|
|
321
|
+
return new InformationalResponse(
|
|
297
322
|
`Failed to click element: ${err.message}`,
|
|
323
|
+
'The element was found but could not be clicked. It may be covered by another element, not interactable, or the page may have changed.',
|
|
298
324
|
[
|
|
299
|
-
"Use get_current_html to check current page state",
|
|
325
|
+
"Use MCPBrowser's get_current_html to check current page state",
|
|
326
|
+
"Use MCPBrowser's take_screenshot to see what's visually blocking the element",
|
|
300
327
|
"Verify the selector or text is correct",
|
|
301
|
-
"Try fetch_webpage to reload if page is stale"
|
|
328
|
+
"Try MCPBrowser's fetch_webpage to reload if page is stale"
|
|
302
329
|
]
|
|
303
330
|
);
|
|
304
331
|
}
|
package/src/actions/close-tab.js
CHANGED
|
@@ -145,7 +145,7 @@ export async function closeTab({ url }) {
|
|
|
145
145
|
'No open tab found for this hostname',
|
|
146
146
|
hostname,
|
|
147
147
|
[
|
|
148
|
-
"Use fetch_webpage to open a new page if needed"
|
|
148
|
+
"Use MCPBrowser's fetch_webpage to open a new page if needed"
|
|
149
149
|
]
|
|
150
150
|
);
|
|
151
151
|
}
|
|
@@ -164,7 +164,7 @@ export async function closeTab({ url }) {
|
|
|
164
164
|
'Tab was already closed',
|
|
165
165
|
hostname,
|
|
166
166
|
[
|
|
167
|
-
"Use fetch_webpage to open a new page if needed"
|
|
167
|
+
"Use MCPBrowser's fetch_webpage to open a new page if needed"
|
|
168
168
|
]
|
|
169
169
|
);
|
|
170
170
|
}
|
|
@@ -181,7 +181,7 @@ export async function closeTab({ url }) {
|
|
|
181
181
|
`Successfully closed tab for ${hostname}`,
|
|
182
182
|
hostname,
|
|
183
183
|
[
|
|
184
|
-
"Use fetch_webpage to open a new page if needed"
|
|
184
|
+
"Use MCPBrowser's fetch_webpage to open a new page if needed"
|
|
185
185
|
]
|
|
186
186
|
);
|
|
187
187
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { getBrowser, domainPages } from '../core/browser.js';
|
|
7
7
|
import { getOrCreatePage, queueRequest, navigateToUrl, waitForPageReady, extractAndProcessHtml, waitForPageStability } from '../core/page.js';
|
|
8
8
|
import { detectRedirectType, waitForAutoAuth, waitForManualAuth } from '../core/auth.js';
|
|
9
|
-
import { MCPResponse, ErrorResponse } from '../core/responses.js';
|
|
9
|
+
import { MCPResponse, ErrorResponse, HttpStatusResponse, InformationalResponse } from '../core/responses.js';
|
|
10
10
|
import logger from '../core/logger.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -156,14 +156,29 @@ async function doFetchPage({ url, hostname, browser, removeUnnecessaryHTML, post
|
|
|
156
156
|
const authCompletionTimeout = 600000;
|
|
157
157
|
const reuseLastKeptPage = true;
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
// Ensure browser connection
|
|
160
|
+
let browserInstance;
|
|
161
|
+
try {
|
|
162
|
+
browserInstance = await getBrowser(browser);
|
|
163
|
+
} catch (err) {
|
|
164
|
+
logger.error(`fetch_webpage: Failed to connect to browser: ${err.message}`);
|
|
165
|
+
return new InformationalResponse(
|
|
166
|
+
`Browser connection failed: ${err.message}`,
|
|
167
|
+
'The browser must be running with remote debugging enabled.',
|
|
168
|
+
[
|
|
169
|
+
'Ensure the browser is installed and running',
|
|
170
|
+
'Check that remote debugging is enabled (--remote-debugging-port)',
|
|
171
|
+
'Try restarting the MCP server'
|
|
172
|
+
]
|
|
173
|
+
);
|
|
174
|
+
}
|
|
160
175
|
|
|
161
176
|
try {
|
|
162
177
|
// Get or create page for this domain (simple - no locks needed)
|
|
163
178
|
let page = await getOrCreatePage(browserInstance, hostname, reuseLastKeptPage);
|
|
164
179
|
|
|
165
180
|
// Navigate to URL (pure navigation)
|
|
166
|
-
await navigateToUrl(page, url, waitUntil, navigationTimeout);
|
|
181
|
+
const { statusCode, statusText } = await navigateToUrl(page, url, waitUntil, navigationTimeout);
|
|
167
182
|
|
|
168
183
|
// Wait for page content to be ready (handles SPAs automatically)
|
|
169
184
|
await waitForPageReady(page);
|
|
@@ -227,8 +242,8 @@ async function doFetchPage({ url, hostname, browser, removeUnnecessaryHTML, post
|
|
|
227
242
|
manualAuthResult.error,
|
|
228
243
|
[
|
|
229
244
|
"Complete authentication in the browser window",
|
|
230
|
-
"Call fetch_webpage again with the same URL to retry",
|
|
231
|
-
"Use close_tab to reset the session if authentication fails"
|
|
245
|
+
"Call MCPBrowser's fetch_webpage again with the same URL to retry",
|
|
246
|
+
"Use MCPBrowser's close_tab to reset the session if authentication fails"
|
|
232
247
|
]
|
|
233
248
|
);
|
|
234
249
|
}
|
|
@@ -257,14 +272,26 @@ async function doFetchPage({ url, hostname, browser, removeUnnecessaryHTML, post
|
|
|
257
272
|
|
|
258
273
|
logger.info(`fetch_webpage completed: ${page.url()}`);
|
|
259
274
|
|
|
275
|
+
// Check for non-2xx HTTP status codes - return informational response (not red error)
|
|
276
|
+
if (statusCode && (statusCode >= 400 && statusCode < 600)) {
|
|
277
|
+
logger.info(`HTTP ${statusCode} ${statusText} - returning as informational response`);
|
|
278
|
+
return new HttpStatusResponse(
|
|
279
|
+
page.url(),
|
|
280
|
+
statusCode,
|
|
281
|
+
statusText,
|
|
282
|
+
processedHtml
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
260
286
|
return new FetchPageSuccessResponse(
|
|
261
287
|
page.url(),
|
|
262
288
|
processedHtml,
|
|
263
289
|
[
|
|
264
|
-
"Use click_element to interact with buttons/links on the page",
|
|
265
|
-
"Use type_text to fill in form fields",
|
|
266
|
-
"Use get_current_html to re-check page state after interactions",
|
|
267
|
-
"Use
|
|
290
|
+
"Use MCPBrowser's click_element to interact with buttons/links on the page",
|
|
291
|
+
"Use MCPBrowser's type_text to fill in form fields",
|
|
292
|
+
"Use MCPBrowser's get_current_html to re-check page state after interactions",
|
|
293
|
+
"Use MCPBrowser's take_screenshot if page has charts, images, or complex visual layout that's hard to understand from HTML",
|
|
294
|
+
"Use MCPBrowser's close_tab when finished to free browser resources"
|
|
268
295
|
]
|
|
269
296
|
);
|
|
270
297
|
} catch (err) {
|
|
@@ -273,8 +300,8 @@ async function doFetchPage({ url, hostname, browser, removeUnnecessaryHTML, post
|
|
|
273
300
|
err.message || String(err),
|
|
274
301
|
[
|
|
275
302
|
"Complete authentication in the browser if prompted",
|
|
276
|
-
"Call fetch_webpage again with the same URL to retry",
|
|
277
|
-
"Use close_tab to reset the session if needed"
|
|
303
|
+
"Call MCPBrowser's fetch_webpage again with the same URL to retry",
|
|
304
|
+
"Use MCPBrowser's close_tab to reset the session if needed"
|
|
278
305
|
]
|
|
279
306
|
);
|
|
280
307
|
}
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* get-current-html.js - Get current HTML from an already-loaded page
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { getBrowser,
|
|
5
|
+
import { getBrowser, getValidatedPage } from '../core/browser.js';
|
|
6
6
|
import { extractAndProcessHtml } from '../core/page.js';
|
|
7
|
-
import { MCPResponse,
|
|
7
|
+
import { MCPResponse, InformationalResponse } from '../core/responses.js';
|
|
8
8
|
import logger from '../core/logger.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -113,15 +113,36 @@ export async function getCurrentHtml({ url, removeUnnecessaryHTML = true }) {
|
|
|
113
113
|
throw new Error(`Invalid URL: ${url}`);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
// Ensure browser connection (triggers domain map rebuild on reconnect)
|
|
117
|
+
try {
|
|
118
|
+
await getBrowser();
|
|
119
|
+
} catch (err) {
|
|
120
|
+
logger.error(`get_current_html: Failed to connect to browser: ${err.message}`);
|
|
121
|
+
return new InformationalResponse(
|
|
122
|
+
`Browser connection failed: ${err.message}`,
|
|
123
|
+
'The browser must be running with remote debugging enabled.',
|
|
124
|
+
[
|
|
125
|
+
'Ensure the browser is installed and running',
|
|
126
|
+
'Check that remote debugging is enabled (--remote-debugging-port)',
|
|
127
|
+
'Try restarting the MCP server'
|
|
128
|
+
]
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Validate page exists and is usable
|
|
133
|
+
const { page, error: pageError } = await getValidatedPage(hostname);
|
|
118
134
|
|
|
119
|
-
if (!page
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
if (!page) {
|
|
136
|
+
const isConnectionLost = pageError && pageError.includes('connection');
|
|
137
|
+
logger.info(`get_current_html: ${pageError || 'No page found for ' + hostname}`);
|
|
138
|
+
return new InformationalResponse(
|
|
139
|
+
isConnectionLost ? `Page connection lost for ${hostname}` : `No open page found for ${hostname}`,
|
|
140
|
+
isConnectionLost
|
|
141
|
+
? 'The browser tab was closed or the connection was lost. The page needs to be reloaded.'
|
|
142
|
+
: 'The page must be loaded before you can get its current HTML',
|
|
123
143
|
[
|
|
124
|
-
"Use fetch_webpage to load the page first"
|
|
144
|
+
"Use MCPBrowser's fetch_webpage tool to load the page first",
|
|
145
|
+
"Then retry MCPBrowser's get_current_html with the same URL"
|
|
125
146
|
]
|
|
126
147
|
);
|
|
127
148
|
}
|
|
@@ -136,18 +157,20 @@ export async function getCurrentHtml({ url, removeUnnecessaryHTML = true }) {
|
|
|
136
157
|
currentUrl,
|
|
137
158
|
html,
|
|
138
159
|
[
|
|
139
|
-
"Use click_element to interact with elements",
|
|
140
|
-
"Use type_text to fill forms",
|
|
141
|
-
"Use
|
|
160
|
+
"Use MCPBrowser's click_element to interact with elements",
|
|
161
|
+
"Use MCPBrowser's type_text to fill forms",
|
|
162
|
+
"Use MCPBrowser's take_screenshot if page layout or visual content is hard to understand from HTML",
|
|
163
|
+
"Use MCPBrowser's close_tab to free resources when done"
|
|
142
164
|
]
|
|
143
165
|
);
|
|
144
166
|
} catch (err) {
|
|
145
167
|
logger.error(`get_current_html failed: ${err.message}`);
|
|
146
|
-
return new
|
|
168
|
+
return new InformationalResponse(
|
|
147
169
|
`Failed to get HTML: ${err.message}`,
|
|
170
|
+
'Could not extract HTML from the page. The page may have navigated away or the connection was lost.',
|
|
148
171
|
[
|
|
149
|
-
"Try fetch_webpage to reload the page",
|
|
150
|
-
"Use close_tab and start fresh if needed"
|
|
172
|
+
"Try MCPBrowser's fetch_webpage to reload the page",
|
|
173
|
+
"Use MCPBrowser's close_tab and start fresh if needed"
|
|
151
174
|
]
|
|
152
175
|
);
|
|
153
176
|
}
|