Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, email [email protected] and we’ll put it right.

PWA Debug Layer

NPM · @ARYANDUNTLEY/PWA-DEBUG · SCANNED AUG 3

Debug PWAs in your real browser via MCP: service-worker, cache, installability & framework state.

+50 this week 74 Trust /100
Trust breakdown (6 categories)

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score →

Supply Chain Security87
  • No malware found by supply-chain analysis.Pass
  • Only part of the dependency tree could be resolved (96 of 100), so this covers what we could see, not the whole tree.Partial
  • No install/post-install scripts declared.Pass
  • Only part of the dependency tree could be resolved (96 of 100), so this covers what we could see, not the whole tree. View diagnostics → Partial
Provenance & Transparency97
  • Source repository is publicly reachable at the declared URL. View diagnostics → Pass
  • Cryptographically verified build provenance (signed, bound to aryanduntley/pwa-debug-layer). View diagnostics → Pass
  • Clear OSI-approved license (MIT).Pass
  • Actively maintained (last published 38 days ago).Pass
  • Disclosure check failed: no security disclosure policy was found in the source repository. See how to fix → Fail
Schema Quality & AI Usability62
  • AI-judged instruction clarity (excellent).Pass
  • Context-footprint check failed: tool/resource definitions use about 17357 tokens (~211/item across 82 items; 82 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
  • Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management27
  • Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.Partial
Tool Coverage67
  • 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
  • 0% of tool parameters carry a description.Fail
Capabilities100
  • Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
Install

Add this component to your MCP client. Where a client-specific snippet is available, pick your client below and copy it straight into your config; otherwise use the connection detail shown.

npm · @aryanduntley/pwa-debug

# add to Claude Code
claude mcp add aryanduntley-pwa-debug-layer -- npx -y @aryanduntley/pwa-debug
# add to Codex CLI
codex mcp add aryanduntley-pwa-debug-layer -- npx -y @aryanduntley/pwa-debug
// opencode.json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "aryanduntley-pwa-debug-layer": {
      "type": "local",
      "command": [
        "npx",
        "-y",
        "@aryanduntley/pwa-debug"
      ],
      "enabled": true
    }
  }
}
# add to OpenClaw
openclaw mcp add aryanduntley-pwa-debug-layer --command npx --arg -y --arg @aryanduntley/pwa-debug
# ~/.hermes/config.yaml
mcp_servers:
  aryanduntley-pwa-debug-layer:
    command: "npx"
    args: ["-y", "@aryanduntley/pwa-debug"]
// mcp.json
{
  "mcpServers": {
    "aryanduntley-pwa-debug-layer": {
      "command": "npx",
      "args": [
        "-y",
        "@aryanduntley/pwa-debug"
      ]
    }
  }
}
Changelog

Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.

  • 3 Aug 26 +4
    • Stability: unverified → 0.27 functional
  • 2 Aug 26 +55
    • Provenance: unverified → pass security
    • Known CVEs: unverified → partial security
    • Install scripts: unverified → pass security
    • Malware scan: unverified → pass security
    • Stability: Stability not yet verified: not enough scan history yet (needs a 30-day window). security
    • The attested source repository moved: aryanduntley/pwa-debug-layer security
    • Tool coverage: 100 → unverified functional
    • MCP protocol: unverified → pass functional
    • Maintenance: unverified → pass functional
    • License: unverified → pass functional
    • Schema quality: unverified → excellent functional
    • Dependency health: unverified → partial functional
    • Licence: MIT functional
  • 1 Aug 26 +10
    • Tool coverage: unverified → 100 functional
  • 31 Jul 26 −15
    • We updated how we score, so this day's move reflects our rubric, not a change to the server See what changed → functional
  • 30 Jul 26 −18
    • Malware scan: pass → unverified security
  • 28 Jul 26 +14
    • Tool coverage: unverified → 100 functional
    • First check of Tool coverage: 0 functional
    • First check of Schema quality: unverified functional
    • First check of Schema quality: fail functional
    • First check of Schema quality: fail functional
  • 27 Jul 26 24

    First indexed and scored.

Diagnostics

Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.

Captured 3 Aug 2026 · Analysed npm/@aryanduntley/[email protected]

Provenance verified

Ecosystem: npm · Outcome: verified

Reason: verified

