MCP server by Nizarius
MCP Browser Server for React Native Web
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
npm install @nizarius/mcp-rnw-browser
npx playwright install chromium
From source
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)
{
"mcpServers": {
"rnw-browser": {
"command": "npx",
"args": ["@nizarius/mcp-rnw-browser"]
}
}
}
Using local installation
{
"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.
{ "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.
// 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.
// 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).
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// No parameters required
{}
Returns:
isRunning: Whether browser is activetabCount: Number of open tabscurrentTabIndex: Index of active tabcurrentUrl: URL of active tabviewport: Browser window dimensions (width x height)
rnw_close
Close the browser and end the session. All tabs are closed and resources are released.
// No parameters required
{}
Returns: Confirmation that browser session has ended.
How It Works
Standard Playwright Click (Doesn't work with RNW)
// This dispatches a synthetic 'click' event that RNW ignores
await element.click();
This MCP Server's Click (Works with RNW)
// 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:
- Multi-step interactions - Navigate, screenshot, interact, screenshot again
- Visual verification - Take screenshots to verify UI state after actions
- Multi-page workflows - Open multiple tabs for complex testing scenarios
- Debugging - Use
rnw_evaluateto 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
testIDprops set - Use
rnw_snapshotto 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_waitbefore 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