mcpbrowser 0.2.34 → 0.2.36
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 +149 -4
- package/package.json +4 -2
- package/server.json +1 -1
- package/src/actions/click-element.js +176 -0
- package/src/actions/close-tab.js +100 -0
- package/src/actions/fetch-page.js +130 -0
- package/src/actions/get-current-html.js +53 -0
- package/src/actions/type-text.js +107 -0
- package/src/core/auth.js +130 -0
- package/src/core/browser.js +256 -0
- package/src/core/html.js +136 -0
- package/src/core/page.js +122 -0
- package/src/mcp-browser.js +147 -818
- package/src/utils.js +78 -0
- package/tests/README.md +147 -48
- package/tests/actions/click-element.test.js +75 -0
- package/tests/actions/close-tab.test.js +368 -0
- package/tests/{integration.test.js → actions/fetch-page.test.js} +57 -11
- package/tests/actions/get-current-html.test.js +101 -0
- package/tests/actions/type-text.test.js +84 -0
- package/tests/{auth-flow.test.js → core/auth.test.js} +1 -1
- package/tests/{domain-tab-pooling.test.js → core/browser.test.js} +1 -1
- package/tests/{prepare-html.test.js → core/html.test.js} +46 -26
- package/tests/{redirect-detection.test.js → core/page.test.js} +1 -1
- package/tests/mcp-browser.test.js +190 -0
- package/tests/run-all.js +100 -33
- package/tests/run-unit.js +98 -0
- package/tests/mcp-server.test.js +0 -154
package/src/core/page.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page management for MCPBrowser
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { domainPages } from './browser.js';
|
|
6
|
+
import { cleanHtml, enrichHtml } from './html.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Get or create a page for the given domain, reusing existing tabs when possible.
|
|
10
|
+
* @param {Browser} browser - The Puppeteer browser instance
|
|
11
|
+
* @param {string} hostname - The hostname to get/create a page for
|
|
12
|
+
* @param {boolean} reuseLastKeptPage - Whether to reuse existing tabs
|
|
13
|
+
* @returns {Promise<Page>} The page for this domain
|
|
14
|
+
*/
|
|
15
|
+
export async function getOrCreatePage(browser, hostname, reuseLastKeptPage = true) {
|
|
16
|
+
let page = null;
|
|
17
|
+
|
|
18
|
+
// Check if we have an existing page for this domain
|
|
19
|
+
if (reuseLastKeptPage && domainPages.has(hostname)) {
|
|
20
|
+
const existingPage = domainPages.get(hostname);
|
|
21
|
+
if (!existingPage.isClosed()) {
|
|
22
|
+
page = existingPage;
|
|
23
|
+
await page.bringToFront().catch(() => {});
|
|
24
|
+
console.error(`[MCPBrowser] Reusing existing tab for domain: ${hostname}`);
|
|
25
|
+
} else {
|
|
26
|
+
// Page was closed externally, remove from map
|
|
27
|
+
domainPages.delete(hostname);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create new tab if no existing page for this domain
|
|
32
|
+
if (!page) {
|
|
33
|
+
try {
|
|
34
|
+
page = await browser.newPage();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
// If newPage() fails (can happen with some profiles), try to reuse existing page
|
|
37
|
+
const pages = await browser.pages();
|
|
38
|
+
for (const p of pages) {
|
|
39
|
+
try {
|
|
40
|
+
const pageUrl = p.url();
|
|
41
|
+
// Skip chrome:// pages and other internal pages
|
|
42
|
+
if (!pageUrl.startsWith('chrome://') && !pageUrl.startsWith('chrome-extension://')) {
|
|
43
|
+
page = p;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
} catch {
|
|
47
|
+
// Skip pages we can't access
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!page) {
|
|
51
|
+
throw new Error('Unable to create or find a controllable page');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Add new page to domain map
|
|
55
|
+
domainPages.set(hostname, page);
|
|
56
|
+
console.error(`[MCPBrowser] Created new tab for domain: ${hostname}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return page;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Navigate to URL with fallback strategy for slow pages.
|
|
64
|
+
* @param {Page} page - The Puppeteer page instance
|
|
65
|
+
* @param {string} url - The URL to navigate to
|
|
66
|
+
* @param {string} waitUntil - Wait condition (networkidle0, load, etc.)
|
|
67
|
+
* @param {number} timeout - Navigation timeout in ms
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
export async function navigateToUrl(page, url, waitUntil, timeout) {
|
|
71
|
+
console.error(`[MCPBrowser] Navigating to: ${url}`);
|
|
72
|
+
|
|
73
|
+
const startTime = Date.now();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Simple, fast navigation - no complex fallback logic
|
|
77
|
+
await page.goto(url, { waitUntil, timeout });
|
|
78
|
+
|
|
79
|
+
const loadTime = Date.now() - startTime;
|
|
80
|
+
console.error(`[MCPBrowser] Navigation completed in ${loadTime}ms: ${page.url()}`);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const elapsed = Date.now() - startTime;
|
|
83
|
+
console.error(`[MCPBrowser] Navigation error after ${elapsed}ms: ${error.message}`);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Wait for page to stabilize after authentication.
|
|
90
|
+
* @param {Page} page - The Puppeteer page instance
|
|
91
|
+
* @returns {Promise<void>}
|
|
92
|
+
*/
|
|
93
|
+
export async function waitForPageStability(page) {
|
|
94
|
+
console.error(`[MCPBrowser] Waiting for page to stabilize...`);
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await page.waitForNetworkIdle({ timeout: 5000 });
|
|
99
|
+
} catch {
|
|
100
|
+
// Ignore timeout - page may have long-polling or websockets
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Extract and process HTML from the page.
|
|
106
|
+
* @param {Page} page - The Puppeteer page instance
|
|
107
|
+
* @param {boolean} removeUnnecessaryHTML - Whether to clean the HTML
|
|
108
|
+
* @returns {Promise<string>} The processed HTML
|
|
109
|
+
*/
|
|
110
|
+
export async function extractAndProcessHtml(page, removeUnnecessaryHTML) {
|
|
111
|
+
const html = await page.evaluate(() => document.documentElement?.outerHTML || "");
|
|
112
|
+
|
|
113
|
+
let processedHtml;
|
|
114
|
+
if (removeUnnecessaryHTML) {
|
|
115
|
+
const cleaned = cleanHtml(html);
|
|
116
|
+
processedHtml = enrichHtml(cleaned, page.url());
|
|
117
|
+
} else {
|
|
118
|
+
processedHtml = enrichHtml(html, page.url());
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return processedHtml;
|
|
122
|
+
}
|