npm package: @nizarius/mcp-rnw-browser
Description: MCP Browser Server optimized for React Native Web with multi-tab support, screenshots, and persistent sessions
Author: nizarius
Latest version: 2.0.1

# MCP Browser Server for React Native Web

[![npm version](https://img.shields.io/npm/v/@nizarius/mcp-rnw-browser.svg)](https://www.npmjs.com/package/@nizarius/mcp-rnw-browser)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A Model Context Protocol (MCP) browser automation server specifically designed to work with **React Native Web** applications. Features multi-tab support, screenshot capabilities, and persistent browser sessions.

## The Problem

React Native Web uses a gesture responder system that listens for `mousedown`/`mouseup` events instead of standard `click` events. This breaks standard browser automation tools like Playwright's `locator.click()` method.

## The Solution

This MCP server uses **coordinate-based clicks** with Playwright's low-level `page.mouse` API, which properly triggers the mouse events that React Native Web components listen for.

## Features

- **Coordinate-based clicking** - Uses `page.mouse.down()` / `page.mouse.up()` instead of synthetic clicks
- **Multiple element finding strategies** - CSS selector, text content, testID, or exact coordinates
- **Custom page snapshots** - Shows interactive elements with positions (not reliant on accessibility tree)
- **Screenshot support** - Returns base64-encoded PNG images that AI agents can view
- **Multi-tab management** - Create, switch between, and close browser tabs
- **Persistent sessions** - Browser stays open between tool calls for continuous interaction
- **Full browser control** - Navigate, type, scroll, press keys, evaluate JavaScript

## Installation

### From npm

```bash
npm install @nizarius/mcp-rnw-browser
npx playwright install chromium
```

### From source

```bash
git clone https://github.com/nizarius/mcp-rnw-browser.git
cd mcp-rnw-browser
npm install
npm run build
npx playwright install chromium
```

## Configuration for Cursor

Add to your Cursor MCP settings (`~/.cursor/mcp.json` or Cursor Settings > MCP):

### Using npm package (recommended)

```json
{
  "mcpServers": {
    "rnw-browser": {
      "command": "npx",
      "args": ["@nizarius/mcp-rnw-browser"]
    }
  }
}
```

### Using local installation

```json
{
  "mcpServers": {
    "rnw-browser": {
      "command": "node",
      "args": ["/path/to/mcp-rnw-browser/dist/index.js"]
    }
  }
}
```

## Available Tools

### Navigation

#### `rnw_navigate`
Navigate to a URL. Creates a browser session if none exists.
```json
{ "url": "http://localhost:8081" }
```
**Returns:** Page snapshot with all interactive elements.

### Screenshots & Snapshots

#### `rnw_snapshot`
Get a text snapshot of interactive elements on the page with their positions. Returns element tags, text content, testIDs, roles, positions, and center coordinates for clicking.
```json
// No parameters required
{}
```
**Returns:** Text list of all interactive elements with their positions.

#### `rnw_screenshot`
Take a screenshot and return as a base64-encoded PNG image.
```json
// Viewport screenshot (default)
{}

// Full page screenshot (captures entire scrollable area)
{ "fullPage": true }

// Element screenshot (captures specific element)
{ "selector": "#my-component" }
```
**Returns:** Base64-encoded PNG image that AI agents can view directly.

### Interactions

#### `rnw_click`
Click on an element using React Native Web compatible mouse events (mousedown/mouseup).
```json
// By CSS selector
{ "selector": "button.submit", "findBy": "css" }

// By text content (partial match)
{ "selector": "Sign In", "findBy": "text" }

// By testID (data-testid attribute)
{ "selector": "login-button", "findBy": "testid" }

// By exact coordinates (useful when element detection fails)
{ "x": 500, "y": 300, "findBy": "coordinates" }
```
**Returns:** Click coordinates and updated page snapshot.

#### `rnw_type`
Type text into a focused element or find an element first and type into it.
```json
// Type into currently focused element
{ "text": "Hello World" }

// Find element first, then type (clicks to focus)
{ "text": "Hello World", "selector": "input", "findBy": "css" }

// Type and press Enter (e.g., for search/submit)
{ "text": "Hello World", "selector": "input", "findBy": "css", "pressEnter": true }
```
**Returns:** Confirmation of typed text.

#### `rnw_scroll`
Scroll the page or a specific scrollable element.
```json
// Scroll page down by 300 pixels
{ "direction": "down", "amount": 300 }

// Scroll page up
{ "direction": "up", "amount": 500 }

// Scroll within a specific container
{ "direction": "down", "amount": 200, "selector": ".scroll-container" }
```
**Parameters:** `direction` (up/down/left/right), `amount` (pixels, default: 300), `selector` (optional).

**Returns:** Updated page snapshot.

#### `rnw_wait`
Wait for a specified time or until an element appears on the page.
```json
// Wait for 1 second (1000ms)
{ "time": 1000 }

// Wait for element to appear (with 5s timeout)
{ "selector": "button.loaded", "findBy": "css" }

// Wait for text to appear
{ "selector": "Loading complete", "findBy": "text" }
```
**Returns:** Confirmation when wait completes or error if timeout.

#### `rnw_press_key`
Press a keyboard key. Useful for navigation, form submission, or triggering shortcuts.
```json
// Press Enter
{ "key": "Enter" }

// Press Escape
{ "key": "Escape" }

// Press arrow keys
{ "key": "ArrowDown" }

// Press Tab to move focus
{ "key": "Tab" }
```
**Common keys:** Enter, Escape, Tab, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, Backspace, Delete, Space.

**Returns:** Confirmation of key pressed.

#### `rnw_evaluate`
Execute JavaScript in the browser context. Useful for debugging, reading state, or performing custom interactions.
```json
// Get page title
{ "script": "document.title" }

// Get current URL
{ "script": "window.location.href" }

// Read localStorage value
{ "script": "localStorage.getItem('authToken')" }

// Get element count
{ "script": "document.querySelectorAll('button').length" }

// Trigger custom action
{ "script": "window.scrollTo(0, document.body.scrollHeight)" }
```
**Returns:** JSON-stringified result of the script execution.

### Tab Management

#### `rnw_tabs_list`
List all open browser tabs with their index, title, and URL.
```json
// No parameters required
{}
```
**Returns:** List of all tabs showing index, active status, title, and URL.

#### `rnw_tabs_new`
Create a new browser tab and optionally navigate to a URL. The new tab becomes the active tab.
```json
// Create empty new tab (about:blank)
{}

// Create new tab and navigate to URL
{ "url": "http://localhost:8081/settings" }
```
**Returns:** New tab index and page snapshot.

#### `rnw_tabs_select`
Switch to a specific tab by index (0-based). Use `rnw_tabs_list` to see available tabs.
```json
// Switch to second tab
{ "index": 1 }

// Switch to first tab
{ "index": 0 }
```
**Returns:** Snapshot of the selected tab's page.

#### `rnw_tabs_close`
Close a browser tab. If the closed tab was active, switches to the nearest remaining tab.
```json
// Close current active tab
{}

// Close specific tab by index
{ "index": 2 }
```
**Returns:** Confirmation and snapshot of the new active tab.

### Session Management

#### `rnw_session_status`
Get current browser session status. Useful for checking if a session is active before performing actions.
```json
// No parameters required
{}
```
**Returns:**
- `isRunning`: Whether browser is active
- `tabCount`: Number of open tabs
- `currentTabIndex`: Index of active tab
- `currentUrl`: URL of active tab
- `viewport`: Browser window dimensions (width x height)

#### `rnw_close`
Close the browser and end the session. All tabs are closed and resources are released.
```json
// No parameters required
{}
```
**Returns:** Confirmation that browser session has ended.

## How It Works

### Standard Playwright Click (Doesn't work with RNW)
```javascript
// This dispatches a synthetic 'click' event that RNW ignores
await element.click();
```

### This MCP Server's Click (Works with RNW)
```javascript
// This triggers real mousedown/mouseup events that RNW responds to
await page.mouse.move(x, y);
await page.mouse.down();
await page.mouse.up();
```

## Example Usage with AI Agent

```
Agent: Let me navigate to your React Native Web app, take a screenshot, and click the login button.

> rnw_navigate { "url": "http://localhost:8081" }

Page loaded. I can see the following interactive elements:
[0] button testid="login-button"
    text: "Sign In"
    center: (640, 400)

> rnw_screenshot {}

[Returns PNG image of the page]

> rnw_click { "selector": "login-button", "findBy": "testid" }

Clicked at (640, 400). The login form is now visible.

> rnw_tabs_new { "url": "http://localhost:8081/settings" }

Created new tab [1] and navigated to settings page.

> rnw_tabs_list {}

Open Tabs (2):
[0] Home - http://localhost:8081/
[1] (active) Settings - http://localhost:8081/settings
```

## Continuous Session Workflow

The browser session persists across tool calls, enabling:

1. **Multi-step interactions** - Navigate, screenshot, interact, screenshot again
2. **Visual verification** - Take screenshots to verify UI state after actions
3. **Multi-page workflows** - Open multiple tabs for complex testing scenarios
4. **Debugging** - Use `rnw_evaluate` to inspect page state

```
Agent: I'll test the multi-step form submission.

> rnw_navigate { "url": "http://localhost:8081/form" }
> rnw_screenshot {}  // Verify initial state
> rnw_type { "text": "John Doe", "selector": "[data-testid='name-input']", "findBy": "css" }
> rnw_screenshot {}  // Verify text entered
> rnw_click { "selector": "Submit", "findBy": "text" }
> rnw_screenshot {}  // Verify submission result
> rnw_close {}       // End session when done
```

## Troubleshooting

### Elements not found
- Make sure your React Native Web components have `testID` props set
- Use `rnw_snapshot` to see available elements
- Try using text content with `findBy: "text"`

### Clicks not registering
- Ensure the element is visible and not covered by another element
- Try increasing the delay with `rnw_wait` before clicking
- Use coordinates directly if element finding fails

### Screenshots not working
- Ensure the browser session is active (`rnw_session_status`)
- For element screenshots, verify the selector matches a visible element

## Version History

### v2.0.0
- Added screenshot support with base64 image return
- Added multi-tab management (list, new, select, close)
- Added session status tool
- Improved tool response format
- Persistent browser sessions

### v1.0.0
- Initial release with RNW-compatible clicking
- Basic navigation, snapshot, and interaction tools

## License

MIT