CCM
/MCP
SkillsMCPMarketplacesDigestLearnAdvertise

This week in Claude

Every Monday: Claude Code, Agent SDK, MCP, and the Anthropic platform moves worth your time.

Skills by Category
Frontend DevelopmentBackend & APIsTesting & QASecurityDevOps & CI/CDGit & Pull RequestsDocumentationCode Review & QualityAI & Agent BuildingSkill Development
MCP Servers by Category
Sales & MarketingWeb & Browser AutomationDatabasesAI & LLM ToolsCloud & InfrastructureCommunication & MessagingDeveloper ToolsDesign & CreativeDocuments & KnowledgeSearch & Web Crawling
Marketplaces by Category
AI Agents & OrchestrationLLM IntegrationDevelopment ToolsFrontend & UIBackend & APIsDatabasesTesting & Code QualityDevOps & CloudSecurity & ComplianceGit & Version Control

Claude Code Marketplaces

Discover Claude Code plugins, extensions, and tools. Automatically updated directory of Anthropic Claude AI marketplaces with development tools, productivity plugins, and integrations.

Resources

  • Browse Skills
  • Browse MCP Servers
  • Browse Marketplaces
  • Plugins Reference

Community

  • About
  • Learn
  • Feedback
  • Privacy Policy
  • Advertise

Built for the Claude Code community with Claude Code by @mertduzgun

Independent project, not affiliated with Anthropic

PWA Debug Layer

aryanduntley/pwa-debug-layer
STDIOregistry active
Summary

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.

CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
Put your SEO on autopilot
Put your SEO on autopilot
An agent that runs the SEO playbooks that move rankings and ships PRs you control.
Get founding access →
Vibe Prospecting MCPVibe Prospecting MCP
Vibe Prospecting MCP
Connect Claude to +800M contacts, +150M companies. Find & Enrich leads in chat.
Try For Free →
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
Put your SEO on autopilot
Put your SEO on autopilot
An agent that runs the SEO playbooks that move rankings and ships PRs you control.
Get founding access →
Vibe Prospecting MCPVibe Prospecting MCP
Vibe Prospecting MCP
Connect Claude to +800M contacts, +150M companies. Find & Enrich leads in chat.
Try For Free →
Featured
CodeRabbit
CodeRabbit
AI writes the code. CodeRabbit catches the slop.
Try For Free →
Make your agent a DeFi expert
Make your agent a DeFi expert
Agent, run crypto. Access onchain data & trade routes via 1inch.
Install now →
AppSignal
AppSignal
Monitor with ease. Code with confidence.
Start Free Trial →
Make money from your Skills
Make money from your Skills
On Capafy, your Skill runs online 24/7 as an agent product, and you get paid every time someone uses it.
Start earning →
Put your SEO on autopilot
Put your SEO on autopilot
An agent that runs the SEO playbooks that move rankings and ships PRs you control.
Get founding access →
Vibe Prospecting MCPVibe Prospecting MCP
Vibe Prospecting MCP
Connect Claude to +800M contacts, +150M companies. Find & Enrich leads in chat.
Try For Free →
Categories
Web & Browser Automation
Registryactive
Package@aryanduntley/pwa-debug
TransportSTDIO
UpdatedJun 6, 2026
View on GitHub

pwa-debug-layer

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.

What it answers

The PWA failures developers actually search for, read straight from your live runtime:

  • Why won't my service worker update? Why are some users on old code? — SW lifecycle with waiting-vs-active versions, plus an update-propagation / version-skew analyzer.
  • Why is my cache stale? Why are chunks 404ing after a deploy? — CacheStorage contents with age, and a cached-HTML-vs-JS skew check.
  • Why won't my PWA install? — structured installability diagnostics with per-gap remediation, not just "manifest invalid."
  • What's actually in IndexedDB / localStorage right now? — live storage inspection (inspect IndexedDB live).
  • What can this browser actually do? — a live capability matrix (Push / Background Sync / Periodic Sync / Badging / File System Access / Window Controls Overlay).

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_browser with chrome-devtools-mcp coexistence.
  • 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).

How it differs from chrome-devtools-mcp

Google'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:

  • Your real, logged-in browser. 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).
  • Framework state. React fiber trees, Vue reactive state, Svelte component graphs, Solid signals — read via the framework's own devtools hooks (__REACT_DEVTOOLS_GLOBAL_HOOK__, __vue_app__, _vnode, etc.). CDP can't see these.
  • Store state. Redux / Zustand / Pinia / Jotai — read, subscribe, and dispatch.
  • Service-worker, cache & installability state. SW lifecycle + versions, CacheStorage contents + age, manifest installability gaps, IndexedDB/web storage, and an update-propagation / version-skew analyzer — the highest-volume PWA pain (stale cache, "SW won't update," install failures, "why are some users on old code") that DevTools makes you piece together by hand.
  • Shadow DOM, iframes, dynamically-injected library widgets. WalletConnect modals, third-party SDK popups, and other widgets that escape standard DOM tooling.
  • Page-world reach in general. A MAIN-world script we inject reaches things isolated-world content scripts can't, and reaches them earlier than initScript-on-next-nav.
  • Persistent ring buffers + rrweb-style replay across navigations and reloads.
  • Configurable filters so the AI receives only the slice it asked for — no full-DOM noise.