Source repo:
aryanduntley/pwa-debug-layer
Certificate issuer:
https://token.actions.githubusercontent.com
Certificate SAN:
https://github.com/aryanduntley/pwa-debug-layer/.github/workflows/publish.yml@refs/tags/v0.2.0
Rekor log index:
1960343536
Predicate type:
https://slsa.dev/provenance/v1
Subject digest:
sha512:530e31ee2acff9791ad2ddaa0b9b083ab331ae98330fb73a3db099aaebcb7f4b18f3f9d645e4e0202d1c39326d0768fda5523796469baf17f9ef1de50
Discovery method:
attestation_endpoint
Dependencies 96 packages

96 packages in the resolved dependency tree · 95 deprecated · 30 stale.

The dependency tree was only partially resolved, so these counts may be incomplete.

MCP tools — 82 exposed · ~17,357 tokens

The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability.

Tool Tokens
cache_inspect ~194

Inspect one CacheStorage cache's entries. Returns CacheInspectResult { supported, found, name, entries: [{ url, method, status, contentType, contentLength, dateHeader, ageSeconds, cacheControl }], entryCount, truncated }. ageSeconds (now − the response's Date header) is the staleness signal — old app-shell HTML or API responses are the usual 'why won't my update show' / 'why is my data stale' cause. Get the cache name from cache_list first. Bodies are NOT read (size is from content-length). Args: { extension_id?, tab_id?, cache_name (required), limit?: default 200, max 1000 }. Page-world read; CDP cannot do this. CALL host_status FIRST.

NameTypeReqDescription
cache_namestringyes
extension_idstring
limitinteger
tab_idinteger

No output schema declared.

No examples provided.

cache_list ~153

List the debugged PWA's CacheStorage caches. Returns CacheListResult { supported, caches: [{ name, entryCount }] } read from the page's caches.* API. Use to see what the service worker has cached, then cache_inspect(cache_name) for per-entry detail (age/size/type) or cache_match(url) to find which cache serves a URL — the core of diagnosing stale-cache bugs. Reads your real profile's caches; CDP/chrome-devtools-mcp does not surface this. Args: { extension_id?, tab_id? }. Runs in page-world via the page-bridge. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

cache_match ~176

Find which CacheStorage cache would serve a URL. Returns CacheMatchResult { supported, url, matched, cacheName, entry } — iterates the caches in order and reports the first hit plus the matched entry (status, content-type, ageSeconds, cache-control). Answers 'is /app.js cached, by which cache, and how old is it'. CAVEAT: a hit means a cached copy EXISTS; the SW's fetch handler decides whether it's actually served (cache-first / network-first / SWR) and that strategy is not observable from here — reported as a heuristic, not a guarantee. Args: { extension_id?, tab_id?, url (required) }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger
urlstringyes

No output schema declared.

No examples provided.

console_tail ~224

Tail the host-side console ring buffer for a target extension with cursor pagination + FilterSpec. Returns { entries: ConsoleEntry[]; cursor: Cursor|null; hasMore: bool }. Each ConsoleEntry has page-world fields (ts, frameUrl, frameKey, level, args, optional stack) intersected with host fields (receivedAt, sessionId, extensionId, sequenceNumber) plus a per-entry cursor. Top-level cursor = newest entry's cursor for forward pagination via filter.since. With no extension_id, targets the single connected NMH (errors if zero or multiple). FilterSpec (all optional): level=ConsoleLevel[] (log|info|warn|error|debug|trace); pattern={include?: regex sources[], exclude?: regex sources[]}; since/until=opaque cursor strings; limit=int 1..1000 (default 200). Errors carry kind in next_steps so AI can self-correct: cursor_invalid, cursor_session_mismatch, pattern_invalid, limit_invalid.

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

error_tail ~283

