npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

mcp-rn-devtools

v0.5.0

Published

MCP server for React Native runtime debugging — zero-config access to Redux state, AsyncStorage, actions, logs, network, and profiling from your running app

Readme

mcp-rn-devtools

CI npm version npm version License: MIT

An MCP server that gives Claude (or any MCP host) real-time access to your running React Native app — including your Redux state and AsyncStorage, with zero app changes. Console logs, errors, network, state, storage, action log, navigation, performance profiling, and more.

Why this one

  • True zero-config state access. A runtime agent injected over the Chrome DevTools Protocol walks the React fiber tree to discover your Redux store — no SDK, no middleware, no exposing the store on a global. Reading AsyncStorage works the same way (native module proxy). Install the server, ask Claude about your state.
  • Headless. No desktop app to keep open, no toggle to remember. Works in fully autonomous agent workflows and CI.
  • Secrets redacted by default. Tokens, passwords, auth headers, and JWT-shaped strings are masked server-side before anything reaches the LLM (MCP_RN_NO_REDACT=1 to opt out).
  • Built for agent loops. clear_buffers → reproduce → read. wait_for_log blocks until the app emits a matching log. get_state_diff shows exactly what changed between two moments.

How It Works

Claude / MCP Host
       │ MCP (stdio)
       ▼
  mcp-rn-devtools (server)
    ├── CDP WebSocket ──► RN App (Hermes / Metro)   ← zero config
    │     └── runtime agent (injected): Redux discovery,
    │         AsyncStorage, action log, navigation
    └── SDK WebSocket ◄── mcp-rn-devtools-sdk        ← optional enhancer

Layer 1 — CDP + runtime agent (zero config): Connects via Chrome DevTools Protocol through Metro's debugger proxy. Captures console logs, errors, warnings, and network requests, and injects a runtime agent that discovers Redux stores / React Navigation / React Query by walking the fiber tree, reads and writes AsyncStorage through the native module proxy, and records every dispatched action. Also provides JS evaluation, memory/CPU profiling, and source map resolution. No app changes needed.

Layer 2 — SDK (optional enhancer): Install mcp-rn-devtools-sdk for what the agent can't reach: Zustand/custom stores, MMKV, per-component render profiling, navigation timing, and a second capture channel that survives CDP drops.

Installation

With Claude Code

claude mcp add rn-devtools -- npx -y mcp-rn-devtools

Or add .mcp.json to your project root (shared with your team):

{
  "mcpServers": {
    "rn-devtools": {
      "command": "npx",
      "args": ["-y", "mcp-rn-devtools"]
    }
  }
}

With Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "rn-devtools": {
      "command": "npx",
      "args": ["-y", "mcp-rn-devtools"]
    }
  }
}

Manual

npm install -g mcp-rn-devtools
mcp-rn-devtools --version

Quick Start

  1. Start your React Native app — Metro must be running, Hermes engine (default since RN 0.70).
  2. Add the MCP server to your Claude config (see above).
  3. Ask Claude about your app:
    • "What's in my Redux store right now?"
    • "Read the AsyncStorage key persist:root"
    • "Clear the buffers, I'll reproduce the bug — then show me what happened"
    • "Dispatch auth/logout and show me the state diff"
    • "Profile the CPU for 3 seconds and show me the hot functions"

Available Tools

Source legend: agent = zero config, injected via CDP. CDP = zero config, protocol-level. SDK = requires mcp-rn-devtools-sdk in the app. Read-only tools are annotated with readOnlyHint so MCP hosts can auto-allow them.

State & Actions

| Tool | Source | Description | |------|--------|-------------| | get_app_state | agent + SDK | Redux store state with dot-path access and depth control. Stores discovered automatically | | get_state_diff | agent + SDK | What changed in the store since the last call (baseline → diff workflow) | | get_action_log | agent + SDK | Dispatched actions with duration and changed slices — recorded automatically, no middleware | | dispatch_action | agent | Dispatch a Redux action to reproduce states or trigger flows |

Storage

| Tool | Source | Description | |------|--------|-------------| | get_storage_keys | agent + SDK | List AsyncStorage (zero-config) or MMKV (SDK) keys with search | | get_storage_value | agent + SDK | Read a storage value — secrets redacted by default |

Logging & Errors

| Tool | Source | Description | |------|--------|-------------| | get_console_logs | CDP + SDK | Console output with level filter and search; capped for the LLM by default, full=true for everything | | get_errors | CDP + SDK | JS errors and exceptions with stack traces (5 frames by default, full=true for all) | | get_warnings | CDP + SDK | LogBox warnings from console.warn, deduplicated across debugger reconnects | | wait_for_log | CDP + SDK | Block until a log matching a pattern appears — synchronize with app activity |

