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: 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 <!--- Options generated by update-readme.js -->
- `--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: > npx @playwright/mcp@latest --help
- `chrome`, `firefox`, `webkit`, `msedge` --allowed-origins <origins> semicolon-separated list of origins to allow the
- Chrome channels: `chrome-beta`, `chrome-canary`, `chrome-dev` browser to request. Default is to allow all.
- Edge channels: `msedge-beta`, `msedge-canary`, `msedge-dev` --blocked-origins <origins> semicolon-separated list of origins to block the
- Default: `chrome` browser from requesting. Blocklist is evaluated
- `--caps <caps>`: Comma-separated list of capabilities to enable, possible values: tabs, pdf, history, wait, files, install. Default is all. before allowlist. If used without the allowlist,
- `--cdp-endpoint <endpoint>`: CDP endpoint to connect to requests not matching the blocklist are still
- `--config <path>`: Path to the configuration file allowed.
- `--device`: Emulate mobile device --block-service-workers block service workers
- `--executable-path <path>`: Path to the browser executable --browser <browser> browser or chrome channel to use, possible
- `--headless`: Run browser in headless mode (headed by default) values: chrome, firefox, webkit, msedge.
- `--host <host>`: Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces --caps <caps> comma-separated list of capabilities to enable,
- `--ignore-https-errors`: Ignore https errors possible values: tabs, pdf, history, wait, files,
- `--isolated`: Keep the browser profile in memory, do not save it to disk install. Default is all.
- `--no-image-responses`: Do not send image responses to the client --cdp-endpoint <endpoint> CDP endpoint to connect to.
- `--no-sandbox`: Disable the sandbox for all process types that are normally sandboxed --config <path> path to the configuration file.
- `--output-dir`: Directory for output files --device <device> device to emulate, for example: "iPhone 15"
- `--port <port>`: Port to listen on for SSE transport --executable-path <path> path to the browser executable.
- `--proxy-bypass <bypass>`: Comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"' --headless run browser in headless mode, headed by default
- `--proxy-server <proxy>`: Proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"' --host <host> host to bind server to. Default is localhost. Use
- `--storage-state <path>`: Path to the storage state file for isolated sessions 0.0.0.0 to bind to all interfaces.
- `--user-agent <ua string>`: Specify user agent string --ignore-https-errors ignore https errors
- `--user-data-dir <path>`: Path to the user data directory. If not specified, a temporary directory will be created --isolated keep the browser profile in memory, do not save
- `--viewport-size <size>`: Specify browser viewport size in pixels, for example "1280, 720" it to disk.
- `--vision`: Run server that uses screenshots (Aria snapshots are used by default) --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 ### 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 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. X Y coordinate space, based on the provided screenshot.
<!--- Generated by update-readme.js --> <!--- Tools generated by update-readme.js -->
<details> <details>
<summary><b>Interactions</b></summary> <summary><b>Interactions</b></summary>
@ -728,4 +748,5 @@ X Y coordinate space, based on the provided screenshot.
</details> </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 testTools from '../lib/tools/testing.js';
import visionTools from '../lib/tools/vision.js'; import visionTools from '../lib/tools/vision.js';
import waitTools from '../lib/tools/wait.js'; import waitTools from '../lib/tools/wait.js';
import { execSync } from 'node:child_process';
// Category definitions for tools
const categories = { const categories = {
'Interactions': [ 'Interactions': [
...snapshotTools, ...snapshotTools,
@ -77,24 +77,22 @@ const categories = {
// NOTE: Can be removed when we drop Node.js 18 support and changed to import.meta.filename. // 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 __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 * @param {import('../src/tools/tool.js').ToolSchema<any>} tool
* @returns {string} * @returns {string[]}
*/ */
function formatToolForReadme(tool) { function formatToolForReadme(tool) {
const lines = /** @type {string[]} */ ([]); const lines = /** @type {string[]} */ ([]);
lines.push(`<!-- NOTE: This has been generated via ${path.basename(__filename)} -->\n\n`); lines.push(`<!-- NOTE: This has been generated via ${path.basename(__filename)} -->`);
lines.push(`- **${tool.name}**\n`); lines.push(``);
lines.push(` - Title: ${tool.title}\n`); lines.push(`- **${tool.name}**`);
lines.push(` - Description: ${tool.description}\n`); lines.push(` - Title: ${tool.title}`);
lines.push(` - Description: ${tool.description}`);
const inputSchema = /** @type {any} */ (zodToJsonSchema(tool.inputSchema || {})); const inputSchema = /** @type {any} */ (zodToJsonSchema(tool.inputSchema || {}));
const requiredParams = inputSchema.required || []; const requiredParams = inputSchema.required || [];
if (inputSchema.properties && Object.keys(inputSchema.properties).length) { if (inputSchema.properties && Object.keys(inputSchema.properties).length) {
lines.push(` - Parameters:\n`); lines.push(` - Parameters:`);
Object.entries(inputSchema.properties).forEach(([name, param]) => { Object.entries(inputSchema.properties).forEach(([name, param]) => {
const optional = !requiredParams.includes(name); const optional = !requiredParams.includes(name);
const meta = /** @type {string[]} */ ([]); const meta = /** @type {string[]} */ ([]);
@ -102,55 +100,94 @@ function formatToolForReadme(tool) {
meta.push(param.type); meta.push(param.type);
if (optional) if (optional)
meta.push('optional'); meta.push('optional');
lines.push(` - \`${name}\` ${meta.length ? `(${meta.join(', ')})` : ''}: ${param.description}\n`); lines.push(` - \`${name}\` ${meta.length ? `(${meta.join(', ')})` : ''}: ${param.description}`);
}); });
} else { } else {
lines.push(` - Parameters: None\n`); lines.push(` - Parameters: None`);
} }
lines.push(` - Read-only: **${tool.type === 'readOnly'}**\n`); lines.push(` - Read-only: **${tool.type === 'readOnly'}**`);
lines.push('\n'); lines.push('');
return lines.join(''); 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...'); console.log('Loading tool information from compiled modules...');
// Count the tools processed
const totalTools = Object.values(categories).flat().length; const totalTools = Object.values(categories).flat().length;
console.log(`Found ${totalTools} tools`); console.log(`Found ${totalTools} tools`);
const generatedLines = /** @type {string[]} */ ([]); const generatedLines = /** @type {string[]} */ ([]);
for (const [category, categoryTools] of Object.entries(categories)) { 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) for (const tool of categoryTools)
generatedLines.push(formatToolForReadme(tool.schema)); generatedLines.push(...formatToolForReadme(tool.schema));
generatedLines.push(`</details>`);
generatedLines.push(`</details>\n\n`); 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 readmePath = path.join(path.dirname(__filename), '..', 'README.md');
const readmeContent = await fs.promises.readFile(readmePath, 'utf-8'); const readmeContent = await fs.promises.readFile(readmePath, 'utf-8');
const startMarker = readmeContent.indexOf(kStartMarker); const withTools = await updateTools(readmeContent);
const endMarker = readmeContent.indexOf(kEndMarker); const withOptions = await updateOptions(withTools);
if (startMarker === -1 || endMarker === -1) await fs.promises.writeFile(readmePath, withOptions, 'utf-8');
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');
console.log('README updated successfully'); console.log('README updated successfully');
} }
// Run the update
updateReadme().catch(err => { updateReadme().catch(err => {
console.error('Error updating README:', err); console.error('Error updating README:', err);
process.exit(1); process.exit(1);