Skip to content

Open source · React Native · iOS · Android

Let agents and tests reach into your running app.

Appduct lets a terminal, a test runner, or an AI agent call functions inside your app while it’s running. You pick what’s callable. Nothing else is reachable.

How Appduct worksA test run, a terminal and an agent each call a function registered inside a running React Native app — login, seed_cart and open_screen — through the appduct service. The app reacts to each call. Only the three registered tools are reachable.Test runcheckout.test.tslogin("u_42")Terminalappduct invokeseed_cart({items:3})Agentclaude code · mcpopen_screen("checkout")appductserviceCartguestu_42 · signed in0Nothing in the cartRunner Low$120Cotton Tee$32Field Cap$24Total$176.00Pay nowAppduct · 3 toolsloginseed_cartopen_screen

npm install -g appduct

Why you’d want this

Skip the setup. Test the part that matters.

Tests stop tapping through setup.

Call login(userId) or seedCart(items) directly and jump to the part you are actually testing. Faster runs, less flakiness.

Agents can drive your app.

One line of MCP config and the agent can find and call your app’s functions. Flip a flag, open a screen, check some state.

No hidden debug UI.

No secret gestures, no admin panel behind a long-press. Only the functions you registered are reachable.

A dev loop that holds.

Reloads, backgrounding, flaky Wi-Fi — the session survives and picks back up. One service, every device you plug in.

What it looks like

Register a function. Call it from anywhere.

01 In your app

CartScreen.tsx
import { useAppductTool } from "@appduct/react-native";
import { z } from "zod";
useAppductTool({
name: "seed_cart",
description: "Fill the cart with test items.",
inputSchema: z.object({ items: z.number() }),
handler: async ({ items }) => ({ added: items }),
});

Registers once per mount. The handler always sees the latest state it closes over. Native apps register the same way from Swift or Kotlin.

02 From your terminal

Terminal
appduct invoke seed_cart --input '{"items":3}'

03 Or from a test

checkout.test.ts
import { connect } from "appduct/client";
const app = await connect();
await app.call("seed_cart", { items: 3 });

The CLI reads your deep-link scheme from app.json, Info.plist, or build.gradle. There is nothing to configure.

Build variants

Nothing ships in your release build.

Debug builds only, by default.

Release builds compile it out, not just switch it off.

Opt in per build for TestFlight or internal tracks.

Build variants

Use it with an agent

One line of config. Your app becomes a toolset.

MCP config
{
"mcpServers": {
"appduct": { "command": "appduct", "args": ["mcp"] }
}
}
MCPClaude Code, Cursor, or any MCP client. The agent lists your app’s tools, reads their schemas, and calls them.
CLI + skillFor agents that work in a shell: npx skills add callstackincubator/appduct --skill appduct
TrustEncrypted connection. The app checks who is on the other end. Security model →
Agents guide

Runs where your app runs

React Native. Or no React Native at all.

Register one tool. See where it goes.