Network

| Tool | Source | Description | |------|--------|-------------| | get_network_requests | CDP + SDK | HTTP requests; verbose adds headers/bodies with secrets redacted, capped unless full=true | | get_failed_requests | CDP + SDK | Requests with status >= 400 or network errors, response bodies capped unless full=true |

Diagnostics

| Tool | Source | Description | |------|--------|-------------| | health_check | — | Verdict first — READY or BLOCKED: cause → fix — then version, debugger owner, stores, counts | | list_targets | — | Targets on every Metro port (8081–8085) with who holds each one (→ this instance, ⊙ another instance, ✗ library runtime) | | select_target | — | Pin a target by id (optionally with metro_port) or by app id prefix; takes it from the instance holding it; library runtimes (Reanimated) are refused | | clear_buffers | — | Reset captured data before reproducing a scenario |

Navigation

| Tool | Source | Description | |------|--------|-------------| | get_navigation_state | agent + SDK | Current route and stack (React Navigation), discovered automatically | | get_navigation_timing | SDK | Screen transition timing with per-route summary |

Memory & Performance

| Tool | Source | Description | |------|--------|-------------| | get_memory_usage | CDP | Current heap usage (used / total / percentage) | | take_heap_snapshot | CDP | Heap snapshot summary — object count, top retainers by size | | get_cpu_profile | CDP | CPU profile for N seconds — hot functions sorted by self time | | force_gc | CDP | Trigger garbage collection, return before/after heap comparison | | get_render_profile | SDK | Component render events — mount/update durations, slow renders |

Advanced

| Tool | Source | Description | |------|--------|-------------| | evaluate_js | CDP | Execute JavaScript in the app: global and globalThis both work, await_promise settles Promises in-app | | resolve_source_location | CDP | Resolve bundled line:column to original source via Metro source maps |

Building on top of the core

The server exports its building blocks (ConnectionManager, SDKBridgeServer, the injected agent bridge, adb helpers) so tools can extend it in the same process — Hermes admits a single debugger, so extensions must share the CDP session rather than compete for it. Unrecognized SDK-channel messages are re-emitted as sdk-message events, and sendToClient() lets extensions answer.

The first product built this way is tapfix: a live QA loop for React Native (mark issues on-device or from a cockpit web UI, and an embedded coding agent fixes them on the fly). Its MCP server is a superset of this one.

Multiple sessions and multiple apps