Tail the host-side page_error ring buffer for a target extension with cursor pagination + FilterSpec. page_error events are UNCAUGHT failures captured app-/framework-agnostically: a window 'error' (ErrorEvent / window.onerror, subkind='error') or an 'unhandledrejection' (a rejected promise the app did not catch, subkind='unhandledrejection'). Returns { entries: PageErrorEntry[]; cursor: Cursor|null; hasMore: bool }. Each entry: { kind:'page_error', subkind, message, name?, stack?, source?(url:line:col), ts, frameUrl, frameKey, ...host fields, cursor }. Use this to see thrown errors and rejected promises (including wallet/connect rejections that bubble) without the app having to log them. NOTE: errors an app fully handles in try/catch do NOT surface here. With no extension_id, targets the single connected NMH (errors if zero or multiple). FilterSpec (all optional): pattern.include/exclude=regex sources matched against each entry's JSON (e.g. include:['rejected','cancelled']); since/until=opaque cursors; limit=int 1..1000 (default 200); level is ignored (page errors have no level field).

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

evaluate ~311

Evaluate a JavaScript expression in the page (MAIN world) via the page-bridge — NO CDP, so this coexists with the user's open DevTools (chrome-devtools-mcp's evaluate_script cannot). Sees framework globals (window.React, __REACT_DEVTOOLS_GLOBAL_HOOK__, store hooks, etc.) that the SW and content script cannot reach. Args: { extension_id?, tab_id?, expression: non-empty string, timeout_ms?: <=3500ms (default 3000), await_promise? }. Returns { extensionId, tabId, value?, truncated?, durationMs, error? } where value is serialized with a 16KB cap (DOM nodes/functions/promises/errors become { __type: ... } tags; cycles become { __type: 'Cycle' }). When await_promise=true and the expression returns a thenable, races resolution against timeout_ms. error{message,stack?} is populated for syntax errors, sync throws, async rejects, and timeouts — the call still returns ok:true so AI can introspect failure detail. Expression mode only (single expression, no statements; same as DevTools console expression eval). With no extension_id/tab_id, targets the single connected NMH and the active tab. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
await_promiseboolean
expressionstringyes
extension_idstring
tab_idinteger
timeout_msinteger

No output schema declared.

No examples provided.

host_list_registrations ~51

Lists registered extension IDs from the host state file. Cheap read-only view; does NOT verify that manifests are on disk or that any extension is connected. For the full setup picture, prefer host_status.

Input schema present but exposes no named parameters.

No output schema declared.

No examples provided.

host_register_extension ~211

Registers an extension ID as an allowed origin for the pwa-debug native messaging host. Detects every Chromium-family install on this machine — Linux native packages, Linux snap (Chromium), Linux flatpak (any vendor), macOS Application Support, Windows HKCU registry — and writes the host manifest into each one. Also drops an install-time launcher script (POSIX sh / Windows .bat) that embeds an absolute node path so the host spawns correctly under sandboxed/stripped PATH (snap, flatpak). Idempotent. ID DISCOVERY: read the pwa-debug service-worker console via chrome-devtools-mcp — the SW logs `[pwa-debug/sw] id=<id>` on every boot. NEVER invent an ID. After this returns requiresReload:true, the user must reload the extension at chrome://extensions for Chrome to re-validate allowed_origins. If next_steps mentions a flatpak caveat, surface it to the user verbatim.

NameTypeReqDescription
extension_idstringyes

No output schema declared.

No examples provided.

host_reset ~77

DESTRUCTIVE: removes ALL registered extension IDs and deletes every per-browser host manifest (POSIX files + Windows HKCU keys + the shared %APPDATA% manifest JSON). Requires confirm:"reset" to invoke (a typed safety guard). Inform the user before calling. Use only when starting setup from scratch.

NameTypeReqDescription
confirmstringyes

No output schema declared.

No examples provided.

host_status ~98

Reports the install/liveness state of the pwa-debug native messaging host: registered extension IDs, expected manifest paths per detected browser install (with on-disk verification for POSIX kinds), launcher script path + presence, and the host binary path. Cheap, idempotent, no side effects. CALL THIS BEFORE ANY OTHER pwa-debug TOOL to confirm setup. The structured response includes a next_steps[] array tailored to the actual state — follow it.

Input schema present but exposes no named parameters.

No output schema declared.

No examples provided.

host_unregister_extension ~98

Removes an extension ID from the pwa-debug host manifest allowed_origins. If at least one ID remains, manifests are rewritten with the new union; if the last ID is removed, manifests are deleted entirely (Windows HKCU keys cleared too). Use to recycle stale IDs after a manifest key change in dev. Idempotent: removing an already-absent ID returns removed:false with no side effects.

NameTypeReqDescription
extension_idstringyes

