react-native-mcp-kit 4.2.0 → 4.3.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/dist/babel/testIdPlugin/collectHooks.d.ts +13 -0
- package/dist/babel/testIdPlugin/collectHooks.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/collectHooks.js +112 -0
- package/dist/babel/testIdPlugin/collectHooks.js.map +1 -0
- package/dist/babel/testIdPlugin/constants.d.ts +6 -0
- package/dist/babel/testIdPlugin/constants.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/constants.js +49 -0
- package/dist/babel/testIdPlugin/constants.js.map +1 -0
- package/dist/babel/testIdPlugin/helpers.d.ts +7 -0
- package/dist/babel/testIdPlugin/helpers.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/helpers.js +77 -0
- package/dist/babel/testIdPlugin/helpers.js.map +1 -0
- package/dist/babel/testIdPlugin/hocUnwrap.d.ts +4 -0
- package/dist/babel/testIdPlugin/hocUnwrap.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/hocUnwrap.js +94 -0
- package/dist/babel/testIdPlugin/hocUnwrap.js.map +1 -0
- package/dist/babel/{testIdPlugin.d.ts → testIdPlugin/index.d.ts} +1 -1
- package/dist/babel/testIdPlugin/index.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/index.js +205 -0
- package/dist/babel/testIdPlugin/index.js.map +1 -0
- package/dist/babel/testIdPlugin/inject.d.ts +7 -0
- package/dist/babel/testIdPlugin/inject.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/inject.js +86 -0
- package/dist/babel/testIdPlugin/inject.js.map +1 -0
- package/dist/babel/testIdPlugin/types.d.ts +59 -0
- package/dist/babel/testIdPlugin/types.d.ts.map +1 -0
- package/dist/babel/testIdPlugin/types.js +3 -0
- package/dist/babel/testIdPlugin/types.js.map +1 -0
- package/dist/bin/ios-hid +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/modules/fiberTree/hooks/extract.d.ts +6 -0
- package/dist/modules/fiberTree/hooks/extract.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/extract.js +187 -0
- package/dist/modules/fiberTree/hooks/extract.js.map +1 -0
- package/dist/modules/fiberTree/hooks/flatten.d.ts +4 -0
- package/dist/modules/fiberTree/hooks/flatten.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/flatten.js +81 -0
- package/dist/modules/fiberTree/hooks/flatten.js.map +1 -0
- package/dist/modules/fiberTree/hooks/index.d.ts +5 -0
- package/dist/modules/fiberTree/hooks/index.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/index.js +10 -0
- package/dist/modules/fiberTree/hooks/index.js.map +1 -0
- package/dist/modules/fiberTree/hooks/options.d.ts +9 -0
- package/dist/modules/fiberTree/hooks/options.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/options.js +27 -0
- package/dist/modules/fiberTree/hooks/options.js.map +1 -0
- package/dist/modules/fiberTree/hooks/patterns.d.ts +2 -0
- package/dist/modules/fiberTree/hooks/patterns.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/patterns.js +26 -0
- package/dist/modules/fiberTree/hooks/patterns.js.map +1 -0
- package/dist/modules/fiberTree/hooks/shape.d.ts +3 -0
- package/dist/modules/fiberTree/hooks/shape.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/shape.js +66 -0
- package/dist/modules/fiberTree/hooks/shape.js.map +1 -0
- package/dist/modules/fiberTree/hooks/slotCount.d.ts +2 -0
- package/dist/modules/fiberTree/hooks/slotCount.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/slotCount.js +93 -0
- package/dist/modules/fiberTree/hooks/slotCount.js.map +1 -0
- package/dist/modules/fiberTree/{hooks.d.ts → hooks/types.d.ts} +1 -34
- package/dist/modules/fiberTree/hooks/types.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/types.js +3 -0
- package/dist/modules/fiberTree/hooks/types.js.map +1 -0
- package/dist/modules/fiberTree/hooks/value.d.ts +2 -0
- package/dist/modules/fiberTree/hooks/value.d.ts.map +1 -0
- package/dist/modules/fiberTree/hooks/value.js +89 -0
- package/dist/modules/fiberTree/hooks/value.js.map +1 -0
- package/dist/modules/fiberTree/utils/constants.d.ts +8 -0
- package/dist/modules/fiberTree/utils/constants.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/constants.js +15 -0
- package/dist/modules/fiberTree/utils/constants.js.map +1 -0
- package/dist/modules/fiberTree/utils/index.d.ts +9 -0
- package/dist/modules/fiberTree/utils/index.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/index.js +34 -0
- package/dist/modules/fiberTree/utils/index.js.map +1 -0
- package/dist/modules/fiberTree/utils/match.d.ts +4 -0
- package/dist/modules/fiberTree/utils/match.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/match.js +169 -0
- package/dist/modules/fiberTree/utils/match.js.map +1 -0
- package/dist/modules/fiberTree/utils/naming.d.ts +4 -0
- package/dist/modules/fiberTree/utils/naming.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/naming.js +52 -0
- package/dist/modules/fiberTree/utils/naming.js.map +1 -0
- package/dist/modules/fiberTree/utils/native.d.ts +5 -0
- package/dist/modules/fiberTree/utils/native.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/native.js +101 -0
- package/dist/modules/fiberTree/utils/native.js.map +1 -0
- package/dist/modules/fiberTree/utils/projection.d.ts +9 -0
- package/dist/modules/fiberTree/utils/projection.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/projection.js +75 -0
- package/dist/modules/fiberTree/utils/projection.js.map +1 -0
- package/dist/modules/fiberTree/utils/root.d.ts +4 -0
- package/dist/modules/fiberTree/utils/root.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/root.js +30 -0
- package/dist/modules/fiberTree/utils/root.js.map +1 -0
- package/dist/modules/fiberTree/utils/screen.d.ts +10 -0
- package/dist/modules/fiberTree/utils/screen.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/screen.js +39 -0
- package/dist/modules/fiberTree/utils/screen.js.map +1 -0
- package/dist/modules/fiberTree/utils/serialize.d.ts +4 -0
- package/dist/modules/fiberTree/utils/serialize.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/serialize.js +102 -0
- package/dist/modules/fiberTree/utils/serialize.js.map +1 -0
- package/dist/modules/fiberTree/utils/traverse.d.ts +12 -0
- package/dist/modules/fiberTree/utils/traverse.d.ts.map +1 -0
- package/dist/modules/fiberTree/utils/traverse.js +107 -0
- package/dist/modules/fiberTree/utils/traverse.js.map +1 -0
- package/dist/server/dispatch.d.ts +15 -0
- package/dist/server/dispatch.d.ts.map +1 -0
- package/dist/server/dispatch.js +121 -0
- package/dist/server/dispatch.js.map +1 -0
- package/dist/server/helpers.d.ts +129 -0
- package/dist/server/helpers.d.ts.map +1 -0
- package/dist/server/helpers.js +250 -0
- package/dist/server/helpers.js.map +1 -0
- package/dist/server/host/deviceResolver/byClient.d.ts +5 -0
- package/dist/server/host/deviceResolver/byClient.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/byClient.js +171 -0
- package/dist/server/host/deviceResolver/byClient.js.map +1 -0
- package/dist/server/host/deviceResolver/byId.d.ts +5 -0
- package/dist/server/host/deviceResolver/byId.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/byId.js +81 -0
- package/dist/server/host/deviceResolver/byId.js.map +1 -0
- package/dist/server/host/deviceResolver/enrich.d.ts +5 -0
- package/dist/server/host/deviceResolver/enrich.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/enrich.js +123 -0
- package/dist/server/host/deviceResolver/enrich.js.map +1 -0
- package/dist/server/host/deviceResolver/index.d.ts +13 -0
- package/dist/server/host/deviceResolver/index.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/index.js +65 -0
- package/dist/server/host/deviceResolver/index.js.map +1 -0
- package/dist/server/host/deviceResolver/list.d.ts +10 -0
- package/dist/server/host/deviceResolver/list.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/list.js +119 -0
- package/dist/server/host/deviceResolver/list.js.map +1 -0
- package/dist/server/host/deviceResolver/scan.d.ts +4 -0
- package/dist/server/host/deviceResolver/scan.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/scan.js +102 -0
- package/dist/server/host/deviceResolver/scan.js.map +1 -0
- package/dist/server/host/{deviceResolver.d.ts → deviceResolver/types.d.ts} +1 -16
- package/dist/server/host/deviceResolver/types.d.ts.map +1 -0
- package/dist/server/host/deviceResolver/types.js +3 -0
- package/dist/server/host/deviceResolver/types.js.map +1 -0
- package/dist/server/host/tools/input/android.d.ts +15 -0
- package/dist/server/host/tools/input/android.d.ts.map +1 -0
- package/dist/server/host/tools/input/android.js +88 -0
- package/dist/server/host/tools/input/android.js.map +1 -0
- package/dist/server/host/tools/input/constants.d.ts +14 -0
- package/dist/server/host/tools/input/constants.d.ts.map +1 -0
- package/dist/server/host/tools/input/constants.js +41 -0
- package/dist/server/host/tools/input/constants.js.map +1 -0
- package/dist/server/host/tools/input/drag.d.ts +4 -0
- package/dist/server/host/tools/input/drag.d.ts.map +1 -0
- package/dist/server/host/tools/input/drag.js +76 -0
- package/dist/server/host/tools/input/drag.js.map +1 -0
- package/dist/server/host/tools/input/index.d.ts +8 -0
- package/dist/server/host/tools/input/index.d.ts.map +1 -0
- package/dist/server/host/tools/input/index.js +18 -0
- package/dist/server/host/tools/input/index.js.map +1 -0
- package/dist/server/host/tools/input/longPress.d.ts +4 -0
- package/dist/server/host/tools/input/longPress.d.ts.map +1 -0
- package/dist/server/host/tools/input/longPress.js +49 -0
- package/dist/server/host/tools/input/longPress.js.map +1 -0
- package/dist/server/host/tools/input/pressKey.d.ts +4 -0
- package/dist/server/host/tools/input/pressKey.d.ts.map +1 -0
- package/dist/server/host/tools/input/pressKey.js +42 -0
- package/dist/server/host/tools/input/pressKey.js.map +1 -0
- package/dist/server/host/tools/input/swipe.d.ts +4 -0
- package/dist/server/host/tools/input/swipe.d.ts.map +1 -0
- package/dist/server/host/tools/input/swipe.js +71 -0
- package/dist/server/host/tools/input/swipe.js.map +1 -0
- package/dist/server/host/tools/input/tap.d.ts +4 -0
- package/dist/server/host/tools/input/tap.d.ts.map +1 -0
- package/dist/server/host/tools/input/tap.js +49 -0
- package/dist/server/host/tools/input/tap.js.map +1 -0
- package/dist/server/host/tools/input/typeText.d.ts +4 -0
- package/dist/server/host/tools/input/typeText.d.ts.map +1 -0
- package/dist/server/host/tools/input/typeText.js +52 -0
- package/dist/server/host/tools/input/typeText.js.map +1 -0
- package/dist/server/host/tools/input/typeTextBatch.d.ts +4 -0
- package/dist/server/host/tools/input/typeTextBatch.d.ts.map +1 -0
- package/dist/server/host/tools/input/typeTextBatch.js +106 -0
- package/dist/server/host/tools/input/typeTextBatch.js.map +1 -0
- package/dist/server/instructions.d.ts +2 -0
- package/dist/server/instructions.d.ts.map +1 -0
- package/dist/server/instructions.js +89 -0
- package/dist/server/instructions.js.map +1 -0
- package/dist/server/mcpServer.d.ts +0 -12
- package/dist/server/mcpServer.d.ts.map +1 -1
- package/dist/server/mcpServer.js +26 -877
- package/dist/server/mcpServer.js.map +1 -1
- package/dist/server/predicate.d.ts +31 -0
- package/dist/server/predicate.d.ts.map +1 -0
- package/dist/server/predicate.js +104 -0
- package/dist/server/predicate.js.map +1 -0
- package/dist/server/tools/assert.d.ts +4 -0
- package/dist/server/tools/assert.d.ts.map +1 -0
- package/dist/server/tools/assert.js +103 -0
- package/dist/server/tools/assert.js.map +1 -0
- package/dist/server/tools/call.d.ts +4 -0
- package/dist/server/tools/call.d.ts.map +1 -0
- package/dist/server/tools/call.js +48 -0
- package/dist/server/tools/call.js.map +1 -0
- package/dist/server/tools/connectionStatus.d.ts +4 -0
- package/dist/server/tools/connectionStatus.d.ts.map +1 -0
- package/dist/server/tools/connectionStatus.js +41 -0
- package/dist/server/tools/connectionStatus.js.map +1 -0
- package/dist/server/tools/describeTool.d.ts +4 -0
- package/dist/server/tools/describeTool.d.ts.map +1 -0
- package/dist/server/tools/describeTool.js +149 -0
- package/dist/server/tools/describeTool.js.map +1 -0
- package/dist/server/tools/listTools.d.ts +4 -0
- package/dist/server/tools/listTools.d.ts.map +1 -0
- package/dist/server/tools/listTools.js +116 -0
- package/dist/server/tools/listTools.js.map +1 -0
- package/dist/server/tools/waitUntil.d.ts +4 -0
- package/dist/server/tools/waitUntil.d.ts.map +1 -0
- package/dist/server/tools/waitUntil.js +169 -0
- package/dist/server/tools/waitUntil.js.map +1 -0
- package/package.json +2 -2
- package/dist/babel/testIdPlugin.d.ts.map +0 -1
- package/dist/babel/testIdPlugin.js +0 -585
- package/dist/babel/testIdPlugin.js.map +0 -1
- package/dist/modules/fiberTree/hooks.d.ts.map +0 -1
- package/dist/modules/fiberTree/hooks.js +0 -550
- package/dist/modules/fiberTree/hooks.js.map +0 -1
- package/dist/modules/fiberTree/utils.d.ts +0 -39
- package/dist/modules/fiberTree/utils.d.ts.map +0 -1
- package/dist/modules/fiberTree/utils.js +0 -641
- package/dist/modules/fiberTree/utils.js.map +0 -1
- package/dist/server/host/deviceResolver.d.ts.map +0 -1
- package/dist/server/host/deviceResolver.js +0 -621
- package/dist/server/host/deviceResolver.js.map +0 -1
- package/dist/server/host/tools/input.d.ts +0 -10
- package/dist/server/host/tools/input.d.ts.map +0 -1
- package/dist/server/host/tools/input.js +0 -503
- package/dist/server/host/tools/input.js.map +0 -1
package/dist/server/mcpServer.js
CHANGED
|
@@ -5,8 +5,16 @@ const node_fs_1 = require("node:fs");
|
|
|
5
5
|
const node_path_1 = require("node:path");
|
|
6
6
|
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
7
7
|
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
8
|
-
const zod_1 = require("zod");
|
|
9
8
|
const protocol_1 = require("../shared/protocol");
|
|
9
|
+
const dispatch_1 = require("./dispatch");
|
|
10
|
+
const helpers_1 = require("./helpers");
|
|
11
|
+
const instructions_1 = require("./instructions");
|
|
12
|
+
const assert_1 = require("./tools/assert");
|
|
13
|
+
const call_1 = require("./tools/call");
|
|
14
|
+
const connectionStatus_1 = require("./tools/connectionStatus");
|
|
15
|
+
const describeTool_1 = require("./tools/describeTool");
|
|
16
|
+
const listTools_1 = require("./tools/listTools");
|
|
17
|
+
const waitUntil_1 = require("./tools/waitUntil");
|
|
10
18
|
// Read the shipped package.json so the MCP handshake reports an accurate
|
|
11
19
|
// server version — keeps clients' connection logs in sync with the installed
|
|
12
20
|
// package without a parallel constant to maintain. __dirname at runtime is
|
|
@@ -20,890 +28,31 @@ const PACKAGE_VERSION = (() => {
|
|
|
20
28
|
return '0.0.0';
|
|
21
29
|
}
|
|
22
30
|
})();
|
|
23
|
-
const BASE_INSTRUCTIONS = `You are connected to a running React Native app via the react-native-mcp-kit bridge.
|
|
24
|
-
|
|
25
|
-
Multiple React Native apps can connect simultaneously — each is identified by a short ID like "ios-1", "android-1", or "client-1". Use \`connection_status\` or \`list_tools\` to see which clients are connected and their IDs, platforms, and labels.
|
|
26
|
-
|
|
27
|
-
## How to interact
|
|
28
|
-
|
|
29
|
-
1. Use \`connection_status\` to check which clients are connected.
|
|
30
|
-
2. Use \`list_tools\` to browse all available tool names and short descriptions. The response is compact — modules that are structurally identical across multiple clients are deduplicated into a single entry with a \`clientIds\` array, and input schemas are omitted. Narrow the listing with \`{ module }\` or \`{ clientId }\`, or pass \`{ compact: true }\` to drop module-level descriptions.
|
|
31
|
-
3. Use \`describe_tool\` with \`{ tool, clientId? }\` to fetch the full input schema of a specific tool before calling it. Required when you need to know the argument shape. Host tools are resolved directly (no clientId needed). For in-app tools, omit \`clientId\` to auto-pick; specify it only when multiple clients have the same tool with different schemas.
|
|
32
|
-
4. Use \`call\` to invoke any tool with format: module${protocol_1.MODULE_SEPARATOR}method (e.g. navigation${protocol_1.MODULE_SEPARATOR}navigate). When more than one client is connected, specify \`clientId\`. When exactly one client is connected, \`clientId\` is optional — it's auto-picked. \`args\` accepts either a plain object or a JSON string — prefer objects to avoid quote escaping.
|
|
33
|
-
5. Use \`wait_until\` to poll any tool until a predicate over its result holds (or timeout). Replaces "screenshot in a loop + sleep" for state-level waits ("wait for network to idle", "wait for state key X to become Y"). Predicate supports compound forms: { all: [...] } (AND), { any: [...] } (OR), { not: predicate }.
|
|
34
|
-
6. For UI-level waits ("wait for a screen to appear", "wait for a spinner to disappear") use \`fiber_tree${protocol_1.MODULE_SEPARATOR}query\` with \`waitFor: { until: "appear" | "disappear", timeout?, interval?, stable? }\` — it polls the same query with cache bypassed until the target state holds. \`stable: <ms>\` requires continuous presence/absence for that many ms to ignore transient matches during screen transitions.
|
|
35
|
-
7. Use \`assert\` for a single-shot checkpoint after actions — same predicate vocabulary as wait_until, returns { pass, actual, expected?, result? }. Natural pair: do action → wait_until / fiber_tree waitFor → assert.
|
|
36
|
-
8. Use \`host${protocol_1.MODULE_SEPARATOR}tap_fiber\` to collapse "fiber_tree__query → host__tap at bounds" into one call. Pass fiber_tree steps; if exactly one fiber matches, its center is tapped. Ambiguous match returns the candidate list so you can add \`index\` or narrow the chain.
|
|
37
|
-
|
|
38
|
-
Some tools run inline on the MCP server host (e.g. \`host${protocol_1.MODULE_SEPARATOR}screenshot\`, \`host${protocol_1.MODULE_SEPARATOR}list_devices\`, \`host${protocol_1.MODULE_SEPARATOR}launch_app\`, \`host${protocol_1.MODULE_SEPARATOR}terminate_app\`, \`host${protocol_1.MODULE_SEPARATOR}restart_app\`, \`metro${protocol_1.MODULE_SEPARATOR}reload\`, \`metro${protocol_1.MODULE_SEPARATOR}symbolicate\`) and work even when no React Native client is connected. They use xcrun simctl / adb on the dev machine. When \`clientId\` is provided, host tools use that client's platform/label/deviceId as hints to resolve the target device; otherwise they prefer the device of the single connected client, falling back to the single booted sim / online device. \`launch_app\`, \`terminate_app\`, and \`restart_app\` accept an \`appId\` arg (iOS bundle ID / Android package name); omit it to reuse the target client's registered \`bundleId\` from its connection metadata.
|
|
39
|
-
|
|
40
|
-
## Driving the UI — pick the right tool
|
|
41
|
-
1. **\`host${protocol_1.MODULE_SEPARATOR}tap_fiber\` with \`steps: [...]\`** — the canonical way to simulate a user tap. One call locates the fiber via fiber_tree__query and taps its center through the real OS gesture pipeline, so Pressable feedback, gesture responders, and hit-test logic all run. Ambiguous matches return a candidate list so you can add \`index\` or narrow \`steps\`. This is what you want whenever the user asks to simulate a tap / press / button click.
|
|
42
|
-
2. **\`fiber_tree${protocol_1.MODULE_SEPARATOR}query\` with \`select: ["mcpId","name","bounds"]\` + \`host${protocol_1.MODULE_SEPARATOR}tap\`** when you want to inspect a match set before committing — e.g. verify bounds, or skim candidates before picking one. \`props\` is opt-in on \`select\` to keep responses small.
|
|
43
|
-
3. **\`fiber_tree${protocol_1.MODULE_SEPARATOR}call\`** for non-gesture callbacks or imperative ref methods. Pass \`prop\` to call a callback prop (\`{ prop: 'onPress' }\`) or \`method\` to call a native-ref method (\`{ method: 'focus' }\`). Good when the component is off-screen / virtualised, when a scroll-handler parent swallows taps, or when you're driving focus / blur / measure / scrollTo via the native ref. For simulating a user tap, prefer tap_fiber (above).
|
|
44
|
-
4. **\`host${protocol_1.MODULE_SEPARATOR}screenshot\` + manual coordinate estimation + \`host${protocol_1.MODULE_SEPARATOR}tap\`** ONLY for non-React surfaces: system permission dialogs, native alerts, the on-screen keyboard, WebView content, native splash. These have no fiber and no bounds. Pair with \`region: { x, y, width, height }\` to screenshot just the area you're inspecting — vision-token cheap.
|
|
45
|
-
|
|
46
|
-
Gesture tools: \`host${protocol_1.MODULE_SEPARATOR}tap\` / \`host${protocol_1.MODULE_SEPARATOR}long_press\` / \`host${protocol_1.MODULE_SEPARATOR}swipe\` / \`host${protocol_1.MODULE_SEPARATOR}drag\` / \`host${protocol_1.MODULE_SEPARATOR}type_text\` / \`host${protocol_1.MODULE_SEPARATOR}type_text_batch\` / \`host${protocol_1.MODULE_SEPARATOR}press_key\` work on both platforms with no external daemons: Android via \`adb shell input\`, iOS via a bundled \`ios-hid\` binary that injects HID events directly into iOS Simulator through SimulatorKit.
|
|
47
|
-
|
|
48
|
-
Stack traces: \`errors${protocol_1.MODULE_SEPARATOR}get_errors\` and \`log_box${protocol_1.MODULE_SEPARATOR}get_logs\` return parsed \`stackFrames\` you can pass straight into \`metro${protocol_1.MODULE_SEPARATOR}symbolicate\` to resolve bundled frames back to source paths via Metro.
|
|
49
|
-
|
|
50
|
-
## Consolidated tools — verb + arg, not verb-per-action
|
|
51
|
-
|
|
52
|
-
Every module exposes one verb per concept rather than one tool per variant. Reach for the listing tool with a filter arg, not a tool named after the filter:
|
|
53
|
-
|
|
54
|
-
- **console** — \`get_logs({ level? })\` for all levels (no per-level shortcuts).
|
|
55
|
-
- **errors** — \`get_errors({ fatal? })\` covers fatal-only too.
|
|
56
|
-
- **network** — \`get_requests({ status?, method?, url? })\` covers errors / pending / URL filter.
|
|
57
|
-
- **log_box** — \`clear({ level? })\` (warn / error / syntax / all); \`set_installed({ enabled })\` toggles install/uninstall.
|
|
58
|
-
- **navigation** — \`navigate({ mode: 'reuse' | 'push' | 'replace' })\` covers navigate/push/replace; \`pop({ to: <number> | <screenName> | 'top' })\` covers pop / pop_to / pop_to_top; \`get_current_route({ withState? })\` covers the route-state read too.
|
|
59
|
-
- **query** (reactQuery) — \`mutate({ action: 'invalidate' | 'refetch' | 'remove' | 'reset', key? })\` covers all four cache mutations.
|
|
60
|
-
- **device** — \`info({ select?: [...] })\` aggregates platform / dimensions / pixelRatio / appearance / appState / accessibility / keyboard / initialUrl / dev / identity / app / battery / memoryStorage. \`open_url({ dryRun? })\` covers the canOpenURL check too.
|
|
61
|
-
- **fiber_tree** — \`query\` (all inspection: mcpId / name / testID / props / hooks / bounds / refMethods / children) and \`call({ prop? | method? })\` (callback prop OR native-ref method) — two tools cover everything fibers offer.
|
|
62
|
-
|
|
63
|
-
## Path-based drill into heavy responses
|
|
64
|
-
|
|
65
|
-
All listing tools that return heavy JSON (console / network / errors / storage / reactQuery / log_box / navigation / metro events / fiber_tree) accept the standard \`path\` / \`depth\` / \`maxBytes\` projection args. Heavy nested values collapse to compact \`\${...}\`-keyed markers; primitives stay raw. Drill into a specific subtree via the same tool with \`path\` — no separate by-id fetcher.
|
|
66
|
-
|
|
67
|
-
\`network__get_requests({ path: '[-1:][0].response.body' })\` — last request's body
|
|
68
|
-
\`network__get_requests({ path: '[-1:][0].response.body', depth: 8 })\` — fully expanded
|
|
69
|
-
\`console__get_logs({ path: '[-3:][0].args[1]' })\` — second arg of the third-from-last log
|
|
70
|
-
\`storage__get_item({ key: 'session', path: 'value.user.email' })\` — drill into a stored value
|
|
71
|
-
\`query__get_data({ key: '["users"]', path: 'data.email' })\` — drill into cached data
|
|
72
|
-
|
|
73
|
-
Path syntax (JS-style):
|
|
74
|
-
\`.key\` or \`["key.with.dots"]\` — object key access
|
|
75
|
-
\`[N]\` — index (numeric for arrays, Nth char for strings, Nth key in insertion order for objects)
|
|
76
|
-
\`[start:end]\` / \`[start:]\` / \`[:end]\` — slice (Python/jq-style; negative indices count from end). Works on arrays, strings, and objects (key slice). After array slice, chained \`.key\` maps over each element; \`[N]\` picks one.
|
|
77
|
-
|
|
78
|
-
Path drilling to a string scalar applies \`previewCap\` as usual — long leaves still wrap in a \`\${str}\` marker. To get a raw substring, end the path with a slice: \`stack[0:500]\` returns the first 500 chars verbatim (slice = explicit truncation request, previewCap bypassed). Default \`previewCap\` is 250; override per call via \`previewCap: <N>\`.
|
|
79
|
-
|
|
80
|
-
\`depth\` (default per tool, max 8) controls how many container levels are walked before collapsing to a marker. Bump \`depth\` for an exploratory survey, use \`path\` for a targeted drill. \`maxBytes\` (default 50KB) caps the total response size — overflow is replaced with a single \`\${str}\` marker carrying the original byte count + a preview.
|
|
81
|
-
|
|
82
|
-
For \`fiber_tree__query\`, heavy fields (\`props\`, \`hooks\`) take projection knobs per-field via \`select\` so the rest of the response stays raw:
|
|
83
|
-
\`select: [{ props: { path: "style", depth: 2 } }]\` — drill into props.style 2 levels deep
|
|
84
|
-
\`select: [{ props: { path: "data[0:5]" } }]\` — slice path: take first 5 items of props.data
|
|
85
|
-
\`select: [{ hooks: { kinds: ["State"], names: ["isLoading"], withValues: true, depth: 2 } }]\` — filter + project hook values
|
|
86
|
-
\`select: [{ children: 5 }]\` — light-only recursive tree walker (5 levels deep); use \`scope: "root"\` as the first step to dump the whole tree. props/hooks not supported inside; sub-children past treeDepth surface as \`\${arr}: N\`.
|
|
87
|
-
|
|
88
|
-
Hook filters: \`kinds\` (State/Effect/Memo/Ref/Custom/...), \`names\` (exact or \`/regex/flags\`), \`withValues\` (adds resolved values), \`expansionDepth\` (caps custom-hook recursion), \`format: "tree"\` (nested children vs flat \`via\` chains). Each hook entry carries \`{ kind, name, hook?, via?, expanded? }\`. Sensitive names (password, token, jwt, secret, credential, apiKey, authorization, Pin suffix) are auto-redacted; configure via \`fiberTreeModule({ redactHookNames, additionalRedactHookNames })\`.
|
|
89
|
-
|
|
90
|
-
Marker format: \`{ "\${obj}": N }\` for collapsed objects (N keys), \`{ "\${arr}": N }\` for arrays (N items), \`{ "\${fun}": "name" }\` for functions, \`{ "\${str}": { len, preview } }\` for long strings (>\`previewCap\`, default 250), \`{ "\${Date}": "iso" }\` for Date, \`{ "\${Err}": { name, msg } }\` for Error, \`{ "\${RegExp}": "/.../i" }\` for RegExp, \`{ "\${map}": N }\` / \`{ "\${set}": N }\` for Map/Set sizes, \`{ "\${cyc}": true }\` for cycles, \`{ "\${ref}": { mcpId, name } }\` for fiber/native refs, \`{ "\${cls}": { name, len } }\` for class instances, \`{ "\${truncated}": { total, slice } }\` first key/item when a container is wider than the cap (30 keys for objects, 50 items for arrays).
|
|
91
|
-
`;
|
|
92
|
-
const jsonError = (msg) => {
|
|
93
|
-
return {
|
|
94
|
-
content: [{ text: JSON.stringify({ error: msg }), type: 'text' }],
|
|
95
|
-
};
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* Drill into a value by dot-path. Arrays accept numeric indices and also
|
|
99
|
-
* respond to `.length` (handy for "wait until list is empty"). Returns
|
|
100
|
-
* undefined when any intermediate segment is missing.
|
|
101
|
-
*/
|
|
102
|
-
const resolvePath = (value, path) => {
|
|
103
|
-
if (!path)
|
|
104
|
-
return value;
|
|
105
|
-
let current = value;
|
|
106
|
-
for (const key of path.split('.')) {
|
|
107
|
-
if (current == null)
|
|
108
|
-
return undefined;
|
|
109
|
-
if (Array.isArray(current)) {
|
|
110
|
-
if (key === 'length') {
|
|
111
|
-
current = current.length;
|
|
112
|
-
continue;
|
|
113
|
-
}
|
|
114
|
-
const idx = Number.parseInt(key, 10);
|
|
115
|
-
current = Number.isNaN(idx) ? undefined : current[idx];
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (typeof current === 'object') {
|
|
119
|
-
current = current[key];
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
return undefined;
|
|
123
|
-
}
|
|
124
|
-
return current;
|
|
125
|
-
};
|
|
126
|
-
const evalLeaf = (actual, op, expected) => {
|
|
127
|
-
switch (op) {
|
|
128
|
-
case 'exists':
|
|
129
|
-
return actual !== undefined && actual !== null;
|
|
130
|
-
case 'notExists':
|
|
131
|
-
return actual === undefined || actual === null;
|
|
132
|
-
case 'equals':
|
|
133
|
-
return Object.is(actual, expected);
|
|
134
|
-
case 'notEquals':
|
|
135
|
-
return !Object.is(actual, expected);
|
|
136
|
-
case 'contains': {
|
|
137
|
-
if (typeof actual === 'string' && typeof expected === 'string') {
|
|
138
|
-
return actual.includes(expected);
|
|
139
|
-
}
|
|
140
|
-
if (Array.isArray(actual))
|
|
141
|
-
return actual.includes(expected);
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
case 'notContains': {
|
|
145
|
-
if (typeof actual === 'string' && typeof expected === 'string') {
|
|
146
|
-
return !actual.includes(expected);
|
|
147
|
-
}
|
|
148
|
-
if (Array.isArray(actual))
|
|
149
|
-
return !actual.includes(expected);
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
case 'gt':
|
|
153
|
-
return typeof actual === 'number' && typeof expected === 'number' && actual > expected;
|
|
154
|
-
case 'gte':
|
|
155
|
-
return typeof actual === 'number' && typeof expected === 'number' && actual >= expected;
|
|
156
|
-
case 'lt':
|
|
157
|
-
return typeof actual === 'number' && typeof expected === 'number' && actual < expected;
|
|
158
|
-
case 'lte':
|
|
159
|
-
return typeof actual === 'number' && typeof expected === 'number' && actual <= expected;
|
|
160
|
-
default:
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
};
|
|
164
|
-
/**
|
|
165
|
-
* Evaluate a predicate (leaf or compound) against a result object. Compound
|
|
166
|
-
* forms short-circuit: all stops on first false, any stops on first true.
|
|
167
|
-
*/
|
|
168
|
-
const evalPredicate = (result, predicate) => {
|
|
169
|
-
if ('all' in predicate && Array.isArray(predicate.all)) {
|
|
170
|
-
for (const sub of predicate.all) {
|
|
171
|
-
if (!evalPredicate(result, sub))
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
return true;
|
|
175
|
-
}
|
|
176
|
-
if ('any' in predicate && Array.isArray(predicate.any)) {
|
|
177
|
-
for (const sub of predicate.any) {
|
|
178
|
-
if (evalPredicate(result, sub))
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
return false;
|
|
182
|
-
}
|
|
183
|
-
if ('not' in predicate && predicate.not && typeof predicate.not === 'object') {
|
|
184
|
-
return !evalPredicate(result, predicate.not);
|
|
185
|
-
}
|
|
186
|
-
const leaf = predicate;
|
|
187
|
-
if (typeof leaf.op !== 'string')
|
|
188
|
-
return false;
|
|
189
|
-
return evalLeaf(resolvePath(result, leaf.path), leaf.op, leaf.value);
|
|
190
|
-
};
|
|
191
|
-
/**
|
|
192
|
-
* Parse a `call`-style args argument that may arrive as a JSON string (older
|
|
193
|
-
* clients) or a plain object (new form). Returns { ok, args } or { ok: false,
|
|
194
|
-
* error } on malformed JSON.
|
|
195
|
-
*/
|
|
196
|
-
const parseCallArgs = (raw) => {
|
|
197
|
-
if (raw === undefined || raw === null)
|
|
198
|
-
return { args: {}, ok: true };
|
|
199
|
-
if (typeof raw === 'string') {
|
|
200
|
-
if (raw.length === 0)
|
|
201
|
-
return { args: {}, ok: true };
|
|
202
|
-
try {
|
|
203
|
-
const parsed = JSON.parse(raw);
|
|
204
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
205
|
-
return { args: parsed, ok: true };
|
|
206
|
-
}
|
|
207
|
-
return { error: 'Parsed args must be an object.', ok: false };
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
return { error: 'Invalid JSON in args.', ok: false };
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
if (typeof raw === 'object' && !Array.isArray(raw)) {
|
|
214
|
-
return { args: raw, ok: true };
|
|
215
|
-
}
|
|
216
|
-
return { error: 'args must be an object or a JSON string.', ok: false };
|
|
217
|
-
};
|
|
218
|
-
/**
|
|
219
|
-
* Recursively serializes a value to JSON with sorted object keys, producing a
|
|
220
|
-
* stable canonical form that's safe to use as a dedup Map key. Arrays keep
|
|
221
|
-
* their original order — caller is responsible for normalizing them when
|
|
222
|
-
* order-independence is desired.
|
|
223
|
-
*/
|
|
224
|
-
const canonicalize = (value) => {
|
|
225
|
-
return JSON.stringify(value, (_key, v) => {
|
|
226
|
-
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
|
227
|
-
const sorted = {};
|
|
228
|
-
for (const k of Object.keys(v).sort()) {
|
|
229
|
-
sorted[k] = v[k];
|
|
230
|
-
}
|
|
231
|
-
return sorted;
|
|
232
|
-
}
|
|
233
|
-
return v;
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
/**
|
|
237
|
-
* Produces a canonical key for a ToolGroup that's independent of tool
|
|
238
|
-
* registration order. Two modules with the same name + tools (regardless of
|
|
239
|
-
* order) + descriptions + schemas produce the same key.
|
|
240
|
-
*/
|
|
241
|
-
const canonicalizeGroup = (group) => {
|
|
242
|
-
const normalized = {
|
|
243
|
-
description: group.description,
|
|
244
|
-
module: group.module,
|
|
245
|
-
tools: [...group.tools].sort((a, b) => {
|
|
246
|
-
return a.name.localeCompare(b.name);
|
|
247
|
-
}),
|
|
248
|
-
};
|
|
249
|
-
return canonicalize(normalized);
|
|
250
|
-
};
|
|
251
|
-
/**
|
|
252
|
-
* Looks up a full tool descriptor on a client by its full name
|
|
253
|
-
* (`module__method`). Checks both static modules and dynamic tools registered
|
|
254
|
-
* via useMcpTool. Returns null if the tool is not on this client.
|
|
255
|
-
*/
|
|
256
|
-
const findToolInClient = (client, toolFullName) => {
|
|
257
|
-
for (const mod of client.modules) {
|
|
258
|
-
const prefix = `${mod.name}${protocol_1.MODULE_SEPARATOR}`;
|
|
259
|
-
if (toolFullName.startsWith(prefix)) {
|
|
260
|
-
const methodName = toolFullName.slice(prefix.length);
|
|
261
|
-
const toolDef = mod.tools.find((t) => {
|
|
262
|
-
return t.name === methodName;
|
|
263
|
-
});
|
|
264
|
-
if (toolDef) {
|
|
265
|
-
return {
|
|
266
|
-
description: toolDef.description,
|
|
267
|
-
inputSchema: toolDef.inputSchema,
|
|
268
|
-
name: toolFullName,
|
|
269
|
-
};
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
const dynamicEntry = client.dynamicTools.get(toolFullName);
|
|
274
|
-
if (dynamicEntry) {
|
|
275
|
-
return {
|
|
276
|
-
description: dynamicEntry.description,
|
|
277
|
-
inputSchema: dynamicEntry.inputSchema,
|
|
278
|
-
name: toolFullName,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
return null;
|
|
282
|
-
};
|
|
283
31
|
class McpServerWrapper {
|
|
284
|
-
bridge;
|
|
285
|
-
hostModules;
|
|
286
|
-
hostToolMap = new Map();
|
|
287
32
|
mcp;
|
|
288
33
|
constructor(bridge, hostModules = []) {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
this.
|
|
34
|
+
const hostToolMap = (0, dispatch_1.buildHostToolMap)(hostModules);
|
|
35
|
+
const dispatchTool = (0, dispatch_1.createDispatcher)(bridge, hostToolMap);
|
|
36
|
+
this.mcp = new mcp_js_1.McpServer({ name: protocol_1.PACKAGE_NAME, version: PACKAGE_VERSION }, { instructions: instructions_1.BASE_INSTRUCTIONS });
|
|
37
|
+
const ctx = {
|
|
38
|
+
bridge,
|
|
39
|
+
dispatchTool,
|
|
40
|
+
formatResult: helpers_1.formatResult,
|
|
41
|
+
hostModules,
|
|
42
|
+
hostToolMap,
|
|
43
|
+
listToolGroups: helpers_1.buildToolGroups,
|
|
44
|
+
};
|
|
45
|
+
(0, call_1.registerCallTool)(this.mcp, ctx);
|
|
46
|
+
(0, waitUntil_1.registerWaitUntilTool)(this.mcp, ctx);
|
|
47
|
+
(0, assert_1.registerAssertTool)(this.mcp, ctx);
|
|
48
|
+
(0, listTools_1.registerListToolsTool)(this.mcp, ctx);
|
|
49
|
+
(0, describeTool_1.registerDescribeToolTool)(this.mcp, ctx);
|
|
50
|
+
(0, connectionStatus_1.registerConnectionStatusTool)(this.mcp, ctx);
|
|
304
51
|
}
|
|
305
52
|
async start() {
|
|
306
53
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
307
54
|
await this.mcp.connect(transport);
|
|
308
55
|
}
|
|
309
|
-
registerTools() {
|
|
310
|
-
this.mcp.registerTool('call', {
|
|
311
|
-
annotations: {
|
|
312
|
-
openWorldHint: true,
|
|
313
|
-
title: 'Call Tool',
|
|
314
|
-
},
|
|
315
|
-
description: 'Call a tool registered by a React Native app client. Use list_tools first to see available tools. When multiple clients are connected, specify clientId; otherwise it is auto-picked. `args` accepts either a plain object or a JSON string — objects are preferred to avoid escaping quotes.',
|
|
316
|
-
inputSchema: {
|
|
317
|
-
args: zod_1.z
|
|
318
|
-
.union([zod_1.z.string(), zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())])
|
|
319
|
-
.optional()
|
|
320
|
-
.describe('Tool arguments as a plain object (e.g. { screen: "AUTH_LOGIN_SCREEN" }) or a JSON string.'),
|
|
321
|
-
clientId: zod_1.z
|
|
322
|
-
.string()
|
|
323
|
-
.optional()
|
|
324
|
-
.describe('Target client ID (e.g. "ios-1", "android-1"). Optional when exactly one client is connected.'),
|
|
325
|
-
tool: zod_1.z
|
|
326
|
-
.string()
|
|
327
|
-
.describe(`Tool name in format "module${protocol_1.MODULE_SEPARATOR}method" (e.g. "navigation${protocol_1.MODULE_SEPARATOR}navigate")`),
|
|
328
|
-
},
|
|
329
|
-
}, async ({ args, clientId, tool }) => {
|
|
330
|
-
const parsed = parseCallArgs(args);
|
|
331
|
-
if (!parsed.ok)
|
|
332
|
-
return jsonError(parsed.error);
|
|
333
|
-
const dispatch = await this.dispatchTool(tool, parsed.args, clientId);
|
|
334
|
-
if (!dispatch.ok)
|
|
335
|
-
return jsonError(dispatch.error);
|
|
336
|
-
return { content: this.formatResult(dispatch.result) };
|
|
337
|
-
});
|
|
338
|
-
this.mcp.registerTool('wait_until', {
|
|
339
|
-
annotations: {
|
|
340
|
-
openWorldHint: true,
|
|
341
|
-
title: 'Wait Until',
|
|
342
|
-
},
|
|
343
|
-
description: `Poll a tool until its result satisfies a predicate, or timeout.
|
|
344
|
-
|
|
345
|
-
Replaces "screenshot in a loop + sleep" with a declarative check. Typical use:
|
|
346
|
-
• wait for navigation to land on a screen
|
|
347
|
-
• wait for a spinner / toast to disappear
|
|
348
|
-
• wait for a fiber_tree.query to return matches (or stop returning them)
|
|
349
|
-
• wait for network.get_requests({ status: "pending" }).length to hit 0
|
|
350
|
-
|
|
351
|
-
PREDICATE
|
|
352
|
-
Leaf form: { op, path?, value? }
|
|
353
|
-
op: equals | notEquals | contains | notContains | exists | notExists | gt | gte | lt | lte
|
|
354
|
-
path drills through objects + array indices; arrays also expose .length.
|
|
355
|
-
Compound forms compose and nest:
|
|
356
|
-
{ all: [predicate, ...] } — AND
|
|
357
|
-
{ any: [predicate, ...] } — OR
|
|
358
|
-
{ not: predicate } — negation
|
|
359
|
-
Example: { all: [{op:"equals", path:"name", value:"CART"}, {op:"gt", path:"items.length", value:0}] }
|
|
360
|
-
|
|
361
|
-
RETURNS
|
|
362
|
-
{ ok: true, attempts, elapsedMs, matched? } on success — matched is the path-
|
|
363
|
-
resolved value for leaf predicates, omitted for compound.
|
|
364
|
-
{ ok: false, reason, attempts, elapsedMs, lastResult, lastError? } on timeout.`,
|
|
365
|
-
inputSchema: {
|
|
366
|
-
args: zod_1.z
|
|
367
|
-
.union([zod_1.z.string(), zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())])
|
|
368
|
-
.optional()
|
|
369
|
-
.describe('Arguments for the polled tool — object or JSON string.'),
|
|
370
|
-
clientId: zod_1.z.string().optional().describe('Target client ID, same semantics as `call`.'),
|
|
371
|
-
intervalMs: zod_1.z
|
|
372
|
-
.number()
|
|
373
|
-
.optional()
|
|
374
|
-
.describe('Delay between poll attempts. Default 300, min 50, max 5000.'),
|
|
375
|
-
predicate: zod_1.z
|
|
376
|
-
.looseObject({})
|
|
377
|
-
.describe('Leaf { op, path?, value? } or compound { all|any: [...] } / { not: predicate }. See tool description for ops and composition.'),
|
|
378
|
-
timeoutMs: zod_1.z
|
|
379
|
-
.number()
|
|
380
|
-
.optional()
|
|
381
|
-
.describe('Total wait budget. Default 10000, min 500, max 60000.'),
|
|
382
|
-
tool: zod_1.z
|
|
383
|
-
.string()
|
|
384
|
-
.describe(`Tool name to poll (e.g. "navigation${protocol_1.MODULE_SEPARATOR}get_current_route").`),
|
|
385
|
-
},
|
|
386
|
-
}, async ({ args, clientId, intervalMs, predicate, timeoutMs, tool }) => {
|
|
387
|
-
const parsedArgs = parseCallArgs(args);
|
|
388
|
-
if (!parsedArgs.ok)
|
|
389
|
-
return jsonError(parsedArgs.error);
|
|
390
|
-
const pred = predicate;
|
|
391
|
-
const isLeaf = typeof pred.op === 'string';
|
|
392
|
-
const leafPath = isLeaf ? pred.path : undefined;
|
|
393
|
-
const timeout = Math.max(500, Math.min(60_000, timeoutMs ?? 10_000));
|
|
394
|
-
const interval = Math.max(50, Math.min(5_000, intervalMs ?? 300));
|
|
395
|
-
const started = Date.now();
|
|
396
|
-
let attempts = 0;
|
|
397
|
-
let lastResult;
|
|
398
|
-
let lastError;
|
|
399
|
-
while (Date.now() - started < timeout) {
|
|
400
|
-
attempts += 1;
|
|
401
|
-
const dispatch = await this.dispatchTool(tool, parsedArgs.args, clientId);
|
|
402
|
-
if (dispatch.ok) {
|
|
403
|
-
lastResult = dispatch.result;
|
|
404
|
-
if (evalPredicate(lastResult, pred)) {
|
|
405
|
-
const payload = {
|
|
406
|
-
attempts,
|
|
407
|
-
elapsedMs: Date.now() - started,
|
|
408
|
-
ok: true,
|
|
409
|
-
};
|
|
410
|
-
if (isLeaf) {
|
|
411
|
-
payload.matched = resolvePath(lastResult, leafPath);
|
|
412
|
-
}
|
|
413
|
-
return {
|
|
414
|
-
content: [{ text: JSON.stringify(payload, null, 2), type: 'text' }],
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
lastError = dispatch.error;
|
|
420
|
-
}
|
|
421
|
-
const remaining = timeout - (Date.now() - started);
|
|
422
|
-
if (remaining <= 0)
|
|
423
|
-
break;
|
|
424
|
-
await new Promise((r) => {
|
|
425
|
-
return setTimeout(r, Math.min(interval, remaining));
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
return {
|
|
429
|
-
content: [
|
|
430
|
-
{
|
|
431
|
-
text: JSON.stringify({
|
|
432
|
-
attempts,
|
|
433
|
-
elapsedMs: Date.now() - started,
|
|
434
|
-
lastError,
|
|
435
|
-
lastResult,
|
|
436
|
-
ok: false,
|
|
437
|
-
reason: lastError
|
|
438
|
-
? `Last dispatch failed: ${lastError}`
|
|
439
|
-
: `Predicate did not hold within ${timeout}ms`,
|
|
440
|
-
}, null, 2),
|
|
441
|
-
type: 'text',
|
|
442
|
-
},
|
|
443
|
-
],
|
|
444
|
-
};
|
|
445
|
-
});
|
|
446
|
-
this.mcp.registerTool('assert', {
|
|
447
|
-
annotations: {
|
|
448
|
-
openWorldHint: true,
|
|
449
|
-
title: 'Assert',
|
|
450
|
-
},
|
|
451
|
-
description: `Single-shot assertion over a tool's result. Same predicate vocabulary (including { all / any / not }) as wait_until, but one attempt and a standardized diff on failure.
|
|
452
|
-
|
|
453
|
-
Returns { pass: true, actual? } on success — actual is the path-resolved value for leaf predicates, omitted for compound.
|
|
454
|
-
Returns { pass: false, actual, expected?, op?, path?, message?, result } on predicate failure.
|
|
455
|
-
Returns { pass: false, error, message? } when the tool dispatch itself threw.
|
|
456
|
-
|
|
457
|
-
Useful after wait_until as a checkpoint — the pair reads "do action → wait → assert" which produces a clean audit trail in session logs.`,
|
|
458
|
-
inputSchema: {
|
|
459
|
-
args: zod_1.z
|
|
460
|
-
.union([zod_1.z.string(), zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())])
|
|
461
|
-
.optional()
|
|
462
|
-
.describe('Arguments for the asserted tool — object or JSON string.'),
|
|
463
|
-
clientId: zod_1.z.string().optional().describe('Target client ID, same semantics as `call`.'),
|
|
464
|
-
message: zod_1.z
|
|
465
|
-
.string()
|
|
466
|
-
.optional()
|
|
467
|
-
.describe('Optional human-readable description of the check; echoed in the failure payload.'),
|
|
468
|
-
predicate: zod_1.z
|
|
469
|
-
.looseObject({})
|
|
470
|
-
.describe('Leaf { op, path?, value? } or compound { all|any: [...] } / { not: predicate }. See wait_until for full semantics.'),
|
|
471
|
-
tool: zod_1.z
|
|
472
|
-
.string()
|
|
473
|
-
.describe(`Tool name to call once (e.g. "fiber_tree${protocol_1.MODULE_SEPARATOR}query").`),
|
|
474
|
-
},
|
|
475
|
-
}, async ({ args, clientId, message, predicate, tool }) => {
|
|
476
|
-
const parsedArgs = parseCallArgs(args);
|
|
477
|
-
if (!parsedArgs.ok)
|
|
478
|
-
return jsonError(parsedArgs.error);
|
|
479
|
-
const dispatch = await this.dispatchTool(tool, parsedArgs.args, clientId);
|
|
480
|
-
if (!dispatch.ok) {
|
|
481
|
-
return {
|
|
482
|
-
content: [
|
|
483
|
-
{
|
|
484
|
-
text: JSON.stringify({ error: dispatch.error, message, pass: false }, null, 2),
|
|
485
|
-
type: 'text',
|
|
486
|
-
},
|
|
487
|
-
],
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
const pred = predicate;
|
|
491
|
-
const isLeaf = typeof pred.op === 'string';
|
|
492
|
-
const leafPath = isLeaf ? pred.path : undefined;
|
|
493
|
-
const leafValue = isLeaf ? pred.value : undefined;
|
|
494
|
-
const leafOp = isLeaf ? pred.op : undefined;
|
|
495
|
-
const pass = evalPredicate(dispatch.result, pred);
|
|
496
|
-
const payload = { pass };
|
|
497
|
-
if (isLeaf)
|
|
498
|
-
payload.actual = resolvePath(dispatch.result, leafPath);
|
|
499
|
-
if (!pass) {
|
|
500
|
-
if (isLeaf) {
|
|
501
|
-
payload.expected = leafValue;
|
|
502
|
-
payload.op = leafOp;
|
|
503
|
-
if (leafPath)
|
|
504
|
-
payload.path = leafPath;
|
|
505
|
-
}
|
|
506
|
-
if (message)
|
|
507
|
-
payload.message = message;
|
|
508
|
-
payload.result = dispatch.result;
|
|
509
|
-
}
|
|
510
|
-
return {
|
|
511
|
-
content: [{ text: JSON.stringify(payload, null, 2), type: 'text' }],
|
|
512
|
-
};
|
|
513
|
-
});
|
|
514
|
-
this.mcp.registerTool('list_tools', {
|
|
515
|
-
annotations: {
|
|
516
|
-
readOnlyHint: true,
|
|
517
|
-
title: 'List Tools',
|
|
518
|
-
},
|
|
519
|
-
description: 'Browse available tools with compact (schema-free) descriptions. Modules with identical shape across multiple clients are deduplicated into a single entry with a clientIds array. Use describe_tool to fetch the full input schema for a specific tool before calling it. Pass `module` to narrow to one module, `clientId` to narrow to one client, `compact: true` to drop long module-level descriptions.',
|
|
520
|
-
inputSchema: {
|
|
521
|
-
clientId: zod_1.z
|
|
522
|
-
.string()
|
|
523
|
-
.optional()
|
|
524
|
-
.describe('Narrow listing to a single client. Omit for all connected clients.'),
|
|
525
|
-
compact: zod_1.z
|
|
526
|
-
.boolean()
|
|
527
|
-
.optional()
|
|
528
|
-
.describe('Drop module-level descriptions (still keeps per-tool one-liners). Default false.'),
|
|
529
|
-
module: zod_1.z
|
|
530
|
-
.string()
|
|
531
|
-
.optional()
|
|
532
|
-
.describe('Narrow listing to a single module name (e.g. "fiber_tree", "host"). Omit for all.'),
|
|
533
|
-
},
|
|
534
|
-
}, async ({ clientId, compact, module }) => {
|
|
535
|
-
const allClients = this.bridge.listClients();
|
|
536
|
-
const clients = clientId
|
|
537
|
-
? allClients.filter((c) => {
|
|
538
|
-
return c.id === clientId;
|
|
539
|
-
})
|
|
540
|
-
: allClients;
|
|
541
|
-
// Dedup tool groups across clients by canonical shape
|
|
542
|
-
const dedupMap = new Map();
|
|
543
|
-
for (const client of clients) {
|
|
544
|
-
const groups = this.buildToolGroups(client);
|
|
545
|
-
for (const group of groups) {
|
|
546
|
-
if (module && group.module !== module)
|
|
547
|
-
continue;
|
|
548
|
-
const key = canonicalizeGroup(group);
|
|
549
|
-
const existing = dedupMap.get(key);
|
|
550
|
-
if (existing) {
|
|
551
|
-
existing.clientIds.push(client.id);
|
|
552
|
-
}
|
|
553
|
-
else {
|
|
554
|
-
dedupMap.set(key, { clientIds: [client.id], group });
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
const modulesPayload = [...dedupMap.values()].map(({ clientIds, group }) => {
|
|
559
|
-
return {
|
|
560
|
-
clientIds,
|
|
561
|
-
description: compact ? undefined : group.description,
|
|
562
|
-
name: group.module,
|
|
563
|
-
tools: group.tools.map((t) => {
|
|
564
|
-
return {
|
|
565
|
-
description: t.description,
|
|
566
|
-
name: t.name,
|
|
567
|
-
};
|
|
568
|
-
}),
|
|
569
|
-
};
|
|
570
|
-
});
|
|
571
|
-
const hostToolsPayload = this.hostModules
|
|
572
|
-
.filter((mod) => {
|
|
573
|
-
return !module || mod.name === module;
|
|
574
|
-
})
|
|
575
|
-
.map((mod) => {
|
|
576
|
-
return {
|
|
577
|
-
description: compact ? undefined : mod.description,
|
|
578
|
-
name: mod.name,
|
|
579
|
-
tools: Object.entries(mod.tools).map(([toolName, tool]) => {
|
|
580
|
-
return {
|
|
581
|
-
description: tool.description,
|
|
582
|
-
name: `${mod.name}${protocol_1.MODULE_SEPARATOR}${toolName}`,
|
|
583
|
-
};
|
|
584
|
-
}),
|
|
585
|
-
};
|
|
586
|
-
});
|
|
587
|
-
const clientsPayload = clients.map((client) => {
|
|
588
|
-
return {
|
|
589
|
-
appName: client.appName,
|
|
590
|
-
appVersion: client.appVersion,
|
|
591
|
-
bundleId: client.bundleId,
|
|
592
|
-
devServer: client.devServer,
|
|
593
|
-
deviceId: client.deviceId,
|
|
594
|
-
id: client.id,
|
|
595
|
-
isSimulator: client.isSimulator,
|
|
596
|
-
label: client.label,
|
|
597
|
-
platform: client.platform,
|
|
598
|
-
};
|
|
599
|
-
});
|
|
600
|
-
const payload = {
|
|
601
|
-
clientCount: clients.length,
|
|
602
|
-
clients: clientsPayload,
|
|
603
|
-
hostTools: hostToolsPayload,
|
|
604
|
-
modules: modulesPayload,
|
|
605
|
-
};
|
|
606
|
-
if (clients.length === 0) {
|
|
607
|
-
payload.clientError = 'No React Native clients connected';
|
|
608
|
-
}
|
|
609
|
-
return {
|
|
610
|
-
content: [{ text: JSON.stringify(payload, null, 2), type: 'text' }],
|
|
611
|
-
};
|
|
612
|
-
});
|
|
613
|
-
this.mcp.registerTool('connection_status', {
|
|
614
|
-
annotations: {
|
|
615
|
-
readOnlyHint: true,
|
|
616
|
-
title: 'Connection Status',
|
|
617
|
-
},
|
|
618
|
-
description: 'List connected React Native clients with their IDs, platforms, labels, and registered module names.',
|
|
619
|
-
}, async () => {
|
|
620
|
-
const clients = this.bridge.listClients();
|
|
621
|
-
const payload = {
|
|
622
|
-
clientCount: clients.length,
|
|
623
|
-
clients: clients.map((c) => {
|
|
624
|
-
return {
|
|
625
|
-
appName: c.appName,
|
|
626
|
-
appVersion: c.appVersion,
|
|
627
|
-
bundleId: c.bundleId,
|
|
628
|
-
connectedAt: new Date(c.connectedAt).toISOString(),
|
|
629
|
-
devServer: c.devServer,
|
|
630
|
-
deviceId: c.deviceId,
|
|
631
|
-
id: c.id,
|
|
632
|
-
label: c.label,
|
|
633
|
-
modules: c.modules.map((m) => {
|
|
634
|
-
return m.name;
|
|
635
|
-
}),
|
|
636
|
-
platform: c.platform,
|
|
637
|
-
};
|
|
638
|
-
}),
|
|
639
|
-
hostModules: this.hostModules.map((m) => {
|
|
640
|
-
return m.name;
|
|
641
|
-
}),
|
|
642
|
-
};
|
|
643
|
-
return {
|
|
644
|
-
content: [{ text: JSON.stringify(payload, null, 2), type: 'text' }],
|
|
645
|
-
};
|
|
646
|
-
});
|
|
647
|
-
this.mcp.registerTool('describe_tool', {
|
|
648
|
-
annotations: {
|
|
649
|
-
readOnlyHint: true,
|
|
650
|
-
title: 'Describe Tool',
|
|
651
|
-
},
|
|
652
|
-
description: 'Fetch the full description and input schema for a single tool. Use this after list_tools to learn how to construct arguments for a tool before calling it. For host tools, clientId is ignored. For in-app tools, omit clientId to auto-pick the shared descriptor; specify it only when multiple clients have the same tool with different schemas.',
|
|
653
|
-
inputSchema: {
|
|
654
|
-
clientId: zod_1.z
|
|
655
|
-
.string()
|
|
656
|
-
.optional()
|
|
657
|
-
.describe('Target client ID for in-app tools. Required only when multiple clients have the same tool with different schemas. Ignored for host tools.'),
|
|
658
|
-
tool: zod_1.z
|
|
659
|
-
.string()
|
|
660
|
-
.describe(`Full tool name in the format "module${protocol_1.MODULE_SEPARATOR}method" (e.g. "navigation${protocol_1.MODULE_SEPARATOR}navigate", "host${protocol_1.MODULE_SEPARATOR}screenshot").`),
|
|
661
|
-
},
|
|
662
|
-
}, async ({ clientId, tool }) => {
|
|
663
|
-
// 1. Host tool path — resolved via hostToolMap, clientId is ignored
|
|
664
|
-
const hostEntry = this.hostToolMap.get(tool);
|
|
665
|
-
if (hostEntry) {
|
|
666
|
-
const mod = this.hostModules.find((m) => {
|
|
667
|
-
return m.name === hostEntry.moduleName;
|
|
668
|
-
});
|
|
669
|
-
const hostTool = mod?.tools[hostEntry.toolName];
|
|
670
|
-
if (!hostTool) {
|
|
671
|
-
return jsonError(`Host tool '${tool}' metadata inconsistent — entry in hostToolMap but missing from hostModules.`);
|
|
672
|
-
}
|
|
673
|
-
return {
|
|
674
|
-
content: [
|
|
675
|
-
{
|
|
676
|
-
text: JSON.stringify({
|
|
677
|
-
description: hostTool.description,
|
|
678
|
-
inputSchema: hostTool.inputSchema,
|
|
679
|
-
name: tool,
|
|
680
|
-
scope: 'host',
|
|
681
|
-
}, null, 2),
|
|
682
|
-
type: 'text',
|
|
683
|
-
},
|
|
684
|
-
],
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
// 2. Explicit clientId — look up the specific client
|
|
688
|
-
if (clientId) {
|
|
689
|
-
const client = this.bridge.getClient(clientId);
|
|
690
|
-
if (!client) {
|
|
691
|
-
const available = this.bridge
|
|
692
|
-
.listClients()
|
|
693
|
-
.map((c) => {
|
|
694
|
-
return c.id;
|
|
695
|
-
})
|
|
696
|
-
.join(', ') || '(none)';
|
|
697
|
-
return jsonError(`Client '${clientId}' not connected. Available: ${available}`);
|
|
698
|
-
}
|
|
699
|
-
const found = findToolInClient(client, tool);
|
|
700
|
-
if (!found) {
|
|
701
|
-
return jsonError(`Tool '${tool}' not found on client '${clientId}'.`);
|
|
702
|
-
}
|
|
703
|
-
return {
|
|
704
|
-
content: [
|
|
705
|
-
{
|
|
706
|
-
text: JSON.stringify({
|
|
707
|
-
clientIds: [clientId],
|
|
708
|
-
description: found.description,
|
|
709
|
-
inputSchema: found.inputSchema,
|
|
710
|
-
name: tool,
|
|
711
|
-
scope: 'client',
|
|
712
|
-
}, null, 2),
|
|
713
|
-
type: 'text',
|
|
714
|
-
},
|
|
715
|
-
],
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
// 3. Auto-pick across all connected clients
|
|
719
|
-
const clients = this.bridge.listClients();
|
|
720
|
-
const matches = [];
|
|
721
|
-
for (const c of clients) {
|
|
722
|
-
const found = findToolInClient(c, tool);
|
|
723
|
-
if (found) {
|
|
724
|
-
matches.push({ clientId: c.id, descriptor: found });
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (matches.length === 0) {
|
|
728
|
-
return jsonError(`Tool '${tool}' not found on any client. Use list_tools to see available tools.`);
|
|
729
|
-
}
|
|
730
|
-
// Group by canonical descriptor shape — same shape across clients is not ambiguous
|
|
731
|
-
const byShape = new Map();
|
|
732
|
-
for (const match of matches) {
|
|
733
|
-
const key = canonicalize(match.descriptor);
|
|
734
|
-
const existing = byShape.get(key);
|
|
735
|
-
if (existing) {
|
|
736
|
-
existing.clientIds.push(match.clientId);
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
byShape.set(key, { clientIds: [match.clientId], descriptor: match.descriptor });
|
|
740
|
-
}
|
|
741
|
-
}
|
|
742
|
-
if (byShape.size === 1) {
|
|
743
|
-
const [first] = byShape.values();
|
|
744
|
-
const { clientIds, descriptor } = first;
|
|
745
|
-
return {
|
|
746
|
-
content: [
|
|
747
|
-
{
|
|
748
|
-
text: JSON.stringify({
|
|
749
|
-
clientIds,
|
|
750
|
-
description: descriptor.description,
|
|
751
|
-
inputSchema: descriptor.inputSchema,
|
|
752
|
-
name: tool,
|
|
753
|
-
scope: 'client',
|
|
754
|
-
}, null, 2),
|
|
755
|
-
type: 'text',
|
|
756
|
-
},
|
|
757
|
-
],
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
const candidates = [...byShape.values()]
|
|
761
|
-
.map(({ clientIds }) => {
|
|
762
|
-
return clientIds.join('+');
|
|
763
|
-
})
|
|
764
|
-
.join('; ');
|
|
765
|
-
return jsonError(`Tool '${tool}' exists on multiple clients with different schemas: ${candidates}. Specify clientId.`);
|
|
766
|
-
});
|
|
767
|
-
}
|
|
768
|
-
/**
|
|
769
|
-
* Execute a single tool by full name, returning the raw handler result.
|
|
770
|
-
* Used by both the `call` tool and meta-tools like `wait_until` that need to
|
|
771
|
-
* invoke other tools without going through the full MCP content wrapping.
|
|
772
|
-
*/
|
|
773
|
-
async dispatchTool(tool, args, clientId) {
|
|
774
|
-
const hostEntry = this.hostToolMap.get(tool);
|
|
775
|
-
if (hostEntry) {
|
|
776
|
-
try {
|
|
777
|
-
const result = await hostEntry.handler(args, {
|
|
778
|
-
bridge: this.bridge,
|
|
779
|
-
dispatch: (nextTool, nextArgs, nextClientId) => {
|
|
780
|
-
return this.dispatchTool(nextTool, nextArgs, nextClientId ?? clientId);
|
|
781
|
-
},
|
|
782
|
-
requestedClientId: clientId,
|
|
783
|
-
});
|
|
784
|
-
return { ok: true, result };
|
|
785
|
-
}
|
|
786
|
-
catch (err) {
|
|
787
|
-
return { error: `Host tool "${tool}" threw: ${err.message}`, ok: false };
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
const resolution = this.bridge.resolveClient(clientId);
|
|
791
|
-
if (!resolution.ok)
|
|
792
|
-
return { error: resolution.error, ok: false };
|
|
793
|
-
const client = resolution.client;
|
|
794
|
-
let mod;
|
|
795
|
-
let moduleName = '';
|
|
796
|
-
let methodName = '';
|
|
797
|
-
for (const m of client.modules) {
|
|
798
|
-
const prefix = `${m.name}${protocol_1.MODULE_SEPARATOR}`;
|
|
799
|
-
if (tool.startsWith(prefix)) {
|
|
800
|
-
mod = m;
|
|
801
|
-
moduleName = m.name;
|
|
802
|
-
methodName = tool.slice(prefix.length);
|
|
803
|
-
break;
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
if (!mod) {
|
|
807
|
-
if (tool.startsWith(protocol_1.DYNAMIC_PREFIX)) {
|
|
808
|
-
moduleName = `${protocol_1.MODULE_SEPARATOR}dynamic`;
|
|
809
|
-
methodName = tool.slice(protocol_1.DYNAMIC_PREFIX.length);
|
|
810
|
-
}
|
|
811
|
-
else {
|
|
812
|
-
const idx = tool.indexOf(protocol_1.MODULE_SEPARATOR);
|
|
813
|
-
if (idx <= 0) {
|
|
814
|
-
return {
|
|
815
|
-
error: `Invalid tool name "${tool}". Use "module${protocol_1.MODULE_SEPARATOR}method" format.`,
|
|
816
|
-
ok: false,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
moduleName = tool.slice(0, idx);
|
|
820
|
-
methodName = tool.slice(idx + protocol_1.MODULE_SEPARATOR.length);
|
|
821
|
-
}
|
|
822
|
-
try {
|
|
823
|
-
const result = await this.bridge.call(client.id, moduleName, methodName, args);
|
|
824
|
-
return { ok: true, result };
|
|
825
|
-
}
|
|
826
|
-
catch {
|
|
827
|
-
const allModules = client.modules
|
|
828
|
-
.map((m) => {
|
|
829
|
-
return m.name;
|
|
830
|
-
})
|
|
831
|
-
.join(', ');
|
|
832
|
-
const dynNames = [...client.dynamicTools.keys()].join(', ');
|
|
833
|
-
return {
|
|
834
|
-
error: `Tool "${tool}" not found on client '${client.id}'. Modules: ${allModules || '(none)'}. Dynamic: ${dynNames || '(none)'}`,
|
|
835
|
-
ok: false,
|
|
836
|
-
};
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
const toolDef = mod.tools.find((t) => {
|
|
840
|
-
return t.name === methodName;
|
|
841
|
-
});
|
|
842
|
-
if (!toolDef) {
|
|
843
|
-
return {
|
|
844
|
-
error: `Tool "${methodName}" not found in module "${moduleName}" on client '${client.id}'. Available: ${mod.tools
|
|
845
|
-
.map((t) => {
|
|
846
|
-
return t.name;
|
|
847
|
-
})
|
|
848
|
-
.join(', ')}`,
|
|
849
|
-
ok: false,
|
|
850
|
-
};
|
|
851
|
-
}
|
|
852
|
-
try {
|
|
853
|
-
const result = await this.bridge.call(client.id, moduleName, methodName, args, toolDef.timeout);
|
|
854
|
-
return { ok: true, result };
|
|
855
|
-
}
|
|
856
|
-
catch (err) {
|
|
857
|
-
return { error: err.message, ok: false };
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
buildToolGroups(client) {
|
|
861
|
-
const groups = client.modules.map((mod) => {
|
|
862
|
-
return {
|
|
863
|
-
description: mod.description,
|
|
864
|
-
module: mod.name,
|
|
865
|
-
tools: mod.tools.map((t) => {
|
|
866
|
-
return {
|
|
867
|
-
description: t.description,
|
|
868
|
-
inputSchema: t.inputSchema,
|
|
869
|
-
name: `${mod.name}${protocol_1.MODULE_SEPARATOR}${t.name}`,
|
|
870
|
-
};
|
|
871
|
-
}),
|
|
872
|
-
};
|
|
873
|
-
});
|
|
874
|
-
if (client.dynamicTools.size > 0) {
|
|
875
|
-
const dynamicByModule = new Map();
|
|
876
|
-
for (const [fullName, info] of client.dynamicTools) {
|
|
877
|
-
const existing = dynamicByModule.get(info.module) ?? [];
|
|
878
|
-
existing.push({
|
|
879
|
-
description: info.description,
|
|
880
|
-
inputSchema: info.inputSchema,
|
|
881
|
-
name: fullName,
|
|
882
|
-
});
|
|
883
|
-
dynamicByModule.set(info.module, existing);
|
|
884
|
-
}
|
|
885
|
-
for (const [module, dynTools] of dynamicByModule) {
|
|
886
|
-
groups.push({
|
|
887
|
-
description: 'Dynamically registered tools from useMcpTool hooks',
|
|
888
|
-
module: `${module} (dynamic)`,
|
|
889
|
-
tools: dynTools,
|
|
890
|
-
});
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
return groups;
|
|
894
|
-
}
|
|
895
|
-
formatResult(result) {
|
|
896
|
-
if (Array.isArray(result) && result.length > 0) {
|
|
897
|
-
const first = result[0];
|
|
898
|
-
if (typeof first === 'object' &&
|
|
899
|
-
first !== null &&
|
|
900
|
-
'type' in first &&
|
|
901
|
-
first.type === 'image') {
|
|
902
|
-
return result;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
return [{ text: JSON.stringify(result, null, 2), type: 'text' }];
|
|
906
|
-
}
|
|
907
56
|
}
|
|
908
57
|
exports.McpServerWrapper = McpServerWrapper;
|
|
909
58
|
//# sourceMappingURL=mcpServer.js.map
|