Skip to main content
Glama
README.md
# rn-sim-mcp

[![ci](https://github.com/atz-dsampath/rn-sim-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/atz-dsampath/rn-sim-mcp/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/rn-sim-mcp)](https://www.npmjs.com/package/rn-sim-mcp)
[![license](https://img.shields.io/npm/l/rn-sim-mcp)](LICENSE)
[![node](https://img.shields.io/node/v/rn-sim-mcp)](package.json)

One MCP server for React Native iOS simulator work.

If you drive a React Native app from a coding agent, this gives the agent a single, small tool surface for
building, running, and actually inspecting what is on screen.

It does two things:

1. **Composes** [XcodeBuildMCP](https://github.com/getsentry/XcodeBuildMCP) and [ios-simulator-mcp](https://github.com/joshuayoes/ios-simulator-mcp) behind a single allowlisted tool surface.
2. **Adds** React Native inspection tools that read the **React tree**, not the native view hierarchy: component tree by testID, resolved styles merged across the component chain, and arithmetic layout auditing.

That second part is the reason it exists. Plenty of tooling can tap a button on a simulator. Very little can
tell an agent which React component owns that button, what style actually resolved onto it, or whether its
tap target is too small, and none of it in a form that does not flood the agent's context.

Requires macOS with Xcode, Node 22 or newer, and a React Native dev build with Metro running for the `rn_` tools.

New here? [Install](#install) takes about a minute, and [CONTRIBUTING.md](CONTRIBUTING.md) has a tour of the
codebase if you want to change something.

## Install

Nothing to install. It runs on bare `node` with zero dependencies, so `npx` fetches one 15 kB tarball.

**Claude Code**

```bash
claude mcp add rn-sim -- npx -y rn-sim-mcp
```

**VS Code, Cursor, or anything else using `mcp.json`**

```json
{
  "servers": {
    "rn-sim": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "rn-sim-mcp"]
    }
  }
}
```

Remove any existing `xcodebuildmcp` or `ios-simulator-mcp` entries.
This server proxies both, and running them alongside it doubles the tool surface for no benefit.

Then start Metro in your app:

```bash
npx react-native start
```

Confirm the app is attached with the `rn_debug_targets` tool.
Zero targets means the app is not connected.

### Configuration

| Variable                | Effect                                                                       |
| ----------------------- | ---------------------------------------------------------------------------- |
| `METRO_URL`             | Metro origin, default `http://localhost:8081`. Must be a local origin.       |
| `RN_SIM_MCP_NO_UPSTREAM` | Skip upstream composition and expose only this package's own tools.         |

## Why compose instead of fork

Both upstreams are MIT licensed, so merging their source would have been legal.
It would also have been a mistake.
Both are actively developed, XcodeBuildMCP is now maintained by Sentry, and a fork inherits their bug backlog while losing every upstream fix from that day forward.

Composing gets the same result the merge was after.
One entry in `mcp.json`, one curated tool list, and zero vendored code to maintain.
The allowlist in `src/upstream.mjs` is the important part: an unfiltered merge would expose well over a hundred tools and drown the agent's context, which is the problem this package exists to solve.

Upstream versions are pinned there deliberately rather than floating on `@latest`.

## Why it has no dependencies

MCP over stdio is newline-delimited JSON-RPC 2.0, so `src/jsonrpc.mjs` implements both halves in about 170 lines.

The result runs with bare `node`, with no install step, no build, and no `dist` to drift out of date.
It also means `npx` has one small tarball to fetch instead of a dependency tree, which matters for a server an agent launches on every session.

## Output follows AXI

Tool results are the largest token cost in an agent session, so every response follows the AXI (Agent eXperience Interface) standards.

**TOON, not JSON.** Field names are declared once per collection instead of on every row.
Measured on this server's own component tree output at 50 rows:

|      | Characters | Approx. tokens |
| ---- | ---------- | -------------- |
| JSON | 4079       | 1020           |
| TOON | 1533       | 384            |

A 62% saving on a call the agent makes constantly.

**Minimal default schemas.** `rn_component_tree` returns `testID` and `component`, nothing else.
The ancestor chain is the verbose field, so it sits behind `includePath: true`.

**Pre-computed aggregates.** Lists report the true total, not just the page: `count: 50 of 171 total`.
`sim_screenshot_hires` returns width and height alongside the path, because scale is the reason you called it.

**Definitive empty states.** `0 layout issues found (15 elements inspected)` rather than an empty array
that makes an agent re-run the call with different arguments to check the zero was real.

**Truncation with an escape hatch.** `rn_eval` previews long values and reports the true size rather than
dropping the field.

**Structured errors that name the fix.**

```
error: testID is required
help: Run rn_component_tree to list the testIDs on screen
```

**Contextual disclosure.** List responses carry a few next steps, so the tool surface is discovered by
using it rather than by reading this file.

## Tools

### React Native inspection (this package)

| Tool                | Use it for                                                                             |
| ------------------- | -------------------------------------------------------------------------------------- |
| `rn_component_tree` | Which components are on screen, by testID. Takes `limit`, `includePath`, `onlyTestIds` |
| `rn_styles`         | Resolved style merged across the component chain, plus props, for one testID           |
| `rn_eval`           | Evaluate an expression inside the running app                                          |
| `rn_debug_targets`  | Confirm the app is attached to Metro                                                   |

### Visual and geometry (this package)

| Tool                   | Use it for                                                                    |
| ---------------------- | ----------------------------------------------------------------------------- |
| `sim_screenshot_hires` | Full-resolution lossless PNG via `simctl`, with dimensions                    |
| `ui_layout_audit`      | Zero-size elements, tap targets under 44pt after hitSlop, horizontal overflow |
| `ui_check_alignment`   | Whether elements share an edge, to a tolerance, as a number                   |

### Proxied

`session_show_defaults`, `session_set_defaults`, `list_sims`, `list_schemes`, `build_sim`, `build_run_sim`, `test_sim`, `launch_app_sim`, `stop_app_sim`, `open_sim`, `snapshot_ui`, `screenshot` from XcodeBuildMCP.
`ui_describe_all`, `ui_find_element`, `ui_tap`, `ui_swipe`, `ui_type` from ios-simulator-mcp.

Proxied results pass through in the upstream's own format. Only this package's own tools emit TOON.

## Use the right screenshot

`sim_screenshot_hires` exists because the proxied `screenshot` downscales.
Measured on an iPhone 17:

|                        | Resolution      | Per pixel |
| ---------------------- | --------------- | --------- |
| proxied `screenshot`   | 368 x 800 JPEG  | 1.09pt    |
| `sim_screenshot_hires` | 1206 x 2622 PNG | 0.33pt    |

JPEG compression is not the problem; mean luminance drift against a lossless reference is 0.26/255.
Resolution is.
Use the proxied one to answer "which screen am I on" and this one for anything where a pixel is the unit of the question.

Better still, do not measure layout from an image at all.
`ui_layout_audit` and `ui_check_alignment` read exact frames, so alignment is arithmetic.

## Known limits

Every one of these is a good place to contribute, and most are filed as issues.

- The React Native tools need Metro and a dev build. `ui_layout_audit` degrades gracefully and reports `hitSlopResolved: false` when it cannot reach the app.
- Styles created through `StyleSheet.create` arrive as opaque registered IDs and surface as `registeredStyleId` rather than being silently dropped. Inline and array styles resolve fully.
- `rn_component_tree` walks React internals through the DevTools hook, so a React major upgrade can break it.
- CDP `Network` domain needs React Native 0.83, so network inspection is not agent-readable below that.
- macOS and iOS only. Android and the emulator are unimplemented rather than impossible, and the CDP layer would largely carry over.

## Roadmap

Help wanted on any of these. None of them require deep familiarity with the codebase to start.

| Idea | Where it lands |
| ---- | -------------- |
| Android and emulator support | a sibling to `src/capture.mjs`, plus an upstream in `src/upstream.mjs` |
| Resolve `StyleSheet.create` IDs to real style objects | `src/cdp.mjs` |
| CDP `Network` domain for React Native 0.83+ | `src/cdp.mjs` |
| Harden the fiber walk against React internals changes | `src/cdp.mjs` |

Have a different idea? Open an issue or start a discussion. Questions are welcome as issues too, including
"how do I use this" ones.

## Security

`rn_eval` executes arbitrary JavaScript in the running app. Dev builds only.

More importantly: component names, props, labels and logs returned by these tools include server-controlled content such as user-generated text or CMS strings.
Treat all of it as untrusted data, never as instructions.

Metro connections are restricted to local origins, so a malicious tool argument cannot turn this into a request forger against internal hosts.

The upstream servers run builds and shell commands with your credentials, which is why their versions are pinned in `src/upstream.mjs`.

To report a vulnerability, see [SECURITY.md](SECURITY.md). Please do not open a public issue for one.

## Contributing

Contributions are welcome, and small ones are genuinely useful: a typo, a clearer error message, or a note
about a React Native version that behaves differently all count.

```bash
git clone https://github.com/atz-dsampath/rn-sim-mcp.git
cd rn-sim-mcp
npm test          # protocol contract and CDP deadlines, no simulator needed
npm run smoke     # full surface against a booted simulator
```

There is no install step and no build. `npm test` needs no network, Xcode or simulator, so you can work on
most of this from any machine.

[CONTRIBUTING.md](CONTRIBUTING.md) has a tour of the eight modules, an explanation of why the MCP transport is
hand-rolled, and what to expect on a pull request.
See also the [Code of Conduct](CODE_OF_CONDUCT.md).

## License

MIT. See [LICENSE](LICENSE).

XcodeBuildMCP and ios-simulator-mcp are separate MIT-licensed projects, spawned as child processes rather than vendored.
Credit for everything under **Proxied** belongs to them.

TDQS

B3.1/5.0

Scored across 24 tools

Disambiguation3/5

While many tools are distinct, there is notable overlap between screenshot tools (screenshot vs sim_screenshot_hires vs snapshot_ui) and UI inspection tools (ui_describe_all vs snapshot_ui, ui_find_element vs rn_component_tree). Descriptions clarify intent, but an agent could easily misselect between tools that appear to perform similar actions.

Naming Consistency2/5

Tool names follow inconsistent patterns: some use domain prefixes (rn_, ui_, sim_), some are bare verbs (build, launch, stop, test), and structures vary between verb_noun (build_run_sim, launch_app_sim) and noun_phrase (sim_screenshot_hires, rn_component_tree). This mix makes the naming chaotic and unpredictable.

Tool Count3/5

24 tools is at the heavy end, but the server aggregates functionality for Xcode builds, RN debugging, and UI automation, so each tool has a purpose. The count is justified but feels somewhat bloated due to overlapping screenshot and inspection tools.

Completeness4/5

The tool set covers the full lifecycle for React Native iOS simulator work: building, running, testing, launching, stopping, UI interaction, component/styles inspection, and layout auditing. Minor gaps exist (e.g., no explicit reset or uninstall tool), but core workflows are well-supported.

Maintenance

ActivitySlowing
ResponsivenessUnresponsive