The two are designed to coexist: install both, the AI uses each for what it does best, with zero tool-surface duplication.

Architecture

┌──────────────────┐  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:

  • Extension owns the page (DOM, content scripts, page-world hooks).
  • Native host owns persistence and the MCP server (long-lived, can hold buffers, can write files).
  • MCP owns the AI contract.

Each does what only it can. See docs/PLAN.md for the full design.

Browser support

Chromium-family only, sideloaded. Tested against:

  • Chromium (native package)
  • Google Chrome (.deb / .rpm)
  • Brave Browser
  • Microsoft Edge (Linux .deb)
  • Vivaldi
  • Opera

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.

Snap browsers are not supported

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:

  • Ubuntu/Debian: 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.
  • Fedora: dnf install chromium is non-snap by default.
  • Arch: 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.

Installation

Prerequisites

  • Node.js ≥ 20.19 (developed on 23.x)
  • pnpm
  • A Chromium-family browser not installed via snap (see above)
  • An MCP-aware client (e.g. Claude Code)

1. Build the host and extension

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)

2. Add the host to your MCP client

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.

3. Load the extension

  1. Open chrome://extensions (or brave://extensions, etc.) in your browser.
  2. Toggle Developer mode on.
  3. Click Load unpacked and select packages/extension/dist/.
  4. Note the extension's ID (shown on the card). Or, ask Claude to discover it for you in the next step — the extension service worker logs [pwa-debug/sw] id=<id> on every boot.

4. Tell Claude to set it up

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 have chrome-devtools-mcp installed.)

Claude will:

  1. Call host_status to see what's already registered.
  2. Call 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).
  3. Tell you to reload the extension at chrome://extensions.
  4. After reload, call 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)

Launching a browser + chrome-devtools-mcp coexistence

pwa-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

Or install pwa-debug as a Claude Code plugin

The 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-mcp is still separate. The plugin declares only the pwa-debug host — chrome-devtools-mcp stays the optional claude mcp add chrome-devtools … above (no version coupling, no owning its launch). The bundled chrome-devtools-coexistence skill 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 --browserUrl has to point at the exact port pwa-debug opens — the launch.defaultPort setting (default 9222), or the active port from a current pdl_launch_browser. Rather than hand-write this, let Claude call pdl_register_chrome_devtools, which runs the claude mcp add above for you pinned to the right port (and pdl_check_setup flags 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 add only takes effect when the server (re)connects. Fastest path: open /mcp and reconnect chrome-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 install chrome-devtools-mcp as a plugin, run /reload-plugins. The bundled chrome-devtools-coexistence skill 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/serve subcommand — dist/main.js auto-detects its mode: launched by Chrome (argv starts with chrome-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:

  1. 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.
  2. 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.)
  3. 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.)
  4. 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).
  5. 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).

Profile modes

pdl_launch_browser takes mode (default existing), browser (defaults to your system-default Chromium browser), and port (default 9222).

ModeProfileWhen to use
existing (default)Your normal browser profileDebugging 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-tempa 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.

Browser support matrix

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.

