From ab20175826066dd8b6591486f2c82e72daf1fd7c Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 13 May 2025 15:52:30 -0700 Subject: [PATCH] chore: generate readme options (#411) --- README.md | 81 ++++++++++++++++++----------- utils/update-readme.js | 115 +++++++++++++++++++++++++++-------------- 2 files changed, 127 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index 4e265eb..dd1db2d 100644 --- a/README.md +++ b/README.md @@ -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 `: 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 `: 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 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 `: Comma-separated list of capabilities to enable, possible values: tabs, pdf, history, wait, files, install. Default is all. -- `--cdp-endpoint `: CDP endpoint to connect to -- `--config `: Path to the configuration file -- `--device`: Emulate mobile device -- `--executable-path `: Path to the browser executable -- `--headless`: Run browser in headless mode (headed by default) -- `--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 to listen on for SSE transport -- `--proxy-bypass `: Comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"' -- `--proxy-server `: Proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"' -- `--storage-state `: Path to the storage state file for isolated sessions -- `--user-agent `: Specify user agent string -- `--user-data-dir `: Path to the user data directory. If not specified, a temporary directory will be created -- `--viewport-size `: Specify browser viewport size in pixels, for example "1280, 720" -- `--vision`: Run server that uses screenshots (Aria snapshots are used by default) + + +``` +> npx @playwright/mcp@latest --help + --allowed-origins semicolon-separated list of origins to allow the + browser to request. Default is to allow all. + --blocked-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 or chrome channel to use, possible + values: chrome, firefox, webkit, msedge. + --caps comma-separated list of capabilities to enable, + possible values: tabs, pdf, history, wait, files, + install. Default is all. + --cdp-endpoint CDP endpoint to connect to. + --config path to the configuration file. + --device device to emulate, for example: "iPhone 15" + --executable-path path to the browser executable. + --headless run browser in headless mode, headed by default + --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 to the directory for output files. + --port port to listen on for SSE transport. + --proxy-bypass comma-separated domains to bypass proxy, for + example ".com,chromium.org,.domain.com" + --proxy-server specify proxy server, for example + "http://myproxy:3128" or "socks5://myproxy:8080" + --storage-state path to the storage state file for isolated + sessions. + --user-agent specify user agent string + --user-data-dir path to the user data directory. If not + specified, a temporary directory will be created. + --viewport-size specify browser viewport size in pixels, for + example "1280, 720" + --vision Run server that uses screenshots (Aria snapshots + are used by default) +``` + + ### 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. - +
Interactions @@ -728,4 +748,5 @@ X Y coordinate space, based on the provided screenshot.
- + + diff --git a/utils/update-readme.js b/utils/update-readme.js index fde7864..90d8c4b 100644 --- a/utils/update-readme.js +++ b/utils/update-readme.js @@ -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 = ``; -const kEndMarker = ``; - /** * @param {import('../src/tools/tool.js').ToolSchema} tool - * @returns {string} + * @returns {string[]} */ function formatToolForReadme(tool) { const lines = /** @type {string[]} */ ([]); - lines.push(`\n\n`); - lines.push(`- **${tool.name}**\n`); - lines.push(` - Title: ${tool.title}\n`); - lines.push(` - Description: ${tool.description}\n`); + lines.push(``); + 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} + */ +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} + */ +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(`
\n${category}\n\n`); - + generatedLines.push(`
\n${category}`); + generatedLines.push(''); for (const tool of categoryTools) - generatedLines.push(formatToolForReadme(tool.schema)); - - generatedLines.push(`
\n\n`); + generatedLines.push(...formatToolForReadme(tool.schema)); + generatedLines.push(`
`); + generatedLines.push(''); } + const startMarker = ``; + const endMarker = ``; + return updateSection(content, startMarker, endMarker, generatedLines); +} + +/** + * @param {string} content + * @returns {Promise} + */ +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 = ``; + const endMarker = ``; + 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);