Hermes admits one debugger per app instance, and every Claude Code session starts its own mcp-rn-devtools process. Ownership is settled per app on a device, not per machine:

  • Sessions on different apps coexist. Two emulators, two apps, two sessions: each holds its own debugger. Instances register in ~/.mcp-rn-devtools/instances/ and ask each other to yield through a per-instance control endpoint — only the one holding that app is asked.
  • The debugger follows the session in use for the same app. An instance attaches on its first tool call (lazy, the default); if a sibling holds that app, it yields and gets it back on its own next tool call. An instance kicked by a sibling does not fight back.
  • A session attaches to its own app. The server reads the app id from the repo it runs in (android/app/build.gradle applicationId, the iOS bundle id, or Expo's app.json; MCP_RN_APP overrides) and finds that app across Metro ports 8081–8085 — a session in the wms repo attaches to wms even while picking runs on 8081. select_target also takes an app id.
  • No orphans. The server exits when its MCP client goes away (stdin closes or the parent process dies).
  • health_check shows the session app, who holds the debugger, the other instances and their apps, and who serves the SDK channel. list_targets marks targets held by other instances with ⊙.

Extensions that record continuously (tapfix) opt into connectMode: 'eager'. Instances older than 0.5 (no registry) are still asked to yield over the SDK port.

Secret Redaction

Every tool that outputs app data (state, storage values, network headers/bodies, action payloads) masks secrets server-side, before the data reaches the LLM:

  • Values under sensitive keys (token, password, authorization, apiKey, session, cookie, …) → [REDACTED]
  • JWT-shaped strings and Bearer … tokens anywhere in text → masked

Redaction is a blocklist (defence in depth, not a guarantee) — audit what your app stores before pointing any LLM at it. Opt out with MCP_RN_NO_REDACT=1.

SDK Setup (optional)

The runtime agent covers Redux, AsyncStorage, actions, and navigation with zero config. Install the SDK only if you need Zustand/custom stores, MMKV, render profiling, or navigation timing:

npm install mcp-rn-devtools-sdk --save-dev
import { RNDevtoolsProvider } from 'mcp-rn-devtools-sdk';

export default function App() {
  return (
    <RNDevtoolsProvider>
      <YourApp />
    </RNDevtoolsProvider>
  );
}

With Zustand / custom stores

const useAuthStore = create((set) => ({ /* ... */ }));

<RNDevtoolsProvider stateManagers={{ auth: useAuthStore }}>
  <YourApp />
</RNDevtoolsProvider>

State snapshots are pull-only: they're serialized only when a tool asks, so the SDK adds zero overhead while idle.

With MMKV

import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();

<RNDevtoolsProvider mmkv={storage}>
  <YourApp />
</RNDevtoolsProvider>

Per-Component Render Profiling

import { RNDevtoolsProfiler } from 'mcp-rn-devtools-sdk';

<RNDevtoolsProfiler id="UserList">
  <UserList />
</RNDevtoolsProfiler>

Provider Props

| Prop | Type | Description | |------|------|-------------| | navigationRef | RefObject | React Navigation container ref for richer route tracking | | stateManagers | Record<string, StateStore> | Zustand/custom stores for state inspection | | reduxMiddlewares | DevtoolsMiddleware[] | Middlewares created via createDevtoolsMiddleware() | | asyncStorage | AsyncStorageLike | AsyncStorage instance (also available zero-config via agent) | | mmkv | MMKVLike | MMKV instance for storage reading | | port | number | WebSocket port (default: 8098) | | host | string | Dev machine host — auto-detected from the bundle URL (SourceCode.scriptURL), works on emulators, physical devices, and adb reverse |

The SDK automatically strips itself from production builds via __DEV__ checks — zero overhead in release.

Configuration

| Environment Variable | Default | Description | |---------------------|---------|-------------| | METRO_PORT | 8081 | Metro bundler port (when it has no app, ports 8082–8085 are checked too) | | SDK_PORT | 8098 | SDK WebSocket port — also marks which instance owns the debugger | | MCP_RN_CONNECT | lazy | lazy attaches on the first tool call; eager attaches at startup | | MCP_RN_APP | from the repo | App id prefix(es) this session works on, comma-separated (overrides the cwd detection) | | MCP_RN_STATE_DIR | ~/.mcp-rn-devtools/instances | Where instances register themselves for per-app ownership | | MCP_RN_NO_REDACT | - | Disable secret redaction | | MCP_RN_NO_UPDATE_CHECK | - | Skip the npm "newer version" lookup in health_check | | MCP_RN_DEBUG | - | Enable debug logging |

Architecture Notes

  • Target selection: RN 0.76+ (Fusebox) no longer advertises vm: 'Hermes' — the server picks the main runtime by reactNative.capabilities.prefersFuseboxFrontend and skips secondary runtimes like Reanimated's. Legacy targets still work via the vm field.
  • Kick-and-poll: CDP's awaitPromise can't resolve React Native's polyfilled Promises, so async in-app operations (AsyncStorage) fire a callback that writes to a result slot, which the server polls.
  • Clock skew: log/error entries carry a server-clock receivedAt — device clocks can drift seconds from the host, which would break "wait for new logs" cuts.
  • Reconnection: exponential backoff, agent re-injected automatically after every bundle reload. A pinned target that keeps failing is unpinned after 3 attempts.
  • Console replay: Runtime.enable replays the runtime's console backlog on every reconnect; entries are deduplicated by (timestamp, message) so warnings do not inflate.
  • global in the evaluate scope: Hermes exposes globalThis but not global to Runtime.evaluate (Metro only passes global to module factories). Injected scripts resolve the global object themselves and evaluate_js aliases global for the duration of the call.
  • Multi-Metro: a known session app is searched across ports 8081–8085 regardless of what the configured port serves. Without a session app: when nothing listens on the configured port and exactly one other port has an app, the server switches to it; a running Metro without an app is left alone (the app is about to appear there), and with several candidates it asks you to select_target.
  • Target identity: ownership is keyed by reactNative.logicalDeviceId (RN 0.73+: a stable hash of app + device, shared by the app's main and Reanimated runtimes), falling back to app id + device name.
  • Action log caveat: the agent wraps store.dispatch at discovery time; components that captured a direct dispatch reference before discovery bypass the log (rare — discovery runs at connect).

Compatibility

  • React Native: 0.71+ (validated against 0.80 / bridgeless / Fusebox)
  • Engine: Hermes (default since RN 0.70)
  • Platforms: iOS, Android
  • Node.js: 20+
  • MCP Hosts: Claude Code, Claude Desktop, or any MCP-compatible client

Development

mise install          # node 22 + pnpm 9 from .mise.toml (pnpm 10+ breaks the build)
pnpm install
pnpm build            # shared → server → sdk
pnpm test             # vitest, includes fake Metro/Hermes integration tests
pnpm typecheck && pnpm lint

Release: bump version in the three packages/*/package.json, push, and publish a GitHub Release — publish.yml runs CI and pnpm -r publish.

License

MIT