BrowserPATH names probedStandard Linux binaryLinux profile dir (existing mode)
Chromegoogle-chrome, google-chrome-stable/opt/google/chrome/chrome, /usr/bin/google-chrome*~/.config/google-chrome
Chromiumchromium, chromium-browser/usr/bin/chromium*, /snap/bin/chromium~/.config/chromium
Edgemicrosoft-edge, microsoft-edge-stable/opt/microsoft/msedge/msedge~/.config/microsoft-edge
Bravebrave-browser, brave/opt/brave.com/brave/brave-browser~/.config/BraveSoftware/Brave-Browser
Vivaldivivaldi, vivaldi-stable/opt/vivaldi/vivaldi~/.config/vivaldi
Operaopera/usr/bin/opera, /opt/opera/opera~/.config/opera
  • System default: the launcher prefers your system-default browser when you don't pass one — Linux via 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.
  • Default debug port is 9222 (the chrome-devtools-mcp convention); override it without passing port each time via the launch.defaultPort setting (settings.set).
  • Snap profiles: when launching a snap-packaged browser (/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.
  • Brave Shields can block the content script on a site — set Shields Down for the site if session_ping reports page_blocks_scripts.
  • Snap browsers are unsupported for the native-messaging host (see below); the launcher can still spawn them, but the host round-trip won't connect. Use a native-package browser.
  • Flatpak installs get a manifest written, but confinement may block exec — run flatpak override --user --filesystem=host <app-id> and retry.

Help wanted: macOS / Windows verification

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?
  • Browser binary detection under /Applications/*.app/Contents/MacOS/….
  • System-default detection: 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

  • Browser detection under %PROGRAMFILES% / %LOCALAPPDATA%.
  • System-default detection: 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.

Troubleshooting

Verification sequence

When something isn't working, walk this ladder — each step localizes the failure:

  1. pdl_check_setup — are all setup gaps closed? (CDP reachable, manifest installed, extension present, ID registered.)
  2. pdl_browser_status — is a browser launched and is its debug port still live? Is the extension service worker connected (recent heartbeat)?
  3. host_status — is the native-messaging host registered and is an NMH instance connected?
  4. session_ping — does a full MCP → SW → page-world round-trip succeed on the active tab? (See the typed pageWorldError table below.)

Common launcher gotchas

  • "I launched in 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.
  • Brave/Chrome says "Opening in existing browser session." That's sub-state b — the binary handed your request to the already-running process instead of starting a fresh one with the port. Same fix as above.
  • Sandbox launch came up but pwa-debug never connects (Google Chrome 142+). Branded Google Chrome 142+ permanently ignores --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.
  • Added the MCP server but the tools don't appear. .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.
  • Tools worked, then stopped after I reloaded the extension. Reloading the extension at 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 pageWorldError

session_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).

pageWorldErrorWhat it meansWhat to do
(absent) with pageWorldSelfHealed: trueThe 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_tabAuto-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_scriptsA 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_blockedThe 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_urlThe 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_tabNo active http(s) tab is focused (DevTools window or extension popup may be focused instead).Focus a regular browser tab and retry.
cs_inject_failedThe 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.

MCP tool surface

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.

Host management & setup

ToolPurpose
host_statusInstall/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_resetRead registered IDs; destructive cleanup to re-bootstrap.
session_pingFull MCP → IPC → NMH → SW → page-world round-trip with typed pageWorldError codes + self-heal.
pdl_check_setupDiagnose 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.

Browser launcher

ToolPurpose
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_statusManaged launches (browser, profile mode, port, pid) with live debug-port re-probe + extension SW heartbeat.

Capture, evaluate, replay

ToolPurpose
console_tail / network_tail / error_tailCursor-paginated, filterable tails of the persistent capture ring buffers (memory + disk spill).
recent_eventsRecent captured events across kinds for quick verification.
evaluateEvaluate an expression in the page world.
session_record / session_replayrrweb session recording + cursor-paginated replay.
source_map_resolveResolve generated stack frames to original src/…:line:col.
settings_list_schema / settings_get / settings_setRead the typed settings schema; get/set values (allowlist, capture filters, disk-spill, etc.).

PWA runtime diagnostics

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.

ToolPurpose
sw_statusService-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_tailCursor-paginated stream of SW lifecycle events (updatefound / statechange / controllerchange).
cache_list / cache_inspect / cache_matchCacheStorage 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_statusDisplay mode / standalone, controller, permissions, and a live capability matrix (Push / Background Sync / Periodic Sync / Badging / File System Access / Window Controls Overlay).
pwa_installabilityDiscover + parse the manifest and run installability checks → structured { supported, gaps[], fixes[] } with per-gap remediation.
storage_getlocalStorage / sessionStorage snapshot (capped).
idb_list / idb_queryIndexedDB databases + object-store schema, then a read-only capped slice of records — inspect IndexedDB live.
pwa_update_analyzeCorrelates 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_snapshotOne capped runtime-state blob (SW + store + web storage + IndexedDB + cache names) for deterministic bug-repro / hand-off.

Framework introspection

Read via each framework's own model — things CDP can't see. Tree/state where the framework persists it; element-level find everywhere.

Tool familyPurpose
react_tree / react_get_state / react_find_by_text / react_find_by_roleReact fiber-tree introspection + props/state/hooks by stable id + component lookup.
vue_tree / vue_get_state / vue_find_by_text / vue_find_by_roleVue 3 ComponentInternalInstance tree + reactive state + lookup (parity with React).
svelte_components / svelte_find_by_text / svelte_find_by_roleSvelte component discovery + __svelte_meta source locations + element lookup (no instance state — Svelte exposes none).
solid_detect / solid_find_by_text / solid_find_by_roleSolid detection + element-level find (no persisted tree/state without @solid-devtools).

Store introspection

Tool familyPurpose
redux_get_state / redux_subscribe / redux_tail / redux_dispatchRedux read, change-delta subscribe/tail, JSONPath-lite slice, (opt-in) dispatch.
store_get_state / store_subscribe / store_tail / store_dispatchFramework-agnostic adapter covering Zustand / Pinia / Jotai through one contract (read / subscribe / dispatch by name).

Interaction & touch gestures

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).

ToolPurpose
pdl_click / pdl_dblclick / pdl_hoverPointer interactions.
pdl_fill / pdl_submit / pdl_select_option / pdl_uncheckForm interactions.
pdl_focus / pdl_blur / pdl_key_press / pdl_type_sequenceFocus + keyboard.
pdl_drag / pdl_scroll / pdl_swipe / pdl_tap / pdl_double_tap / pdl_long_press / pdl_pinchTouch/gesture primitives (direction/distance/duration/steps model — no hand-built coordinates).

Library popups

ToolPurpose
popup_tailTail the library-popup event stream (WalletConnect / RainbowKit / SDK modals, incl. shadow-root + nested).
popup_record / popup_replayIntent-driven recording of a popup's full primary+nested stream (immune to ring-buffer eviction) + flat/primary/tree replay.
popup_failuresCorrelate a popup's auth/connect failure with the console errors + failed requests during its open window.

Roadmap

  • Foundation ✅ — pnpm workspace + build pipeline; MV3 extension loads cleanly; native-messaging round-trip; AI-managed host registration; cross-platform install (Linux native + macOS + Windows registry; snap unsupported); MCP↔IPC↔NMH↔SW bridge.
  • Capture ✅ — console / network (fetch/XHR/WebSocket) / DOM-mutation / lifecycle producers; host ring buffers with disk spill + archive pruning; filterable, cursor-paginated console_tail / network_tail.
  • Framework introspection ✅ — React, Vue, Svelte, Solid (trees/state where the framework persists them, find-by-text/role everywhere); page-world evaluate.
  • Store introspection ✅ — Redux (read/subscribe/tail/dispatch) plus a framework-agnostic adapter covering Zustand, Pinia, Jotai.
  • Interaction & gestures ✅ — discrete actions (click/fill/submit/hover/focus/blur/select/key/type) + touch gestures (drag/scroll/swipe/tap/double-tap/long-press/pinch) over a unified locator.
  • Library popups ✅ — popup_record / popup_replay / popup_tail / popup_failures for WalletConnect & SDK modals (incl. shadow-root + nested).
  • Replay & source maps ✅ — rrweb session_record / session_replay; source_map_resolve for stack frames.
  • Settings ✅ — typed schema store (allowlist/blocklist, per-kind capture filters, per-site read controls, disk-spill).
  • Browser launcher ✅ — 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.
  • PWA Runtime Diagnostics ✅ — the namesake suite, live-verified against a real PWA. Service-worker introspection (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.
  • Later — DevTools panel for human observation of an AI session; multi-tab routing model; event-causality graph (click → action → request → SW → cache → re-render); production-safe diagnostic mode for real-user failure capture.
  • Deferred — Firefox port (needs WebDriver BiDi, not CDP); macOS/Windows live verification (help wanted); mobile; hosted/team mode.
  • Intentionally not pursued — Chrome Web Store distribution. The extension grants broad page access (DOM, framework state, stores, network) and is only meaningful alongside its MCP host. It ships bundled with the MCP only and is installed via a manual, dev-mode "Load unpacked" (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.

Code style

  • FP-only: pure functions, immutability, no OOP, no classes-with-methods.
  • Side effects (CDP calls, file I/O, native messaging, MCP transport) at the edges; core logic pure.
  • OOP library interfaces (Chrome APIs, MCP SDK, winreg) wrapped in thin functional adapters with injection points for tests.

Repo layout

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

Contributing

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.

License

MIT — see LICENSE.

Related Web & Browser Automation MCP Servers

View all →
Browser Use

therealtimex/browser-use

AI browser automation - navigate, click, type, extract content, and run autonomous web tasks
Fetcher

jae-jae/fetcher-mcp

Fetch web page content using a Playwright headless browser with intelligent content extraction and Markdown/HTML output.
1k
Puppeteer

merajmehrabi/puppeteer-mcp-server

This MCP server provides browser automation capabilities through Puppeteer, allowing interaction with both new browser instances and existing Chrome windows.
449
Browser

saik0s/mcp-browser-use

Provides a browser automation MCP server that lets AI assistants control a real browser for navigation, form interaction, data extraction, and more.
933
Browser Use

kontext-dev/browser-use-mcp-server

Browse the web, directly from Cursor etc.
822
Stealth Browser

vibheksoni/stealth-browser-mcp

The only browser automation that bypasses anti-bot systems. AI writes network hooks, clones UIs pixel-perfect via simple chat.
643