No output schema declared.

No examples provided.

idb_list ~172

List the debugged PWA's IndexedDB databases and their schema. Returns IdbListResult { supported, databases: [{ name, version, stores: [{ name, keyPath, autoIncrement, indexes: [{ name, keyPath, unique, multiEntry }] }], error? }] } read from the page's indexedDB API. Use to discover where the app keeps structured/offline data, then idb_query(db, store) to read records — the recurring 'inspect IndexedDB live' need that CDP/chrome-devtools-mcp does not surface for your real profile. Read-only: opening a database never creates one. Args: { extension_id?, tab_id? }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

idb_query ~199

Read a capped slice of records from one IndexedDB object store. Returns IdbQueryResult { supported, found, db, store, records: [{ key, value, truncated? }], returned, truncated } read read-only from the page's indexedDB. Get the db + store names from idb_list first. Use to inspect the app's offline/cached structured data — the 'what's actually in IndexedDB' need CDP/chrome-devtools-mcp does not surface for your real profile. Read-only (no writes); values over 16KB are truncated. Args: { extension_id?, tab_id?, db (required), store (required), limit?: default 100, max 1000 }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
dbstringyes
extension_idstring
limitinteger
storestringyes
tab_idinteger

No output schema declared.

No examples provided.

network_tail ~274

Tail the host-side network ring buffer (fetch + xhr + websocket events) for a target extension with cursor pagination + FilterSpec. Returns { entries: NetworkEntry[]; cursor: Cursor|null; hasMore: bool }. Each NetworkEntry is discriminated by kind: 'fetch' | 'xhr' (request/response/error phases correlated by captureId, with method, url, headers, status, body, durationMs; xhr adds responseType) | 'websocket' (subkind=open|frame|close|error, connectionId, url, direction=send|receive, frameType=text|binary, data, code, reason). All carry host fields (receivedAt, sessionId, extensionId, sequenceNumber) plus per-entry cursor. With no extension_id, targets the single connected NMH (errors if zero or multiple). FilterSpec (all optional): pattern={include?: regex sources[], exclude?: regex sources[]} matches against JSON.stringify of each entry; since/until=opaque cursor strings; limit=int 1..1000 (default 200); level applies only to console events and returns empty entries when set on the network buffer; selectors reserved for DOM tail tools. Errors carry kind in next_steps for AI self-correction.

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

pdl_blur ~210

Blur an element. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_browser_status ~141

Live state of the browsers pdl_launch_browser has started or attached to: each managed launch (browser, profile mode, port, pid, browserUrl) with a fresh debug-port liveness re-probe, plus the pwa-debug extension connections (extensionId + lastSeenAt heartbeat age). Cheap, no side effects. Use it to confirm a launch is still alive, find the browserUrl to hand to chrome-devtools-mcp, or see whether the extension SW is connected. Launch records persist across host restarts (launches.json); the liveness re-probe distinguishes still-running browsers from ones that have since closed. Follow next_steps[].

Input schema present but exposes no named parameters.

No output schema declared.

No examples provided.

pdl_check ~223

Check a checkbox/radio (idempotent; native click path so onChange fires). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_check_setup ~307

Diagnose pwa-debug + chrome-devtools-mcp setup and return { ok, gaps[], recommendations[], detail }. Checks: chrome-devtools-mcp registration (read from the `claude` CLI via `claude mcp get`) AND that its configured --browserUrl matches the active managed debug port (or launch.defaultPort) — flagging both not-registered and registered-at-the-wrong-port; native-messaging host manifest installed for a detected browser, the per-profile manifest present under each ACTIVE sandbox launch's <user-data-dir>/NativeMessagingHosts/ (a custom --user-data-dir searches there, not the install location), bundled extension dist present, an extension ID registered, live NMH connections, AND extension-id / allow-list consistency — it derives the bundled extension's id from its pinned manifest key and probes any managed browser's debug port for loaded extension ids, flagging the case where an extension is loaded but its id is not whitelisted in allowed_origins (so it loads yet never connects). ok=true means no gaps. When gaps exist, next_steps carries the exact remediation (the `claude mcp add chrome-devtools …` snippet, the host install/register command, the host_register_extension <id> fix for a mismatch, or a pdl_install_extension pointer). detail also reports bundledExtensionId + loadedExtensionIds. Cheap, no side effects. Run this first on a new machine, then chain pdl_install_extension → pdl_launch_browser.

