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

@vitrion/expo-state-mcp

v1.2.0

Published

MCP server + in-app HTTP bridge for Expo SQLite and Zustand — works with Cursor, Claude, OpenAI Codex (CLI + IDE), and any MCP client (dev-only)

Readme

expo-state-mcp

npm npm downloads CI Release Provenance License: MIT

Inspect and mutate a running Expo app's live expo-sqlite database and zustand stores from any MCP client (Cursor, Claude, OpenAI Codex, …) — dev-only, zero production footprint.

One package (@vitrion/expo-state-mcp): Metro resolves the react-native export (TCP bridge in the app); Node runs the expo-state-mcp CLI for your MCP host.

MCP client  ←stdio→  CLI  ←HTTP→  bridge (in the RN app)

Expo app setup

1. Install

yarn add -D @vitrion/expo-state-mcp

react-native-tcp-socket is included as a dependency of this package. Rebuild the native app once (expo run:ios / expo run:android) so autolinking picks it up.

For a local checkout next to your app:

yarn add -D file:../expo-state-mcp

Run yarn build in this repo first (dist/ is not committed). Ensure Metro resolves package.json exports (Expo SDK 54+ / Metro 0.82+ usually does; otherwise resolver.unstable_enablePackageExports = true in metro.config.js).

2. Wire the bridge (__DEV__)

import { setupBridge } from "@vitrion/expo-state-mcp";
import * as SQLite from "expo-sqlite";

const db = SQLite.openDatabaseSync("my.db");
import { useAuthStore } from "./stores/auth";

void setupBridge({
  port: 9778,
  appName: "my-app",
  db,
  stores: { "zustand.auth": useAuthStore },
});

setupBridge returns a Promise (it collects device metadata before listening). You can void setupBridge(...) at module scope or await setupBridge(...) during app init. Call once early (e.g. next to Reactotron). No-ops when __DEV__ is false.

Android Emulator: from the host machine, run adb reverse tcp:9778 tcp:9778 so http://127.0.0.1:9778 reaches the emulated app.

Optional Bearer token: app token + env EXPO_STATE_MCP_TOKEN on the machine running the MCP CLI.

bindAllInterfaces (optional)

By default the bridge listens on loopback (127.0.0.1). That is enough in many setups, including a lot of iOS Simulator + Mac flows, so you do not need to set anything extra to start.

Turn it on when the MCP CLI (or curl on your Mac) cannot reach the app — errors like “bridge unreachable” or 127.0.0.1:9778 connection refused while the app is running:

  • iOS Simulator: the simulator and the Mac each have their own loopback; on some OS / simulator versions, only the simulator can see a 127.0.0.1 listener. If the host cannot connect, pass bindAllInterfaces: true (listens on 0.0.0.0) so traffic from your Mac reaches the bridge. Example:

    void setupBridge({
      // …
      bindAllInterfaces: true,
    });

    To limit binding to simulator only (not physical devices), you can use Platform.OS === "ios" && !Device.isDevice from react-native / expo-device.

  • Physical device: use bindAllInterfaces: true (or bind to your LAN as needed), read the LAN IP from console logs if provided, and set EXPO_STATE_MCP_BRIDGE_URL on your machine to http://<device-ip>:9778.

If everything already works without it, leave it unset.

3. Wire your MCP client

