react-native-mcp-kit 1.0.1 → 2.1.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.
Files changed (170) hide show
  1. package/README.md +220 -720
  2. package/dist/babel/stripPlugin.d.ts.map +1 -1
  3. package/dist/babel/stripPlugin.js +40 -3
  4. package/dist/babel/stripPlugin.js.map +1 -1
  5. package/dist/bin/ios-hid +0 -0
  6. package/dist/client/contexts/McpContext/McpProvider.d.ts +1 -1
  7. package/dist/client/contexts/McpContext/McpProvider.d.ts.map +1 -1
  8. package/dist/client/contexts/McpContext/McpProvider.js +67 -4
  9. package/dist/client/contexts/McpContext/McpProvider.js.map +1 -1
  10. package/dist/client/contexts/McpContext/types.d.ts +11 -1
  11. package/dist/client/contexts/McpContext/types.d.ts.map +1 -1
  12. package/dist/client/core/McpClient.d.ts +8 -0
  13. package/dist/client/core/McpClient.d.ts.map +1 -1
  14. package/dist/client/core/McpClient.js +112 -22
  15. package/dist/client/core/McpClient.js.map +1 -1
  16. package/dist/client/utils/connection.d.ts +7 -0
  17. package/dist/client/utils/connection.d.ts.map +1 -1
  18. package/dist/client/utils/connection.js +14 -1
  19. package/dist/client/utils/connection.js.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/modules/device/device.d.ts.map +1 -1
  25. package/dist/modules/device/device.js +16 -4
  26. package/dist/modules/device/device.js.map +1 -1
  27. package/dist/modules/fiberTree/fiberTree.d.ts.map +1 -1
  28. package/dist/modules/fiberTree/fiberTree.js +61 -15
  29. package/dist/modules/fiberTree/fiberTree.js.map +1 -1
  30. package/dist/modules/fiberTree/types.d.ts +9 -0
  31. package/dist/modules/fiberTree/types.d.ts.map +1 -1
  32. package/dist/modules/fiberTree/utils.d.ts +2 -1
  33. package/dist/modules/fiberTree/utils.d.ts.map +1 -1
  34. package/dist/modules/fiberTree/utils.js +57 -1
  35. package/dist/modules/fiberTree/utils.js.map +1 -1
  36. package/dist/modules/index.d.ts +0 -1
  37. package/dist/modules/index.d.ts.map +1 -1
  38. package/dist/modules/index.js +1 -3
  39. package/dist/modules/index.js.map +1 -1
  40. package/dist/modules/navigation/navigation.d.ts.map +1 -1
  41. package/dist/modules/navigation/navigation.js +32 -40
  42. package/dist/modules/navigation/navigation.js.map +1 -1
  43. package/dist/modules/navigation/types.d.ts +2 -1
  44. package/dist/modules/navigation/types.d.ts.map +1 -1
  45. package/dist/server/bridge.d.ts +38 -12
  46. package/dist/server/bridge.d.ts.map +1 -1
  47. package/dist/server/bridge.js +158 -56
  48. package/dist/server/bridge.js.map +1 -1
  49. package/dist/server/cli.js +10 -1
  50. package/dist/server/cli.js.map +1 -1
  51. package/dist/server/host/deviceResolver.d.ts +53 -0
  52. package/dist/server/host/deviceResolver.d.ts.map +1 -0
  53. package/dist/server/host/deviceResolver.js +555 -0
  54. package/dist/server/host/deviceResolver.js.map +1 -0
  55. package/dist/server/host/helpers.d.ts +33 -0
  56. package/dist/server/host/helpers.d.ts.map +1 -0
  57. package/dist/server/host/helpers.js +42 -0
  58. package/dist/server/host/helpers.js.map +1 -0
  59. package/dist/server/host/hostModule.d.ts +4 -0
  60. package/dist/server/host/hostModule.d.ts.map +1 -0
  61. package/dist/server/host/hostModule.js +26 -0
  62. package/dist/server/host/hostModule.js.map +1 -0
  63. package/dist/server/host/index.d.ts +4 -0
  64. package/dist/server/host/index.d.ts.map +1 -0
  65. package/dist/server/host/index.js +8 -0
  66. package/dist/server/host/index.js.map +1 -0
  67. package/dist/server/host/iosInput.d.ts +15 -0
  68. package/dist/server/host/iosInput.d.ts.map +1 -0
  69. package/dist/server/host/iosInput.js +93 -0
  70. package/dist/server/host/iosInput.js.map +1 -0
  71. package/dist/server/host/modules/screenshot.d.ts +4 -0
  72. package/dist/server/host/modules/screenshot.d.ts.map +1 -0
  73. package/dist/server/host/modules/screenshot.js +615 -0
  74. package/dist/server/host/modules/screenshot.js.map +1 -0
  75. package/dist/server/host/processRunner.d.ts +19 -0
  76. package/dist/server/host/processRunner.d.ts.map +1 -0
  77. package/dist/server/host/processRunner.js +58 -0
  78. package/dist/server/host/processRunner.js.map +1 -0
  79. package/dist/server/host/tools/capture.d.ts +6 -0
  80. package/dist/server/host/tools/capture.d.ts.map +1 -0
  81. package/dist/server/host/tools/capture.js +148 -0
  82. package/dist/server/host/tools/capture.js.map +1 -0
  83. package/dist/server/host/tools/devices.d.ts +4 -0
  84. package/dist/server/host/tools/devices.d.ts.map +1 -0
  85. package/dist/server/host/tools/devices.js +17 -0
  86. package/dist/server/host/tools/devices.js.map +1 -0
  87. package/dist/server/host/tools/input.d.ts +7 -0
  88. package/dist/server/host/tools/input.d.ts.map +1 -0
  89. package/dist/server/host/tools/input.js +286 -0
  90. package/dist/server/host/tools/input.js.map +1 -0
  91. package/dist/server/host/tools/lifecycle.d.ts +6 -0
  92. package/dist/server/host/tools/lifecycle.d.ts.map +1 -0
  93. package/dist/server/host/tools/lifecycle.js +271 -0
  94. package/dist/server/host/tools/lifecycle.js.map +1 -0
  95. package/dist/server/host/types.d.ts +17 -0
  96. package/dist/server/host/types.d.ts.map +1 -0
  97. package/dist/{modules/components → server/host}/types.js.map +1 -1
  98. package/dist/server/host/wda.d.ts +15 -0
  99. package/dist/server/host/wda.d.ts.map +1 -0
  100. package/dist/server/host/wda.js +100 -0
  101. package/dist/server/host/wda.js.map +1 -0
  102. package/dist/server/index.d.ts.map +1 -1
  103. package/dist/server/index.js +10 -17
  104. package/dist/server/index.js.map +1 -1
  105. package/dist/server/mcpServer.d.ts +5 -10
  106. package/dist/server/mcpServer.d.ts.map +1 -1
  107. package/dist/server/mcpServer.js +438 -153
  108. package/dist/server/mcpServer.js.map +1 -1
  109. package/dist/server/types.d.ts +2 -8
  110. package/dist/server/types.d.ts.map +1 -1
  111. package/dist/shared/protocol.d.ts +39 -1
  112. package/dist/shared/protocol.d.ts.map +1 -1
  113. package/dist/shared/protocol.js +12 -1
  114. package/dist/shared/protocol.js.map +1 -1
  115. package/package.json +6 -12
  116. package/dist/client/contexts/McpTreeContext/McpTracker.d.ts +0 -10
  117. package/dist/client/contexts/McpTreeContext/McpTracker.d.ts.map +0 -1
  118. package/dist/client/contexts/McpTreeContext/McpTracker.js +0 -101
  119. package/dist/client/contexts/McpTreeContext/McpTracker.js.map +0 -1
  120. package/dist/client/contexts/McpTreeContext/McpTreeContext.d.ts +0 -2
  121. package/dist/client/contexts/McpTreeContext/McpTreeContext.d.ts.map +0 -1
  122. package/dist/client/contexts/McpTreeContext/McpTreeContext.js +0 -6
  123. package/dist/client/contexts/McpTreeContext/McpTreeContext.js.map +0 -1
  124. package/dist/client/contexts/McpTreeContext/McpTreeRegistry.d.ts +0 -16
  125. package/dist/client/contexts/McpTreeContext/McpTreeRegistry.d.ts.map +0 -1
  126. package/dist/client/contexts/McpTreeContext/McpTreeRegistry.js +0 -96
  127. package/dist/client/contexts/McpTreeContext/McpTreeRegistry.js.map +0 -1
  128. package/dist/client/contexts/McpTreeContext/index.d.ts +0 -5
  129. package/dist/client/contexts/McpTreeContext/index.d.ts.map +0 -1
  130. package/dist/client/contexts/McpTreeContext/index.js +0 -10
  131. package/dist/client/contexts/McpTreeContext/index.js.map +0 -1
  132. package/dist/client/contexts/McpTreeContext/types.d.ts +0 -14
  133. package/dist/client/contexts/McpTreeContext/types.d.ts.map +0 -1
  134. package/dist/client/contexts/McpTreeContext/types.js.map +0 -1
  135. package/dist/modules/components/components.d.ts +0 -8
  136. package/dist/modules/components/components.d.ts.map +0 -1
  137. package/dist/modules/components/components.js +0 -315
  138. package/dist/modules/components/components.js.map +0 -1
  139. package/dist/modules/components/index.d.ts +0 -3
  140. package/dist/modules/components/index.d.ts.map +0 -1
  141. package/dist/modules/components/index.js +0 -6
  142. package/dist/modules/components/index.js.map +0 -1
  143. package/dist/modules/components/types.d.ts +0 -18
  144. package/dist/modules/components/types.d.ts.map +0 -1
  145. package/dist/modules/components/types.js +0 -3
  146. package/dist/modules/components/utils.d.ts +0 -18
  147. package/dist/modules/components/utils.d.ts.map +0 -1
  148. package/dist/modules/components/utils.js +0 -386
  149. package/dist/modules/components/utils.js.map +0 -1
  150. package/dist/modules/screenshot/index.d.ts +0 -3
  151. package/dist/modules/screenshot/index.d.ts.map +0 -1
  152. package/dist/modules/screenshot/index.js +0 -6
  153. package/dist/modules/screenshot/index.js.map +0 -1
  154. package/dist/modules/screenshot/screenshot.d.ts +0 -4
  155. package/dist/modules/screenshot/screenshot.d.ts.map +0 -1
  156. package/dist/modules/screenshot/screenshot.js +0 -89
  157. package/dist/modules/screenshot/screenshot.js.map +0 -1
  158. package/dist/modules/screenshot/types.d.ts +0 -5
  159. package/dist/modules/screenshot/types.d.ts.map +0 -1
  160. package/dist/modules/screenshot/types.js +0 -3
  161. package/dist/modules/screenshot/types.js.map +0 -1
  162. package/dist/modules/tree/index.d.ts +0 -2
  163. package/dist/modules/tree/index.d.ts.map +0 -1
  164. package/dist/modules/tree/index.js +0 -6
  165. package/dist/modules/tree/index.js.map +0 -1
  166. package/dist/modules/tree/tree.d.ts +0 -3
  167. package/dist/modules/tree/tree.d.ts.map +0 -1
  168. package/dist/modules/tree/tree.js +0 -274
  169. package/dist/modules/tree/tree.js.map +0 -1
  170. /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