Input schema present but exposes no named parameters.

No output schema declared.

No examples provided.

pdl_click ~224

Click an element (full pointer/mouse event chain so React/Vue delegated onClick fires). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_close_browser ~225

Cleanly close a browser that pdl_launch_browser started — the symmetric counterpart to launch. Operates STRICTLY off the managed-launch registry, so it can NEVER touch your own/normal browser: a launch we only ATTACHED to (didn't spawn) is detached from the registry, never killed. Shutdown prefers a clean CDP Browser.close (no 'restore tabs' crash prompt), falling back to SIGTERM then SIGKILL of the spawned process. Args: target by browser, port, or pid (or all:true for every managed launch); session? = 'persist' (default — keep the profile dir), 'discard' (also delete the sandbox profile dir; ignored for the user's 'existing' profile), or 'detach' (drop the registry record, leave the browser running). With no target it does nothing (returns an error) — closing requires intent. Follow next_steps[].

NameTypeReqDescription
allboolean
browserstring
pidinteger
portinteger
sessionstring

No output schema declared.

No examples provided.

pdl_dblclick ~212

Double-click an element. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_double_tap ~214

Double-tap the located element. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_drag ~272

Drag the located element to a point (toX/toY) or onto targetSelector; pointer drag + optional HTML5 DnD. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
html5boolean
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
stepsnumber
tab_idinteger
targetSelectorstring
textstring
toXnumber
toYnumber

No output schema declared.

No examples provided.

pdl_fill ~233

Set an input/textarea/select value via the native setter + input/change (works with React controlled inputs). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring
valuestringyes

No output schema declared.

No examples provided.

pdl_focus ~209

Focus an element. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_hover ~218

Hover an element (pointer/mouse over/enter/move). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_install_extension ~133

Copy the bundled pwa-debug extension to a folder for manual unpacked install in a Chromium browser. Args: target? (destination dir; defaults to ~/Downloads/pwa-debug-extension). Returns { source, dest } and step-by-step chrome://extensions Developer-mode 'Load unpacked' instructions in next_steps, plus the host_register_extension follow-up. Errors with build guidance if the bundled extension isn't present. Note: pdl_launch_browser sandbox-persistent/sandbox-temp preload the extension automatically, so this tool is only needed for installing into the user's normal (existing-mode) profile.

NameTypeReqDescription
targetstring

No output schema declared.

No examples provided.

pdl_key_press ~221

Press a single key on an element. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
keystringyes
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_launch_browser ~614

Launch or attach to a Chromium-family browser with a live remote-debugging port, for use alongside chrome-devtools-mcp. Modes: mode='existing' (default) targets the user's normal profile and degrades gracefully — (a) port already live → attach; (b) running without a debug port → opens a NEW WINDOW in the existing session (never kills it), attached:false + degradation message; (c) not running → spawns fresh with --remote-debugging-port + --user-data-dir=<your profile>. mode='sandbox-persistent' spawns a dedicated, persistent dev profile at ~/.pwa-debug/profiles/<browser>/ beside your normal browser, with the pwa-debug extension PRELOADED (no reload needed); mode='sandbox-temp' is the same but in a throwaway mkdtemp profile cleaned up on host shutdown. Sandbox modes always work standalone (separate profile → no lock collision) and both pwa-debug + CDP tools are available. Args: browser? (chrome|chromium|edge|brave|vivaldi|opera; defaults to system-default), port? (default 9222), mode?, packaging? (native|snap|flatpak). When the same browser is installed under multiple packagings (e.g. snap AND flatpak chromium), pass packaging to pick one; without it the default preference is native > snap > flatpak and next_steps lists the alternatives so you can re-target. isolateExtensions? (sandbox modes only, default true): true pins the dedicated profile to ONLY the pwa-debug extension (clean room — every other extension is disabled); pass false to let other extensions coexist (pwa-debug still preloads, while extensions already in the persistent profile or Load-unpacked/installed after launch stay enabled) — use this to debug a PWA alongside other extensions or to test your own extension with pwa-debug. existing mode already keeps all your normal-profile extensions. extraArgs? (string[]): extra Chromium startup flags appended after pwa-debug's managed flags — e.g. extraArgs=['--enable-speech-dispatcher'] to enable system TTS/speech-dispatcher, or any other --flag the browser ne…

