chore: generate readme options (#411)

This commit is contained in:
Pavel Feldman 2025-05-13 15:52:30 -07:00 committed by GitHub
parent c506027aec
commit ab20175826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 127 additions and 69 deletions

View File

@ -110,34 +110,54 @@ Follow the MCP install [guide](https://modelcontextprotocol.io/quickstart/user),
Playwright MCP server supports following arguments. They can be provided in the JSON configuration above, as a part of the `"args"` list:
- `--allowed-origins <origins>`: Semicolon-separated list of origins to allow the browser to request. Default is to allow all. Origins matching both `--allowed-origins` and `--blocked-origins` will be blocked
- `--blocked-origins <origins>`: Semicolon-separated list of origins to block the browser to request. Origins matching both `--allowed-origins` and `--blocked-origins` will be blocked
- `--block-service-workers`: Block service workers
- `--browser <browser>`: Browser or chrome channel to use. Possible values:
- `chrome`, `firefox`, `webkit`, `msedge`
- Chrome channels: `chrome-beta`, `chrome-canary`, `chrome-dev`
- Edge channels: `msedge-beta`, `msedge-canary`, `msedge-dev`
- Default: `chrome`
- `--caps <caps>`: Comma-separated list of capabilities to enable, possible values: tabs, pdf, history, wait, files, install. Default is all.
- `--cdp-endpoint <endpoint>`: CDP endpoint to connect to
- `--config <path>`: Path to the configuration file
- `--device`: Emulate mobile device
- `--executable-path <path>`: Path to the browser executable
- `--headless`: Run browser in headless mode (headed by default)
- `--host <host>`: Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces
- `--ignore-https-errors`: Ignore https errors
- `--isolated`: Keep the browser profile in memory, do not save it to disk
- `--no-image-responses`: Do not send image responses to the client
- `--no-sandbox`: Disable the sandbox for all process types that are normally sandboxed
- `--output-dir`: Directory for output files
- `--port <port>`: Port to listen on for SSE transport
- `--proxy-bypass <bypass>`: Comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"'
- `--proxy-server <proxy>`: Proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"'
- `--storage-state <path>`: Path to the storage state file for isolated sessions
- `--user-agent <ua string>`: Specify user agent string
- `--user-data-dir <path>`: Path to the user data directory. If not specified, a temporary directory will be created
- `--viewport-size <size>`: Specify browser viewport size in pixels, for example "1280, 720"
- `--vision`: Run server that uses screenshots (Aria snapshots are used by default)
<!--- Options generated by update-readme.js -->
```
> npx @playwright/mcp@latest --help
--allowed-origins <origins> semicolon-separated list of origins to allow the
browser to request. Default is to allow all.
--blocked-origins <origins> semicolon-separated list of origins to block the
browser from requesting. Blocklist is evaluated
before allowlist. If used without the allowlist,
requests not matching the blocklist are still
allowed.
--block-service-workers block service workers
--browser <browser> browser or chrome channel to use, possible
values: chrome, firefox, webkit, msedge.
--caps <caps> comma-separated list of capabilities to enable,
possible values: tabs, pdf, history, wait, files,
install. Default is all.
--cdp-endpoint <endpoint> CDP endpoint to connect to.
--config <path> path to the configuration file.
--device <device> device to emulate, for example: "iPhone 15"
--executable-path <path> path to the browser executable.
--headless run browser in headless mode, headed by default
--host <host> host to bind server to. Default is localhost. Use
0.0.0.0 to bind to all interfaces.
--ignore-https-errors ignore https errors
--isolated keep the browser profile in memory, do not save
it to disk.
--no-image-responses do not send image responses to the client.
--no-sandbox disable the sandbox for all process types that
are normally sandboxed.
--output-dir <path> path to the directory for output files.
--port <port> port to listen on for SSE transport.
--proxy-bypass <bypass> comma-separated domains to bypass proxy, for
example ".com,chromium.org,.domain.com"
--proxy-server <proxy> specify proxy server, for example
"http://myproxy:3128" or "socks5://myproxy:8080"
--storage-state <path> path to the storage state file for isolated
sessions.
--user-agent <ua string> specify user agent string
--user-data-dir <path> path to the user data directory. If not
specified, a temporary directory will be created.
--viewport-size <size> specify browser viewport size in pixels, for
example "1280, 720"
--vision Run server that uses screenshots (Aria snapshots
are used by default)
```
<!--- End of options generated section -->
### User profile
@ -362,7 +382,7 @@ To use Vision Mode, add the `--vision` flag when starting the server:
Vision Mode works best with the computer use models that are able to interact with elements using
X Y coordinate space, based on the provided screenshot.
<!--- Generated by update-readme.js -->
<!--- Tools generated by update-readme.js -->
<details>
<summary><b>Interactions</b></summary>
@ -728,4 +748,5 @@ X Y coordinate space, based on the provided screenshot.
</details>
<!--- End of generated section -->
<!--- End of tools generated section -->

View File

@ -36,8 +36,8 @@ import screenshotTools from '../lib/tools/screenshot.js';
import testTools from '../lib/tools/testing.js';
import visionTools from '../lib/tools/vision.js';
import waitTools from '../lib/tools/wait.js';
import { execSync } from 'node:child_process';
// Category definitions for tools
const categories = {
'Interactions': [
...snapshotTools,
@ -77,24 +77,22 @@ const categories = {
// NOTE: Can be removed when we drop Node.js 18 support and changed to import.meta.filename.
const __filename = url.fileURLToPath(import.meta.url);
const kStartMarker = `<!--- Generated by ${path.basename(__filename)} -->`;
const kEndMarker = `<!--- End of generated section -->`;
/**
* @param {import('../src/tools/tool.js').ToolSchema<any>} tool
* @returns {string}
* @returns {string[]}
*/
function formatToolForReadme(tool) {
const lines = /** @type {string[]} */ ([]);
lines.push(`<!-- NOTE: This has been generated via ${path.basename(__filename)} -->\n\n`);
lines.push(`- **${tool.name}**\n`);
lines.push(` - Title: ${tool.title}\n`);
lines.push(` - Description: ${tool.description}\n`);
lines.push(`<!-- NOTE: This has been generated via ${path.basename(__filename)} -->`);
lines.push(``);
lines.push(`- **${tool.name}**`);
lines.push(` - Title: ${tool.title}`);
lines.push(` - Description: ${tool.description}`);
const inputSchema = /** @type {any} */ (zodToJsonSchema(tool.inputSchema || {}));
const requiredParams = inputSchema.required || [];
if (inputSchema.properties && Object.keys(inputSchema.properties).length) {
lines.push(` - Parameters:\n`);
lines.push(` - Parameters:`);
Object.entries(inputSchema.properties).forEach(([name, param]) => {
const optional = !requiredParams.includes(name);
const meta = /** @type {string[]} */ ([]);
@ -102,55 +100,94 @@ function formatToolForReadme(tool) {
meta.push(param.type);
if (optional)
meta.push('optional');
lines.push(` - \`${name}\` ${meta.length ? `(${meta.join(', ')})` : ''}: ${param.description}\n`);
lines.push(` - \`${name}\` ${meta.length ? `(${meta.join(', ')})` : ''}: ${param.description}`);
});
} else {
lines.push(` - Parameters: None\n`);
lines.push(` - Parameters: None`);
}
lines.push(` - Read-only: **${tool.type === 'readOnly'}**\n`);
lines.push('\n');
return lines.join('');
lines.push(` - Read-only: **${tool.type === 'readOnly'}**`);
lines.push('');
return lines;
}
async function updateReadme() {
/**
* @param {string} content
* @param {string} startMarker
* @param {string} endMarker
* @param {string[]} generatedLines
* @returns {Promise<string>}
*/
async function updateSection(content, startMarker, endMarker, generatedLines) {
const startMarkerIndex = content.indexOf(startMarker);
const endMarkerIndex = content.indexOf(endMarker);
if (startMarkerIndex === -1 || endMarkerIndex === -1)
throw new Error('Markers for generated section not found in README');
return [
content.slice(0, startMarkerIndex + startMarker.length),
'',
generatedLines.join('\n'),
'',
content.slice(endMarkerIndex),
].join('\n');
}
/**
* @param {string} content
* @returns {Promise<string>}
*/
async function updateTools(content) {
console.log('Loading tool information from compiled modules...');
// Count the tools processed
const totalTools = Object.values(categories).flat().length;
console.log(`Found ${totalTools} tools`);
const generatedLines = /** @type {string[]} */ ([]);
for (const [category, categoryTools] of Object.entries(categories)) {
generatedLines.push(`<details>\n<summary><b>${category}</b></summary>\n\n`);
generatedLines.push(`<details>\n<summary><b>${category}</b></summary>`);
generatedLines.push('');
for (const tool of categoryTools)
generatedLines.push(formatToolForReadme(tool.schema));
generatedLines.push(`</details>\n\n`);
generatedLines.push(...formatToolForReadme(tool.schema));
generatedLines.push(`</details>`);
generatedLines.push('');
}
const startMarker = `<!--- Tools generated by ${path.basename(__filename)} -->`;
const endMarker = `<!--- End of tools generated section -->`;
return updateSection(content, startMarker, endMarker, generatedLines);
}
/**
* @param {string} content
* @returns {Promise<string>}
*/
async function updateOptions(content) {
console.log('Listing options...');
const output = execSync('node cli.js --help');
const lines = output.toString().split('\n');
const firstLine = lines.findIndex(line => line.includes('--version'));
lines.splice(0, firstLine + 1);
const lastLine = lines.findIndex(line => line.includes('--help'));
lines.splice(lastLine);
const startMarker = `<!--- Options generated by ${path.basename(__filename)} -->`;
const endMarker = `<!--- End of options generated section -->`;
return updateSection(content, startMarker, endMarker, [
'```',
'> npx @playwright/mcp@latest --help',
...lines,
'```',
]);
}
async function updateReadme() {
const readmePath = path.join(path.dirname(__filename), '..', 'README.md');
const readmeContent = await fs.promises.readFile(readmePath, 'utf-8');
const startMarker = readmeContent.indexOf(kStartMarker);
const endMarker = readmeContent.indexOf(kEndMarker);
if (startMarker === -1 || endMarker === -1)
throw new Error('Markers for generated section not found in README');
const newReadmeContent = [
readmeContent.slice(0, startMarker),
kStartMarker + '\n\n',
generatedLines.join(''),
kEndMarker,
readmeContent.slice(endMarker + kEndMarker.length),
].join('');
// Write updated README
await fs.promises.writeFile(readmePath, newReadmeContent, 'utf-8');
const withTools = await updateTools(readmeContent);
const withOptions = await updateOptions(withTools);
await fs.promises.writeFile(readmePath, withOptions, 'utf-8');
console.log('README updated successfully');
}
// Run the update
updateReadme().catch(err => {
console.error('Error updating README:', err);
process.exit(1);