Lets Claude inspect and debug Progressive Web Apps in your actual browser profile, not a sterile automated tab. It reads service worker lifecycle state, CacheStorage contents, installability diagnostics, IndexedDB, and framework internals (React fiber, Vue reactive state, Svelte components, Redux/Zustand stores) as structured data. Works by injecting a content script and page-world hooks into your live browser, then exposing operations like DOM inspection, network capture, interaction replay, and version skew analysis over MCP. Reach for it when debugging "why won't my service worker update" or "why is my cache stale" questions that require seeing your logged-in session state, extensions, and cookies. Complements chrome-devtools-mcp by targeting the runtime state CDP can't reach. Currently verified on Linux with Chrome.
An AI-native debugging layer for PWAs and modern web apps. It lets an AI agent (e.g. Claude Code via MCP) see and act on your live, logged-in browser the way a developer with full DevTools open would — DOM, console, network, framework state, store state, service workers, caches, and direct interaction — as structured data the model consumes natively.
It's built for the questions developers actually search for and that Chrome DevTools makes you assemble by hand: why won't my service worker update? why is my cache stale? why won't my PWA install? why does this component have the wrong state? — answered by an agent reading the runtime directly, against your real browser profile (extensions, auth, and all), not a sterile automated tab.
The PWA failures developers actually search for, read straight from your live runtime:
All read from your real, logged-in profile — service-worker, cache, and extension state included — which chrome-devtools-mcp's sterile automated Chrome can't see.
The goal is to eliminate the "user is the AI's eyes and hands" loop. Today, debugging a PWA with AI usually means the human copy/pastes DOM snippets, describes console errors, screenshots UI state, and hand-executes clicks. This project replaces that with direct, structured access.
Status: working on Linux. The full MCP→IPC→native-host→service-worker→page-world round-trip is live, and a broad debugging surface is shipped:
- Capture — console / network / error / DOM-mutation / lifecycle, with persistent ring buffers + disk spill.
- Framework introspection — React, Vue, Svelte, and Solid (component/element trees, state, find-by-text/role).
- Store introspection — Redux, Zustand, Pinia, and Jotai (read, subscribe, dispatch).
- Interaction + touch gestures — click, fill, submit, hover, focus/blur, select, key/type, drag, scroll, swipe, tap, double-tap, long-press, pinch.
- Library-popup capture/replay — WalletConnect / SDK modals: record, replay, tail, failure correlation.
- Replay & source maps — rrweb
session_record/session_replay,source_map_resolve.- Browser launcher — one-call
pdl_launch_browserwithchrome-devtools-mcpcoexistence.- PWA Runtime Diagnostics — service-worker lifecycle + versions, CacheStorage contents + age, installability gaps, a live capability matrix, IndexedDB/web-storage inspection, update-propagation / version-skew analysis, and a one-shot runtime-state snapshot.
Verified on Linux (the full suite live-tested against a real PWA). macOS/Windows code paths are implemented with unit coverage but still need real-machine retest (help wanted). Firefox is not supported (it doesn't speak CDP).
chrome-devtools-mcpGoogle's chrome-devtools-mcp gives an AI Chrome DevTools Protocol access (DOM, console, network, screenshots). That covers a lot.
pwa-debug-layer is complementary — it targets the things CDP can't reach:
chrome-devtools-mcp spawns a fresh automated Chrome — which shows "controlled by automated test software," blocks extension loading, and has none of your auth/session state, so authenticated apps are hard to debug (the extensions/auth gap, #265). pwa-debug-layer's default existing mode attaches to your normal profile, with its real cookies, extensions, and service-worker state — exactly the real-profile SW/extension visibility people keep asking Google for (#1173, #96).__REACT_DEVTOOLS_GLOBAL_HOOK__, __vue_app__, _vnode, etc.). CDP can't see these.initScript-on-next-nav.The two are designed to coexist: install both, the AI uses each for what it does best, with zero tool-surface duplication.
┌──────────────────┐ MCP (stdio) ┌──────────────────────────┐
│ Claude Code │ ◄───────────► │ Native Messaging Host │
│ (or any MCP │ │ - MCP server │
│ client) │ │ - Ring buffers │
└──────────────────┘ │ - Replay/snapshot store │
└────────────┬─────────────┘
│ Native Messaging
│ (JSON over stdio)
▼
┌──────────────────────────┐
│ Extension Service Worker│
│ - chrome.debugger (CDP) │
│ - Tab/router │
└────────────┬─────────────┘
│
┌────────────────────────┼────────────────────────┐
▼ ▼ ▼
┌────────────────────┐ ┌────────────────────┐ ┌────────────────────┐
│ Content Script │ │ Page-World Script │ │ DevTools Panel │
│ (isolated world) │ │ (MAIN world) │ │ (planned) │
│ - DOM observe │ │ - React/Vue hooks │ │ - Human inspector │
│ - Action exec │ │ - fetch/XHR patch │ │ of AI session │
│ - Bridge to SW │ │ - Bus/RxJS taps │ │ │
└────────────────────┘ └────────────────────┘ └────────────────────┘
└────────── live page (the PWA being debugged) ─────────┘
Three components, one installable unit:
Each does what only it can. See docs/PLAN.md for the full design.
Chromium-family only, sideloaded. Tested against:
.deb / .rpm).deb)macOS Application Support paths and Windows HKCU-registry registration are implemented and have unit-test coverage; the manual round-trip retest currently runs on Linux.
If you installed your browser via snap (e.g. snap install chromium on Ubuntu), it will not work with pwa-debug-layer.
Why: snap's home interface allows the browser to read files in $HOME but blocks exec of any binary whose resolved path crosses a hidden directory (~/.nvm/..., ~/.config/...). The native messaging host launcher and the node binary it invokes both live under hidden paths in a normal install, so spawn fails with Permission denied and the service worker reports Native host has exited. There is no fix on the extension/host side that doesn't require copying ~125 MB of node into a non-hidden install dir per registration; not worth the install bloat for a setup most distros let you avoid.
What to do: install your Chromium-family browser from a native package source instead:
apt install chromium from the universe repo if you've enabled the non-snap source, or apt install brave-browser / microsoft-edge-stable from their respective .deb repos. The Chromium tarball from chromium.org also works.dnf install chromium is non-snap by default.pacman -S chromium.Flatpak browsers are detected and have a manifest written, but flatpak confinement may also block exec — if it fails, run flatpak override --user --filesystem=host <app-id> and retry.
git clone https://github.com/<your-fork>/pwa-debug-layer
cd pwa-debug-layer
pnpm install
pnpm build # builds packages/host/dist/main.js and packages/extension/dist/
pnpm test # full workspace unit suite (shared + host + extension)
For Claude Code, add to your .mcp.json (project-scoped) or ~/.claude/mcp.json (global):
{
"mcpServers": {
"pwa-debug": {
"type": "stdio",
"command": "node",
"args": ["/absolute/path/to/pwa-debug-layer/packages/host/dist/main.js"]
}
}
}
Restart Claude Code so it picks up the server.
chrome://extensions (or brave://extensions, etc.) in your browser.packages/extension/dist/.[pwa-debug/sw] id=<id> on every boot.The host registration is AI-driven. Six MCP tools are exposed for setup; Claude calls them via a guided flow:
Set up pwa-debug. The extension ID is
<your-id>(or omit the ID and Claude can fetch it from the SW console if you also havechrome-devtools-mcpinstalled.)
Claude will:
host_status to see what's already registered.host_register_extension(id) — this writes a per-browser native-messaging manifest into every detected install (Chromium-family native, macOS Application Support, Windows HKCU registry), and drops a launcher script with an absolute node path (so the host spawns under sandboxed PATH environments).chrome://extensions.host_status again to confirm the manifest is on disk and the SW is connecting.When the round-trip works you'll see in the SW console (Inspect views: service worker on the extension card):
[pwa-debug/sw] connected to host
[pwa-debug/sw] pong …
[pwa-debug/sw] hello … (5s after connect — host-pushed message proving bidirectional flow)
chrome-devtools-mcp coexistencepwa-debug and chrome-devtools-mcp are two separate MCP servers that share one browser. pwa-debug launches (or attaches to) a Chromium browser with a live remote-debugging port; chrome-devtools-mcp attaches to that same port over CDP. No proxying, no version coupling.
Register both with your client. For Claude Code:
# pwa-debug (this project) — adjust the path to your checkout
claude mcp add pwa-debug --scope user -- node /absolute/path/to/pwa-debug-layer/packages/host/dist/main.js
# chrome-devtools-mcp (optional but recommended) — runs via npx, no global install
# The --browserUrl port MUST match the port pwa-debug launches on (launch.defaultPort, default 9222).
claude mcp add chrome-devtools --scope user -- npx -y chrome-devtools-mcp@latest --browserUrl http://127.0.0.1:9222
pwa-debug as a Claude Code pluginThe repo ships a .claude-plugin/ manifest whose MCP server runs the published npm package (npx -y @aryanduntley/pwa-debug@latest), so the plugin installs with no clone and no build — and picks up updates with /reload-plugins, no full restart:
# Add the marketplace straight from GitHub (no checkout needed)
/plugin marketplace add aryanduntley/pwa-debug-layer
# Install + enable the pwa-debug plugin
/plugin install pwa-debug@pwa-debug
# Bring the MCP server up with no restart:
/reload-plugins
No build needed. The plugin's MCP entry is
npx -y @aryanduntley/pwa-debug@latest, which fetches the prebuilt host (and its bundled extension) from npm. Working on a local clone instead? Skip the plugin and register your own build directly:claude mcp add pwa-debug --scope user -- node /absolute/path/to/pwa-debug-layer/packages/host/dist/main.js.
chrome-devtools-mcpis still separate. The plugin declares only thepwa-debughost —chrome-devtools-mcpstays the optionalclaude mcp add chrome-devtools …above (no version coupling, no owning its launch). The bundledchrome-devtools-coexistenceskill walks you through registering it; with a plugin install, its "make the tools appear" step is/reload-plugins.
Port must match.
chrome-devtools-mcp's--browserUrlhas to point at the exact portpwa-debugopens — thelaunch.defaultPortsetting (default9222), or the active port from a currentpdl_launch_browser. Rather than hand-write this, let Claude callpdl_register_chrome_devtools, which runs theclaude mcp addabove for you pinned to the right port (andpdl_check_setupflags a registration that is unpinned — no--browserUrl, so it spawns its own isolated browser — or pointed at the wrong port).
Mind the (re)connect. A registration added or changed via
claude mcp addonly takes effect when the server (re)connects. Fastest path: open/mcpand reconnectchrome-devtools— this reloads it from the current registration (verified to pick up a changed--browserUrl) with no full restart and no context loss. If the client won't load it that way, fully restart Claude Code as a fallback. If instead you installchrome-devtools-mcpas a plugin, run/reload-plugins. The bundledchrome-devtools-coexistenceskill walks Claude through all paths and, for the full-restart fallback, hands you a context note to paste back afterward.
The host has no
install/servesubcommand —dist/main.jsauto-detects its mode: launched by Chrome (argv starts withchrome-extension://) it runs as the native-messaging host; launched by your MCP client it runs as the MCP server.
Then let Claude drive setup and launch:
pdl_check_setup — reports { ok, gaps[], recommendations[] }: whether chrome-devtools-mcp is registered (read from the claude CLI) and pinned with a --browserUrl at the right debug port — it flags both an unpinned registration (which would spawn its own isolated browser instead of attaching to yours) and one pointed at the wrong port; the host manifest is installed (including per active sandbox profile); the extension dist is present; and an extension ID is registered. Follow its next_steps to close any gap.pdl_register_chrome_devtools — writes the chrome-devtools-mcp registration for you, pinned to the active/launch.defaultPort port (idempotent; re-points it if it's unpinned or on the wrong port). (Mutates your MCP config — Claude will ask first; a restart/reconnect follows, see above.)pdl_install_extension — copies the extension to ~/Downloads/pwa-debug-extension (or a target you pass) with chrome://extensions "Load unpacked" instructions. (On most browsers a sandbox mode preloads the extension so you can skip this; on branded Google Chrome 142+ preload is impossible — this is the manual install the sandbox launch will point you to.)pdl_launch_browser — launches/attaches a browser with the debug port live and returns the browserUrl to hand to chrome-devtools-mcp. Always launch first: call this before any chrome-devtools-mcp tool, so the browser + debug port exist for it to attach to. Follow its next_steps — they carry the brand/version-specific guidance (preloaded vs. manual Load unpacked, or the modern-Chromium port caveat).pdl_browser_status — shows what's been launched (browser, profile mode, port, pid), re-probes each debug port for liveness, and reports the extension service-worker heartbeat.The validated one-profile recipe (what these tools converge on, verified live on Brave 148):
pdl_launch_browser({ browser: "brave", mode: "sandbox-persistent" })
→ dedicated profile at ~/.pwa-debug/profiles/brave with:
• the native-messaging manifest auto-written into the profile (so the extension connects), and
• the pwa-debug extension preloaded via --load-extension (Brave honors it), and
• --remote-debugging-port live (custom profile dir, so Chromium 136+ opens it).
pdl_register_chrome_devtools() → pins chrome-devtools-mcp at that port
restart / reconnect chrome-devtools-mcp → it attaches to the SAME browser
Now chrome-devtools-mcp (CDP) and pwa-debug (extension → page-world) drive the same tab simultaneously — independent, non-contending channels (the extension uses page-world injection, not chrome.debugger, so there's no CDP-attacher contention).
pdl_launch_browser takes mode (default existing), browser (defaults to your system-default Chromium browser), and port (default 9222).
| Mode | Profile | When to use |
|---|---|---|
existing (default) | Your normal browser profile | Debugging your real browsing session with pwa-debug (your real cookies, extensions, SW/cache state). Degrades gracefully: (a) debug port already live → attaches; (b) browser running without a debug port → opens a new window in the existing session (your extension tools work, chrome-devtools-mcp does not); (c) browser not running → spawns fresh with your profile. Modern-Chromium caveat (136+): Chromium refuses --remote-debugging-port on the default profile, so on a current browser existing mode gives you pwa-debug but not a CDP port — the launch reports this (attached:false, no false browserUrl) and steers you to sandbox-persistent for chrome-devtools-mcp. |
sandbox-persistent | ~/.pwa-debug/profiles/<browser>/ (persists across restarts) | The canonical both-tools profile — a dedicated profile beside your main browser, with its own --user-data-dir (so the debug port works on Chromium 136+). The launcher auto-writes the native-messaging manifest into the profile and, on most browsers, preloads the pwa-debug extension via --load-extension. Branded-Google-Chrome 142+ caveat: Chrome removed --load-extension, so there the launch brings up the debug port (for chrome-devtools-mcp) but not the extension — it returns a one-time manual Load unpacked walkthrough (persists in this profile), or steers you to a non-Google Chromium where preload just works. |
sandbox-temp | a fresh mktemp dir (removed on host shutdown) | One-off / CI / clean-state runs. Same as sandbox-persistent, but throwaway — so the Chrome-142+ manual-load step won't persist usefully here; prefer a non-Google Chromium for temp runs that need the extension. |
Extension preload depends on the browser brand + version (the launcher detects it from --version and adapts):
| Browser | --load-extension (sandbox preload) |
|---|---|
| Brave, Chromium, Edge, Opera, Vivaldi, Chrome for Testing | ✅ works on current versions |
| Google Chrome ≤ 136 | ✅ works |
| Google Chrome 137–141 | ✅ works — launcher adds --disable-features=DisableLoadExtensionCommandLineSwitch |
| Google Chrome ≥ 142 | ❌ removed — launcher omits the dead flag and guides a one-time manual Load unpacked (or use Brave/Chromium) |
Recommended coexistence setup: sandbox-persistent on a non-Google Chromium (e.g. Brave or Chromium). That single profile gets the extension preloaded and a live debug port, so pwa-debug and chrome-devtools-mcp drive the same browser with zero manual steps.
Chromium-family only (Firefox doesn't speak CDP). Linux is first-class. macOS/Windows binary detection, profile/user-data-dir paths, and system-default detection (macOS LaunchServices, Windows UserChoice registry) are all implemented with unit coverage, but live verification on real macOS/Windows machines is still needed — see Help wanted.
| Browser | PATH names probed | Standard Linux binary | Linux profile dir (existing mode) |
|---|---|---|---|
| Chrome | google-chrome, google-chrome-stable | /opt/google/chrome/chrome, /usr/bin/google-chrome* | ~/.config/google-chrome |
| Chromium | chromium, chromium-browser | /usr/bin/chromium*, /snap/bin/chromium | ~/.config/chromium |
| Edge | microsoft-edge, microsoft-edge-stable | /opt/microsoft/msedge/msedge | ~/.config/microsoft-edge |
| Brave | brave-browser, brave | /opt/brave.com/brave/brave-browser | ~/.config/BraveSoftware/Brave-Browser |
| Vivaldi | vivaldi, vivaldi-stable | /opt/vivaldi/vivaldi | ~/.config/vivaldi |
| Opera | opera | /usr/bin/opera, /opt/opera/opera | ~/.config/opera |
xdg-settings get default-web-browser, macOS via LaunchServices (defaults read … LSHandlers), Windows via the HKCU UrlAssociations\http\UserChoice ProgId. The macOS/Windows paths are implemented + unit-tested but not yet exercised on a real machine.9222 (the chrome-devtools-mcp convention); override it without passing port each time via the launch.defaultPort setting (settings.set)./snap/bin/…) in existing mode, the launcher now resolves its confined profile (~/snap/<snap>/common/<cfg>) instead of ~/.config — but note snap browsers still can't run the native-messaging host (see snap section), so this only matters if/when that confinement is lifted.session_ping reports page_blocks_scripts.flatpak override --user --filesystem=host <app-id> and retry.Development happens on Linux, so the macOS and Windows code paths are written and unit-tested with injected fakes, but never run on a real machine. If you're on macOS or Windows, trying these and reporting back (open an issue with the output) is the single most useful contribution right now:
macOS
pdl_check_setup / pdl_install_extension — does the extension resolve and copy, and do the chrome://extensions instructions work?/Applications/*.app/Contents/MacOS/….defaults read com.apple.LaunchServices/com.apple.launchservices.secure LSHandlers — does the parser pick the right browser? (Paste the raw output if it doesn't.)pdl_launch_browser mode existing (profile under ~/Library/Application Support/…) and both sandbox modes.Windows
%PROGRAMFILES% / %LOCALAPPDATA%.reg query "HKCU\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice" /v ProgId — does the ProgId map to the right browser?pdl_launch_browser (profile under %LOCALAPPDATA%\…\User Data) and the HKCU native-messaging registration.Any OS
pdl_browser_status after restarting the host — launches now persist to launches.json, so previously-launched browsers should still be listed (with a fresh liveness re-probe). Confirm the list survives a restart and that closed browsers show as not-live.The launcher never kills your running browser and sandbox modes use throwaway/dedicated profiles, so this is low-risk to try.
When something isn't working, walk this ladder — each step localizes the failure:
pdl_check_setup — are all setup gaps closed? (CDP reachable, manifest installed, extension present, ID registered.)pdl_browser_status — is a browser launched and is its debug port still live? Is the extension service worker connected (recent heartbeat)?host_status — is the native-messaging host registered and is an NMH instance connected?session_ping — does a full MCP → SW → page-world round-trip succeed on the active tab? (See the typed pageWorldError table below.)existing mode but chrome-devtools-mcp can't attach." Two causes. (1) Chrome opens --remote-debugging-port only at process start — if the browser was already running without it, pdl_launch_browser opens a new window (sub-state b) but can't add the port to the live process (attached:false). (2) On Chromium 136+, the port is refused on the default profile entirely, even on a fresh start. Either way: use mode: sandbox-persistent (a dedicated profile where the port works) for chrome-devtools-mcp.--load-extension, so the sandbox can't auto-preload the extension — the launch says so and gives you a one-time chrome://extensions → Developer mode → Load unpacked walkthrough (it persists in that dedicated profile). Or relaunch with browser: "brave" / "chromium", where preload works automatically. (chrome-devtools-mcp still works either way — the debug port is live.)chrome-devtools-mcp opened its own blank browser instead of using yours. Its registration is unpinned (no --browserUrl). Run pdl_register_chrome_devtools (or re-add with --browserUrl http://127.0.0.1:<port>) and restart/reconnect it; pdl_check_setup flags this..mcp.json / client MCP config is read at startup — restart your MCP client (or /mcp-reconnect that server) after adding pwa-debug or chrome-devtools.chrome://extensions detaches content scripts from already-open tabs. Hard-refresh the page tab (Ctrl+Shift+R); the SW also auto-reinjects on the next session_ping (look for pageWorldSelfHealed: true). Sandbox modes avoid this entirely (extension preloaded before tabs open).session_ping returns pageWorld: null with a typed pageWorldErrorsession_ping reports failure modes as typed codes in pageWorldError (machine-readable) plus the original chrome-runtime string in pageWorldErrorMessage (for logs). The MCP next_steps[] field carries imperative, code-specific guidance — AI clients should relay it verbatim. Tabs that simply predated the extension reload (the most common dev-loop friction) are auto-recovered by the SW via chrome.scripting.executeScript; when that succeeds, pageWorld is populated and pageWorldSelfHealed: true appears alongside it. The table below is the canonical mapping (single source of truth: NEXT_STEPS_BY_CODE in packages/host/src/mcp/tools/session_ping.ts).
pageWorldError | What it means | What to do |
|---|---|---|
(absent) with pageWorldSelfHealed: true | The static content script was missing on the active tab; the SW silently re-injected content-script.js + page-world.js and retried. No action needed. | Informational only. |
cs_not_attached_refresh_tab | Auto-recovery was attempted but did not stick (page rejected the injection or reloaded mid-flight). | Hard-refresh the page tab (Ctrl+Shift+R) and retry. If it repeats, reload the extension at chrome://extensions then hard-refresh. |
page_blocks_scripts | A content blocker is rejecting the script (Brave Shields, uBlock Origin, AdGuard, or similar). Site CSP is also possible. | Brave: click the lion icon → set Shields Down for the site → refresh → retry. uBlock Origin / similar: disable for this site → refresh → retry. If neither, the site's own CSP is blocking and pwa-debug cannot bypass it. |
page_world_blocked | The content script attached but the MAIN-world page-world bridge cannot be reached — the site's Content-Security-Policy blocks the inline script tag. | Site-level restriction; cannot bypass. Console + network capture may still work via the content-script side, but live page-world reads (state, evaluate) will not. |
restricted_url | The active tab is on a URL browsers do not allow extensions to touch (chrome://, chromewebstore.google.com, about:, devtools://, file://, view-source:, etc.). | Switch focus to a regular http(s) tab of the PWA, then retry. |
no_active_tab | No active http(s) tab is focused (DevTools window or extension popup may be focused instead). | Focus a regular browser tab and retry. |
cs_inject_failed | The auto-recovery chrome.scripting.executeScript itself failed. The extension cannot reach this tab. | Reload the extension at chrome://extensions and hard-refresh the page (Ctrl+Shift+R). If it persists, the URL may be one the browser blocks all extensions from — check the address bar. |
To confirm the content script attached after a successful round-trip, open the page tab's DevTools (F12 on the page itself, not the SW console) and look for [pwa-debug/cs] attached at <url> in the Console.
Every tool returns a structured response of the form { ok, data, error?, next_steps[] }. The next_steps array encodes the rules of engagement for the AI — what to call next based on the actual response shape — mirroring the AIMFP return_statements pattern. The canonical list lives in packages/host/src/mcp/tools/index.ts.
| Tool | Purpose |
|---|---|
host_status | Install/liveness state: registered IDs, manifest paths, launcher path, active connections. Cheap, idempotent. Always call first. |
host_register_extension(id) / host_unregister_extension(id) | Add/remove an extension ID across per-browser manifests + launcher script. Idempotent. |
host_list_registrations / host_reset | Read registered IDs; destructive cleanup to re-bootstrap. |
session_ping | Full MCP → IPC → NMH → SW → page-world round-trip with typed pageWorldError codes + self-heal. |
pdl_check_setup | Diagnose setup → { ok, gaps[], recommendations[] } (CDP reachable, manifest installed, extension present, ID registered). |
pdl_install_extension({ target? }) | Copy the extension to a folder for unpacked install. |
| Tool | Purpose |
|---|---|
pdl_launch_browser({ browser?, port?, mode? }) | Launch/attach a Chromium browser with a live debug port. mode: existing (default), sandbox-persistent, sandbox-temp. Returns browserUrl for chrome-devtools-mcp. |
pdl_browser_status | Managed launches (browser, profile mode, port, pid) with live debug-port re-probe + extension SW heartbeat. |
| Tool | Purpose |
|---|---|
console_tail / network_tail / error_tail | Cursor-paginated, filterable tails of the persistent capture ring buffers (memory + disk spill). |
recent_events | Recent captured events across kinds for quick verification. |
evaluate | Evaluate an expression in the page world. |
session_record / session_replay | rrweb session recording + cursor-paginated replay. |
source_map_resolve | Resolve generated stack frames to original src/…:line:col. |
settings_list_schema / settings_get / settings_set | Read the typed settings schema; get/set values (allowlist, capture filters, disk-spill, etc.). |
The namesake suite — read the debugged PWA's service-worker, cache, installability, storage, and update state. All page-world reads against your real profile; CDP / chrome-devtools-mcp can't surface these.
| Tool | Purpose |
|---|---|
sw_status | Service-worker registrations: installing/waiting/active versions, updateViaCache, controller, and whether an update is stuck waiting (the #1 "why isn't my update showing"). |
sw_lifecycle_tail | Cursor-paginated stream of SW lifecycle events (updatefound / statechange / controllerchange). |
cache_list / cache_inspect / cache_match | CacheStorage caches + per-entry { url, status, content-type, size, ageSeconds, cache-control }, and which cache serves a given URL — the core of stale-cache debugging. |
pwa_status | Display mode / standalone, controller, permissions, and a live capability matrix (Push / Background Sync / Periodic Sync / Badging / File System Access / Window Controls Overlay). |
pwa_installability | Discover + parse the manifest and run installability checks → structured { supported, gaps[], fixes[] } with per-gap remediation. |
storage_get | localStorage / sessionStorage snapshot (capped). |
idb_list / idb_query | IndexedDB databases + object-store schema, then a read-only capped slice of records — inspect IndexedDB live. |
pwa_update_analyze | Correlates SW status + cache ages + recent 404s into a verdict: waiting-update-on-active-client, cached-HTML-older-than-JS version skew, chunk 404s — why are some users on old code? |
pwa_snapshot | One capped runtime-state blob (SW + store + web storage + IndexedDB + cache names) for deterministic bug-repro / hand-off. |
Read via each framework's own model — things CDP can't see. Tree/state where the framework persists it; element-level find everywhere.
| Tool family | Purpose |
|---|---|
react_tree / react_get_state / react_find_by_text / react_find_by_role | React fiber-tree introspection + props/state/hooks by stable id + component lookup. |
vue_tree / vue_get_state / vue_find_by_text / vue_find_by_role | Vue 3 ComponentInternalInstance tree + reactive state + lookup (parity with React). |
svelte_components / svelte_find_by_text / svelte_find_by_role | Svelte component discovery + __svelte_meta source locations + element lookup (no instance state — Svelte exposes none). |
solid_detect / solid_find_by_text / solid_find_by_role | Solid detection + element-level find (no persisted tree/state without @solid-devtools). |
| Tool family | Purpose |
|---|---|
redux_get_state / redux_subscribe / redux_tail / redux_dispatch | Redux read, change-delta subscribe/tail, JSONPath-lite slice, (opt-in) dispatch. |
store_get_state / store_subscribe / store_tail / store_dispatch | Framework-agnostic adapter covering Zustand / Pinia / Jotai through one contract (read / subscribe / dispatch by name). |
Framework-agnostic native-event sequences dispatched so React/Vue/etc. delegated synthetic-event systems fire. Each targets a unified locator (selector / role / text / framework stable-id).
| Tool | Purpose |
|---|---|
pdl_click / pdl_dblclick / pdl_hover | Pointer interactions. |
pdl_fill / pdl_submit / pdl_select_option / pdl_uncheck | Form interactions. |
pdl_focus / pdl_blur / pdl_key_press / pdl_type_sequence | Focus + keyboard. |
pdl_drag / pdl_scroll / pdl_swipe / pdl_tap / pdl_double_tap / pdl_long_press / pdl_pinch | Touch/gesture primitives (direction/distance/duration/steps model — no hand-built coordinates). |
| Tool | Purpose |
|---|---|
popup_tail | Tail the library-popup event stream (WalletConnect / RainbowKit / SDK modals, incl. shadow-root + nested). |
popup_record / popup_replay | Intent-driven recording of a popup's full primary+nested stream (immune to ring-buffer eviction) + flat/primary/tree replay. |
popup_failures | Correlate a popup's auth/connect failure with the console errors + failed requests during its open window. |
console_tail / network_tail.evaluate.popup_record / popup_replay / popup_tail / popup_failures for WalletConnect & SDK modals (incl. shadow-root + nested).session_record / session_replay; source_map_resolve for stack frames.pdl_launch_browser (existing + sandbox-persistent + sandbox-temp), pdl_check_setup, pdl_browser_status, pdl_close_browser, pdl_install_extension, pdl_register_chrome_devtools, and chrome-devtools-mcp coexistence.sw_status + sw_lifecycle_tail: installing/waiting/active versions, updateViaCache, skipWaiting/claim/controllerchange); CacheStorage inspection (cache_list / cache_inspect / cache_match with size/age + match-by-URL); pwa_status (display mode, controller, permissions, live capability matrix — Push/Background Sync/Periodic Sync/Badging/File System Access/Window Controls Overlay); installability diagnostics (pwa_installability → structured {gaps[], fixes[]}); IndexedDB/web-storage inspection (idb_list / idb_query / storage_get); update-propagation / version-skew analysis (pwa_update_analyze); and a one-shot runtime-state snapshot (pwa_snapshot). Closes the stale-cache / "SW won't update" / install-failure / version-skew pain cluster against your real profile.pdl_install_extension hands you the path + steps) — so every user knows exactly what they're running and why. Disabling Chrome Developer mode auto-disables it.winreg) wrapped in thin functional adapters with injection points for tests.pwa-debug-layer/
├── packages/
│ ├── host/ Native-messaging host + MCP server (Node, ESM, rollup-bundled)
│ ├── extension/ WebExtension (MV3) — service worker, content script, page-world
│ └── shared/ Cross-package types
├── docs/
│ ├── PLAN.md Full design doc (architecture, capability matrix, phased plan)
│ └── goals.txt
├── examples/ (future: test PWAs)
└── reference/ Read-only reference checkouts
This is a personal project under active redesign. PRs welcome but please open an issue first to discuss scope — the architecture is still settling. The FP / no-OOP discipline applies to all contributions; see CLAUDE.md for the full rules.
MIT — see LICENSE.
therealtimex/browser-use
jae-jae/fetcher-mcp
merajmehrabi/puppeteer-mcp-server
saik0s/mcp-browser-use
vibheksoni/stealth-browser-mcp