Same stdio server everywhere: npx -y @vitrion/expo-state-mcp. Optional env EXPO_STATE_MCP_BRIDGE_URL (default http://127.0.0.1:9778).

Pick your client — then open the matching full details block for copy-paste config.

| Client | Where you wire it | |--------|-------------------| | Cursor | Project .cursor/mcp.jsonmcpServers | | Claude Desktop | claude_desktop_config.json (paths below) → same mcpServers JSON shape | | Claude Code | claude mcp add … (MCP docs) | | OpenAI Codex (CLI + IDE) | Shared ~/.codex/config.toml or codex mcp add … (Codex MCP); IDE: gear → MCP settings → Open config.toml |

Create .cursor/mcp.json:

{
  "mcpServers": {
    "expo-state-mcp": {
      "command": "npx",
      "args": ["-y", "@vitrion/expo-state-mcp"],
      "env": {
        "EXPO_STATE_MCP_BRIDGE_URL": "http://127.0.0.1:9778"
      }
    }
  }
}

Edit claude_desktop_config.json:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Same mcpServers shape as Cursor:

{
  "mcpServers": {
    "expo-state-mcp": {
      "command": "npx",
      "args": ["-y", "@vitrion/expo-state-mcp"],
      "env": {
        "EXPO_STATE_MCP_BRIDGE_URL": "http://127.0.0.1:9778"
      }
    }
  }
}

From a terminal. Flags go before the server name; -- separates Claude’s options from the command that starts the MCP server.

claude mcp add --scope user --env EXPO_STATE_MCP_BRIDGE_URL=http://127.0.0.1:9778 expo-state-mcp -- npx -y @vitrion/expo-state-mcp

See Claude Code MCP docs.

OpenAI Codex reads MCP from ~/.codex/config.toml or project .codex/config.toml (trusted projects). CLI and IDE extension share one file — configure once, use in both.

Terminal:

codex mcp add expo-state-mcp --env EXPO_STATE_MCP_BRIDGE_URL=http://127.0.0.1:9778 -- npx -y @vitrion/expo-state-mcp

Or edit config.toml (in the IDE: MCP settings → Open config.toml from the gear menu). Env block matches Codex docs ([mcp_servers.<name>.env]):

[mcp_servers.expo-state-mcp]
command = "npx"
args = ["-y", "@vitrion/expo-state-mcp"]

[mcp_servers.expo-state-mcp.env]
EXPO_STATE_MCP_BRIDGE_URL = "http://127.0.0.1:9778"

More options: codex mcp help, timeouts, streamable HTTP servers — Model Context Protocol – Codex.

Use command: node and point args at ../expo-state-mcp/dist/cli/cli.js (after yarn build in the clone). Copy-paste JSON and TOML per client in DEVELOPMENT.md.

Connectivity

| Target | MCP URL on your machine | Notes | |--------|-------------------------|--------| | iOS Simulator | Usually http://127.0.0.1:9778 | Add bindAllInterfaces only if the Mac cannot reach the bridge (see above). | | Android Emulator | http://127.0.0.1:9778 after adb reverse tcp:9778 tcp:9778 | Reverse forwards host port to the emulator. | | Physical device | http://<lan-ip>:9778 | Use bindAllInterfaces / LAN binding; align EXPO_STATE_MCP_BRIDGE_URL with logs. |

Devices (MCP)

Each successful bridge response includes a device object (platform, model, id, optional lanIp, …). The MCP tools return JSON shaped as { "device": { … }, "data": … } so you always know which bridge answered.

  • list_devices — probes GET /device on every configured bridge and returns { default, devices }. Use optional refresh: true to drop the cache and re-probe.
  • Per-tool device — every SQLite/Zustand tool accepts an optional device string: a device id or alias from list_devices. If you configure more than one bridge URL, set EXPO_STATE_MCP_DEFAULT_DEVICE to an id or alias, or pass device on each call (otherwise the CLI errors with a hint).

One bridge (default): only EXPO_STATE_MCP_BRIDGE_URL (default http://127.0.0.1:9778) — same as before.

Several bridges (e.g. iOS simulator + Android emulator on different ports):

  • EXPO_STATE_MCP_BRIDGES — JSON array or comma-separated list of base URLs:
    • JSON: [{"url":"http://127.0.0.1:9778"},{"url":"http://127.0.0.1:9779","alias":"android"}]
    • Shorthand: http://127.0.0.1:9778,http://127.0.0.1:9779
  • EXPO_STATE_MCP_DEFAULT_DEVICE — id or alias used when a tool omits device.

If the app bridge predates GET /device, the CLI still probes /health and assigns a stable legacy-… device id so list_devices and routing keep working after a CLI-only upgrade.

MCP tools

list_devices, sqlite_list_tables, sqlite_describe_table, sqlite_query, sqlite_explain, zustand_list_stores, zustand_get, zustand_set, zustand_call

Repo layout

  • src/app/ — bridge (bundled by Metro)
  • src/cli/ — MCP stdio server

Contributor / agent process: AGENTS.md. Maintainer workflow: DEVELOPMENT.md.

CLI environment

| Variable | Default | |----------|---------| | EXPO_STATE_MCP_BRIDGE_URL | http://127.0.0.1:9778 | | EXPO_STATE_MCP_BRIDGES | (empty — use EXPO_STATE_MCP_BRIDGE_URL only) | | EXPO_STATE_MCP_DEFAULT_DEVICE | (empty — required when multiple bridges and tool omits device) | | EXPO_STATE_MCP_TOKEN | (empty) |

Security

Dev-only: loopback by default, optional Bearer token (compared in constant time), request bodies capped at 5 MiB, setupBridge does nothing in production builds. Use a shared secret when binding beyond loopback.

License

MIT — see LICENSE.