Uvilo OS Playwright MCP Research
Problem
The LibreChat instance on Railway needs web browsing capabilities equivalent to the “Claude in Chrome” setup on the local Mac. The AI assistant should be able to navigate to URLs, read page content, interact with web elements, and extract information from JavaScript-heavy sites.
Candidate: Microsoft Playwright MCP (@playwright/mcp)
The clear winner for this use case. It’s the official Microsoft Playwright MCP server, actively maintained (GitHub: microsoft/playwright-mcp), and purpose-built for giving LLMs browser control.
Key Features
- Accessibility snapshot mode (default): Uses the browser’s accessibility tree — a structured, text-based representation of the page — instead of screenshots. Fast, token-efficient, no vision model needed.
- Vision mode (optional): Takes screenshots for complex visual layouts. Requires a vision-capable model.
- Full Playwright API: Navigate, click, type, take screenshots, handle dialogs, inspect network activity, run JavaScript, manage tabs, upload/download files.
- Headless Chromium in Docker: Official image at
mcr.microsoft.com/playwright/mcpships with Chromium and all dependencies pre-installed. - HTTP transport: Supports
--portflag to expose an HTTP/MCP endpoint that any MCP client can connect to via URL.
Tools Provided (Core)
| Tool | Description |
|---|---|
browser_navigate | Navigate to a URL |
browser_click | Click an element by accessibility ref |
browser_type | Type text into form fields |
browser_snapshot | Get accessibility tree snapshot of current page |
browser_screenshot | Take a screenshot (vision mode) |
browser_tab_* | Tab management (new, close, select, list) |
browser_network_requests | Inspect network activity |
browser_console_messages | Read browser console |
browser_file_upload | Upload files |
browser_pdf_save | Save page as PDF |
browser_evaluate | Run JavaScript in page context |
Deployment Options Evaluated
Option A: Sidecar Service on Railway (Selected ✅)
Deploy the Playwright MCP Docker image as a separate Railway service in the same project. LibreChat connects to it over Railway’s private network via HTTP URL.
Architecture:
Pros:
- Clean separation of concerns — browser process is isolated
- Uses official pre-built Docker image (zero custom build)
- Independent resource allocation (memory/CPU)
- LibreChat container stays lean
- Easy to restart/update independently
Cons:
- Adds a service to the Railway project (cost ~$5-10/mo idle)
- Requires Railway private networking (already available)
- Need to configure
mcpSettings.allowedDomainsin librechat.yaml
Railway Service Configuration:
- Service name:
playwright-mcp - Image:
mcr.microsoft.com/playwright/mcp - Custom Start Command:
node cli.js --headless --browser chromium --no-sandbox --port 8931 --host 0.0.0.0(Railway’s start command overrides the image’s ENTRYPOINT in exec form — no separate entrypoint field) - Serverless: Enabled — service sleeps after 10 min of inactivity, wakes on private network traffic from LibreChat. Cold start ~5–15s (Chromium is heavy); covered by
initTimeout: 30000in librechat.yaml. - No public domain needed (internal only)
- Memory: 512MB–1GB recommended
LibreChat config (librechat.yaml):
Note: SSE vs Streamable-HTTP — The
/mcpendpoint (streamable-http) requires OAuth per the MCP spec. Since the Playwright service is on Railway’s private network, the user’s browser can’t reach it for the OAuth redirect flow. The/sseendpoint is the legacy transport that doesn’t require OAuth and works for server-to-server connections.Note: Allowed Hosts — The
@playwright/mcpimage has a separate access control list (--allowed-hosts) that defaults to the bound host (localhost). Even with--host 0.0.0.0, connections from Railway private network IPs are rejected with HTTP 403 unless--allowed-hosts *is set. LibreChat misinterprets this 403 as an OAuth error.⚠️ Env var name bug (microsoft/playwright-mcp#1373): The README documents
PLAYWRIGHT_MCP_ALLOWED_HOSTSbut the actual source code readsPLAYWRIGHT_MCP_ALLOWED_HOSTNAMES. Use the correct name on Railway:PLAYWRIGHT_MCP_HOST=0.0.0.0andPLAYWRIGHT_MCP_ALLOWED_HOSTNAMES=*. CLI flags may be swallowed by the Docker ENTRYPOINT.
Option B: Embedded via npx (Rejected)
Add Chromium to the custom LibreChat Docker image (ghcr.io/erikdakoda/librechat-git)
and run Playwright MCP as a stdio process alongside the other MCP servers.
Rejected because:
- Docker image becomes much larger (+400MB for Chromium)
- Browser and LibreChat compete for memory in one container
- Every redeploy must re-download/build the larger image
- Harder to debug browser issues vs app issues
Option C: Cloud Browser Service (Deferred)
Use a hosted browser service like Browser Use Cloud, Browserbase, or Browserless. These provide remote browser instances via API/MCP.
Deferred because:
- Monthly cost ($29+/mo minimum)
- External dependency and latency
- Not needed for current scale
Worth revisiting if anti-detection or multi-browser concurrency becomes necessary.
Implementation Steps
- Create new Railway service
playwright-mcpin theuvilo-libre-chatproject - Set Docker image to
mcr.microsoft.com/playwright/mcp - Set Custom Start Command to:
node cli.js --headless --browser chromium --no-sandbox --port 8931 --host 0.0.0.0(this overrides the image’s ENTRYPOINT — no separate entrypoint field in Railway) - Enable Serverless in Settings → Deploy (sleeps after 10 min, wakes on traffic)
- Verify it starts and is reachable on the private network
- Update
librechat.yamlon dev branch with the new MCP server config - Deploy config:
cp .../Assistant/Configs/librechat.yaml /workspace/librechat/ - Redeploy LibreChat (
railway redeploy --service LibreChat) - Test: ask the assistant to navigate to a URL and read the page
Estimated effort: 30–60 minutes
Key Decision: Railway Private Networking
Railway services in the same project communicate over an encrypted Wireguard tunnel
using internal DNS names (<service-name>.railway.internal). No public domain is needed
for the Playwright MCP service — LibreChat connects directly over the private network.
Important: internal domains require mcpSettings.allowedDomains in librechat.yaml
to whitelist the internal hostname, or LibreChat will refuse to connect.
References
- GitHub: https://github.com/microsoft/playwright-mcp
- npm: https://www.npmjs.com/package/@playwright/mcp
- Docker:
mcr.microsoft.com/playwright/mcp - Railway Private Networking: https://docs.railway.com/networking/private-networking
- LibreChat MCP Config: https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/mcp_servers
- LibreChat MCP Settings (allowedDomains): https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/config