mcpbrowser 0.3.50 → 0.3.53
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 +3 -3
- package/package.json +4 -3
- package/scripts/build-mcpb.js +190 -0
- package/src/actions/click-element.js +7 -0
- package/src/actions/close-tab.js +7 -0
- package/src/actions/detect-forms.js +7 -0
- package/src/actions/execute-javascript.js +7 -0
- package/src/actions/fetch-page.js +9 -2
- package/src/actions/get-current-html.js +7 -0
- package/src/actions/navigate-history.js +1 -0
- package/src/actions/plugin-action.js +7 -0
- package/src/actions/plugin-info.js +7 -0
- package/src/actions/scroll-page.js +7 -0
- package/src/actions/take-screenshot.js +7 -0
- package/src/actions/type-text.js +7 -0
- package/src/core/prompts.js +148 -0
- package/src/mcp-browser.js +16 -2
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://modelcontextprotocol.io/quickstart/user)
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
|
|
8
|
-
> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude, Kiro, Antigravity), 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.
|
|
8
|
+
> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude, Kiro, Antigravity), 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. MCPBrowser combines private data access, untrusted content exposure, and external communication (the MCP "[lethal trifecta](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/)") — all tool annotations accurately declare these risks so MCP clients can enforce appropriate safety controls. See [MCP_COMPLIANCE.md](docs/MCP_COMPLIANCE.md) for details.
|
|
9
9
|
|
|
10
10
|
> 💡 **Why MCPBrowser over Puppeteer/Playwright MCP servers?** Puppeteer and Playwright are browser automation libraries — their MCP servers give agents raw, low-level browser commands. MCPBrowser uses Puppeteer under the hood and was built specifically for AI agents, adding an intelligence layer that handles the hard parts automatically.
|
|
11
11
|
>
|
|
@@ -167,7 +167,7 @@ For VS Code, Kiro, Antigravity, or other editors — manual MCP setup. Add to yo
|
|
|
167
167
|
|
|
168
168
|
### `browser_fetch_webpage`
|
|
169
169
|
|
|
170
|
-
Fetches web pages using your Chrome/Edge browser. Handles authentication, CAPTCHA, SSO, anti-bot protection, and JavaScript-heavy sites. Opens the URL in a browser tab (reuses existing tab for same domain) and waits for the page to fully load before returning content. **Automatically detects SPAs** (React, Vue, Angular) and waits for JavaScript to render content.
|
|
170
|
+
Fetches web pages using your Chrome/Edge/Brave browser. Handles authentication, CAPTCHA, SSO, anti-bot protection, and JavaScript-heavy sites. Opens the URL in a browser tab (reuses existing tab for same domain) and waits for the page to fully load before returning content. **Automatically detects SPAs** (React, Vue, Angular) and waits for JavaScript to render content.
|
|
171
171
|
|
|
172
172
|
**Parameters:**
|
|
173
173
|
- `url` (string, required) - The URL to fetch
|
|
@@ -458,7 +458,7 @@ Environment variables for advanced setup:
|
|
|
458
458
|
|
|
459
459
|
| Variable | Description | Default |
|
|
460
460
|
|----------|-------------|---------|
|
|
461
|
-
| `CHROME_PATH` | Path to Chrome/Edge | Auto-detect |
|
|
461
|
+
| `CHROME_PATH` | Path to Chrome/Edge/Brave | Auto-detect |
|
|
462
462
|
| `CHROME_USER_DATA_DIR` | Browser profile directory | `%LOCALAPPDATA%/ChromeAuthProfile` |
|
|
463
463
|
| `CHROME_REMOTE_DEBUG_PORT` | DevTools port | `9222` |
|
|
464
464
|
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcpbrowser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.53",
|
|
4
4
|
"mcpName": "io.github.cherchyk/mcpbrowser",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"description": "MCP browser server —
|
|
6
|
+
"description": "MCP browser server — loads and interacts with web pages using the user's Chromium-based browser with full JavaScript execution. Handles authentication, SSO, and anti-bot protection automatically via the user's existing browser session.",
|
|
7
7
|
"main": "src/mcp-browser.js",
|
|
8
8
|
"bin": {
|
|
9
9
|
"mcpbrowser": "src/mcp-browser.js"
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
"test": "node tests/run-all.js",
|
|
14
14
|
"test:unit": "node tests/run-unit.js",
|
|
15
15
|
"test:cli": "node tests/cli.test.js",
|
|
16
|
-
"test:descriptions": "node tests/tool-selection/run-tool-selection-tests.js"
|
|
16
|
+
"test:descriptions": "node tests/tool-selection/run-tool-selection-tests.js",
|
|
17
|
+
"build:mcpb": "node scripts/build-mcpb.js"
|
|
17
18
|
},
|
|
18
19
|
"keywords": [
|
|
19
20
|
"mcp",
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Build script for MCP Bundle (.mcpb) format.
|
|
4
|
+
* Creates a portable ZIP archive that MCP clients can install with one click.
|
|
5
|
+
*
|
|
6
|
+
* Usage: node scripts/build-mcpb.js
|
|
7
|
+
* Output: dist/mcpbrowser-<version>.mcpb
|
|
8
|
+
*
|
|
9
|
+
* See: https://github.com/modelcontextprotocol/mcpb/blob/main/MANIFEST.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, rmSync } from 'fs';
|
|
13
|
+
import { join, dirname } from 'path';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
|
|
17
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
18
|
+
const __dirname = dirname(__filename);
|
|
19
|
+
const ROOT = join(__dirname, '..');
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// 1. Read package.json for version and metadata
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
|
25
|
+
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// 2. Dynamically import action modules to extract tool name + description
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
async function getTools() {
|
|
30
|
+
const actionFiles = [
|
|
31
|
+
'fetch-page.js', 'get-current-html.js', 'take-screenshot.js',
|
|
32
|
+
'detect-forms.js', 'click-element.js', 'type-text.js',
|
|
33
|
+
'scroll-page.js', 'navigate-history.js', 'execute-javascript.js',
|
|
34
|
+
'close-tab.js', 'plugin-info.js', 'plugin-action.js'
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const tools = [];
|
|
38
|
+
for (const file of actionFiles) {
|
|
39
|
+
const mod = await import(pathToFileURL(join(ROOT, 'src', 'actions', file)).href);
|
|
40
|
+
// Each module exports a *_TOOL constant
|
|
41
|
+
const toolExport = Object.values(mod).find(v => v && typeof v === 'object' && v.name && v.description);
|
|
42
|
+
if (toolExport) {
|
|
43
|
+
tools.push({ name: toolExport.name, description: toolExport.description });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return tools;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// 3. Import prompts
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
async function getPrompts() {
|
|
53
|
+
const { PROMPTS } = await import(pathToFileURL(join(ROOT, 'src', 'core', 'prompts.js')).href);
|
|
54
|
+
return PROMPTS.map(p => ({
|
|
55
|
+
name: p.name,
|
|
56
|
+
description: p.description,
|
|
57
|
+
arguments: p.arguments?.map(a => a.name) || []
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// 4. Build manifest.json
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
async function buildManifest() {
|
|
65
|
+
const tools = await getTools();
|
|
66
|
+
const prompts = await getPrompts();
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
manifest_version: "0.3",
|
|
70
|
+
name: "mcpbrowser",
|
|
71
|
+
display_name: "MCP Browser",
|
|
72
|
+
version: pkg.version,
|
|
73
|
+
description: pkg.description,
|
|
74
|
+
long_description: "Browser automation MCP server that connects to the user's existing Chromium-based browser (Chrome, Edge, Brave) with all cookies, logins, and SSO sessions intact. Provides 12 tools for fetching pages, clicking elements, typing text, taking screenshots, detecting forms, executing JavaScript, and more. Includes site-specific plugins for optimized interactions with known services.",
|
|
75
|
+
author: {
|
|
76
|
+
name: "cherchyk",
|
|
77
|
+
url: "https://github.com/cherchyk"
|
|
78
|
+
},
|
|
79
|
+
repository: {
|
|
80
|
+
type: "git",
|
|
81
|
+
url: "https://github.com/cherchyk/MCPBrowser.git"
|
|
82
|
+
},
|
|
83
|
+
homepage: "https://github.com/cherchyk/MCPBrowser#readme",
|
|
84
|
+
support: "https://github.com/cherchyk/MCPBrowser/issues",
|
|
85
|
+
icon: "icon.png",
|
|
86
|
+
license: "MIT",
|
|
87
|
+
server: {
|
|
88
|
+
type: "node",
|
|
89
|
+
entry_point: "server/src/mcp-browser.js",
|
|
90
|
+
mcp_config: {
|
|
91
|
+
command: "node",
|
|
92
|
+
args: ["${__dirname}/server/src/mcp-browser.js"],
|
|
93
|
+
env: {}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
tools,
|
|
97
|
+
tools_generated: false,
|
|
98
|
+
prompts,
|
|
99
|
+
prompts_generated: false,
|
|
100
|
+
keywords: [
|
|
101
|
+
"browser", "web-automation", "puppeteer", "chrome", "edge",
|
|
102
|
+
"authentication", "sso", "mcp-server", "scraping"
|
|
103
|
+
],
|
|
104
|
+
compatibility: {
|
|
105
|
+
platforms: ["darwin", "win32", "linux"],
|
|
106
|
+
runtimes: {
|
|
107
|
+
node: ">=18.0.0"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
// 5. Package into .mcpb (ZIP)
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
async function build() {
|
|
117
|
+
const distDir = join(ROOT, 'dist');
|
|
118
|
+
const stageDir = join(distDir, '_mcpb_stage');
|
|
119
|
+
|
|
120
|
+
// Clean previous build
|
|
121
|
+
if (existsSync(stageDir)) rmSync(stageDir, { recursive: true });
|
|
122
|
+
mkdirSync(join(stageDir, 'server'), { recursive: true });
|
|
123
|
+
|
|
124
|
+
// Generate manifest.json
|
|
125
|
+
const manifest = await buildManifest();
|
|
126
|
+
writeFileSync(join(stageDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
127
|
+
console.log(`✅ manifest.json (${manifest.tools.length} tools, ${manifest.prompts.length} prompts)`);
|
|
128
|
+
|
|
129
|
+
// Copy icon
|
|
130
|
+
const iconSrc = join(ROOT, '..', 'VSCodeExtension', 'icon.png');
|
|
131
|
+
if (existsSync(iconSrc)) {
|
|
132
|
+
cpSync(iconSrc, join(stageDir, 'icon.png'));
|
|
133
|
+
console.log('✅ icon.png');
|
|
134
|
+
} else {
|
|
135
|
+
console.log('⚠️ icon.png not found at VSCodeExtension/icon.png — skipping');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Copy server files (src/ + package.json)
|
|
139
|
+
cpSync(join(ROOT, 'src'), join(stageDir, 'server', 'src'), { recursive: true });
|
|
140
|
+
cpSync(join(ROOT, 'package.json'), join(stageDir, 'server', 'package.json'));
|
|
141
|
+
console.log('✅ server/src + server/package.json');
|
|
142
|
+
|
|
143
|
+
// Install production dependencies in staging (clean, minimal node_modules)
|
|
144
|
+
console.log('📦 Installing production dependencies...');
|
|
145
|
+
execSync('npm install --omit=dev --ignore-scripts', {
|
|
146
|
+
cwd: join(stageDir, 'server'),
|
|
147
|
+
stdio: 'pipe'
|
|
148
|
+
});
|
|
149
|
+
// Move node_modules up to bundle root (MCPB convention)
|
|
150
|
+
const nmStaged = join(stageDir, 'server', 'node_modules');
|
|
151
|
+
if (existsSync(nmStaged)) {
|
|
152
|
+
cpSync(nmStaged, join(stageDir, 'node_modules'), { recursive: true });
|
|
153
|
+
rmSync(nmStaged, { recursive: true });
|
|
154
|
+
}
|
|
155
|
+
console.log('✅ node_modules (production only)');
|
|
156
|
+
|
|
157
|
+
// Create .mcpb ZIP using .NET ZipFile (fast, cross-platform on PowerShell/Node)
|
|
158
|
+
const outFile = join(distDir, `mcpbrowser-${pkg.version}.mcpb`);
|
|
159
|
+
if (existsSync(outFile)) rmSync(outFile);
|
|
160
|
+
|
|
161
|
+
if (process.platform === 'win32') {
|
|
162
|
+
// .NET ZipFile is orders of magnitude faster than Compress-Archive
|
|
163
|
+
execSync(
|
|
164
|
+
`powershell -NoProfile -Command "Add-Type -Assembly System.IO.Compression.FileSystem; [IO.Compression.ZipFile]::CreateFromDirectory('${stageDir}', '${outFile}')"`,
|
|
165
|
+
{ stdio: 'inherit' }
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
execSync(`cd "${stageDir}" && zip -r "${outFile}" .`, { stdio: 'inherit' });
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Clean staging (use system rmdir on Windows — much faster for deep node_modules)
|
|
172
|
+
if (process.platform === 'win32') {
|
|
173
|
+
execSync(`rmdir /s /q "${stageDir}"`, { stdio: 'pipe' });
|
|
174
|
+
} else {
|
|
175
|
+
rmSync(stageDir, { recursive: true });
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const stats = readFileSync(outFile);
|
|
179
|
+
const sizeMB = (stats.length / 1024 / 1024).toFixed(2);
|
|
180
|
+
console.log(`\n📦 ${outFile}`);
|
|
181
|
+
console.log(` Size: ${sizeMB} MB`);
|
|
182
|
+
console.log(` Version: ${pkg.version}`);
|
|
183
|
+
console.log(` Tools: ${manifest.tools.length}`);
|
|
184
|
+
console.log(` Prompts: ${manifest.prompts.length}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
build().catch(err => {
|
|
188
|
+
console.error('❌ Build failed:', err.message);
|
|
189
|
+
process.exit(1);
|
|
190
|
+
});
|
|
@@ -159,6 +159,13 @@ export const CLICK_ELEMENT_TOOL = {
|
|
|
159
159
|
},
|
|
160
160
|
required: ["status", "fallbackUsed", "nativeAttempt", "currentUrl", "message", "html", "nextSteps"],
|
|
161
161
|
additionalProperties: false
|
|
162
|
+
},
|
|
163
|
+
annotations: {
|
|
164
|
+
title: "Click Element",
|
|
165
|
+
readOnlyHint: false,
|
|
166
|
+
destructiveHint: false,
|
|
167
|
+
idempotentHint: false,
|
|
168
|
+
openWorldHint: true
|
|
162
169
|
}
|
|
163
170
|
};
|
|
164
171
|
|
package/src/actions/close-tab.js
CHANGED
|
@@ -81,6 +81,13 @@ export const CLOSE_TAB_TOOL = {
|
|
|
81
81
|
},
|
|
82
82
|
required: ["message", "hostname", "nextSteps"],
|
|
83
83
|
additionalProperties: false
|
|
84
|
+
},
|
|
85
|
+
annotations: {
|
|
86
|
+
title: "Close Tab",
|
|
87
|
+
readOnlyHint: false,
|
|
88
|
+
destructiveHint: true,
|
|
89
|
+
idempotentHint: true,
|
|
90
|
+
openWorldHint: false
|
|
84
91
|
}
|
|
85
92
|
};
|
|
86
93
|
|
|
@@ -149,6 +149,13 @@ export const DETECT_FORMS_TOOL = {
|
|
|
149
149
|
},
|
|
150
150
|
required: ["forms", "orphanedFields", "totalFieldCount", "summary", "nextSteps"],
|
|
151
151
|
additionalProperties: false
|
|
152
|
+
},
|
|
153
|
+
annotations: {
|
|
154
|
+
title: "Detect Forms",
|
|
155
|
+
readOnlyHint: true,
|
|
156
|
+
destructiveHint: false,
|
|
157
|
+
idempotentHint: true,
|
|
158
|
+
openWorldHint: false
|
|
152
159
|
}
|
|
153
160
|
};
|
|
154
161
|
|
|
@@ -90,6 +90,13 @@ export const EXECUTE_JAVASCRIPT_TOOL = {
|
|
|
90
90
|
},
|
|
91
91
|
required: ['type', 'executionTimeMs', 'truncated', 'urlChanged', 'currentUrl', 'nextSteps'],
|
|
92
92
|
additionalProperties: false
|
|
93
|
+
},
|
|
94
|
+
annotations: {
|
|
95
|
+
title: 'Execute JavaScript',
|
|
96
|
+
readOnlyHint: false,
|
|
97
|
+
destructiveHint: true,
|
|
98
|
+
idempotentHint: false,
|
|
99
|
+
openWorldHint: true
|
|
93
100
|
}
|
|
94
101
|
};
|
|
95
102
|
|
|
@@ -74,14 +74,14 @@ export class FetchPageSuccessResponse extends MCPResponse {
|
|
|
74
74
|
export const FETCH_WEBPAGE_TOOL = {
|
|
75
75
|
name: "browser_fetch_webpage",
|
|
76
76
|
title: "Fetch Web Page",
|
|
77
|
-
description: "Load any URL and return its content using a real browser with full JavaScript execution. Use
|
|
77
|
+
description: "Load any URL and return its content using a real browser with full JavaScript execution. Use whenever a webpage needs to be loaded — whether to read content, interact with a web app, or access any HTTP/HTTPS resource. Connects to the user's existing browser session with all cookies, logins, and SSO sessions available automatically. Handles JavaScript rendering, authentication, and anti-bot protection. Prefer this over generic HTTP fetch for authenticated or JS-heavy pages.",
|
|
78
78
|
inputSchema: {
|
|
79
79
|
type: "object",
|
|
80
80
|
properties: {
|
|
81
81
|
url: { type: "string", description: "The URL to fetch" },
|
|
82
82
|
browser: {
|
|
83
83
|
type: "string",
|
|
84
|
-
description: "Browser to use: 'chrome' or 'edge'. Leave empty for auto-detection.
|
|
84
|
+
description: "Browser to use: 'chrome' or 'edge'. Leave empty for auto-detection. Uses CDP to connect to the user's existing browser session.",
|
|
85
85
|
enum: ["", "chrome", "edge"]
|
|
86
86
|
},
|
|
87
87
|
removeUnnecessaryHTML: { type: "boolean", description: "Remove Unnecessary HTML for size reduction by 90%.", default: true },
|
|
@@ -113,6 +113,13 @@ export const FETCH_WEBPAGE_TOOL = {
|
|
|
113
113
|
},
|
|
114
114
|
required: ["currentUrl", "html", "nextSteps"],
|
|
115
115
|
additionalProperties: false
|
|
116
|
+
},
|
|
117
|
+
annotations: {
|
|
118
|
+
title: "Fetch Web Page",
|
|
119
|
+
readOnlyHint: true,
|
|
120
|
+
destructiveHint: false,
|
|
121
|
+
idempotentHint: true,
|
|
122
|
+
openWorldHint: true
|
|
116
123
|
}
|
|
117
124
|
};
|
|
118
125
|
|
|
@@ -105,6 +105,13 @@ export const GET_CURRENT_HTML_TOOL = {
|
|
|
105
105
|
},
|
|
106
106
|
required: ["currentUrl", "html", "nextSteps"],
|
|
107
107
|
additionalProperties: false
|
|
108
|
+
},
|
|
109
|
+
annotations: {
|
|
110
|
+
title: "Get Current HTML",
|
|
111
|
+
readOnlyHint: true,
|
|
112
|
+
destructiveHint: false,
|
|
113
|
+
idempotentHint: true,
|
|
114
|
+
openWorldHint: false
|
|
108
115
|
}
|
|
109
116
|
};
|
|
110
117
|
|
|
@@ -70,6 +70,13 @@ export const PLUGIN_ACTION_TOOL = {
|
|
|
70
70
|
},
|
|
71
71
|
required: ["nextSteps"],
|
|
72
72
|
additionalProperties: true
|
|
73
|
+
},
|
|
74
|
+
annotations: {
|
|
75
|
+
title: "Plugin Action",
|
|
76
|
+
readOnlyHint: false,
|
|
77
|
+
destructiveHint: false,
|
|
78
|
+
idempotentHint: false,
|
|
79
|
+
openWorldHint: true
|
|
73
80
|
}
|
|
74
81
|
};
|
|
75
82
|
|
|
@@ -81,6 +81,13 @@ export const PLUGIN_INFO_TOOL = {
|
|
|
81
81
|
},
|
|
82
82
|
required: ["nextSteps"],
|
|
83
83
|
additionalProperties: true
|
|
84
|
+
},
|
|
85
|
+
annotations: {
|
|
86
|
+
title: "Plugin Info",
|
|
87
|
+
readOnlyHint: true,
|
|
88
|
+
destructiveHint: false,
|
|
89
|
+
idempotentHint: true,
|
|
90
|
+
openWorldHint: false
|
|
84
91
|
}
|
|
85
92
|
};
|
|
86
93
|
|
|
@@ -146,6 +146,13 @@ export const SCROLL_PAGE_TOOL = {
|
|
|
146
146
|
},
|
|
147
147
|
required: ["currentUrl", "scrollX", "scrollY", "pageWidth", "pageHeight", "viewportWidth", "viewportHeight", "nextSteps"],
|
|
148
148
|
additionalProperties: false
|
|
149
|
+
},
|
|
150
|
+
annotations: {
|
|
151
|
+
title: "Scroll Page",
|
|
152
|
+
readOnlyHint: false,
|
|
153
|
+
destructiveHint: false,
|
|
154
|
+
idempotentHint: false,
|
|
155
|
+
openWorldHint: false
|
|
149
156
|
}
|
|
150
157
|
};
|
|
151
158
|
|
|
@@ -113,6 +113,13 @@ export const TAKE_SCREENSHOT_TOOL = {
|
|
|
113
113
|
},
|
|
114
114
|
required: ["currentUrl", "screenshotBase64", "mimeType", "nextSteps"],
|
|
115
115
|
additionalProperties: false
|
|
116
|
+
},
|
|
117
|
+
annotations: {
|
|
118
|
+
title: "Take Screenshot",
|
|
119
|
+
readOnlyHint: true,
|
|
120
|
+
destructiveHint: false,
|
|
121
|
+
idempotentHint: true,
|
|
122
|
+
openWorldHint: false
|
|
116
123
|
}
|
|
117
124
|
};
|
|
118
125
|
|
package/src/actions/type-text.js
CHANGED
|
@@ -111,6 +111,13 @@ export const TYPE_TEXT_TOOL = {
|
|
|
111
111
|
},
|
|
112
112
|
required: ["currentUrl", "message", "html", "nextSteps"],
|
|
113
113
|
additionalProperties: false
|
|
114
|
+
},
|
|
115
|
+
annotations: {
|
|
116
|
+
title: "Type Text",
|
|
117
|
+
readOnlyHint: false,
|
|
118
|
+
destructiveHint: false,
|
|
119
|
+
idempotentHint: false,
|
|
120
|
+
openWorldHint: false
|
|
114
121
|
}
|
|
115
122
|
};
|
|
116
123
|
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Prompts — reusable workflow templates exposed as slash-commands.
|
|
3
|
+
* Each prompt returns a message array that guides the LLM through
|
|
4
|
+
* a multi-tool workflow using MCPBrowser's tools.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// PROMPT DEFINITIONS
|
|
9
|
+
// ============================================================================
|
|
10
|
+
|
|
11
|
+
export const PROMPTS = [
|
|
12
|
+
{
|
|
13
|
+
name: "scrape-page",
|
|
14
|
+
description: "Fetch a web page and extract its content as clean HTML",
|
|
15
|
+
arguments: [
|
|
16
|
+
{ name: "url", description: "URL to scrape", required: true },
|
|
17
|
+
{ name: "selector", description: "CSS selector to scope extraction (optional)", required: false }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "fill-form",
|
|
22
|
+
description: "Detect form fields on a page and fill them with provided values",
|
|
23
|
+
arguments: [
|
|
24
|
+
{ name: "url", description: "URL of the page with the form", required: true },
|
|
25
|
+
{ name: "values", description: "JSON object mapping field selectors to values, e.g. {\"#email\": \"user@example.com\", \"#password\": \"secret\"}", required: true }
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "visual-audit",
|
|
30
|
+
description: "Take a screenshot and get HTML of a page for visual comparison or accessibility review",
|
|
31
|
+
arguments: [
|
|
32
|
+
{ name: "url", description: "URL to audit", required: true }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "authenticated-workflow",
|
|
37
|
+
description: "Navigate an authenticated site — fetch the page, handle login if needed, then continue",
|
|
38
|
+
arguments: [
|
|
39
|
+
{ name: "url", description: "URL that may require authentication", required: true }
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// PROMPT MESSAGE GENERATORS
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the messages array for a given prompt name and arguments.
|
|
50
|
+
* @param {string} name - Prompt name
|
|
51
|
+
* @param {Record<string, string>} args - Prompt arguments
|
|
52
|
+
* @returns {{ description: string, messages: Array<{ role: string, content: { type: string, text: string } }> }}
|
|
53
|
+
*/
|
|
54
|
+
export function getPromptMessages(name, args = {}) {
|
|
55
|
+
const prompt = PROMPTS.find(p => p.name === name);
|
|
56
|
+
if (!prompt) {
|
|
57
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Validate required arguments
|
|
61
|
+
const missing = (prompt.arguments || [])
|
|
62
|
+
.filter(a => a.required && !args[a.name])
|
|
63
|
+
.map(a => a.name);
|
|
64
|
+
if (missing.length > 0) {
|
|
65
|
+
throw new Error(`Missing required argument(s) for prompt "${name}": ${missing.join(', ')}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (name) {
|
|
69
|
+
case "scrape-page":
|
|
70
|
+
return buildScrapePage(args);
|
|
71
|
+
case "fill-form":
|
|
72
|
+
return buildFillForm(args);
|
|
73
|
+
case "visual-audit":
|
|
74
|
+
return buildVisualAudit(args);
|
|
75
|
+
case "authenticated-workflow":
|
|
76
|
+
return buildAuthenticatedWorkflow(args);
|
|
77
|
+
default:
|
|
78
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// BUILDERS
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
function buildScrapePage({ url, selector }) {
|
|
87
|
+
const selectorInstruction = selector
|
|
88
|
+
? ` Focus on the content matching the CSS selector \`${selector}\`.`
|
|
89
|
+
: '';
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
description: `Scrape content from ${url}`,
|
|
93
|
+
messages: [
|
|
94
|
+
{
|
|
95
|
+
role: "user",
|
|
96
|
+
content: {
|
|
97
|
+
type: "text",
|
|
98
|
+
text: `Fetch the web page at ${url} using browser_fetch_webpage.${selectorInstruction} Return the extracted HTML content. If the page requires scrolling to load more content, use browser_scroll_page to load it, then browser_get_current_html to capture the full page.`
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildFillForm({ url, values }) {
|
|
106
|
+
return {
|
|
107
|
+
description: `Fill form on ${url}`,
|
|
108
|
+
messages: [
|
|
109
|
+
{
|
|
110
|
+
role: "user",
|
|
111
|
+
content: {
|
|
112
|
+
type: "text",
|
|
113
|
+
text: `Fill out the form on ${url}:\n\n1. Fetch the page with browser_fetch_webpage.\n2. Detect available forms with browser_detect_forms.\n3. For each field in the values below, use browser_type_text to enter the value:\n\n${values}\n\n4. After filling all fields, use browser_click_element to submit the form (look for a submit button).\n5. Return the resulting page HTML with browser_get_current_html.`
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function buildVisualAudit({ url }) {
|
|
121
|
+
return {
|
|
122
|
+
description: `Visual audit of ${url}`,
|
|
123
|
+
messages: [
|
|
124
|
+
{
|
|
125
|
+
role: "user",
|
|
126
|
+
content: {
|
|
127
|
+
type: "text",
|
|
128
|
+
text: `Perform a visual audit of ${url}:\n\n1. Fetch the page with browser_fetch_webpage.\n2. Take a full-page screenshot with browser_take_screenshot.\n3. Get the current HTML with browser_get_current_html.\n4. Analyze the screenshot and HTML for:\n - Layout issues or broken elements\n - Missing alt text on images\n - Color contrast problems\n - Mobile responsiveness concerns\n5. Provide a summary of findings.`
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildAuthenticatedWorkflow({ url }) {
|
|
136
|
+
return {
|
|
137
|
+
description: `Authenticated workflow for ${url}`,
|
|
138
|
+
messages: [
|
|
139
|
+
{
|
|
140
|
+
role: "user",
|
|
141
|
+
content: {
|
|
142
|
+
type: "text",
|
|
143
|
+
text: `Navigate to ${url} using browser_fetch_webpage. This page may require authentication.\n\nIf the page shows a login form or redirects to a login page:\n1. Notify me that authentication is needed.\n2. Wait for me to complete the login in the browser window.\n3. After I confirm login is complete, use browser_fetch_webpage again with the original URL.\n\nOnce authenticated, return the page content using browser_get_current_html.`
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
};
|
|
148
|
+
}
|
package/src/mcp-browser.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
|
-
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
12
12
|
import { readFileSync } from 'fs';
|
|
13
13
|
import { dirname, join } from 'path';
|
|
@@ -38,6 +38,9 @@ import { detectForms, DETECT_FORMS_TOOL } from './actions/detect-forms.js';
|
|
|
38
38
|
import { pluginAction, PLUGIN_ACTION_TOOL } from './actions/plugin-action.js';
|
|
39
39
|
import { pluginInfo, PLUGIN_INFO_TOOL } from './actions/plugin-info.js';
|
|
40
40
|
|
|
41
|
+
// Import prompt definitions
|
|
42
|
+
import { PROMPTS, getPromptMessages } from './core/prompts.js';
|
|
43
|
+
|
|
41
44
|
// Import functions for testing exports
|
|
42
45
|
import { getBrowser, closeBrowser } from './core/browser.js';
|
|
43
46
|
import { getOrCreatePage, queueRequest, navigateToUrl, waitForPageReady, extractAndProcessHtml } from './core/page.js';
|
|
@@ -62,7 +65,10 @@ async function main() {
|
|
|
62
65
|
|
|
63
66
|
const server = new Server(
|
|
64
67
|
{ name: "MCPBrowser", version: packageJson.version },
|
|
65
|
-
{
|
|
68
|
+
{
|
|
69
|
+
capabilities: { tools: {}, logging: {}, prompts: {} },
|
|
70
|
+
instructions: "Browser automation server using the user's existing browser session (cookies and auth intact). Workflow: browser_fetch_webpage → browser_take_screenshot (visual content) or browser_get_current_html (re-read after interaction) → browser_click_element / browser_type_text (interact) → browser_close_tab (cleanup). All tools except browser_fetch_webpage and browser_plugin_info require a page loaded first. One tab per domain — same-domain navigations reuse the existing tab. Requests are queued and processed sequentially. Maximum one browser connection at a time. Pages loaded via browser_fetch_webpage persist until browser_close_tab is called or the server shuts down. Screenshots return base64 PNG — prefer browser_get_current_html for text extraction. browser_execute_javascript runs in the page context with access to the full DOM and JavaScript APIs. If plugins are loaded, browser_plugin_info provides site-specific optimized actions for known sites. If authentication is required, the user must complete login in the browser window, then retry the same URL."
|
|
71
|
+
}
|
|
66
72
|
);
|
|
67
73
|
|
|
68
74
|
// Wire server to logger so logs flow to agent via notifications/message.
|
|
@@ -102,6 +108,14 @@ async function main() {
|
|
|
102
108
|
|
|
103
109
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
|
|
104
110
|
|
|
111
|
+
// --- Prompts handlers ---
|
|
112
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS }));
|
|
113
|
+
|
|
114
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
115
|
+
const { name, arguments: args } = request.params;
|
|
116
|
+
return getPromptMessages(name, args);
|
|
117
|
+
});
|
|
118
|
+
|
|
105
119
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
106
120
|
const { name, arguments: args } = request.params;
|
|
107
121
|
const safeArgs = args || {};
|