react-native-mcp-kit 1.0.1 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +220 -720
- package/dist/babel/stripPlugin.d.ts.map +1 -1
- package/dist/babel/stripPlugin.js +40 -3
- package/dist/babel/stripPlugin.js.map +1 -1
- package/dist/bin/ios-hid +0 -0
- package/dist/client/contexts/McpContext/McpProvider.d.ts +1 -1
- package/dist/client/contexts/McpContext/McpProvider.d.ts.map +1 -1
- package/dist/client/contexts/McpContext/McpProvider.js +67 -4
- package/dist/client/contexts/McpContext/McpProvider.js.map +1 -1
- package/dist/client/contexts/McpContext/types.d.ts +11 -1
- package/dist/client/contexts/McpContext/types.d.ts.map +1 -1
- package/dist/client/core/McpClient.d.ts +7 -0
- package/dist/client/core/McpClient.d.ts.map +1 -1
- package/dist/client/core/McpClient.js +73 -2
- package/dist/client/core/McpClient.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/modules/device/device.d.ts.map +1 -1
- package/dist/modules/device/device.js +16 -4
- package/dist/modules/device/device.js.map +1 -1
- package/dist/modules/fiberTree/fiberTree.d.ts.map +1 -1
- package/dist/modules/fiberTree/fiberTree.js +61 -15
- package/dist/modules/fiberTree/fiberTree.js.map +1 -1
- package/dist/modules/fiberTree/types.d.ts +9 -0
- package/dist/modules/fiberTree/types.d.ts.map +1 -1
- package/dist/modules/fiberTree/utils.d.ts +2 -1
- package/dist/modules/fiberTree/utils.d.ts.map +1 -1
- package/dist/modules/fiberTree/utils.js +57 -1
- package/dist/modules/fiberTree/utils.js.map +1 -1
- package/dist/modules/index.d.ts +0 -1
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -3
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/navigation/navigation.d.ts.map +1 -1
- package/dist/modules/navigation/navigation.js +32 -40
- package/dist/modules/navigation/navigation.js.map +1 -1
- package/dist/modules/navigation/types.d.ts +2 -1
- package/dist/modules/navigation/types.d.ts.map +1 -1
- package/dist/server/bridge.d.ts +38 -12
- package/dist/server/bridge.d.ts.map +1 -1
- package/dist/server/bridge.js +136 -56
- package/dist/server/bridge.js.map +1 -1
- package/dist/server/cli.js +10 -1
- package/dist/server/cli.js.map +1 -1
- package/dist/server/host/deviceResolver.d.ts +53 -0
- package/dist/server/host/deviceResolver.d.ts.map +1 -0
- package/dist/server/host/deviceResolver.js +555 -0
- package/dist/server/host/deviceResolver.js.map +1 -0
- package/dist/server/host/helpers.d.ts +33 -0
- package/dist/server/host/helpers.d.ts.map +1 -0
- package/dist/server/host/helpers.js +42 -0
- package/dist/server/host/helpers.js.map +1 -0
- package/dist/server/host/hostModule.d.ts +4 -0
- package/dist/server/host/hostModule.d.ts.map +1 -0
- package/dist/server/host/hostModule.js +26 -0
- package/dist/server/host/hostModule.js.map +1 -0
- package/dist/server/host/index.d.ts +4 -0
- package/dist/server/host/index.d.ts.map +1 -0
- package/dist/server/host/index.js +8 -0
- package/dist/server/host/index.js.map +1 -0
- package/dist/server/host/iosInput.d.ts +15 -0
- package/dist/server/host/iosInput.d.ts.map +1 -0
- package/dist/server/host/iosInput.js +93 -0
- package/dist/server/host/iosInput.js.map +1 -0
- package/dist/server/host/modules/screenshot.d.ts +4 -0
- package/dist/server/host/modules/screenshot.d.ts.map +1 -0
- package/dist/server/host/modules/screenshot.js +615 -0
- package/dist/server/host/modules/screenshot.js.map +1 -0
- package/dist/server/host/processRunner.d.ts +19 -0
- package/dist/server/host/processRunner.d.ts.map +1 -0
- package/dist/server/host/processRunner.js +58 -0
- package/dist/server/host/processRunner.js.map +1 -0
- package/dist/server/host/tools/capture.d.ts +6 -0
- package/dist/server/host/tools/capture.d.ts.map +1 -0
- package/dist/server/host/tools/capture.js +148 -0
- package/dist/server/host/tools/capture.js.map +1 -0
- package/dist/server/host/tools/devices.d.ts +4 -0
- package/dist/server/host/tools/devices.d.ts.map +1 -0
- package/dist/server/host/tools/devices.js +17 -0
- package/dist/server/host/tools/devices.js.map +1 -0
- package/dist/server/host/tools/input.d.ts +7 -0
- package/dist/server/host/tools/input.d.ts.map +1 -0
- package/dist/server/host/tools/input.js +286 -0
- package/dist/server/host/tools/input.js.map +1 -0
- package/dist/server/host/tools/lifecycle.d.ts +6 -0
- package/dist/server/host/tools/lifecycle.d.ts.map +1 -0
- package/dist/server/host/tools/lifecycle.js +271 -0
- package/dist/server/host/tools/lifecycle.js.map +1 -0
- package/dist/server/host/types.d.ts +17 -0
- package/dist/server/host/types.d.ts.map +1 -0
- package/dist/{modules/components → server/host}/types.js.map +1 -1
- package/dist/server/host/wda.d.ts +15 -0
- package/dist/server/host/wda.d.ts.map +1 -0
- package/dist/server/host/wda.js +100 -0
- package/dist/server/host/wda.js.map +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +10 -17
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcpServer.d.ts +5 -10
- package/dist/server/mcpServer.d.ts.map +1 -1
- package/dist/server/mcpServer.js +438 -153
- package/dist/server/mcpServer.js.map +1 -1
- package/dist/server/types.d.ts +2 -8
- package/dist/server/types.d.ts.map +1 -1
- package/dist/shared/protocol.d.ts +6 -0
- package/dist/shared/protocol.d.ts.map +1 -1
- package/package.json +6 -12
- package/dist/client/contexts/McpTreeContext/McpTracker.d.ts +0 -10
- package/dist/client/contexts/McpTreeContext/McpTracker.d.ts.map +0 -1
- package/dist/client/contexts/McpTreeContext/McpTracker.js +0 -101
- package/dist/client/contexts/McpTreeContext/McpTracker.js.map +0 -1
- package/dist/client/contexts/McpTreeContext/McpTreeContext.d.ts +0 -2
- package/dist/client/contexts/McpTreeContext/McpTreeContext.d.ts.map +0 -1
- package/dist/client/contexts/McpTreeContext/McpTreeContext.js +0 -6
- package/dist/client/contexts/McpTreeContext/McpTreeContext.js.map +0 -1
- package/dist/client/contexts/McpTreeContext/McpTreeRegistry.d.ts +0 -16
- package/dist/client/contexts/McpTreeContext/McpTreeRegistry.d.ts.map +0 -1
- package/dist/client/contexts/McpTreeContext/McpTreeRegistry.js +0 -96
- package/dist/client/contexts/McpTreeContext/McpTreeRegistry.js.map +0 -1
- package/dist/client/contexts/McpTreeContext/index.d.ts +0 -5
- package/dist/client/contexts/McpTreeContext/index.d.ts.map +0 -1
- package/dist/client/contexts/McpTreeContext/index.js +0 -10
- package/dist/client/contexts/McpTreeContext/index.js.map +0 -1
- package/dist/client/contexts/McpTreeContext/types.d.ts +0 -14
- package/dist/client/contexts/McpTreeContext/types.d.ts.map +0 -1
- package/dist/client/contexts/McpTreeContext/types.js.map +0 -1
- package/dist/modules/components/components.d.ts +0 -8
- package/dist/modules/components/components.d.ts.map +0 -1
- package/dist/modules/components/components.js +0 -315
- package/dist/modules/components/components.js.map +0 -1
- package/dist/modules/components/index.d.ts +0 -3
- package/dist/modules/components/index.d.ts.map +0 -1
- package/dist/modules/components/index.js +0 -6
- package/dist/modules/components/index.js.map +0 -1
- package/dist/modules/components/types.d.ts +0 -18
- package/dist/modules/components/types.d.ts.map +0 -1
- package/dist/modules/components/types.js +0 -3
- package/dist/modules/components/utils.d.ts +0 -18
- package/dist/modules/components/utils.d.ts.map +0 -1
- package/dist/modules/components/utils.js +0 -386
- package/dist/modules/components/utils.js.map +0 -1
- package/dist/modules/screenshot/index.d.ts +0 -3
- package/dist/modules/screenshot/index.d.ts.map +0 -1
- package/dist/modules/screenshot/index.js +0 -6
- package/dist/modules/screenshot/index.js.map +0 -1
- package/dist/modules/screenshot/screenshot.d.ts +0 -4
- package/dist/modules/screenshot/screenshot.d.ts.map +0 -1
- package/dist/modules/screenshot/screenshot.js +0 -89
- package/dist/modules/screenshot/screenshot.js.map +0 -1
- package/dist/modules/screenshot/types.d.ts +0 -5
- package/dist/modules/screenshot/types.d.ts.map +0 -1
- package/dist/modules/screenshot/types.js +0 -3
- package/dist/modules/screenshot/types.js.map +0 -1
- package/dist/modules/tree/index.d.ts +0 -2
- package/dist/modules/tree/index.d.ts.map +0 -1
- package/dist/modules/tree/index.js +0 -6
- package/dist/modules/tree/index.js.map +0 -1
- package/dist/modules/tree/tree.d.ts +0 -3
- package/dist/modules/tree/tree.d.ts.map +0 -1
- package/dist/modules/tree/tree.js +0 -274
- package/dist/modules/tree/tree.js.map +0 -1
- /package/dist/{client/contexts/McpTreeContext → server/host}/types.js +0 -0
package/README.md
CHANGED
|
@@ -1,54 +1,27 @@
|
|
|
1
1
|
# react-native-mcp-kit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**See, drive, and debug a running React Native app from an AI agent — or from any other MCP client.**
|
|
4
|
+
|
|
5
|
+
`react-native-mcp-kit` connects a running RN app (on simulator, emulator, or physical device) to any process that speaks the [Model Context Protocol](https://modelcontextprotocol.io). You wrap your app in one provider, add a babel plugin, point your AI tool at a small Node server that ships with the package, and every interesting thing inside the app becomes addressable: component trees, navigation state, network traffic, React Query cache, logs, errors, translations, storage — plus the OS gesture pipeline (taps, swipes, text input, screenshots) via a bundled binary that needs no WebDriverAgent or idb.
|
|
4
6
|
|
|
5
7
|
```
|
|
6
|
-
AI Agent
|
|
8
|
+
AI Agent / Cursor / Claude Code --stdio/MCP--> Node server --WebSocket--> RN app (device)
|
|
9
|
+
│
|
|
10
|
+
└─ host tools (adb / xcrun / ios-hid) --USB/sim--> device
|
|
7
11
|
```
|
|
8
12
|
|
|
9
|
-
##
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
- [network](#network)
|
|
22
|
-
- [reactQuery](#reactquery)
|
|
23
|
-
- [screenshot](#screenshot)
|
|
24
|
-
- [storage](#storage)
|
|
25
|
-
- [Hooks](#hooks)
|
|
26
|
-
- [useMcpState](#usemcpstate)
|
|
27
|
-
- [useMcpTool](#usemcptool)
|
|
28
|
-
- [useMcpModule](#usemcpmodule)
|
|
29
|
-
- [Babel Plugins](#babel-plugins)
|
|
30
|
-
- [testIdPlugin](#testidplugin)
|
|
31
|
-
- [stripPlugin](#stripplugin)
|
|
32
|
-
- [Dev vs Production](#dev-vs-production)
|
|
33
|
-
- [MCP Server Tools](#mcp-server-tools)
|
|
34
|
-
- [Custom Modules](#custom-modules)
|
|
35
|
-
- [Debug Logging](#debug-logging)
|
|
36
|
-
- [API Reference](#api-reference)
|
|
37
|
-
|
|
38
|
-
## Features
|
|
39
|
-
|
|
40
|
-
- **11 built-in modules** — navigation, fiber tree, screenshot, network, console, storage, device, errors, i18next, React Query, alerts
|
|
41
|
-
- **React fiber inspection** — walk the component tree, read props, invoke callbacks, call ref methods
|
|
42
|
-
- **Screenshots** — capture app screenshots via `@shopify/react-native-skia` with resize and JPEG compression
|
|
43
|
-
- **Developer hooks** — expose state and tools from any component with `useMcpState` and `useMcpTool`
|
|
44
|
-
- **Navigation history** — full log of screen transitions with timestamps and slice access
|
|
45
|
-
- **Modular** — register only the modules you need, or write your own with `description` for AI context
|
|
46
|
-
- **Zero production overhead** — Babel strip plugin removes all MCP code from prod builds
|
|
47
|
-
- **Babel testID plugin** — auto-adds `data-mcp-id` attributes for component identification
|
|
48
|
-
|
|
49
|
-
## Quick Start
|
|
50
|
-
|
|
51
|
-
### 1. Install
|
|
13
|
+
## Why would I want this?
|
|
14
|
+
|
|
15
|
+
A few concrete scenarios this unlocks:
|
|
16
|
+
|
|
17
|
+
- **End-to-end automation without a separate test harness.** Describe a multi-step flow in natural language — "sign in, open settings, flip the notifications toggle, verify the confirmation toast" — and an agent walks it: locates components by name/testID, fires real taps through the OS gesture pipeline, asserts on the resulting state, and reports back. The same flow works as a scripted smoke test driven by any MCP client.
|
|
18
|
+
- **Interactive inspection of a live app from your editor.** Ask "what screen am I on?", "what React Query keys are stale?", "what did the last POST return?", "which translation keys are missing in the current locale?" — no rebuild, no DevTools panel, no "add more logs and reload" loop.
|
|
19
|
+
- **Debug gesture-arbitration bugs that unit tests can't catch.** `host__tap` goes through the real iOS/Android touch pipeline, so issues like "the close button inside a horizontally-scrolling list swallows taps" surface naturally. `fiber_tree__invoke` bypasses the pipeline for the rare cases you want to call `onPress` directly.
|
|
20
|
+
- **Write your own tools from inside components.** `useMcpTool`/`useMcpState`/`useMcpModule` let a component expose a named state key or an ad-hoc tool. Agents can then read feature-flag state, force a particular loading scenario, or trigger an internal-only action without you shipping a debug menu.
|
|
21
|
+
|
|
22
|
+
Everything the library adds to your bundle is stripped in production builds via the companion babel plugin — so you can wire it up once and leave it in, without shipping it to users.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
52
25
|
|
|
53
26
|
```bash
|
|
54
27
|
yarn add react-native-mcp-kit
|
|
@@ -56,813 +29,340 @@ yarn add react-native-mcp-kit
|
|
|
56
29
|
npm install react-native-mcp-kit
|
|
57
30
|
```
|
|
58
31
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
import {
|
|
63
|
-
McpClient,
|
|
64
|
-
McpProvider,
|
|
65
|
-
consoleModule,
|
|
66
|
-
deviceModule,
|
|
67
|
-
fiberTreeModule,
|
|
68
|
-
navigationModule,
|
|
69
|
-
networkModule,
|
|
70
|
-
screenshotModule,
|
|
71
|
-
} from 'react-native-mcp-kit';
|
|
72
|
-
import { createRef } from 'react';
|
|
73
|
-
import { View } from 'react-native';
|
|
74
|
-
|
|
75
|
-
const rootRef = createRef<View>();
|
|
76
|
-
const navigationRef = createNavigationContainerRef();
|
|
32
|
+
**Peer dependencies**: `react >= 19`, `react-native >= 0.79`, `react-native-device-info >= 10`.
|
|
77
33
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// Register modules
|
|
82
|
-
client.registerModules([
|
|
83
|
-
consoleModule(),
|
|
84
|
-
deviceModule(),
|
|
85
|
-
fiberTreeModule({ rootRef }),
|
|
86
|
-
navigationModule(navigationRef),
|
|
87
|
-
networkModule(),
|
|
88
|
-
screenshotModule({ rootRef }),
|
|
89
|
-
]);
|
|
90
|
-
```
|
|
34
|
+
## Setup
|
|
35
|
+
|
|
36
|
+
Three pieces need to be wired up: the **provider** at the root of your RN app, a pair of **babel plugins** so components are identifiable and production builds stay clean, and the **MCP server** that the AI agent talks to.
|
|
91
37
|
|
|
92
|
-
###
|
|
38
|
+
### 1. Wrap the app in `McpProvider`
|
|
39
|
+
|
|
40
|
+
Put it at the root of the tree. Optional props opt specific modules in — omit a prop and that module isn't registered.
|
|
93
41
|
|
|
94
42
|
```tsx
|
|
95
|
-
|
|
43
|
+
import { NavigationContainer, createNavigationContainerRef } from '@react-navigation/native';
|
|
44
|
+
import { McpProvider } from 'react-native-mcp-kit';
|
|
45
|
+
|
|
46
|
+
const navigationRef = createNavigationContainerRef();
|
|
47
|
+
|
|
48
|
+
export const App = () => {
|
|
96
49
|
return (
|
|
97
|
-
<
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
50
|
+
<McpProvider
|
|
51
|
+
debug
|
|
52
|
+
// Optional — each prop opts a module in:
|
|
53
|
+
navigationRef={navigationRef} // → navigation module
|
|
54
|
+
queryClient={queryClient} // → reactQuery module
|
|
55
|
+
i18n={i18nInstance} // → i18next module
|
|
56
|
+
storages={[{ name: 'mmkv', adapter: mmkvAdapter }]} // → storage module
|
|
57
|
+
>
|
|
58
|
+
<NavigationContainer ref={navigationRef}>{/* your app */}</NavigationContainer>
|
|
59
|
+
</McpProvider>
|
|
102
60
|
);
|
|
103
61
|
};
|
|
104
62
|
```
|
|
105
63
|
|
|
106
|
-
|
|
64
|
+
These modules register automatically on mount — no prop required:
|
|
107
65
|
|
|
108
|
-
|
|
66
|
+
`alert`, `console`, `device`, `errors`, `network`, `fiber_tree`
|
|
109
67
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
68
|
+
If the dependency lives deeper in the tree (e.g. the `QueryClient` is created inside a feature-specific provider), skip the prop and use `useMcpModule` there instead — see [Hooks](#hooks).
|
|
69
|
+
|
|
70
|
+
### 2. Babel plugins — why and how
|
|
71
|
+
|
|
72
|
+
Two plugins ship under `react-native-mcp-kit/babel`. You want both.
|
|
73
|
+
|
|
74
|
+
**`test-id-plugin`** — compiles every capitalized JSX element with a stable `data-mcp-id="ComponentName:path/to/file:line"` attribute. `fiber_tree` uses this attribute as an identifier that survives renders, minification, and refactors. Without the plugin you can still find components by `name` or `testID`, but mcpId is what makes "find the nth occurrence of `ComponentName` on a specific line" reliable across a large codebase. Run this in **development**.
|
|
75
|
+
|
|
76
|
+
**`strip-plugin`** — strips every trace of mcp-kit from a bundle: imports from `react-native-mcp-kit`, calls to `McpClient.*` / `useMcpState` / `useMcpTool` / `useMcpModule`, the `<McpProvider>` JSX wrapper (its children are preserved), and every `data-mcp-id` attribute. Run this in **production** and none of the library code reaches your users.
|
|
77
|
+
|
|
78
|
+
```js
|
|
79
|
+
// babel.config.js
|
|
80
|
+
module.exports = (api) => {
|
|
81
|
+
return {
|
|
82
|
+
presets: ['module:@react-native/babel-preset'],
|
|
83
|
+
plugins: [
|
|
84
|
+
__DEV__
|
|
85
|
+
? 'react-native-mcp-kit/babel/test-id-plugin'
|
|
86
|
+
: 'react-native-mcp-kit/babel/strip-plugin',
|
|
87
|
+
],
|
|
88
|
+
};
|
|
89
|
+
};
|
|
119
90
|
```
|
|
120
91
|
|
|
121
|
-
|
|
92
|
+
Both plugins accept options (attribute name, include/exclude lists, extra import sources, extra function names to strip) when you need to customize — pass them as the 2nd array element in the usual babel style. Defaults cover the common case.
|
|
93
|
+
|
|
94
|
+
After editing `babel.config.js`, clear Metro's cache once: `yarn start --reset-cache`.
|
|
95
|
+
|
|
96
|
+
### 3. Configure the MCP server
|
|
97
|
+
|
|
98
|
+
The MCP server is a Node process that brokers between your agent (stdio/MCP) and the RN app (WebSocket). It ships as a bin in the package — `npx react-native-mcp-kit` boots it.
|
|
99
|
+
|
|
100
|
+
Point your agent at it via the usual MCP config. For Claude Code / Cursor / Continue etc., a project-local `.mcp.json`:
|
|
122
101
|
|
|
123
102
|
```json
|
|
124
103
|
{
|
|
125
104
|
"mcpServers": {
|
|
126
105
|
"react-native-mcp-kit": {
|
|
127
106
|
"command": "npx",
|
|
128
|
-
"args": ["react-native-mcp-kit"
|
|
107
|
+
"args": ["react-native-mcp-kit"]
|
|
129
108
|
}
|
|
130
109
|
}
|
|
131
110
|
}
|
|
132
111
|
```
|
|
133
112
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
1. Start Metro and run your app
|
|
137
|
-
2. The AI agent connects via MCP and can now inspect and interact with your app
|
|
138
|
-
3. For Android emulator, run `adb reverse tcp:8347 tcp:8347` to forward the port
|
|
113
|
+
**CLI flags:**
|
|
139
114
|
|
|
140
|
-
|
|
115
|
+
- `--port <number>` — WebSocket port the RN app connects to (default `8347`).
|
|
116
|
+
- `--no-host` — disable the host module (the server no longer exposes `host__tap`, `host__screenshot`, etc.). Use this when you only want in-app modules.
|
|
141
117
|
|
|
142
|
-
|
|
143
|
-
| ------------------------- | ------------------------------- | --------------------------------------------------------------- |
|
|
144
|
-
| [alert](#alert) | `alertModule()` | Show native alerts with custom buttons and styles |
|
|
145
|
-
| [fiberTree](#fibertree) | `fiberTreeModule({ rootRef })` | React fiber tree inspection, invoke callbacks, call ref methods |
|
|
146
|
-
| [console](#console) | `consoleModule(options?)` | Capture console.log/warn/error/info/debug |
|
|
147
|
-
| [device](#device) | `deviceModule()` | Device info, app state, keyboard, linking, reload |
|
|
148
|
-
| [errors](#errors) | `errorsModule(options?)` | Capture unhandled errors and promise rejections |
|
|
149
|
-
| [i18next](#i18next) | `i18nextModule(i18n)` | Translation inspection and language management |
|
|
150
|
-
| [navigation](#navigation) | `navigationModule(ref)` | Navigation state, history, navigate, push, pop, replace, reset |
|
|
151
|
-
| [network](#network) | `networkModule(options?)` | HTTP request/response interception |
|
|
152
|
-
| [reactQuery](#reactquery) | `reactQueryModule(queryClient)` | React Query cache inspection and management |
|
|
153
|
-
| [screenshot](#screenshot) | `screenshotModule({ rootRef })` | Capture screenshots via Skia |
|
|
154
|
-
| [storage](#storage) | `storageModule(...storages)` | Key-value storage inspection (MMKV, AsyncStorage, custom) |
|
|
155
|
-
|
|
156
|
-
---
|
|
118
|
+
For **Android emulators** you need the adb port forward so the app can reach the server:
|
|
157
119
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
Show native alert dialogs with custom buttons and styles.
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
client.registerModules([alertModule()]);
|
|
120
|
+
```bash
|
|
121
|
+
adb reverse tcp:8347 tcp:8347
|
|
164
122
|
```
|
|
165
123
|
|
|
166
|
-
|
|
167
|
-
| ------ | ----------------- | ---------------------------------------------------------- |
|
|
168
|
-
| `show` | Show alert dialog | `title?: string`, `message?: string`, `buttons?: Button[]` |
|
|
124
|
+
iOS simulators share localhost with the host machine, no forwarding needed.
|
|
169
125
|
|
|
170
|
-
|
|
126
|
+
### 4. Run
|
|
171
127
|
|
|
172
|
-
|
|
173
|
-
// Simple
|
|
174
|
-
call(tool: "alert__show", args: '{"title": "Confirm", "buttons": ["Cancel", "OK"]}')
|
|
128
|
+
Start Metro + your app. The `McpProvider` connects to `ws://localhost:8347` on mount. The agent calls `connection_status` to confirm; after that every tool is callable.
|
|
175
129
|
|
|
176
|
-
|
|
177
|
-
call(tool: "alert__show", args: '{"title": "Delete?", "buttons": [{"text": "Cancel", "style": "cancel"}, {"text": "Delete", "style": "destructive"}]}')
|
|
178
|
-
```
|
|
130
|
+
## `McpProvider` reference
|
|
179
131
|
|
|
180
|
-
|
|
132
|
+
```ts
|
|
133
|
+
interface McpProviderProps {
|
|
134
|
+
children: ReactNode;
|
|
135
|
+
debug?: boolean; // colored console logs for all MCP traffic
|
|
136
|
+
navigationRef?: NavigationRef; // → navigationModule
|
|
137
|
+
queryClient?: QueryClientLike; // → reactQueryModule
|
|
138
|
+
i18n?: I18nLike; // → i18nextModule
|
|
139
|
+
storages?: NamedStorage[]; // → storageModule(...storages)
|
|
140
|
+
modules?: McpModule[]; // arbitrary extra modules
|
|
141
|
+
}
|
|
142
|
+
```
|
|
181
143
|
|
|
182
|
-
|
|
144
|
+
Wrap your whole app in it — every optional prop opts a module in when supplied.
|
|
183
145
|
|
|
184
|
-
|
|
146
|
+
## MCP server tools
|
|
185
147
|
|
|
186
|
-
|
|
148
|
+
The Node server itself exposes a small set of entry-point tools for agents: discovering connected clients, browsing what they can do, reading state exposed via `useMcpState`, and dispatching any in-app tool through `call`. Agents see these straight through the MCP interface; you don't register or configure anything on your side.
|
|
187
149
|
|
|
188
|
-
|
|
189
|
-
client.registerModules([fiberTreeModule({ rootRef })]);
|
|
190
|
-
```
|
|
150
|
+
## Multi-client
|
|
191
151
|
|
|
192
|
-
|
|
152
|
+
One server can hold multiple RN clients at once — iOS simulator, Android emulator, physical device, any mix. Useful for driving iOS and Android builds of the same app in lockstep from a single agent session.
|
|
193
153
|
|
|
194
|
-
|
|
195
|
-
| --------------- | ---------------------------- | -------------------------------------------------------------------- |
|
|
196
|
-
| `get_tree` | Get full component tree | `depth?: number` (default: 10) |
|
|
197
|
-
| `get_component` | Find a component | `name?`, `testID?`, `text?`, `mcpId?`, `within?`, `index?`, `depth?` |
|
|
198
|
-
| `find_all` | Find all matching components | `name?`, `testID?`, `text?`, `mcpId?`, `hasProps?`, `within?` |
|
|
199
|
-
| `get_props` | Get component props | same find params |
|
|
200
|
-
| `get_children` | Get component children | same find params, `depth?` |
|
|
154
|
+
## Host tools (device-level control)
|
|
201
155
|
|
|
202
|
-
**
|
|
156
|
+
When the `host` module is enabled (the default), the server also exposes tools that operate **on the host machine** — they run `adb` / `xcrun simctl` / a bundled `ios-hid` binary. These work even when the RN app is frozen, not launched yet, or between reloads.
|
|
203
157
|
|
|
204
|
-
|
|
205
|
-
| ----------------- | -------------------------- | --------------------------------------------------- |
|
|
206
|
-
| `invoke` | Call any callback prop | find params, `callback: string`, `args?: unknown[]` |
|
|
207
|
-
| `call_ref` | Call method on native ref | find params, `method: string`, `args?: unknown[]` |
|
|
208
|
-
| `get_ref_methods` | List available ref methods | find params |
|
|
158
|
+
What you get:
|
|
209
159
|
|
|
210
|
-
**
|
|
160
|
+
- **Real OS input** — tap, swipe, type, press semantic keys. Goes through the real iOS/Android touch pipeline, so Pressable feedback, gesture responders, and hit testing all run as if a human touched the device.
|
|
161
|
+
- **Screenshots** with automatic diffing — the server returns `unchanged:true` when the screen hasn't changed since the last capture, so polling is cheap.
|
|
162
|
+
- **App lifecycle** — launch, terminate, restart an installed app. Useful for cold-start assertions or recovering from a crashed state without clicking the simulator.
|
|
163
|
+
- **Device enumeration** — list all visible simulators / emulators / devices, annotated with which ones have a live MCP client.
|
|
211
164
|
|
|
212
|
-
|
|
165
|
+
iOS input goes through a bundled `ios-hid` Swift binary. **No WebDriverAgent, no idb, no Appium server.**
|
|
213
166
|
|
|
214
|
-
|
|
215
|
-
// Find by testID
|
|
216
|
-
call(tool: "fiber_tree__get_component", args: '{"testID": "login-button"}')
|
|
217
|
-
|
|
218
|
-
// Find by name within a parent
|
|
219
|
-
call(tool: "fiber_tree__get_component", args: '{"name": "Pressable", "within": "LoginForm"}')
|
|
167
|
+
## Hooks
|
|
220
168
|
|
|
221
|
-
|
|
222
|
-
call(tool: "fiber_tree__get_component", args: '{"name": "TextInput", "within": "LoginForm", "index": 1}')
|
|
169
|
+
For when the thing you want to expose lives deeper than `McpProvider`:
|
|
223
170
|
|
|
224
|
-
|
|
225
|
-
|
|
171
|
+
```ts
|
|
172
|
+
useMcpState(key, factory, deps) // expose reactive state to the agent
|
|
173
|
+
useMcpTool(name, factory, deps) // register an ad-hoc tool tied to the component lifecycle
|
|
174
|
+
useMcpModule(factory, deps) // register a whole module from inside a component
|
|
226
175
|
```
|
|
227
176
|
|
|
228
|
-
|
|
177
|
+
Each follows `useMemo` / `useEffect` semantics — the factory re-runs on dep changes, registration cleans up on unmount.
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
const UserProvider = ({ children }) => {
|
|
181
|
+
const [user, setUser] = useState(null);
|
|
229
182
|
|
|
230
|
-
|
|
231
|
-
// Press a button
|
|
232
|
-
call(tool: "fiber_tree__invoke", args: '{"testID": "submit-btn", "callback": "onPress"}')
|
|
183
|
+
useMcpState('user', () => ({ id: user?.id, loggedIn: user !== null }), [user]);
|
|
233
184
|
|
|
234
|
-
|
|
235
|
-
|
|
185
|
+
useMcpTool('logout', () => ({
|
|
186
|
+
description: 'Log out the current user',
|
|
187
|
+
handler: async () => { await logout(); return { success: true }; },
|
|
188
|
+
}), [logout]);
|
|
236
189
|
|
|
237
|
-
|
|
238
|
-
|
|
190
|
+
return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
|
|
191
|
+
};
|
|
239
192
|
```
|
|
240
193
|
|
|
241
|
-
|
|
194
|
+
## Modules
|
|
242
195
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
196
|
+
| Module | Factory | Requires |
|
|
197
|
+
| ------------------------- | ------------------------------- | ----------------------------------------- |
|
|
198
|
+
| [alert](#alert) | `alertModule()` | — |
|
|
199
|
+
| [console](#console) | `consoleModule(options?)` | — |
|
|
200
|
+
| [device](#device) | `deviceModule()` | — |
|
|
201
|
+
| [errors](#errors) | `errorsModule(options?)` | — |
|
|
202
|
+
| [fiber_tree](#fiber_tree) | `fiberTreeModule({ rootRef })` | root ref (auto-supplied by `McpProvider`) |
|
|
203
|
+
| [i18n](#i18n) | `i18nextModule(i18n)` | i18next instance |
|
|
204
|
+
| [navigation](#navigation) | `navigationModule(ref)` | React Navigation ref |
|
|
205
|
+
| [network](#network) | `networkModule(options?)` | — |
|
|
206
|
+
| [query](#query) | `reactQueryModule(queryClient)` | `QueryClient` |
|
|
207
|
+
| [storage](#storage) | `storageModule(...storages)` | one or more `NamedStorage` |
|
|
208
|
+
|
|
209
|
+
The full tool list for every module is always available via `list_tools` at runtime — the sections below describe what each module gives you, not each tool.
|
|
246
210
|
|
|
247
|
-
|
|
248
|
-
call(tool: "fiber_tree__get_ref_methods", args: '{"testID": "email-input"}')
|
|
249
|
-
```
|
|
211
|
+
### alert
|
|
250
212
|
|
|
251
|
-
|
|
213
|
+
Show a native `Alert.alert` from the agent with any combination of `default` / `cancel` / `destructive` buttons and get back which one was pressed. Useful for "are you sure?" prompts driven by the agent, or for surfacing a decision point to a human tester.
|
|
252
214
|
|
|
253
215
|
### console
|
|
254
216
|
|
|
255
|
-
|
|
217
|
+
Tails `console.log` / `warn` / `error` / `info` / `debug` into a ring buffer the agent can read or clear. Complex values (Errors, Dates, class instances, cyclic refs, functions, Symbols) are serialized safely. Buffer size, captured levels, and whether stack traces are collected are all configurable.
|
|
256
218
|
|
|
257
|
-
```
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
maxEntries: 200,
|
|
264
|
-
levels: ['error', 'warn', 'log'],
|
|
265
|
-
stackTrace: ['error', 'warn'], // or true for all levels
|
|
266
|
-
}),
|
|
267
|
-
]);
|
|
219
|
+
```ts
|
|
220
|
+
consoleModule({
|
|
221
|
+
maxEntries: 200,
|
|
222
|
+
levels: ['error', 'warn', 'log'],
|
|
223
|
+
stackTrace: ['error', 'warn'], // or `true` / `false`
|
|
224
|
+
});
|
|
268
225
|
```
|
|
269
226
|
|
|
270
|
-
| Tool | Description | Args |
|
|
271
|
-
| -------------- | ---------------- | ---------------------------------- |
|
|
272
|
-
| `get_logs` | Get all logs | `level?: string`, `limit?: number` |
|
|
273
|
-
| `get_errors` | Get error logs | `limit?: number` |
|
|
274
|
-
| `get_warnings` | Get warning logs | `limit?: number` |
|
|
275
|
-
| `get_info` | Get info logs | `limit?: number` |
|
|
276
|
-
| `get_debug` | Get debug logs | `limit?: number` |
|
|
277
|
-
| `clear_logs` | Clear all logs | — |
|
|
278
|
-
|
|
279
|
-
Serializes complex values: functions, class instances, circular refs, Errors, Dates, RegExp, Symbols.
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
227
|
### device
|
|
284
228
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
client.registerModules([deviceModule()]);
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
| Tool | Description | Args |
|
|
292
|
-
| ------------------------ | ----------------------------------------------------------------------------------- | ------------------- |
|
|
293
|
-
| `get_device_info` | Comprehensive device info (platform, dimensions, pixel ratio, appearance, dev mode) | — |
|
|
294
|
-
| `get_platform` | Platform (OS, version, constants) | — |
|
|
295
|
-
| `get_dimensions` | Screen and window dimensions | — |
|
|
296
|
-
| `get_pixel_ratio` | Pixel density and font scale | — |
|
|
297
|
-
| `get_appearance` | Color scheme (light/dark) | — |
|
|
298
|
-
| `get_app_state` | App state (active/background/inactive) | — |
|
|
299
|
-
| `get_accessibility_info` | Accessibility settings | — |
|
|
300
|
-
| `get_keyboard_state` | Keyboard visibility and metrics | — |
|
|
301
|
-
| `dismiss_keyboard` | Dismiss keyboard | — |
|
|
302
|
-
| `open_url` | Open URL in appropriate app | `url: string` |
|
|
303
|
-
| `can_open_url` | Check if URL can be opened | `url: string` |
|
|
304
|
-
| `get_initial_url` | Get deep link that launched app | — |
|
|
305
|
-
| `open_settings` | Open app settings | — |
|
|
306
|
-
| `reload` | Reload app (dev only) | — |
|
|
307
|
-
| `vibrate` | Vibrate device | `duration?: number` |
|
|
308
|
-
|
|
309
|
-
---
|
|
229
|
+
Read-only view of platform facts (OS, version, dimensions in DP and physical pixels, pixel ratio, appearance, app state, accessibility settings, keyboard state) plus a few imperative actions — open URLs / settings, dismiss the keyboard, vibrate, reload the JS bundle in dev.
|
|
310
230
|
|
|
311
231
|
### errors
|
|
312
232
|
|
|
313
|
-
Captures unhandled JS errors and promise rejections.
|
|
233
|
+
Captures unhandled JS errors (via `ErrorUtils.setGlobalHandler`) and unhandled promise rejections with deduplication, so the agent can inspect what crashed without tailing native logs.
|
|
314
234
|
|
|
315
|
-
|
|
316
|
-
client.registerModules([errorsModule()]);
|
|
235
|
+
### fiber_tree
|
|
317
236
|
|
|
318
|
-
|
|
319
|
-
client.registerModules([errorsModule({ maxEntries: 100 })]);
|
|
320
|
-
```
|
|
237
|
+
The heart of UI inspection. The agent walks the component tree, finds elements by name / testID / text / which callback props they have, reads their props, calls their ref methods, and invokes arbitrary callbacks on them. Coordinates it returns (`bounds`) are in physical pixels and pair directly with `host__tap` / `host__swipe`.
|
|
321
238
|
|
|
322
|
-
|
|
323
|
-
| -------------- | --------------------- | ------------------------------------------------------ |
|
|
324
|
-
| `get_errors` | Get captured errors | `source?: string`, `fatal?: boolean`, `limit?: number` |
|
|
325
|
-
| `get_fatal` | Get fatal errors only | `limit?: number` |
|
|
326
|
-
| `get_stats` | Error statistics | — |
|
|
327
|
-
| `clear_errors` | Clear all errors | — |
|
|
239
|
+
### i18n
|
|
328
240
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
---
|
|
332
|
-
|
|
333
|
-
### i18next
|
|
334
|
-
|
|
335
|
-
Translation inspection and language management. Accepts an i18next instance.
|
|
336
|
-
|
|
337
|
-
```typescript
|
|
338
|
-
import i18n from './i18n';
|
|
339
|
-
|
|
340
|
-
client.registerModules([i18nextModule(i18n)]);
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
| Tool | Description | Args |
|
|
344
|
-
| ----------------- | ------------------------------------------------- | ---------------------------------------- |
|
|
345
|
-
| `get_info` | Current language, available languages, namespaces | — |
|
|
346
|
-
| `get_resource` | Get full translation resource | `language?`, `namespace?` |
|
|
347
|
-
| `get_keys` | List translation keys | `language?`, `namespace?` |
|
|
348
|
-
| `translate` | Translate a key | `key: string`, `options?: string` (JSON) |
|
|
349
|
-
| `search` | Search keys and values | `query: string`, `language?` |
|
|
350
|
-
| `change_language` | Change current language | `language: string` |
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
// Translate with interpolation
|
|
354
|
-
call(tool: "i18n__translate", args: '{"key": "welcome", "options": "{\"name\": \"John\"}"}')
|
|
355
|
-
|
|
356
|
-
// Search translations
|
|
357
|
-
call(tool: "i18n__search", args: '{"query": "password"}')
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
---
|
|
241
|
+
Inspect and manipulate an `i18next` instance: list keys, dump a whole translation resource, run a substring search, translate with interpolation, switch language at runtime.
|
|
361
242
|
|
|
362
243
|
### navigation
|
|
363
244
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
import { createNavigationContainerRef } from '@react-navigation/native';
|
|
368
|
-
|
|
369
|
-
const navigationRef = createNavigationContainerRef();
|
|
370
|
-
|
|
371
|
-
client.registerModules([navigationModule(navigationRef)]);
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
| Tool | Description | Args |
|
|
375
|
-
| ------------------------- | ------------------------------------ | -------------------------------------------------- |
|
|
376
|
-
| `get_state` | Full navigation state tree | — |
|
|
377
|
-
| `get_current_route` | Current focused route | — |
|
|
378
|
-
| `get_current_route_state` | Current route with nested state | — |
|
|
379
|
-
| `get_history` | Log of all screen transitions | `offset?`, `limit?`, `full?: boolean` |
|
|
380
|
-
| `navigate` | Navigate to screen (reuses existing) | `screen: string`, `params?: object` |
|
|
381
|
-
| `push` | Push new screen onto stack | `screen: string`, `params?: object` |
|
|
382
|
-
| `pop` | Pop screens | `count?: number` |
|
|
383
|
-
| `pop_to` | Pop to specific screen | `screen: string`, `params?: object` |
|
|
384
|
-
| `pop_to_top` | Pop to first screen | — |
|
|
385
|
-
| `go_back` | Go back to previous screen | — |
|
|
386
|
-
| `replace` | Replace current screen | `screen: string`, `params?: object` |
|
|
387
|
-
| `reset` | Reset navigation state | `routes: Array<{name, params?}>`, `index?: number` |
|
|
388
|
-
|
|
389
|
-
**Navigation history:**
|
|
390
|
-
|
|
391
|
-
```typescript
|
|
392
|
-
// Get simplified history (name, key, params, timestamp)
|
|
393
|
-
call(tool: "navigation__get_history")
|
|
394
|
-
|
|
395
|
-
// Get last 5 transitions
|
|
396
|
-
call(tool: "navigation__get_history", args: '{"limit": 5}')
|
|
397
|
-
|
|
398
|
-
// Get full navigation state for each transition
|
|
399
|
-
call(tool: "navigation__get_history", args: '{"full": true}')
|
|
400
|
-
|
|
401
|
-
// Slice: skip first 10, get next 5
|
|
402
|
-
call(tool: "navigation__get_history", args: '{"offset": 10, "limit": 5}')
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
---
|
|
245
|
+
Drive React Navigation from outside — navigate, push, pop, replace, reset — and read the current route, nested state, and the last 100 transitions with timestamps. Needs a `createNavigationContainerRef()` passed to both `<NavigationContainer ref={…}>` and `<McpProvider navigationRef={…}>` (or `navigationModule(ref)` directly).
|
|
406
246
|
|
|
407
247
|
### network
|
|
408
248
|
|
|
409
|
-
Intercepts `fetch` and `XMLHttpRequest
|
|
410
|
-
|
|
411
|
-
```typescript
|
|
412
|
-
client.registerModules([networkModule()]);
|
|
413
|
-
|
|
414
|
-
// With options
|
|
415
|
-
client.registerModules([
|
|
416
|
-
networkModule({
|
|
417
|
-
maxEntries: 200,
|
|
418
|
-
includeBodies: true,
|
|
419
|
-
ignoreUrls: ['https://analytics.example.com', /\.png$/],
|
|
420
|
-
}),
|
|
421
|
-
]);
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
| Tool | Description | Args |
|
|
425
|
-
| ---------------- | ------------------------------ | -------------------------------------- |
|
|
426
|
-
| `get_requests` | Get captured requests | `method?`, `status?`, `url?`, `limit?` |
|
|
427
|
-
| `get_request` | Find requests by URL substring | `url: string` |
|
|
428
|
-
| `get_pending` | Get in-flight requests | — |
|
|
429
|
-
| `get_errors` | Get failed requests | `limit?: number` |
|
|
430
|
-
| `get_stats` | Request statistics | — |
|
|
431
|
-
| `clear_requests` | Clear captured requests | — |
|
|
249
|
+
Intercepts `fetch` and `XMLHttpRequest` into a ring buffer — method, URL, status, duration, headers, bodies. The agent can list recent traffic, filter by method/status/URL, find a request by URL substring, see what's in flight, or just the failures. WebSocket, Metro, and symbolicate traffic are auto-ignored.
|
|
432
250
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
React Query cache inspection and management. Accepts a `QueryClient` instance.
|
|
440
|
-
|
|
441
|
-
```typescript
|
|
442
|
-
import { QueryClient } from '@tanstack/react-query';
|
|
443
|
-
|
|
444
|
-
const queryClient = new QueryClient();
|
|
445
|
-
|
|
446
|
-
client.registerModules([reactQueryModule(queryClient)]);
|
|
447
|
-
```
|
|
448
|
-
|
|
449
|
-
| Tool | Description | Args |
|
|
450
|
-
| ------------- | ------------------------------- | ----------------------------- |
|
|
451
|
-
| `get_queries` | List all cached queries | `status?`, `key?` (substring) |
|
|
452
|
-
| `get_data` | Get cached data for a query | `key: string` (JSON format) |
|
|
453
|
-
| `get_stats` | Cache statistics | — |
|
|
454
|
-
| `invalidate` | Invalidate queries (mark stale) | `key?: string` |
|
|
455
|
-
| `refetch` | Refetch queries | `key?: string` |
|
|
456
|
-
| `remove` | Remove from cache | `key?: string` |
|
|
457
|
-
| `reset` | Reset to initial state | `key?: string` |
|
|
458
|
-
|
|
459
|
-
```typescript
|
|
460
|
-
// Get data for a specific query
|
|
461
|
-
call(tool: "query__get_data", args: '{"key": "[\"users\",\"list\"]"}')
|
|
462
|
-
|
|
463
|
-
// Invalidate all user queries
|
|
464
|
-
call(tool: "query__invalidate", args: '{"key": "users"}')
|
|
251
|
+
```ts
|
|
252
|
+
networkModule({
|
|
253
|
+
maxEntries: 200,
|
|
254
|
+
includeBodies: true,
|
|
255
|
+
ignoreUrls: ['https://analytics.example.com', /\.png$/],
|
|
256
|
+
});
|
|
465
257
|
```
|
|
466
258
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
### screenshot
|
|
470
|
-
|
|
471
|
-
Capture screenshots of the app via `@shopify/react-native-skia`. Requires Skia as a peer dependency.
|
|
472
|
-
|
|
473
|
-
```bash
|
|
474
|
-
yarn add @shopify/react-native-skia
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
```typescript
|
|
478
|
-
import { createRef } from 'react';
|
|
479
|
-
import { View } from 'react-native';
|
|
480
|
-
|
|
481
|
-
const rootRef = createRef<View>();
|
|
259
|
+
### query
|
|
482
260
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
// JSX — rootRef must have collapsable={false}
|
|
486
|
-
<View ref={rootRef} collapsable={false} style={{ flex: 1 }}>
|
|
487
|
-
{/* app */}
|
|
488
|
-
</View>
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
| Tool | Description | Args |
|
|
492
|
-
| --------- | ------------------ | ----------------------------------------------------------------- |
|
|
493
|
-
| `capture` | Capture screenshot | `format?: 'jpeg'\|'png'`, `quality?: number`, `maxWidth?: number` |
|
|
494
|
-
|
|
495
|
-
Default: JPEG, quality 80, max width 600px (height scales proportionally). Images are resized via `Skia.Surface.Make` + `drawImageRectOptions` for optimal size.
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
// Default (JPEG, 600px wide)
|
|
499
|
-
call(tool: "screenshot__capture")
|
|
500
|
-
|
|
501
|
-
// High quality PNG
|
|
502
|
-
call(tool: "screenshot__capture", args: '{"format": "png", "quality": 100}')
|
|
503
|
-
|
|
504
|
-
// Smaller image
|
|
505
|
-
call(tool: "screenshot__capture", args: '{"maxWidth": 400, "quality": 50}')
|
|
506
|
-
```
|
|
507
|
-
|
|
508
|
-
---
|
|
261
|
+
React Query cache inspection: list cached queries, fetch cached data by key, get stats, and run `invalidate` / `refetch` / `remove` / `reset` against specific keys or the whole cache.
|
|
509
262
|
|
|
510
263
|
### storage
|
|
511
264
|
|
|
512
|
-
|
|
265
|
+
Reads and writes to one or more named key-value stores. Each store is a `{ name, adapter }` pair; the adapter can wrap MMKV, AsyncStorage, or any custom implementation that provides at least a `get`:
|
|
513
266
|
|
|
514
|
-
```
|
|
515
|
-
// Single storage
|
|
516
|
-
client.registerModules([storageModule({ name: 'app', adapter: myStorageAdapter })]);
|
|
517
|
-
|
|
518
|
-
// Multiple storages
|
|
519
|
-
client.registerModules([
|
|
520
|
-
storageModule({ name: 'app', adapter: appStorage }, { name: 'cache', adapter: cacheStorage }),
|
|
521
|
-
]);
|
|
522
|
-
```
|
|
523
|
-
|
|
524
|
-
**Adapter interface** — only `get` is required:
|
|
525
|
-
|
|
526
|
-
```typescript
|
|
267
|
+
```ts
|
|
527
268
|
interface StorageAdapter {
|
|
528
269
|
get(key: string): string | undefined | null | Promise<string | undefined | null>;
|
|
529
270
|
set?(key: string, value: string): void | Promise<void>;
|
|
530
271
|
delete?(key: string): void | Promise<void>;
|
|
531
272
|
getAllKeys?(): string[] | Promise<string[]>;
|
|
532
273
|
}
|
|
533
|
-
```
|
|
534
274
|
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
| `delete_item` | Delete key | `key: string`, `storage?: string` |
|
|
540
|
-
| `list_keys` | List all keys | `storage?: string` |
|
|
541
|
-
| `get_all` | Get all key-value pairs | `storage?: string` |
|
|
542
|
-
| `list_storages` | List registered storages | — |
|
|
543
|
-
|
|
544
|
-
Works with MMKV, AsyncStorage, or any custom adapter.
|
|
545
|
-
|
|
546
|
-
## Hooks
|
|
547
|
-
|
|
548
|
-
Hooks let you expose state and tools from React components.
|
|
549
|
-
|
|
550
|
-
### useMcpState
|
|
551
|
-
|
|
552
|
-
Expose reactive state to the AI agent:
|
|
553
|
-
|
|
554
|
-
```typescript
|
|
555
|
-
useMcpState(
|
|
556
|
-
key: string,
|
|
557
|
-
factory: () => unknown,
|
|
558
|
-
deps: DependencyList
|
|
559
|
-
): void
|
|
560
|
-
```
|
|
561
|
-
|
|
562
|
-
```typescript
|
|
563
|
-
const UserProvider = ({ children }) => {
|
|
564
|
-
const [user, setUser] = useState(null);
|
|
565
|
-
|
|
566
|
-
useMcpState(
|
|
567
|
-
'user',
|
|
568
|
-
() => ({
|
|
569
|
-
email: user?.email,
|
|
570
|
-
id: user?.id,
|
|
571
|
-
loggedIn: user !== null,
|
|
572
|
-
}),
|
|
573
|
-
[user]
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
// ...
|
|
577
|
-
};
|
|
275
|
+
storageModule(
|
|
276
|
+
{ name: 'mmkv', adapter: mmkvAdapter },
|
|
277
|
+
{ name: 'async', adapter: asyncStorageAdapter }
|
|
278
|
+
);
|
|
578
279
|
```
|
|
579
280
|
|
|
580
|
-
|
|
281
|
+
Without an `adapter.set` / `delete` / `getAllKeys`, the corresponding tools just report the operation as unsupported.
|
|
581
282
|
|
|
582
|
-
|
|
283
|
+
## Custom modules
|
|
583
284
|
|
|
584
|
-
|
|
285
|
+
Write your own module by returning an `McpModule`:
|
|
585
286
|
|
|
586
|
-
```
|
|
587
|
-
|
|
588
|
-
name: string,
|
|
589
|
-
factory: () => ToolHandler,
|
|
590
|
-
deps: DependencyList
|
|
591
|
-
): void
|
|
592
|
-
```
|
|
287
|
+
```ts
|
|
288
|
+
import { type McpModule } from 'react-native-mcp-kit';
|
|
593
289
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
return { success: true };
|
|
607
|
-
},
|
|
608
|
-
}),
|
|
609
|
-
[logout]
|
|
610
|
-
);
|
|
290
|
+
const myModule = (): McpModule => ({
|
|
291
|
+
name: 'myModule',
|
|
292
|
+
description: 'Custom tools exposed to AI agents',
|
|
293
|
+
tools: {
|
|
294
|
+
greet: {
|
|
295
|
+
description: 'Returns a greeting',
|
|
296
|
+
handler: async (args) => ({ message: `Hello, ${args.name}!` }),
|
|
297
|
+
inputSchema: { name: { type: 'string' } },
|
|
298
|
+
timeout: 5000, // optional per-tool timeout, default 10s
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
611
302
|
|
|
612
|
-
|
|
613
|
-
|
|
303
|
+
<McpProvider modules={[myModule()]}>{…}</McpProvider>
|
|
304
|
+
// or
|
|
305
|
+
useMcpModule(() => myModule(), []);
|
|
614
306
|
```
|
|
615
307
|
|
|
616
|
-
|
|
308
|
+
Agents see the module + its tools in `list_tools` and call them via `call(tool: "myModule__greet")`.
|
|
617
309
|
|
|
618
|
-
|
|
310
|
+
## Dev vs production
|
|
619
311
|
|
|
620
|
-
|
|
312
|
+
- **Development** — test-id plugin on, strip plugin off. The `McpProvider` boots, tries to connect to `ws://localhost:8347`; if the server isn't running, no harm done — the bridge just stays disconnected and retries.
|
|
313
|
+
- **Production** — strip plugin on (test-id plugin off). The provider, all hook calls, every import from `react-native-mcp-kit`, and every `data-mcp-id` attribute vanish from the bundle. Nothing ships to users.
|
|
621
314
|
|
|
622
|
-
|
|
623
|
-
useMcpModule(
|
|
624
|
-
factory: () => McpModule,
|
|
625
|
-
deps: DependencyList
|
|
626
|
-
): void
|
|
627
|
-
```
|
|
315
|
+
You don't need `if (__DEV__)` guards around mcp-kit usage — the babel plugin handles it.
|
|
628
316
|
|
|
629
|
-
|
|
630
|
-
const App = () => {
|
|
631
|
-
const queryClient = useQueryClient();
|
|
317
|
+
## Debug logging
|
|
632
318
|
|
|
633
|
-
|
|
319
|
+
Pass `debug` to the provider to print every incoming request and outgoing response with color-coded module names and arrows. Logs use the pre-intercept `console.log`, so they never pollute the `console` module's buffer.
|
|
634
320
|
|
|
635
|
-
|
|
636
|
-
}
|
|
321
|
+
```tsx
|
|
322
|
+
<McpProvider debug>{…}</McpProvider>
|
|
637
323
|
```
|
|
638
324
|
|
|
639
|
-
##
|
|
325
|
+
## Local development (symlink / portal)
|
|
640
326
|
|
|
641
|
-
|
|
327
|
+
If you're developing the library next to an app and symlinking it in, Metro needs to know about the extra path:
|
|
642
328
|
|
|
643
|
-
|
|
329
|
+
```js
|
|
330
|
+
// metro.config.js
|
|
331
|
+
const path = require('path');
|
|
332
|
+
const mcpPath = path.resolve(__dirname, '../path-to/react-native-mcp-kit');
|
|
644
333
|
|
|
645
|
-
```javascript
|
|
646
|
-
// babel.config.js
|
|
647
334
|
module.exports = {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
separator: ':', // separator (default)
|
|
654
|
-
exclude: ['Fragment'], // components to skip
|
|
655
|
-
include: ['Button', 'Input'], // if set, only these get IDs
|
|
656
|
-
},
|
|
335
|
+
watchFolders: [mcpPath],
|
|
336
|
+
resolver: {
|
|
337
|
+
nodeModulesPaths: [
|
|
338
|
+
path.resolve(__dirname, 'node_modules'),
|
|
339
|
+
path.resolve(mcpPath, 'node_modules'),
|
|
657
340
|
],
|
|
658
|
-
|
|
659
|
-
};
|
|
660
|
-
```
|
|
661
|
-
|
|
662
|
-
Generated ID format: `ComponentName:filePath:line`
|
|
663
|
-
|
|
664
|
-
```tsx
|
|
665
|
-
// Before
|
|
666
|
-
<LoginButton onPress={handleLogin} />
|
|
667
|
-
|
|
668
|
-
// After (dev build)
|
|
669
|
-
<LoginButton onPress={handleLogin} data-mcp-id="LoginButton:src/screens/Login:42" />
|
|
670
|
-
```
|
|
671
|
-
|
|
672
|
-
### stripPlugin
|
|
673
|
-
|
|
674
|
-
Removes all MCP code from production builds. Zero MCP code in the final bundle.
|
|
675
|
-
|
|
676
|
-
```javascript
|
|
677
|
-
// babel.config.js
|
|
678
|
-
module.exports = (api) => {
|
|
679
|
-
const isDev = api.cache(() => process.env.NODE_ENV !== 'production');
|
|
680
|
-
|
|
681
|
-
return {
|
|
682
|
-
plugins: [
|
|
683
|
-
isDev && ['react-native-mcp-kit/babel/test-id-plugin'],
|
|
684
|
-
!isDev && ['react-native-mcp-kit/babel/strip-plugin'],
|
|
685
|
-
].filter(Boolean),
|
|
686
|
-
};
|
|
687
|
-
};
|
|
688
|
-
```
|
|
689
|
-
|
|
690
|
-
**What it removes:**
|
|
691
|
-
|
|
692
|
-
- All imports from `react-native-mcp-kit`
|
|
693
|
-
- `McpClient.initialize()`, `registerModule()`, `registerModules()` calls
|
|
694
|
-
- `useMcpState()`, `useMcpTool()`, `useMcpModule()` calls
|
|
695
|
-
- `<McpProvider>` JSX (replaced with children)
|
|
696
|
-
- `data-mcp-id` JSX attributes
|
|
697
|
-
|
|
698
|
-
With the strip plugin, you don't need `if (__DEV__)` guards — just write MCP code normally and it gets removed in production.
|
|
699
|
-
|
|
700
|
-
## Dev vs Production
|
|
701
|
-
|
|
702
|
-
Two strategies for production safety:
|
|
703
|
-
|
|
704
|
-
1. **Strip plugin** (Babel) — removes all MCP imports, calls, and JSX from the production bundle. No MCP code ships to users.
|
|
705
|
-
2. **Without strip plugin** — MCP code stays but WebSocket connection to non-existent server is harmless.
|
|
706
|
-
|
|
707
|
-
## MCP Server Tools
|
|
708
|
-
|
|
709
|
-
The server exposes 5 static tools (no dynamic registration needed):
|
|
710
|
-
|
|
711
|
-
| Tool | Description |
|
|
712
|
-
| ------------------- | ------------------------------------------------------------- |
|
|
713
|
-
| `call` | Universal proxy — calls any tool registered by the RN app |
|
|
714
|
-
| `list_tools` | Lists all available tools grouped by module with descriptions |
|
|
715
|
-
| `connection_status` | Check if the RN app is connected |
|
|
716
|
-
| `state_get` | Read state exposed by `useMcpState` |
|
|
717
|
-
| `state_list` | List all available state keys |
|
|
718
|
-
|
|
719
|
-
**Using `call`:**
|
|
720
|
-
|
|
721
|
-
```
|
|
722
|
-
call(tool: "navigation__navigate", args: '{"screen": "Settings"}')
|
|
723
|
-
call(tool: "console__get_errors")
|
|
724
|
-
call(tool: "_dynamic_logout")
|
|
725
|
-
```
|
|
726
|
-
|
|
727
|
-
The `tool` argument uses `module__method` format (double underscore). Dynamic tools from `useMcpTool` use the `_dynamic_` prefix.
|
|
728
|
-
|
|
729
|
-
The server includes instructions and tool annotations to help AI agents understand how to interact with the app.
|
|
730
|
-
|
|
731
|
-
## Custom Modules
|
|
732
|
-
|
|
733
|
-
Create your own module by returning an `McpModule` object:
|
|
734
|
-
|
|
735
|
-
```typescript
|
|
736
|
-
import { type McpModule } from 'react-native-mcp-kit';
|
|
737
|
-
|
|
738
|
-
const myModule = (): McpModule => {
|
|
739
|
-
return {
|
|
740
|
-
description: 'My custom module for AI agents',
|
|
741
|
-
name: 'myModule',
|
|
742
|
-
tools: {
|
|
743
|
-
greet: {
|
|
744
|
-
description: 'Returns a greeting',
|
|
745
|
-
handler: async (args) => {
|
|
746
|
-
const name = args.name as string;
|
|
747
|
-
return { message: `Hello, ${name}!` };
|
|
748
|
-
},
|
|
749
|
-
},
|
|
750
|
-
getStatus: {
|
|
751
|
-
description: 'Get current status',
|
|
752
|
-
handler: () => {
|
|
753
|
-
return { status: 'ok', timestamp: Date.now() };
|
|
754
|
-
},
|
|
755
|
-
timeout: 5000, // custom timeout in ms (default: 10s)
|
|
756
|
-
},
|
|
757
|
-
},
|
|
758
|
-
};
|
|
341
|
+
},
|
|
759
342
|
};
|
|
760
|
-
|
|
761
|
-
// Register
|
|
762
|
-
client.registerModules([myModule()]);
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
**Module registration methods:**
|
|
766
|
-
|
|
767
|
-
```typescript
|
|
768
|
-
// At init time
|
|
769
|
-
const client = McpClient.initialize();
|
|
770
|
-
client.registerModules([myModule()]);
|
|
771
|
-
|
|
772
|
-
// After init
|
|
773
|
-
McpClient.getInstance().registerModule(myModule());
|
|
774
|
-
|
|
775
|
-
// From a component (tied to lifecycle)
|
|
776
|
-
useMcpModule(() => myModule(), []);
|
|
777
343
|
```
|
|
778
344
|
|
|
779
|
-
##
|
|
780
|
-
|
|
781
|
-
Enable colored console output for all MCP communication:
|
|
782
|
-
|
|
783
|
-
```typescript
|
|
784
|
-
McpClient.initialize({ debug: true });
|
|
785
|
-
```
|
|
786
|
-
|
|
787
|
-
Output shows:
|
|
788
|
-
|
|
789
|
-
- `[rn-mcp-kit]` tag (bold purple)
|
|
790
|
-
- Colored module names (12 bold ANSI colors, assigned by registration order)
|
|
791
|
-
- Bold method names
|
|
792
|
-
- `→` incoming tool requests (cyan)
|
|
793
|
-
- `←` responses (green)
|
|
794
|
-
- `✕` errors (red)
|
|
345
|
+
## API reference
|
|
795
346
|
|
|
796
|
-
|
|
347
|
+
The recommended entry point is `<McpProvider />` — it owns the client singleton and you rarely need to touch `McpClient` directly. For advanced cases the class exposes `McpClient.initialize` / `getInstance` / `registerModule(s)` / `registerTool` / `setState` / `removeState` / `dispose` / `enableDebug` (all idempotent, `initialize` returns the existing instance on repeat calls).
|
|
797
348
|
|
|
798
|
-
|
|
349
|
+
Module and tool types:
|
|
799
350
|
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
```typescript
|
|
803
|
-
// Initialize (creates singleton)
|
|
804
|
-
static initialize(options?: { debug?: boolean; host?: string; port?: number }): McpClient
|
|
805
|
-
|
|
806
|
-
// Get existing instance (throws if not initialized)
|
|
807
|
-
static getInstance(): McpClient
|
|
808
|
-
|
|
809
|
-
// Module registration
|
|
810
|
-
registerModule(module: McpModule): void
|
|
811
|
-
registerModules(modules: McpModule[]): void
|
|
812
|
-
|
|
813
|
-
// Dynamic tools
|
|
814
|
-
registerTool(name: string, tool: ToolHandler): void
|
|
815
|
-
unregisterTool(name: string): void
|
|
816
|
-
|
|
817
|
-
// State
|
|
818
|
-
setState(key: string, value: unknown): void
|
|
819
|
-
removeState(key: string): void
|
|
820
|
-
|
|
821
|
-
// Lifecycle
|
|
822
|
-
dispose(): void
|
|
823
|
-
enableDebug(enabled: boolean): void
|
|
824
|
-
```
|
|
825
|
-
|
|
826
|
-
### McpModule
|
|
827
|
-
|
|
828
|
-
```typescript
|
|
351
|
+
```ts
|
|
829
352
|
interface McpModule {
|
|
830
|
-
description?: string;
|
|
831
353
|
name: string;
|
|
354
|
+
description?: string;
|
|
832
355
|
tools: Record<string, ToolHandler>;
|
|
833
356
|
}
|
|
834
|
-
```
|
|
835
|
-
|
|
836
|
-
### ToolHandler
|
|
837
357
|
|
|
838
|
-
```typescript
|
|
839
358
|
interface ToolHandler {
|
|
840
359
|
description: string;
|
|
841
360
|
handler: (args: Record<string, unknown>) => unknown | Promise<unknown>;
|
|
842
361
|
inputSchema?: Record<string, unknown>;
|
|
843
|
-
timeout?: number; //
|
|
362
|
+
timeout?: number; // default 10s
|
|
844
363
|
}
|
|
845
364
|
```
|
|
846
365
|
|
|
847
|
-
## Symlink Setup (for local development)
|
|
848
|
-
|
|
849
|
-
If you're developing with the library linked locally via symlink/portal:
|
|
850
|
-
|
|
851
|
-
```javascript
|
|
852
|
-
// metro.config.js
|
|
853
|
-
const mcpPath = require('path').resolve(__dirname, '../path-to/react-native-mcp-kit');
|
|
854
|
-
|
|
855
|
-
module.exports = {
|
|
856
|
-
watchFolders: [mcpPath],
|
|
857
|
-
resolver: {
|
|
858
|
-
nodeModulesPaths: [
|
|
859
|
-
path.resolve(__dirname, 'node_modules'),
|
|
860
|
-
path.resolve(mcpPath, 'node_modules'),
|
|
861
|
-
],
|
|
862
|
-
},
|
|
863
|
-
};
|
|
864
|
-
```
|
|
865
|
-
|
|
866
366
|
## License
|
|
867
367
|
|
|
868
368
|
MIT
|