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

PWA Debug Layer

NPM · @ARYANDUNTLEY/PWA-DEBUG · SCANNED SEP 20

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

0 this week 88 Trust /100
Trust breakdown (7 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 → Why this is hard to score →

Supply Chain Security98
  • No malware found by supply-chain analysis.Pass
  • No known CVEs affecting this package version or its production dependencies.Pass
  • No install/post-install scripts declared.Pass
  • 32 of 97 dependencies flagged as unhealthy. 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 86 days ago).Pass
  • Security-disclosure policy not yet verified: we couldn't inspect the source repository.Unverified
Schema Quality & AI Usability67
  • 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 Management93
  • Stability observed for 28 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
Tool Safety75
  • No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
  • 0 of 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "host_unregister_extension" implies "remove" and declares no destructiveHint at all, which the MCP spec reads as destructive by default. See how to fix → Fail
  • An AI judge read all 82 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
  • Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
Install

How do I install the PWA Debug Layer MCP server?

PWA Debug Layer runs locally as an npm package, launched with npx -y @aryanduntley/pwa-debug. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

npm · @aryanduntley/pwa-debug

# add to Claude Code
claude mcp add aryanduntley-pwa-debug-layer -- npx -y @aryanduntley/pwa-debug
// .cursor/mcp.json
{
  "mcpServers": {
    "aryanduntley-pwa-debug-layer": {
      "command": "npx",
      "args": [
        "-y",
        "@aryanduntley/pwa-debug"
      ]
    }
  }
}
// .vscode/mcp.json
{
  "servers": {
    "aryanduntley-pwa-debug-layer": {
      "command": "npx",
      "args": [
        "-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"]
// ~/.netclaw/config/netclaw.json
{
  "McpServers": {
    "aryanduntley-pwa-debug-layer": {
      "Transport": "stdio",
      "Command": "npx",
      "Arguments": [
        "-y",
        "@aryanduntley/pwa-debug"
      ]
    }
  }
}
# add to Vellum
assistant mcp add aryanduntley-pwa-debug-layer -t stdio -c npx -a -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.

  • 20 Sept 26 +1
    • Security disclosure: fail → unverified functional
  • 18 Sept 26 +1

    No change was recorded against any check on this day. Stability & Change Management went from 83 to 87. That category is still filling its 30-day observation window: 25 days of observed history at the previous scan, 26 at this one. The score rises as the window fills, whether or not the server changes.

  • 16 Sept 26 −3
    • Stability: pass → 0.80 functional
  • 15 Sept 26 +1
    • Stability: 0.97 → pass security
  • 13 Sept 26 +1

    No change was recorded against any check on this day. Stability & Change Management went from 90 to 93. That category is still filling its 30-day observation window: 27 days of observed history at the previous scan, 28 at this one. The score rises as the window fills, whether or not the server changes.

  • 12 Sept 26 0
    • Security disclosure: unverified → fail functional
  • 11 Sept 26 +1
    • Security disclosure: fail → unverified functional
  • 9 Sept 26 −3
    • Stability: pass → 0.80 functional
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 20 Sept 2026 · Analysed npm/@aryanduntley/pwa-debug@0.2.0

Provenance Verified

A signed build attestation was found and verified, binding this exact artifact to the source repository it claims to come from.

Result Verified
Ecosystem npm
Reason Verified
Discovered via Registry attestation endpoint
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

Background: How many MCP packages publish verified provenance →

Dependencies 97 packages
Packages resolved 97
Stale 32
Tree resolution Complete

Background: SBOMs and build attestations, explained →

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. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →

Tool Tokens
react_find_by_role ~275

Find React components whose rendered host node has a given ARIA role, optionally narrowed by an accessible-name regex. Args: { extension_id?, tab_id?, role: required ARIA role string (e.g. 'button','link','heading','navigation','region','textbox','checkbox','img' — matched against an explicit role attribute or a simplified implicit-role mapping; exact, lowercase), name?: regex source string matched against the element’s accessible name (aria-label > first aria-labelledby ref > text content; compiled with new RegExp, no flags), root_index?: limit to one React root (default: all), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { stableId, displayName, key?, role, name? }[], truncated, rootCount }. Feed any stableId into react_get_state. Tool-level error on an invalid name regex. Runs in page-world via the page-bridge — no CDP, coexists with the user’s DevTools. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
extension_idstring
max_matchesinteger
namestring
rolestringyes
root_indexinteger
tab_idinteger

No output schema declared.

No examples provided.

react_find_by_text ~236

Find React components whose rendered host-node text matches a regex. Args: { extension_id?, tab_id?, pattern: required regex source string (compiled with new RegExp — no flags, case-sensitive), exact?: bool=false (true = regex must match the FULL trimmed text of the component’s host node; false = substring match), root_index?: limit to one React root (default: all), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { stableId, displayName, key?, matchedText }[], truncated, rootCount }. Feed any stableId into react_get_state for that component’s props/state/hooks. Tool-level error on an invalid regex pattern. Runs in page-world via the page-bridge — no CDP, coexists with the user’s DevTools. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
exactboolean
extension_idstring
max_matchesinteger
patternstringyes
root_indexinteger
tab_idinteger

No output schema declared.

No examples provided.

react_get_state ~242

Return the props, state, and hooks of a single React component identified by stable_id (obtained from a prior react_tree call). Args: { extension_id?, tab_id?, stable_id: required non-empty string, root_index?: number=0 (must match the root used to compute the id), include_props?: bool=true, include_hooks?: bool=true }. Returns { extensionId, tabId, stableId, displayName, key?, props?, state?, hooks?: SerializedHook[], truncated? }. SerializedHook entries: { type: 'state'|'memo'|'effect'|'ref'|'custom'; index; value?; deps?; truncated? }. Tool-level error when stable_id no longer resolves — in that case re-call react_tree to refresh ids. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see connections.

NameTypeReqDescription
extension_idstring
include_hooksboolean
include_propsboolean
root_indexinteger
stable_idstringyes
tab_idinteger

No output schema declared.

No examples provided.

react_tree ~251

Return the React component tree of the active (or specified) tab as a structured ReactTreeNode[]. Each node has { stableId, displayName, key?, hasState, hasHooks, children } where stableId is a re-render-stable component identity (path + displayName + key/index). Args: { extension_id?, tab_id?, root_index?: pick one root (default: all), depth_limit?: max depth (default 8, cap 64), max_nodes?: total cap (default 200, cap 5000) }. Returns { extensionId, tabId, roots, truncated, rootCount }. truncated:true means the walk hit depth_limit or max_nodes; re-call with a tighter root_index or higher caps. Pass any node's stableId to react_get_state for that component's props/state/hooks. Runs in page-world (MAIN world) via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
depth_limitinteger
extension_idstring
max_nodesinteger
root_indexinteger
tab_idinteger

No output schema declared.

No examples provided.

recent_events ~229

Returns recent CapturedEvents from the extension's SW-side ring buffer (in-memory, lost on SW restart; M11 will add cross-restart persistence). Each event has shape: { kind, ts, frameUrl, frameKey, ...kind-specific-fields } where kind is 'console' (currently) or 'fetch'/'xhr'/'websocket' (M9 Task 8). Filters: kinds=['console','fetch'] restricts to listed kinds; since_ms is a strict greater-than ts cutoff; limit caps the result to the most-recent N (default 50, hard max = SW bufferSize). Returns { extensionId, events, stats: { totalReceived, perKind, bufferSize } }. With no extension_id, targets the single connected NMH (errors if zero or multiple). CALL host_status FIRST to see which extensions are connected, and session_ping to confirm the page-bridge half is healthy on the active tab.

NameTypeReqDescription
extension_idstring
kindsarray
limitinteger
since_msnumber

No output schema declared.

No examples provided.

redux_dispatch ~168

DEPRECATED — prefer store_dispatch (unified, framework auto-detect). Dispatch an action into the active Redux store (the only WRITE surface in the store-introspection family). DISABLED BY DEFAULT — opt in via settings.set { key: 'capture.stores.allowDispatch', value: true }. Args: { extension_id?, tab_id?, action: { type: non-empty string; payload? } }. Returns { extensionId, tabId, dispatched: true, action, scopeUrl } on success. Tool-level errors (no store detected; user reducer threw) follow the same { error: { message } } convention as redux_get_state. CALL host_status FIRST to see connections.

NameTypeReqDescription
actionobjectyes
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

redux_get_state ~148

DEPRECATED — prefer store_get_state (unified, framework auto-detect). Return the current Redux store state from the active tab, optionally pruned to a dotted/bracket path. Args: { extension_id?, tab_id?, path?: 'counter.value' | 'todos[0].text' | "users['by-id']" }. Returns { extensionId, tabId, state, path?, truncated?, scopeUrl }. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see connections.

NameTypeReqDescription
extension_idstring
pathstring
tab_idinteger

No output schema declared.

No examples provided.

redux_subscribe ~174

DEPRECATED — prefer store_subscribe (unified, framework auto-detect). Start or stop a Redux store subscription on the active tab. While active, each store.subscribe callback whose path-narrowed snapshot differs from the prior snapshot emits a store_change event that flows through the standard capture pipeline; read accumulated events via redux_tail. Args: { extension_id?, tab_id?, action: 'start' | 'stop', path?: 'counter' | 'todos.items' }. Returns { extensionId, tabId, active, path?, scopeUrl }. Single subscription per page-world; calling action='start' again replaces any prior subscription. CALL host_status FIRST to see connections.

NameTypeReqDescription
actionstringyes
extension_idstring
pathstring
tab_idinteger

No output schema declared.

No examples provided.

redux_tail ~197

DEPRECATED — prefer store_tail (unified, framework-tagged entries). Tail the host-side store_change ring buffer (populated while redux_subscribe is active) for a target extension. Returns { entries: StoreChangeEntry[]; cursor: Cursor|null; hasMore: bool }. Each StoreChangeEntry carries page-world fields (ts, frameUrl, frameKey, storeId, path?, diff{added, changed, removed}, snapshot, truncated?) plus host fields (receivedAt, sessionId, extensionId, sequenceNumber, cursor). FilterSpec (all optional): pattern={include?: regex sources[], exclude?: regex sources[]}; since/until=opaque cursor strings; limit=int 1..1000 (default 200). level is ignored (store_change has no console-level field). Call redux_subscribe(action='start') first to start producing events.

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

session_ping ~263

Sends a ping through the full MCP → IPC → NMH → SW → CS → page-world chain and returns round-trip metadata: { hostVersion, extensionVersion, attachedTabId, extensionId, latencyMs, pageWorld, pageWorldError?, pageWorldErrorMessage?, pageWorldSelfHealed? }. pageWorld is { url, title, readyState } read live from the active tab's MAIN-world page-world script. pageWorld is null with pageWorldError set (a typed code: cs_not_attached_refresh_tab | page_blocks_scripts | page_world_blocked | restricted_url | no_active_tab | cs_inject_failed) when the SW round-trip succeeded but the page-bridge half failed. The SW also auto-recovers tabs that loaded before the extension reload by programmatically re-injecting the content script and page-world bundle — when this works, pageWorld is populated and pageWorldSelfHealed:true. The tool's next_steps[] field carries imperative, code-specific guidance the AI should relay verbatim to the user. With no args, targets the single connected NMH (errors if zero or multiple). Pass extension_id to target a specific extension. CALL host_status FIRST to see which extensions are currently connected.

NameTypeReqDescription
extension_idstring

No output schema declared.

No examples provided.

session_record ~162

Start or stop a rrweb session recording on the active tab. Each recorded event flows through the capture pipeline as a 'replay' CaptureKind and accumulates in the host replay ring buffer (readable via session_replay). Args: { extension_id?, tab_id?, action: 'start' | 'stop', session_id?: stable id for grouping (auto-generated when missing), duration_cap_ms?: int > 0 (auto-stop deadline) }. Single recording per page-world; action='start' replaces any prior recording. CALL host_status FIRST to see connections.

NameTypeReqDescription
actionstringyes
duration_cap_msinteger
extension_idstring
session_idstring
tab_idinteger

No output schema declared.

No examples provided.

session_replay ~138

Tail the host-side replay ring buffer (populated while session_record is active) for a target extension. Returns { entries: ReplayEntry[]; cursor; hasMore }. Each ReplayEntry carries page-world fields (ts, frameUrl, frameKey, sessionId, rrwebType, data, timestamp) plus host fields (receivedAt, sessionId, extensionId, sequenceNumber, cursor). FilterSpec: pattern (regex over JSON.stringify), since/until cursors, limit. level is ignored. Call session_record(action='start') first to start producing events.

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

settings_get ~66

Reads host settings. Omit key to receive every value. Provide key to receive { value, entry } where entry is the wire-safe schema metadata (type, default, scope, description, enumValues). Unknown keys return an error referencing settings_list_schema.

NameTypeReqDescription
keystring

No output schema declared.

No examples provided.

settings_list_schema ~93

Returns every user-tunable host setting as data: key, runtime type tag (number | boolean | string[] | enum[]), default, consuming scope (host | extension | both), human description, and enumValues (for enum[] entries). Order is stable schema-declaration order. Use this BEFORE settings_set so the value shape matches the entry type — settings_set rejects invalid shapes with a schema-contextualized error.

Input schema present but exposes no named parameters.

No output schema declared.

No examples provided.

settings_set ~85

Writes a single host setting. Validates the value against the per-key schema validator (rejects with a schema-contextualized error). On accept: atomic-persists to ~/.config/pwa-debug/settings.json and notifies in-process subscribers. Unknown keys and invalid values are rejected with a next_step pointing at settings_list_schema.

NameTypeReqDescription
keystringyes
valueyes

No output schema declared.

No examples provided.

solid_detect ~183

Detect SolidJS on the active (or specified) tab. Args: { extension_id?, tab_id? }. Returns { extensionId, tabId, present, devtoolsHook, hydration, delegatedEventCount }. Solid has no virtual DOM and no persisted component tree, so unlike React/Vue there is NO solid_components or solid_get_state — detection is best-effort (the @solid-devtools hook window.__SOLID_DEVTOOLS__, the _$HY hydration global, and a heuristic count of elements carrying Solid's $$-delegated-event props). devtoolsHook:true means @solid-devtools is installed (deeper data may be reachable). Use solid_find_by_text / solid_find_by_role for DOM-level element matching. Runs in page-world via the page-bridge. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

solid_find_by_role ~185

Find DOM ELEMENTS with a given ARIA role on a Solid page, optionally narrowed by an accessible-name regex. Args: { extension_id?, tab_id?, role: ARIA role string (exact, lowercase), name?: regex source (new RegExp, no flags), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { locator, tag, role, name? }[], truncated }. NOTE: Solid has no component identity, so matches are ELEMENTS (locator = best-effort CSS-ish selector), NOT components — documented Solid degradation. Runs in page-world via the page-bridge. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
max_matchesinteger
namestring
rolestringyes
tab_idinteger

No output schema declared.

No examples provided.

solid_find_by_text ~175

Find DOM ELEMENTS whose rendered text matches a regex on a Solid page. Args: { extension_id?, tab_id?, pattern: regex source (new RegExp, no flags, case-sensitive), exact?: bool=false, max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { locator, tag, matchedText }[], truncated }. NOTE: Solid has no component identity, so matches are ELEMENTS (locator = best-effort CSS-ish selector), NOT components — this is the documented Solid degradation. Runs in page-world via the page-bridge. CALL host_status FIRST.

NameTypeReqDescription
exactboolean
extension_idstring
max_matchesinteger
patternstringyes
tab_idinteger

No output schema declared.

No examples provided.

source_map_resolve ~173

Resolve a single generated stack frame (script_url + line + column) to its original-source location using the script's source map. Args: { extension_id?, tab_id?, script_url: non-empty string, line: int >= 1, column: int >= 0 }. Returns { extensionId, tabId, original?: { source, line, column, name? }, scopeUrl }. original is undefined when no map is available or no mapping exists at the requested coordinates. Disabled via capture.sourceMap.enabled=false. M13 ships query-time resolution; M13.5 will add capture-time auto-annotation when needed.

NameTypeReqDescription
columnintegeryes
extension_idstring
lineintegeryes
script_urlstringyes
tab_idinteger

No output schema declared.

No examples provided.

storage_get ~204

Snapshot the debugged PWA's web storage. Returns StorageGetResult { supported, area, entries: [{ key, value, truncated? }], entryCount, truncated } read from the page's localStorage or sessionStorage. Use to inspect auth tokens, feature flags, cached app state, and 'why is the app in this state' bugs that live in storage. Reads your REAL profile's storage; CDP/chrome-devtools-mcp does not surface this. Values over 8KB are truncated. For structured/large data the app keeps in IndexedDB, use idb_list + idb_query instead. Args: { extension_id?, tab_id?, area?: 'local' (default) | 'session', limit?: default 500, max 2000 }. Page-world read. CALL host_status FIRST.

NameTypeReqDescription
areastring
extension_idstring
limitinteger
tab_idinteger

No output schema declared.

No examples provided.

store_dispatch ~177

Dispatch an action into the active tab's store (the only WRITE surface in the store-introspection family). DISABLED BY DEFAULT — opt in via settings.set { key: 'capture.stores.allowDispatch', value: true }. Auto-detects the framework or pass framework to force one. Args: { extension_id?, tab_id?, framework?, action: { type: non-empty string; payload? } }. Returns { extensionId, tabId, dispatched: true, framework, action, scopeUrl }. Note: stores without a Redux-style dispatch (e.g. some Zustand/Jotai setups) return a tool-level error from the page-world handler. CALL host_status FIRST to see connections.

NameTypeReqDescription
actionobjectyes
extension_idstring
frameworkstring
tab_idinteger

No output schema declared.

No examples provided.

store_get_state ~168

Return the current state of the active tab's JS store, optionally pruned to a dotted/bracket path. Auto-detects the store framework (Redux today; Zustand/Pinia/Jotai as adapters land), or pass framework to force one. Args: { extension_id?, tab_id?, path?: 'counter.value' | 'todos[0].text', framework?: 'redux' }. Returns { extensionId, tabId, framework, state, path?, truncated?, scopeUrl }. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see connections.

NameTypeReqDescription
extension_idstring
frameworkstring
pathstring
tab_idinteger

No output schema declared.

No examples provided.

store_subscribe ~188

Start or stop a store subscription on the active tab. While active, each store change whose path-narrowed snapshot differs from the prior snapshot emits a store_change event (tagged with the detecting framework) that flows through the standard capture pipeline; read accumulated events via store_tail. Auto-detects the framework or pass framework to force one. Args: { extension_id?, tab_id?, framework?, action: 'start' | 'stop', path?: 'counter' | 'todos.items' }. Returns { extensionId, tabId, active, framework?, path?, scopeUrl }. Single subscription per page-world; calling action='start' again replaces any prior subscription. CALL host_status FIRST to see connections.

NameTypeReqDescription
actionstringyes
extension_idstring
frameworkstring
pathstring
tab_idinteger

No output schema declared.

No examples provided.

store_tail ~202

Tail the host-side store_change ring buffer (populated while store_subscribe is active) for a target extension. Framework-agnostic: each entry's framework field names the store library that produced it. Returns { entries: StoreChangeEntry[]; cursor: Cursor|null; hasMore: bool }. Each StoreChangeEntry carries page-world fields (ts, frameUrl, frameKey, storeId, framework?, path?, diff{added, changed, removed}, snapshot, truncated?) plus host fields (receivedAt, sessionId, extensionId, sequenceNumber, cursor). FilterSpec (all optional): pattern={include?: regex sources[], exclude?: regex sources[]}; since/until=opaque cursor strings; limit=int 1..1000 (default 200). level is ignored (store_change has no console-level field). Call store_subscribe(action='start') first to start producing events.

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

svelte_components ~207

List the Svelte components rendered on the active (or specified) tab. Args: { extension_id?, tab_id? }. Returns { extensionId, tabId, present, dev, metaElementCount, components: { stableId, file, firstLoc?, elementCount }[] }. Svelte compiles components to closures with no instance tree, so discovery uses the dev-only __svelte_meta source tags: each component == one .svelte file (the file path is its stableId), and elementCount is how many rendered elements belong to it. dev:false means a production build (no __svelte_meta) → components is empty. There is NO svelte_get_state (Svelte exposes no readable instance/state). Use svelte_find_by_text / svelte_find_by_role to locate components by content. Runs in page-world via the page-bridge — no CDP. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

svelte_find_by_role ~211

Find Svelte components whose rendered DOM node has a given ARIA role, optionally narrowed by an accessible-name regex. Args: { extension_id?, tab_id?, role: ARIA role string (exact, lowercase; explicit role attr or simplified implicit mapping), name?: regex source for the accessible name (new RegExp, no flags), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { stableId, file, role, name? }[], truncated }. Matches map to the owning component .svelte file (stableId === file), de-duped per file. DEV-mode only (relies on __svelte_meta). No state read exists for Svelte. Runs in page-world via the page-bridge. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
max_matchesinteger
namestring
rolestringyes
tab_idinteger

No output schema declared.

No examples provided.

svelte_find_by_text ~193

Find Svelte components whose rendered DOM text matches a regex. Args: { extension_id?, tab_id?, pattern: regex source (new RegExp, no flags, case-sensitive), exact?: bool=false (full-text vs substring), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { stableId, file, matchedText }[], truncated }. Matches map to the owning component .svelte file (stableId === file), de-duped per file. DEV-mode only (relies on __svelte_meta); empty on production builds. No state read exists for Svelte. Runs in page-world via the page-bridge. CALL host_status FIRST.

NameTypeReqDescription
exactboolean
extension_idstring
max_matchesinteger
patternstringyes
tab_idinteger

No output schema declared.

No examples provided.

sw_lifecycle_tail ~243

Tail the DEBUGGED PWA's service-worker lifecycle event stream (kind 'sw_state') with cursor pagination + FilterSpec. Returns { entries: SwStateEntry[]; cursor: Cursor|null; hasMore: bool }. Each SwStateEntry: { subkind, scope?, scriptURL?, state?, slot? } + host fields (receivedAt, sessionId, extensionId, sequenceNumber, cursor). subkind: 'updatefound' = a new worker started installing; 'statechange' = a worker advanced lifecycle state (installing→installed→activating→activated→redundant); 'controllerchange' = the page's controlling worker changed. This is the EVENT STREAM (transitions over time) — for the point-in-time snapshot (waiting/active/controller, hasWaitingUpdate) use sw_status. Captured in page-world against your real profile; CDP/chrome-devtools-mcp does not surface this. Events only fire on transitions, so a stable app may return none. With no extension_id, targets the single connected NMH. CALL host_status FIRST.

NameTypeReqDescription
extension_idstring
filterobject

No output schema declared.

No examples provided.

sw_status ~183

Inspect the DEBUGGED PWA's service worker(s). Returns SwStatusSnapshot { supported, controller, registrations[], hasWaitingUpdate } read from the page's navigator.serviceWorker — the installing/waiting/active worker for each registration (with script URLs + lifecycle state), which worker controls the page, and whether an update is stuck WAITING (the #1 'why isn't my update showing' signal). Reads the app's real SW state in your actual browser profile — something CDP / chrome-devtools-mcp does not surface. Args: { extension_id?, tab_id? }. Runs in page-world via the page-bridge (no CDP). For the lifecycle event stream (updatefound/statechange/controllerchange) use sw_lifecycle_tail. CALL host_status FIRST to see connected extensions.

NameTypeReqDescription
extension_idstring
tab_idinteger

No output schema declared.

No examples provided.

vue_find_by_role ~290

Find Vue 3 components whose rendered DOM node has a given ARIA role, optionally narrowed by an accessible-name regex. Args: { extension_id?, tab_id?, role: required ARIA role string (e.g. 'button','link','heading','navigation','region','textbox','checkbox','img' — matched against an explicit role attribute or a simplified implicit-role mapping; exact, lowercase), name?: regex source string matched against the element's accessible name (aria-label > first aria-labelledby ref > text content; compiled with new RegExp, no flags), root_index?: limit to one Vue root (default: all), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { stableId, displayName, key?, role, name? }[], truncated, rootCount }. Matching elements are mapped to their owning component (one entry per component). Feed any stableId into vue_get_state. Tool-level error on an invalid name regex. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
extension_idstring
max_matchesinteger
namestring
rolestringyes
root_indexinteger
tab_idinteger

No output schema declared.

No examples provided.

vue_find_by_text ~249

Find Vue 3 components whose rendered DOM text matches a regex. Args: { extension_id?, tab_id?, pattern: required regex source string (compiled with new RegExp — no flags, case-sensitive), exact?: bool=false (true = regex must match the FULL trimmed text of a rendered element; false = substring match), root_index?: limit to one Vue root (default: all), max_matches?: cap (default 20, max 500) }. Returns { extensionId, tabId, matches: { stableId, displayName, key?, matchedText }[], truncated, rootCount }. Matching elements are mapped to their owning component (one entry per component). Feed any stableId into vue_get_state for that component's props/setupState/data. Tool-level error on an invalid regex pattern. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
exactboolean
extension_idstring
max_matchesinteger
patternstringyes
root_indexinteger
tab_idinteger

No output schema declared.

No examples provided.

vue_get_state ~215

Return the props, setup() bindings, and options-API data of a single Vue 3 component identified by stable_id (obtained from a prior vue_tree call). Args: { extension_id?, tab_id?, stable_id: required non-empty string, include_props?: bool=true, include_state?: bool=true }. Returns { extensionId, tabId, stableId, displayName, key?, props?, setupState?, data?, truncated? }. setupState holds <script setup>/setup() bindings (refs auto-unwrapped); data holds options-API reactive state. Empty surfaces are omitted. Tool-level error when stable_id no longer resolves — re-call vue_tree to refresh ids. Runs in page-world via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see connections.

NameTypeReqDescription
extension_idstring
include_propsboolean
include_stateboolean
stable_idstringyes
tab_idinteger

No output schema declared.

No examples provided.

vue_tree ~253

Return the Vue 3 component tree of the active (or specified) tab as a structured VueTreeNode[]. Each node has { stableId, displayName, key?, hasProps, hasState, children } where stableId is a re-render-stable component identity (path + name + key/index). Args: { extension_id?, tab_id?, root_index?: pick one root (default: all), depth_limit?: max depth (default 8, cap 64), max_nodes?: total cap (default 200, cap 5000) }. Returns { extensionId, tabId, roots, truncated, rootCount }. truncated:true means the walk hit depth_limit or max_nodes; re-call with a tighter root_index or higher caps. Pass any node's stableId to vue_get_state for that component's props/setupState/data. Runs in page-world (MAIN world) via the page-bridge — no CDP, coexists with the user's DevTools. CALL host_status FIRST to see which extensions are connected.

NameTypeReqDescription
depth_limitinteger
extension_idstring
max_nodesinteger
root_indexinteger
tab_idinteger

No output schema declared.

No examples provided.

Common questions

What is the PWA Debug Layer MCP server?

PWA Debug Layer is an MCP server listed in the public MCP registry as io.github.aryanduntley/pwa-debug-layer. Debug PWAs in your real browser via MCP: service-worker, cache, installability & framework state. This page covers its npm package (@aryanduntley/pwa-debug).

Is the PWA Debug Layer MCP server safe to use?

PWA Debug Layer scores 88 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 20 September 2026. It declares no install or post-install scripts. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

What tools does the PWA Debug Layer MCP server expose?

PWA Debug Layer exposes 82 tools: host_status, host_register_extension, host_unregister_extension, host_list_registrations, host_reset, and 77 more. Their descriptions and schemas cost roughly 17,357 tokens of context every time the server is loaded.

Is the PWA Debug Layer MCP server still maintained?

PWA Debug Layer is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

What licence is the PWA Debug Layer MCP server under?

PWA Debug Layer declares the MIT licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.