tauri-mcp
# tauri-mcp
A generic [MCP](https://modelcontextprotocol.io) server that drives a **built
Tauri app's UI over WebDriver** (tauri-driver + msedgedriver on Windows /
WebKitWebDriver on Linux): launch the real app, then click, type, scroll, drag,
read, and screenshot it like a human — or drop to raw JS when needed.
Not tied to any one project. Point it at any local Tauri app with `projectDir`
or `appPath`. This server only knows how to drive a window; anything
app-specific (reading an app's own database or config) belongs in a separate
server of your own.
## Tools
All tools are prefixed `app_` and operate on a single running session (one app
at a time).
### Session
| Tool | What it does |
| --- | --- |
| `app_launch` | Launch a built Tauri app under WebDriver. `projectDir` (or `appPath`) picks which app; `TAURI_PROJECT_DIR` is the fallback. Hard-errors if the window never renders (see Troubleshooting). |
| `app_close` | Close the session and stop tauri-driver. |
| `app_status` | Whether a session is running, and if so the binary + driver port. |
| `app_force_cleanup` | Kill orphaned tauri-driver/msedgedriver and reset state, when a crashed run left things half-up. |
| `app_window` | Resize / move / maximize / minimize / fullscreen the window; reports resulting size+position. |
| `app_reload` | Reload the webview (resets front-end state; backend process keeps its state). |
### Acting (mouse / keyboard)
| Tool | What it does |
| --- | --- |
| `app_click` | Click by CSS `selector` **or** `text` (visible text / accessible name). `button` left/right/middle, `count` 2 for double-click. |
| `app_click_at` | Left/right/middle click at raw x,y viewport coordinates (canvas / native-drawn UI). |
| `app_scroll` | Mouse-wheel scroll by a pixel delta (+down/-up, +right/-left); optional `selector` to scroll a specific pane. |
| `app_type` | Type `value` into an input/textarea found by `selector` or `text` (placeholder/label); optional Enter. |
| `app_press_key` | Raw keyboard: any key — literal chars, F1-F12, arrows, Home/End/PageUp/Down, and modifier chords (Ctrl+A, Ctrl+Shift+K). |
| `app_hover` | Hover an element (by selector or text) to trigger tooltips / hover menus. |
| `app_select` | Pick an option from a native `<select>` by `optionText`/`optionValue`/`optionIndex`. |
| `app_drag` | Drag an element onto another (`to`) or by a pixel `offset` — sliders, range inputs, reordering, canvas handles. |
### Reading / observing
| Tool | What it does |
| --- | --- |
| `app_snapshot` | Title + visible text + every visible interactive element (role, text, selector). The "what can I act on" view. |
| `app_read` | One element's live state: text, current input value (DOM property, reflects controlled React inputs), tag, displayed/enabled, optional attribute. |
| `app_wait_for` | Poll until a selector/text appears or disappears — for async loading, toasts, dialogs. |
| `app_console_logs` | Browser console output (`log`/`warn`/`error` + uncaught exceptions) since launch. |
| `app_screenshot` | PNG screenshot of the window, or of a single element (selector/text) for focused visual diffing. |
| `app_list_windows` / `app_switch_window` | List/switch windows, for apps with more than one `WebviewWindow`. |
### Escape hatch
| Tool | What it does |
| --- | --- |
| `app_execute_js` | Run arbitrary JavaScript in the window and return the result. |
`app_execute_js` is unrestricted: it can read/write any DOM or JS state and
invoke **any** Tauri backend command directly via
`await window.__TAURI_INTERNALS__.invoke("cmd_name", { arg })` (always present
in Tauri v2, regardless of `withGlobalTauri`) — anything the app's own frontend
can do, including commands with real side effects (writing config, filesystem,
launching processes). No sandboxing beyond what the app's own command handlers
enforce. It's also the **workaround for OS-native dialogs** (see below).
## Targeting elements: prefer `text`
`app_snapshot` emits positional CSS selectors like
`div > div:nth-of-type(3) > button`, which break the moment the DOM is
restructured. Wherever a tool takes a target, prefer `text` (visible text or
accessible name) — `app_click({ text: "Browse" })` is how a human would say it
and survives layout changes. `exact: true` requires an exact match; otherwise
it tries exact text, then substring, then ARIA accessible name (covers
icon-only buttons with `aria-label`).
## Requirements
- Node 24+ and `npm install` once in this folder.
- [`tauri-driver`](https://v2.tauri.app/develop/tests/webdriver/): `cargo install tauri-driver --locked`.
- Windows: Microsoft Edge WebDriver **version-matched to your installed
WebView2 Runtime** (Settings → About Microsoft Edge WebView2, or
`HKLM\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}`
→ `pv`). A mismatched major version connects fine but silently never leaves
`about:blank` — `app_launch` now detects this and errors. `winget install
Microsoft.EdgeDriver` does **not** reliably match; download the exact version
from `https://msedgedriver.microsoft.com/<version>/edgedriver_win64.zip` and
drop it at `./bin/msedgedriver.exe` (checked first) or set `EDGEDRIVER_PATH`.
- The target app must already be built, **and not with plain `cargo build`** — a
debug binary points at `devUrl` and only renders if a Vite dev server is up.
Use `cargo tauri build --debug --no-bundle` (fast) or `cargo tauri build
--no-bundle` (release). `app_launch` does not build for you.
When `projectDir`/`appPath` is omitted, `app_launch` uses the `TAURI_PROJECT_DIR`
environment variable; if that is unset too, it returns a clear error. There is
no built-in default project.
## Run / verify
```bash
npm install
# point it at any built Tauri app:
TAURI_PROJECT_DIR=/path/to/tauri-project npm run verify-automation
# or:
TAURI_APP_PATH=/path/to/built/app npm run verify-automation
```
The verify script launches the app, snapshots it, clicks the first interactive
element, screenshots, and closes.
## Known limitation: OS-native dialogs
WebDriver can only see the webview DOM. Tauri file/folder pickers, message
boxes, and other OS-native dialogs are **not** part of the DOM and can't be
clicked through — a flow that opens one will stall. Workaround: skip the dialog
and drive the backend directly with `app_execute_js`, e.g.
```js
// instead of clicking a "Choose folder" button that opens a native picker:
return await window.__TAURI_INTERNALS__.invoke("set_output_dir", { path: "/some/folder" });
```
## Troubleshooting
- **`app_launch` errors "window never rendered"** — msedgedriver/WebView2
version mismatch, or a plain `cargo build` binary. See Requirements.
- **"a session is already running" after a crash** — `app_force_cleanup`, then
relaunch.
- **Selectors keep breaking** — target by `text` instead (see above).
## Register with Claude Code
Add it to an `.mcp.json` (project root, or `~/.claude.json` for all projects),
then restart Claude Code. Tools then appear as `tauri:app_launch`, etc.
```json
{
"mcpServers": {
"tauri": {
"command": "node",
"args": ["--no-warnings", "/absolute/path/to/tauri-mcp/index.mjs"]
}
}
}
```
Or via the CLI:
```bash
claude mcp add tauri -- node --no-warnings /absolute/path/to/tauri-mcp/index.mjs
```
TDQS
Scored across 22 tools
Each tool has a clearly distinct purpose, from app_click (element click) to app_click_at (coordinate click), app_type (input typing), app_read (single element state), and app_snapshot (full window state). No two tools serve overlapping roles.
All tools follow the consistent pattern 'app_verb_noun' in snake_case (e.g., app_list_windows, app_execute_js, app_force_cleanup). No mixing of conventions or chaotic naming.
With 22 tools, the set is well-scoped for automating Tauri desktop apps, covering launching, interaction, state inspection, window management, and debugging without being bloated.
The toolset covers the full lifecycle (launch/close), all major user interactions (click, type, scroll, drag, select, hover), state reading (read, snapshot, console logs), window management, and debugging (execute_js, force_cleanup). No obvious gaps for typical UI automation.