Cordierite exists so developers, QA, and automation can drive registered tools and influence in-app state from a CLI or agent - without shipping hidden debug screens, secret gestures, or admin panels inside the app. The app exposes only the tool surface you define in code; control stays behind a long-lived daemon that owns a pinned wss:// listener and a hardened local control plane, so the same tools are reachable from a terminal and from an MCP-speaking agent (Claude Code, Cursor, CI) alike.
Shipping ad-hoc debug UIs in production builds is risky: they leak intent, widen attack surface, and are hard to gate consistently. Cordierite inverts that: production-capable builds can still participate in Cordierite when a trusted host is available, because trust is not "anyone on Wi-Fi" or "whoever crafted a link" - it is TLS + SPKI pinning to identities you embed, plus short-lived session bootstrap so deep links are hints, not proof of authority. A single cordierite daemon absorbs common dev-loop churn - Metro reloads, backgrounding, and network flaps - by suspending and resuming sessions instead of dying with them, and serves any number of devices at once. Resume credentials live only in the native app process: process death requires a fresh bootstrap link.
- No backdoor UI: nothing extra in the app UI for attackers to discover; capability is tool APIs + transport, not mystery menus.
- Encrypted transport:
wss://end-to-end; no cleartext control traffic on the wire. - Pinned server identity: the native client matches your daemon's public key (SPKI) against an embedded pin set; IP, DNS, and deep-link origin are not enough to impersonate it.
- Session bootstrap: one-time, session-bound channel after claim - appropriate for production when pins and provisioning match your threat model.
- Hardened local control plane: the CLI and MCP server talk to the daemon over a Unix domain socket gated by filesystem permissions (
0700directory,0600socket) - not an open localhost port. - Policy + audit: production deployments can deny classes of tool by annotation (
destructiveHint) or by name, and every call is appended to an audit log regardless of outcome. Seedocs/SECURITY.mdfor the full threat model and a key-rotation runbook.
| Package | Role |
|---|---|
cordierite |
CLI, daemon, and MCP server |
@cordierite/shared |
Wire protocol v2 types shared by the CLI and React Native |
@cordierite/react-native |
TurboModule client + Expo config plugin |
Clone the repo and install with pnpm (pnpm install). The playground is the reference dev app - it's also the fastest way to see the whole flow working end to end.
Cordierite has two sides:
- the operator/agent side, where you run the
cordieriteCLI (which transparently starts its own daemon) or addcordierite mcpto an agent's MCP config - the app side, where your React Native app imports
@cordierite/react-nativeand registers tools
Install the CLI where the operator, test runner, or agent will run it:
npm install -g cordieriteInstall the React Native package in your app:
npm install @cordierite/react-native zodcordierite keygenThis writes an unencrypted PEM private key to ~/.cordierite/key.pem (override with --out) and prints a sha256/... SPKI fingerprint. Copy that value into the app configuration as a trusted Cordierite pin - see docs/SECURITY.md for what this key protects and how to rotate it later.
For Expo, add the config plugin and the generated pin to app.json / app.config.*:
{
"expo": {
"scheme": "myapp",
"plugins": [
[
"@cordierite/react-native",
{
"cliPins": ["sha256/REPLACE_WITH_KEYGEN_OUTPUT"],
"allowPrivateLanOnly": true
}
]
]
}
}For bare React Native, configure the equivalent native keys:
- iOS
Info.plist:CordieriteCliPinsand optionallyCordieriteAllowPrivateLanOnly - Android
<application>meta-data:com.callstackincubator.cordierite.CLI_PINSand optionallycom.callstackincubator.cordierite.ALLOW_PRIVATE_LAN_ONLY
Your app also needs a URL scheme, and that scheme must match the one you pass to cordierite link --scheme ... (or set once in ~/.cordierite/config.json's "scheme" field).
After changing native configuration, rebuild the app. Use a development build or bare native app. Expo Go is not enough.
Import the side-effect entry once near your app's entry point so the deep-link bootstrap listener installs on startup:
import "@cordierite/react-native/auto";import "@cordierite/react-native/auto";
import { useCordieriteTool } from "@cordierite/react-native";
import { z } from "zod";
export function CordieriteBootstrap() {
useCordieriteTool(
{
name: "sum",
description: "Add two numbers inside the app.",
inputSchema: z.object({ a: z.number(), b: z.number() }),
outputSchema: z.object({ total: z.number() }),
handler: async ({ a, b }) => ({ total: a + b }),
},
[]
);
return null;
}Mount that component near app startup, or register tools from another early-loading module. Cordierite only exposes the tools you register.
cordierite auto-spawns its daemon on first use, so most commands just work. link needs to know the app's URL scheme - pass it once with --scheme, or set it once in ~/.cordierite/config.json's "scheme" field and drop the flag from every command below:
cordierite link --scheme myapp --qrScan the printed QR (or open the deep link) on the device. On a simulator/emulator, skip the deep link entirely:
cordierite link --scheme myapp --open ios-sim # or: --open androidOnce the app claims the session, list and call its tools - no session id needed when only one session is active:
cordierite tools
cordierite invoke sum --input '{"a":2,"b":3}'Use cordierite ls to see aliases when more than one device is connected, and pass an alias or session id as the optional selector (cordierite invoke pixel-8 sum --input '{"a":2,"b":3}').
For an agent instead of a human operator, point its MCP config at the CLI - no separate server to run, the daemon auto-spawns the same way:
{
"mcpServers": {
"cordierite": {
"command": "cordierite",
"args": ["mcp"]
}
}
}Add that to Claude Code's or Cursor's MCP config and the connected app's tools appear as MCP tools automatically (tools/list reflects the live registry; a cordierite_connect tool lets the agent mint and deliver a bootstrap link itself, and cordierite_wait_for_session lets it wait for the device to claim it - no shell access required).
- Pinning: the app trusts the daemon by SPKI hash, not IP/DNS/deep-link origin; deep links are bootstrap hints, never proof of authority.
- Tokens: pending-session tokens are short-lived, single-use, and compared with
crypto.timingSafeEqual; a rotating resume token lets a suspended session recover without minting a new link while the native app process remains alive. Resume credentials are process-memory-only and are never persisted to disk. - Control plane: CLI and MCP talk to the daemon over a Unix domain socket (
0700/0600), not an open localhost port. - Policy + audit: production deployments can deny tool classes or specific tools by policy, and every invocation attempt is audited (args hashed, never logged raw).
Full threat model, key-handling rules, and a rotation runbook: docs/SECURITY.md.
| Surface | Support |
|---|---|
| CLI / daemon / MCP | any modern JavaScript runtime (Node ≥ 20 semantics) that can run the published package and open a Unix domain socket + TLS wss:// listener |
| React Native — iOS | 15.1+, New Architecture |
| React Native — Android | New Architecture, autolinked |
| React Native — Web | safe no-op stub only |
| Windows control plane | best-effort (named pipe), not yet exercised in CI |
docs/ARCHITECTURE.md- the current architecture referencedocs/PROTOCOL.md- the wire protocol, byte layouts, message catalog, state machine, close codesdocs/SECURITY.md- threat model and key rotation runbookdocs/REQUIREMENTS.md- product requirements
cordierite is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. Callstack is a group of React and React Native geeks, contact us at hello@callstack.com if you need any help with these or just want to say hi!
Like the project? ⚛️ Join the team who does amazing stuff for clients and drives React Native Open Source! 🔥