reticle_* tools, then add the SDK to your app so there is something for those tools to talk to. You need both, in that order.
Do only the first half and the tools appear but every call reports no session. Do only the second and your app connects to a daemon nobody is asking questions. Stop after the first half and your agent can still check the running app with the zero-install lease, which installs nothing. It asks you before it runs init.
If you want the CLI without the script:
reticle setup mcp is exactly the registration half of the installer, and it prints any client it could not write for you.
Sandbox and container images
Install the CLI once, in the image:reticle init and reticle setup mcp register its absolute path (node <path>/bin/reticle.js mcp). Each boot then starts the MCP server from the local copy, and your agent does not wait for a download.
Without an install, the registration is npx @reticlehq/server@<version> mcp, with one exact version. npx uses its cache again only when that cache is in the image. A fresh sandbox with an empty npm cache downloads the package at each boot, and your agent waits for it before its first turn.
If you remove the installed copy later, the agent shows the server as failed. reticle doctor then prints a launcher row with the missing path and the command to run.
Part 1. Register the MCP server
There is no config file that all agents share. Each harness has its own path and its own schema, so write only the one you actually use.Claude Code
Register once, globally, and every project has it:claude mcp list. reticle should be there.
If the claude CLI isn’t available, merge one key into mcpServers in ~/.claude.json. It is a large stateful file, so merge, never rewrite:
Cursor, VS Code, Windsurf
Same shape, different file and root key. Cursor and Windsurf usemcpServers; VS Code uses servers:
Codex CLI
TOML, in~/.codex/config.toml. With the codex command installed, init runs codex mcp add reticle -- npx @reticlehq/server mcp for you, and only when the file has no reticle entry yet, because that command replaces one without asking. Without the command, init cannot write it for you:
[mcp_servers.reticle] table is there, Reticle adds one line under it: default_tools_approval_mode = "approve". This line approves Reticle’s own tools, so Codex does not ask before each call. Reticle changes no other line, and it keeps a mode that you set. To undo it, change that line to default_tools_approval_mode = "prompt". Reticle does not change a mode that is already there.
OpenCode
Note the flat command array and the requiredtype:
Part 2. Wire the SDK into your app
Vite
src/components/Login.tsx:81. It also injects connect() with the pairing token, so you can’t forget either.
Anything without the plugin
Runnpx @reticlehq/server init even here: it wires plain HTML, Angular, Remix, Astro, SvelteKit and the rest, and it is the part that gets the pairing token to the page without writing it into anything you ship.
If you really are wiring connect() by hand, pass the token. The daemon writes it to ~/.reticle/pairing-token on first start and refuses a page that does not present it, on localhost too. Without it the page console says bridge refused the connection: authentication failed: no pairing token on the page….
init gives you a module for this: it writes the token to reticle.local.js at the project root, adds that file to .gitignore, and prints a snippet that imports it. To do it without init, pass the token yourself:
VITE_RETICLE_TOKEN="$(cat ~/.reticle/pairing-token)", or pin your own secret with RETICLE_TOKEN=<secret> in the MCP server’s environment and give the page the same value. Set it on the dev command only, never in a .env file: a production build reads .env and would embed the token in the JavaScript it ships. Not on Vite? Do the same with your bundler’s dev-only flag and its way of passing a value to the browser.
connect() is dev-only by design. Guard it with whatever your bundler gives you:
import.meta.env.DEV, process.env.NODE_ENV !== 'production', or a build flag. Reticle also
self-guards, but two locks on a door that leads to your users is a reasonable number.Tauri, and the step whose failure is silent
The frontend is identical to any web app. The CSP is not optional:reticle_capture refuses and on_page_load does nothing, so the binary you sign and ship carries no Reticle capability. A release build you test with Reticle (a CI fixture) turns it on with reticle-tauri = { version = "3.8", features = ["in-release"] }.
IPC observation needs nothing on the Rust side. An invoke('load_todos') already reaches Reticle as ipc://load_todos.
Electron, and the rest of desktop
Main-process and renderer wiring, IPC observation, and what differs from the web install.
Part 3. Prove it
connect() without the pairing token. The browser console will carry [Reticle] this page could not open a websocket to … naming the exact address it tried; set it explicitly with RETICLE_PORT=4400 pnpm dev (Vite plugin), reticle({ port: 4400 }) in vite.config, or reticle.connect({ url: 'ws://localhost:4400/reticle' }) by hand.
Still stuck? npx @reticlehq/server doctor checks Chromium, the daemon and the port in one command.
Instrument your app
Register stores, signals and testids so your verdicts get stronger than “the DOM changed”.
Quickstart
Drive your first flow and read a real verdict.