- A bidirectional [MCP](https://modelcontextprotocol.io/) bridge that connects AI agents to running React Native apps. The server is a proxy all business logic runs inside your RN app.
3
+ **See, drive, and debug a running React Native app from an AI agentor 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 --stdio/MCP--> MCP Server (Node.js) --WebSocket--> RN App (device)
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
- ## Table of Contents
10
-
11
- - [Features](#features)
12
- - [Quick Start](#quick-start)
13
- - [Modules](#modules)
14
- - [alert](#alert)
15
- - [fiberTree](#fibertree)
16
- - [console](#console)
17
- - [device](#device)
18
- - [errors](#errors)
19
- - [i18next](#i18next)
20
- - [navigation](#navigation)
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
- ### 2. Initialize in your app
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
- // Initialize client (call once, before any module registration)
79
- const client = McpClient.initialize({ debug: true });
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
- ### 3. Wrap your app with McpProvider
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
- const App = () => {
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
- <View ref={rootRef} collapsable={false} style={{ flex: 1 }}>
98
- <NavigationContainer ref={navigationRef}>
99
- <McpProvider>{/* your app */}</McpProvider>
100
- </NavigationContainer>
101
- </View>
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
- ### 4. Configure the MCP server
64
+ These modules register automatically on mount — no prop required:
107
65
 
108
- Add to your project's `.mcp.json`:
66
+ `alert`, `console`, `device`, `errors`, `network`, `fiber_tree`
109
67
 
110
- ```json
111
- {
112
- "mcpServers": {
113
- "react-native-mcp-kit": {
114
- "command": "npx",
115
- "args": ["react-native-mcp-kit"]
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
- Or with a custom port:
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", "--port", "8347"]
107
+ "args": ["react-native-mcp-kit"]
129
108
  }
130
109
  }
131
110
  }
132
111
  ```
133
112
 
134
- ### 5. Connect
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
- ## Modules
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
- | Module | Factory | Description |
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
- ### alert
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
- | Tool | Description | Args |
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
- Buttons can be strings or objects with style:
126
+ ### 4. Run
171
127
 
172
- ```typescript
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
- // With styles
177
- call(tool: "alert__show", args: '{"title": "Delete?", "buttons": [{"text": "Cancel", "style": "cancel"}, {"text": "Delete", "style": "destructive"}]}')
178
- ```
130
+ ## `McpProvider` reference
179
131
 
180
- Button styles: `default`, `cancel`, `destructive`. Returns `{ button: string, index: number }`. Timeout: 60s.
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
- ### fiberTree
146
+ ## MCP server tools
185
147
 
186
- React fiber tree inspection with the ability to invoke callbacks and call ref methods.
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
- ```typescript
189
- client.registerModules([fiberTreeModule({ rootRef })]);
190
- ```
150
+ ## Multi-client
191
151
 
192
- **Inspection tools:**
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
- | Tool | Description | Args |
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
- **Interaction tools:**
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
- | Tool | Description | Args |
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
- **Finding components:**
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
- Components can be found by `testID`, `name`, `text`, or `mcpId` (from the Babel testID plugin). Use `within` to scope the search to children of a specific component:
165
+ iOS input goes through a bundled `ios-hid` Swift binary. **No WebDriverAgent, no idb, no Appium server.**
213
166
 
214
- ```typescript
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
- // Use index for multiple matches (0-based)
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
- // Nested within path with index
225
- call(tool: "fiber_tree__get_component", args: '{"name": "Text", "within": "Button:1/Pressable"}')
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
- **Invoking callbacks:**
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
- ```typescript
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
- // Type text
235
- call(tool: "fiber_tree__invoke", args: '{"mcpId": "Input:screens/Login:42", "callback": "onChangeText", "args": ["user@example.com"]}')
185
+ useMcpTool('logout', () => ({
186
+ description: 'Log out the current user',
187
+ handler: async () => { await logout(); return { success: true }; },
188
+ }), [logout]);
236
189
 
237
- // Toggle checkbox with custom args
238
- call(tool: "fiber_tree__invoke", args: '{"name": "Checkbox", "within": "TermsForm", "callback": "onPress", "args": [true]}')
190
+ return <UserContext.Provider value={user}>{children}</UserContext.Provider>;
191
+ };
239
192
  ```
240
193
 
241
- **Calling ref methods:**
194
+ ## Modules
242
195
 
243
- ```typescript
244
- // Focus an input
245
- call(tool: "fiber_tree__call_ref", args: '{"testID": "email-input", "method": "focus"}')
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
- // List available methods
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
- Intercepts console output with a ring buffer.
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
- ```typescript
258
- client.registerModules([consoleModule()]);
259
-
260
- // With options
261
- client.registerModules([
262
- consoleModule({
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
- Device info and system APIs.
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
- ```typescript
316
- client.registerModules([errorsModule()]);
235
+ ### fiber_tree
317
236
 
318
- // With options
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
- | Tool | Description | Args |
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
- Error sources: `global` (ErrorUtils), `promise` (unhandled rejections). Deduplicates within 100ms window.
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
- Full navigation control with history tracking. Accepts a React Navigation ref.
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`. Captures request/response bodies, headers, status, duration.
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
- Auto-ignores WebSocket, Metro, and symbolicate URLs.
434
-
435
- ---
436
-
437
- ### reactQuery
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
- client.registerModules([screenshotModule({ rootRef })]);
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
- Key-value storage inspection. Supports multiple named storage instances with flexible adapters.
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
- ```typescript
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
- | Tool | Description | Args |
536
- | --------------- | ------------------------ | -------------------------------------------------- |
537
- | `get_item` | Get value by key | `key: string`, `storage?: string` |
538
- | `set_item` | Set value | `key: string`, `value: string`, `storage?: string` |
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
- The agent reads state via `state_get` and `state_list` tools no WebSocket roundtrip needed.
281
+ Without an `adapter.set` / `delete` / `getAllKeys`, the corresponding tools just report the operation as unsupported.
581
282
 
582
- ### useMcpTool
283
+ ## Custom modules
583
284
 
584
- Register a dynamic tool from a component:
285
+ Write your own module by returning an `McpModule`:
585
286
 
586
- ```typescript
587
- useMcpTool(
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
- ```typescript
595
- const UserProvider = ({ children }) => {
596
- const logout = useCallback(() => {
597
- /* ... */
598
- }, []);
599
-
600
- useMcpTool(
601
- 'logout',
602
- () => ({
603
- description: 'Log out the current user',
604
- handler: () => {
605
- logout();
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
- Dynamic tools are accessible via `call(tool: "_dynamic_logout")` and appear in `list_tools` with a `(dynamic)` label.
308
+ Agents see the module + its tools in `list_tools` and call them via `call(tool: "myModule__greet")`.
617
309
 
618
- ### useMcpModule
310
+ ## Dev vs production
619
311
 
620
- Register a full module from a component (tied to component lifecycle):
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
- ```typescript
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
- ```typescript
630
- const App = () => {
631
- const queryClient = useQueryClient();
317
+ ## Debug logging
632
318
 
633
- useMcpModule(() => reactQueryModule(queryClient), [queryClient]);
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
- ## Babel Plugins
325
+ ## Local development (symlink / portal)
640
326
 
641
- ### testIdPlugin
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
- Auto-adds `data-mcp-id` attributes to JSX components for reliable component identification.
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
- plugins: [
649
- [
650
- 'react-native-mcp-kit/babel/test-id-plugin',
651
- {
652
- attr: 'data-mcp-id', // attribute name (default)
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
- ## Debug Logging
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
- Debug logs use the original `console.log` (captured before the console module intercepts), so they don't appear in the console module buffer.
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
- ## API Reference
349
+ Module and tool types:
799
350
 
800
- ### McpClient
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; // per-tool timeout in ms (default: 10s)
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