mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-19 16:43:24 +02:00
reviewed: fix: detached frame error & feature: Puppeteer launch arguments support
This commit is contained in:
@@ -8,7 +8,10 @@ A Model Context Protocol server that provides browser automation capabilities us
|
|||||||
|
|
||||||
- **puppeteer_navigate**
|
- **puppeteer_navigate**
|
||||||
- Navigate to any URL in the browser
|
- Navigate to any URL in the browser
|
||||||
- Input: `url` (string)
|
- Inputs:
|
||||||
|
- `url` (string, required): URL to navigate to
|
||||||
|
- `launchOptions` (object, optional): PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: `{ headless: true, args: ['--user-data-dir="C:/Data"'] }`
|
||||||
|
- `allowDangerous` (boolean, optional): Allow dangerous LaunchOptions that reduce security. When false, dangerous args like `--no-sandbox`, `--disable-web-security` will throw errors. Default false.
|
||||||
|
|
||||||
- **puppeteer_screenshot**
|
- **puppeteer_screenshot**
|
||||||
- Capture screenshots of the entire page or specific elements
|
- Capture screenshots of the entire page or specific elements
|
||||||
@@ -61,6 +64,7 @@ The server provides access to two types of resources:
|
|||||||
- Screenshot capabilities
|
- Screenshot capabilities
|
||||||
- JavaScript execution
|
- JavaScript execution
|
||||||
- Basic web interaction (navigation, clicking, form filling)
|
- Basic web interaction (navigation, clicking, form filling)
|
||||||
|
- Customizable Puppeteer launch options
|
||||||
|
|
||||||
## Configuration to use Puppeteer Server
|
## Configuration to use Puppeteer Server
|
||||||
Here's the Claude Desktop configuration to use the Puppeter server:
|
Here's the Claude Desktop configuration to use the Puppeter server:
|
||||||
@@ -93,6 +97,39 @@ Here's the Claude Desktop configuration to use the Puppeter server:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Launch Options
|
||||||
|
|
||||||
|
You can customize Puppeteer's browser behavior in two ways:
|
||||||
|
|
||||||
|
1. **Environment Variable**: Set `PUPPETEER_LAUNCH_OPTIONS` with a JSON-encoded string in the MCP configuration's `env` parameter:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"mcp-puppeteer": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["-y", "@modelcontextprotocol/server-puppeteer"]
|
||||||
|
"env": {
|
||||||
|
"PUPPETEER_LAUNCH_OPTIONS": "{ \"headless\": false, \"executablePath\": \"C:/Program Files/Google/Chrome/Application/chrome.exe\", \"args\": [] }",
|
||||||
|
"ALLOW_DANGEROUS": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Tool Call Arguments**: Pass `launchOptions` and `allowDangerous` parameters to the `puppeteer_navigate` tool:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"url": "https://example.com",
|
||||||
|
"launchOptions": {
|
||||||
|
"headless": false,
|
||||||
|
"defaultViewport": {"width": 1280, "height": 720}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
Docker build:
|
Docker build:
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ const TOOLS: Tool[] = [
|
|||||||
inputSchema: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
properties: {
|
properties: {
|
||||||
url: { type: "string" },
|
url: { type: "string", description: "URL to navigate to" },
|
||||||
|
launchOptions: { type: "object", description: "PuppeteerJS LaunchOptions. Default null. If changed and not null, browser restarts. Example: { headless: true, args: ['--no-sandbox'] }" },
|
||||||
|
allowDangerous: { type: "boolean", description: "Allow dangerous LaunchOptions that reduce security. When false, dangerous args like --no-sandbox will throw errors. Default false." },
|
||||||
},
|
},
|
||||||
required: ["url"],
|
required: ["url"],
|
||||||
},
|
},
|
||||||
@@ -101,16 +103,65 @@ const TOOLS: Tool[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Global state
|
// Global state
|
||||||
let browser: Browser | undefined;
|
let browser: Browser | null;
|
||||||
let page: Page | undefined;
|
let page: Page | null;
|
||||||
const consoleLogs: string[] = [];
|
const consoleLogs: string[] = [];
|
||||||
const screenshots = new Map<string, string>();
|
const screenshots = new Map<string, string>();
|
||||||
|
let previousLaunchOptions: any = null;
|
||||||
|
|
||||||
|
async function ensureBrowser({ launchOptions, allowDangerous }: any) {
|
||||||
|
|
||||||
|
const DANGEROUS_ARGS = [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-setuid-sandbox',
|
||||||
|
'--single-process',
|
||||||
|
'--disable-web-security',
|
||||||
|
'--ignore-certificate-errors',
|
||||||
|
'--disable-features=IsolateOrigins',
|
||||||
|
'--disable-site-isolation-trials',
|
||||||
|
'--allow-running-insecure-content'
|
||||||
|
];
|
||||||
|
|
||||||
|
// Parse environment config safely
|
||||||
|
let envConfig = {};
|
||||||
|
try {
|
||||||
|
envConfig = JSON.parse(process.env.PUPPETEER_LAUNCH_OPTIONS || '{}');
|
||||||
|
} catch (error: any) {
|
||||||
|
console.warn('Failed to parse PUPPETEER_LAUNCH_OPTIONS:', error?.message || error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep merge environment config with user-provided options
|
||||||
|
const mergedConfig = deepMerge(envConfig, launchOptions || {});
|
||||||
|
|
||||||
|
// Security validation for merged config
|
||||||
|
if (mergedConfig?.args) {
|
||||||
|
const dangerousArgs = mergedConfig.args?.filter?.((arg: string) => DANGEROUS_ARGS.some((dangerousArg: string) => arg.startsWith(dangerousArg)));
|
||||||
|
if (dangerousArgs?.length > 0 && !(allowDangerous || (process.env.ALLOW_DANGEROUS === 'true'))) {
|
||||||
|
throw new Error(`Dangerous browser arguments detected: ${dangerousArgs.join(', ')}. Fround from environment variable and tool call argument. ` +
|
||||||
|
'Set allowDangerous: true in the tool call arguments to override.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ((browser && !browser.connected) ||
|
||||||
|
(launchOptions && (JSON.stringify(launchOptions) != JSON.stringify(previousLaunchOptions)))) {
|
||||||
|
await browser?.close();
|
||||||
|
browser = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
browser = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousLaunchOptions = launchOptions;
|
||||||
|
|
||||||
async function ensureBrowser() {
|
|
||||||
if (!browser) {
|
if (!browser) {
|
||||||
const npx_args = { headless: false }
|
const npx_args = { headless: false }
|
||||||
const docker_args = { headless: true, args: ["--no-sandbox", "--single-process", "--no-zygote"] }
|
const docker_args = { headless: true, args: ["--no-sandbox", "--single-process", "--no-zygote"] }
|
||||||
browser = await puppeteer.launch(process.env.DOCKER_CONTAINER ? docker_args : npx_args);
|
browser = await puppeteer.launch(deepMerge(
|
||||||
|
process.env.DOCKER_CONTAINER ? docker_args : npx_args,
|
||||||
|
mergedConfig
|
||||||
|
));
|
||||||
const pages = await browser.pages();
|
const pages = await browser.pages();
|
||||||
page = pages[0];
|
page = pages[0];
|
||||||
|
|
||||||
@@ -126,6 +177,25 @@ async function ensureBrowser() {
|
|||||||
return page!;
|
return page!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deep merge utility function
|
||||||
|
function deepMerge(target: any, source: any): any {
|
||||||
|
const output = Object.assign({}, target);
|
||||||
|
if (typeof target !== 'object' || typeof source !== 'object') return source;
|
||||||
|
|
||||||
|
for (const key of Object.keys(source)) {
|
||||||
|
const targetVal = target[key];
|
||||||
|
const sourceVal = source[key];
|
||||||
|
if (Array.isArray(targetVal) && Array.isArray(sourceVal)) {
|
||||||
|
output[key] = [...targetVal, ...sourceVal];
|
||||||
|
} else if (sourceVal instanceof Object && key in target) {
|
||||||
|
output[key] = deepMerge(targetVal, sourceVal);
|
||||||
|
} else {
|
||||||
|
output[key] = sourceVal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
mcpHelper: {
|
mcpHelper: {
|
||||||
@@ -136,7 +206,7 @@ declare global {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
|
async function handleToolCall(name: string, args: any): Promise<CallToolResult> {
|
||||||
const page = await ensureBrowser();
|
const page = await ensureBrowser(args);
|
||||||
|
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "puppeteer_navigate":
|
case "puppeteer_navigate":
|
||||||
|
|||||||
Reference in New Issue
Block a user