NameTypeReqDescription
browserstring
extraArgsarray
isolateExtensionsboolean
modestring
packagingstring
portinteger

No output schema declared.

No examples provided.

pdl_long_press ~228

Long-press the located element (holds, then releases + contextmenu). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
durationnumber
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_navigate ~238

Navigate a browser tab to a URL, driven through the pwa-debug extension's service worker (chrome.tabs.update) — NO CDP, so it works on the user's real, logged-in profile without chrome-devtools-mcp being registered or attached. Targets the active tab in the last-focused window unless tab_id is given. The URL may omit the scheme (https:// is assumed); a javascript: URL is rejected. Waits for the page to reach document 'complete' (up to timeout_ms, default 10000, max 12000) before returning { extensionId, tabId, url, status, windowId? }, where status is 'complete' or 'loading' (load still in flight at timeout — the navigation still happened). Args: { url: non-empty string, tab_id?, timeout_ms?, extension_id? }. With no extension_id/tab_id, targets the single connected NMH and the active tab. CALL host_status FIRST to confirm a connection.

NameTypeReqDescription
extension_idstring
tab_idinteger
timeout_msinteger
urlstringyes

No output schema declared.

No examples provided.

pdl_new_tab ~215

Open a NEW browser tab at a URL via the pwa-debug extension's service worker (chrome.tabs.create) — NO CDP, works on the user's real profile with no chrome-devtools-mcp dependency. The URL may omit the scheme (https:// is assumed); a javascript: URL is rejected. active? controls foreground vs. background (defaults to foreground). Waits for the page to reach document 'complete' (up to timeout_ms, default 10000, max 12000) before returning { extensionId, tabId, url, status, windowId?, created:true }. Pass the returned tabId to other pwa-debug tools to target this tab specifically. Args: { url: non-empty string, active?, timeout_ms?, extension_id? }. With no extension_id, targets the single connected NMH. CALL host_status FIRST to confirm a connection.

NameTypeReqDescription
activeboolean
extension_idstring
timeout_msinteger
urlstringyes

No output schema declared.

No examples provided.

pdl_pinch ~232

Pinch-zoom on the located element with two touches. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
scalenumberyes
selectorstring
stable_idstring
stepsnumber
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_register_chrome_devtools ~242

Register the separate, optional chrome-devtools-mcp server with Claude Code on the user's behalf, pinned to the active debug port. Runs `claude mcp add chrome-devtools --scope user -- npx -y chrome-devtools-mcp@latest --browserUrl http://127.0.0.1:<port>` via the `claude` CLI. Port resolves to: the `port` arg if given, else the active managed launch port (from pdl_launch_browser), else the host launch.defaultPort. Idempotent: no-op when already registered at the correct port; removes + re-adds when registered at the wrong port. IMPORTANT: this MUTATES the user's global (user-scope) MCP config — ALWAYS ask the user for confirmation before calling it. Because it writes a DIRECT MCP registration, a full Claude Code restart is required afterward for the tools to load (next_steps explains the restart, the context-handoff to hand the user before restarting, and the lower-friction plugin alternative that needs only /reload-plugins). Args: port? (override the debug port).

NameTypeReqDescription
portinteger

No output schema declared.

No examples provided.

pdl_scroll ~248

Scroll the located element by delta (dispatches wheel + scrollBy) or scrollIntoView. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
deltaXnumber
deltaYnumber
exactboolean
extension_idstring
frameworkstring
intoViewboolean
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_select_option ~235

Select a <select> option by value or visible label (one required). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
labelstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring
valuestring

No output schema declared.

No examples provided.

pdl_submit ~216

Submit the form owning the located element (requestSubmit). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_swipe ~238

Swipe a touch across the located element in a direction. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
directionstringyes
distancenumber
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
stepsnumber
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_tap ~218

Tap (touchstart/touchend) the located element. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

pdl_type_sequence ~224

Type a string into an editable element char-by-char. Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring
valuestringyes

No output schema declared.

No examples provided.

pdl_uncheck ~215

Uncheck a checkbox (idempotent). Locator: pass ONE of { selector } | { role, name? } | { text, exact? } | { framework, stable_id }. framework: react|vue|svelte|solid|dom (only meaningful for stable_id; svelte stable_id is a file, not an element — use role/text/selector). Disambiguate multiple matches with nth (0-based) or require_unique. Also extension_id?/tab_id?. CALL host_status FIRST. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools.

NameTypeReqDescription
exactboolean
extension_idstring
frameworkstring
namestring
nthinteger
require_uniqueboolean
rolestring
selectorstring
stable_idstring
tab_idinteger
textstring

No output schema declared.

No examples provided.

popup_failures ~374

Surface auth/connect FAILURES from library popups for a target extension. Correlates each tracked popup's in-widget failure (PopupState.failure/alerts captured by popup_tail's producer) with the console errors and failed network requests (fetch/xhr phase 'error' | status>=400 | status===0, websocket 'error') that fired during that popup's open window, matched by frameKey. Returns { reports: PopupFailureReport[] }, each: { popupId, library, detection, frameKey, role=primary|nested, parentPopupId, reason?, alerts?, window{from,to,open}, console[{level,text,ts,sequenceNumber}], network[{kind,url?,method?,status?,phase?,ts,sequenceNumber}] }. reason precedence = in-widget failure text > first uncaught page error (window error/unhandledrejection) > first console error (structured-logger args unwrapped to msg/message) > network error. Each report also carries errors[] (uncaught page errors in the window). By DEFAULT only PRIMARY popups WITH a failure signal are returned — one report per logical widget, so a component-heavy modal (e.g. a Reown/WalletConnect modal of ~hundreds of nested web components) yields ONE failure report, not hundreds. The primary's window already aggregates the whole widget's console/network errors by frameKey. Pass include_nested=true to also report nested component popups (each carries parentPopupId), include_all=true to include primary windows without a failure signal, or popup_id to filter to one. With no extension_id, targets the single connected NMH (errors if zero or multiple). Read-only.

NameTypeReqDescription
extension_idstring
include_allboolean
include_nestedboolean
popup_idstring

No output schema declared.

No examples provided.

popup_record ~221

Bounded, intent-driven recording of the library_popup event stream for a target extension. action='start' subscribes to the extension's capture intake and buffers EVERY library_popup event (primary + nested, in arrival order) in memory — immune to ring-buffer eviction — until action='stop', which persists the stream to <config>/pwa-debug/popup-recordings/<label>/events.jsonl (+ meta.json) and returns { path, count }. action='status' reports the active recording { active, label, startedAt, count }. Forward-only: only events between start and stop are recorded — start with intent, perform the interactions, stop, then view with popup_replay. One recording per extension (start while active returns the in-progress one). Optional label (defaults to rec-<timestamp>); optional extension_id (defaults to the single connected NMH). Use this to capture a specific debugging episode for sequential review instead of always-on noise.

NameTypeReqDescription
actionstringyes
extension_idstring
labelstring

No output schema declared.

No examples provided.

popup_replay ~202

Read back a popup recording captured by popup_record. With no label, lists available recordings on disk ({label, count, startedAt, stoppedAt}). With a label, projects the recorded library_popup events three ways via `mode`: 'primary' (default — only role!=='nested' events, one stream per logical widget; paginated by offset/limit), 'flat' (the raw full-fidelity sequence including nested components; paginated), or 'tree' (a hierarchy of popup nodes — primary roots with nested children attached by parentPopupId, each node summarizing popupId/role/library/detection/host/phases). Reads from <config>/pwa-debug/popup-recordings/<label>/events.jsonl. Use it to review a recorded debugging episode sequentially or hierarchically. Read-only.

NameTypeReqDescription
labelstring
limitinteger
modestring
offsetinteger

No output schema declared.

No examples provided.

popup_tail ~418

Tail the host-side library_popup ring buffer (injected library widgets/popups: WalletConnect, RainbowKit, ConnectKit, Privy, and generic shadow/portal overlays) for a target extension with cursor pagination + FilterSpec. Returns { entries: PopupEntry[]; cursor: Cursor|null; hasMore: bool }. Each PopupEntry carries page-world fields (ts, frameUrl, frameKey, popupId, phase=appeared|updated|disappeared, detection=shadow|portal, library tag ('unknown' when no signature matched), host{tagName, id?, classes?, selector}, role=primary|nested, parentPopupId, and on appeared/updated a state snapshot {visible, title?, text?, buttons?[{label,role}], content?, truncated?} of the widget content) plus host fields (receivedAt, sessionId, extensionId, sequenceNumber) and a per-entry cursor. By DEFAULT only PRIMARY popups are returned — one entry per logical widget — so a component-heavy modal (e.g. a Reown/WalletConnect modal built from ~50 nested shadow-DOM web components) surfaces as a SINGLE popup instead of dozens. Pass include_nested=true to also return the nested component events; each nested entry's parentPopupId points at its enclosing popup so you can reconstruct the widget tree. popupId is stable across a popup's appeared→updated→disappeared lifecycle. With no extension_id, targets the single connected NMH (errors if zero or multiple). FilterSpec (all optional): pattern={include?: regex sources[], exclude?: regex sources[]} matches JSON.stringify of each entry (use it to filter by library, e.g. include:['walletconnect']); since/until=opaque cursor strings; limit=int 1..1000 (default 200); level is ignored (popup events have no console-level field). Errors carry kind in next_steps for AI self-correction.

NameTypeReqDescription
extension_idstring
filterobject
include_nestedboolean

No output schema declared.

No examples provided.

pwa_installability ~179

Diagnose whether the debugged PWA is installable, with actionable gaps instead of 'manifest invalid'. Fetches + parses the web app manifest and checks: manifest present/valid, name/short_name, start_url, app display mode, icons (192 AND 512 AND a maskable purpose), secure context (HTTPS/localhost), and a registered service worker. Returns InstallabilityResult { installable, manifestUrl, manifestFound, secureContext, hasServiceWorker, manifest, gaps: [{ code, severity('error' blocks / 'warning' recommended), message, fix }] }. Each gap names exactly what's wrong and how to fix it. Reads your real page. Args: { extension_id?, tab_id? }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

pwa_snapshot ~197

Capture ONE capped runtime-state blob of the debugged PWA for deterministic bug-repro context. Returns RuntimeSnapshot { url, title, capturedAt, sw (service-worker status), store (auto-detected Redux/Pinia/Jotai/Zustand state, value-capped, or null), webStorage: { local, session }, idb (IndexedDB db/store schema — not records), cacheNames (CacheStorage names + counts) }. Composes the existing sw_status / store_get_state / storage_get / idb_list / cache_list reads into one moment-in-time record you can reason over or hand off to reproduce a bug. Read-only; no new capture surface. For deeper detail use idb_query / cache_inspect / store_get_state. Args: { extension_id?, tab_id? }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

pwa_status ~173

Snapshot the debugged PWA's runtime status + capability matrix. Returns PwaStatusSnapshot { displayMode, standalone (installed?), controlledBySW + controllerScriptURL, permissions: { notifications, push, periodicBackgroundSync }, capabilities: { serviceWorker, pushManager, backgroundSync, periodicBackgroundSync, badging, fileSystemAccess, windowControlsOverlay, webShare, notifications } }. capabilities = live feature-detection in THIS browser (answers 'why does push work on Android but not here'); permissions = current Permissions-API grants. One cheap call assembles what DevTools makes you gather piecemeal. Reads your real profile. Args: { extension_id?, tab_id? }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

pwa_update_analyze ~241

Diagnose service-worker update propagation + version skew for the debugged PWA. Composes sw_status (waiting worker + controller), CacheStorage entry ages (cached HTML vs JS), and recent network 404s into UpdateAnalysisResult { supported, hasWaitingUpdate, controller, findings: [{ code, severity, message }], cachedHtml, cachedJs, chunk404s, summary }. Detects: waiting_update_active_client (an installed SW is waiting while this client stays on the old worker — 'my update won't show'), html_older_js (stale cached HTML referencing chunk hashes the newer JS dropped), and chunk_404 (live chunk misses corroborating the skew). Use for 'why are some users on old code' / 'why are chunks 404ing after deploy'. Analysis over existing reads; no new capture. Args: { extension_id?, tab_id?, per_cache_limit?: default 100, skew_threshold_seconds?: default 3600 }. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
per_cache_limitinteger
skew_threshold_secondsinteger
tab_idinteger

No output schema declared.

No examples provided.