infra/scripts/workstation/playwright/playwright-mcp@.service

36 lines
2 KiB
SYSTEMD
Raw Normal View History

workstation: per-user playwright browser MCP for all users, reproducible from git Viktor asked that the playwright browser MCP be available for every devvm user in every directory, with each user running their own server and multiple concurrent sessions per user. Before this, playwright was hand-set-up per user (~/.config/systemd/user/ playwright-mcp.service on 8931/8932/8933) and only wizard was actually wired — emo's and anca's servers ran but their ~/.claude.json had no playwright entry, so their Claude never connected. None of it was reproducible from git (units, refresh script, and the Vault snapshot token lived only in user homes), so a devvm rebuild would silently lose it. This makes it reproducible and fixes the unwired users: - roster_engine.py: sticky per-user PLAYWRIGHT_PORT (PLAYWRIGHT_BASE_PORT=8931, allocated for every roster user incl. the admin), emitted in the derive JSON. - scripts/workstation/playwright/: system-level TEMPLATE units (playwright-mcp@.service + playwright-snapshot-refresh@.{service,timer}, User=%i — system manager, so no systemd --user / linger) + the refresh script. @playwright/mcp pinned to 0.0.76 (avoids the @latest silent-fleet-roll footgun, same rationale as T3_PIN). - setup-devvm.sh: install the templates + script (9e); stage the chrome-service snapshot bearer token from Vault to a root file (8c) — the hourly root reconcile has no Vault token, mirrors the Claude OAuth staging in 8a. - t3-provision-users.sh: install_playwright() (ALL tiers incl. admin) writes PLAYWRIGHT_PORT, seeds the token if-absent, wires the user-scope ~/.claude.json by running `claude mcp add` AS the user (clobber-proof + if-absent, so it fixes existing/new/admin without rewriting a populated config), and enable --now's the instances (idempotent, never restarts a running server). Also hardened the section-1 *.env scan to skip the new playwright-*.env files (no T3_PORT -> grep no-match would abort under set -e -o pipefail). - Docs: chrome-service-snapshot runbook (new Provisioning section + system-unit commands), multi-tenancy.md, and the 2026-06-07 plan Task 2.3. Supersedes the hand-made per-user --user units (one-time idle-gated migration to follow on the live host). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 20:33:47 +00:00
[Unit]
# Per-user isolated playwright-mcp HTTP server — the browser MCP each user's
# Claude Code sessions connect to (user-scope `.claude.json` entry "playwright"
# -> http://localhost:<PLAYWRIGHT_PORT>/mcp). System-level TEMPLATE unit (one
# committed file, one instance per OS user: playwright-mcp@<user>.service), so
# it is reproducible from git and root-manageable WITHOUT systemd --user / linger.
# Installed to /etc/systemd/system by setup-devvm.sh; enabled per-user by
# t3-provision-users.sh. Supersedes the hand-made ~/.config/systemd/user units.
Description=Per-user isolated playwright-mcp HTTP server (%i)
After=network-online.target playwright-snapshot-refresh@%i.service
Wants=network-online.target playwright-snapshot-refresh@%i.service
[Service]
Type=simple
User=%i
# PLAYWRIGHT_PORT is written per-user by t3-provision-users.sh from roster_engine
# (PLAYWRIGHT_BASE_PORT, sticky allocation). Required (no `-`): a missing port
# file should fail loudly rather than start npx with an empty --port.
EnvironmentFile=/etc/t3-serve/playwright-%i.env
Restart=on-failure
RestartSec=5
# --isolated: each MCP HTTP connection (= each Claude Code session) gets a fresh
# ephemeral BrowserContext, so a single user's concurrent sessions never share
# tabs. --storage-state seeds each context from the hourly cookie snapshot
# harvested from in-cluster chrome-service (warm logged-in state).
# Version PINNED (see the T3_PIN rationale in setup-devvm.sh): @latest re-resolves
# on every restart, so an upstream breaking release would silently roll the
# whole fleet. Bump deliberately in git. %h is NOT used (it resolves to /root
# in a system unit even with User=); the home path is spelled out as /home/%i.
ExecStart=/usr/bin/npx -y @playwright/mcp@0.0.76 --port ${PLAYWRIGHT_PORT} --host localhost --headless --browser chrome --isolated --storage-state /home/%i/.cache/playwright-shared-storage-state.json
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target