diff --git a/README.md b/README.md index 05ec368..b4a6e3f 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,7 @@ The Playwright MCP server supports the following command-line options: - `--executable-path `: Path to the browser executable - `--headless`: Run browser in headless mode (headed by default) - `--port `: Port to listen on for SSE transport +- `--host `: Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces. - `--user-data-dir `: Path to the user data directory - `--vision`: Run server that uses screenshots (Aria snapshots are used by default) @@ -89,8 +90,7 @@ Playwright MCP will launch the browser with the new profile, located at All the logged in information will be stored in that profile, you can delete it between sessions if you'd like to clear the offline state. - -### Running headless browser (Browser without GUI). +### Running headless browser (Browser without GUI) This mode is useful for background or batch operations. @@ -129,6 +129,24 @@ And then in MCP client config, set the `url` to the SSE endpoint: } ``` +When running in a remote server, you can use the `--host` flag to bind the server to `0.0.0.0` to make it accessible from outside. + +```bash +npx @playwright/mcp@latest --port 8931 --host 0.0.0.0` +``` + +In MCP client config, `$server-ip` is the IP address of the server: + +```js +{ + "mcpServers": { + "playwright": { + "url": "http://{$server-ip}:8931/sse" + } + } +} +``` + ### Docker **NOTE:** The Docker implementation only supports headless chromium at the moment. diff --git a/src/program.ts b/src/program.ts index 17e074a..c678c37 100644 --- a/src/program.ts +++ b/src/program.ts @@ -38,6 +38,7 @@ program .option('--executable-path ', 'Path to the browser executable.') .option('--headless', 'Run browser in headless mode, headed by default') .option('--port ', 'Port to listen on for SSE transport.') + .option('--host ', 'Host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.') .option('--user-data-dir ', 'Path to the user data directory') .option('--vision', 'Run server that uses screenshots (Aria snapshots are used by default)') .action(async options => { @@ -53,7 +54,7 @@ program setupExitWatchdog(serverList); if (options.port) { - startSSEServer(+options.port, serverList); + startSSEServer(+options.port, options.host || 'localhost', serverList); } else { const server = await serverList.create(); await server.connect(new StdioServerTransport()); @@ -74,7 +75,7 @@ function setupExitWatchdog(serverList: ServerList) { program.parse(process.argv); -function startSSEServer(port: number, serverList: ServerList) { +function startSSEServer(port: number, host: string, serverList: ServerList) { const sessions = new Map(); const httpServer = http.createServer(async (req, res) => { if (req.method === 'POST') { @@ -110,7 +111,7 @@ function startSSEServer(port: number, serverList: ServerList) { } }); - httpServer.listen(port, () => { + httpServer.listen(port, host, () => { const address = httpServer.address(); assert(address, 'Could not bind server socket'); let url: string; @@ -120,7 +121,7 @@ function startSSEServer(port: number, serverList: ServerList) { const resolvedPort = address.port; let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`; if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]') - resolvedHost = 'localhost'; + resolvedHost = host === 'localhost' ? 'localhost' : resolvedHost; url = `http://${resolvedHost}:${resolvedPort}`; } console.log(`Listening on ${url}`);