@hypothesi/tauri-mcp-server 0.2.2 → 0.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/README.md CHANGED
@@ -13,10 +13,8 @@ A **Model Context Protocol (MCP) server** that enables AI assistants like Claude
13
13
  |----------|-------------|
14
14
  | 🎯 **UI Automation** | Screenshots, clicks, typing, scrolling, element finding |
15
15
  | 🔍 **IPC Monitoring** | Capture and inspect Tauri IPC calls in real-time |
16
- | 📱 **Mobile Dev** | Manage Android emulators & iOS simulators |
17
- | 🛠️ **CLI Integration** | Run any Tauri command (`init`, `dev`, `build`, etc.) |
18
- | ⚙️ **Configuration** | Read/write Tauri config files with validation |
19
- | 📋 **Logs** | Stream Android logcat, iOS device logs, system logs |
16
+ | 📱 **Mobile Dev** | List Android emulators & iOS simulators |
17
+ | 📋 **Logs** | Stream console, Android logcat, iOS, and system logs |
20
18
 
21
19
  ## Quick Start
22
20
 
@@ -54,51 +52,38 @@ npx -y install-mcp @hypothesi/tauri-mcp-server --client claude-code
54
52
 
55
53
  Supported clients: `claude-code`, `cursor`, `windsurf`, `vscode`, `cline`, `roo-cline`, `claude`, `zed`, `goose`, `warp`, `codex`
56
54
 
57
- ## Available Tools
55
+ ## Available Tools (16 total)
58
56
 
59
57
  ### UI Automation
60
58
 
61
59
  | Tool | Description |
62
60
  |------|-------------|
63
- | `tauri_webview_screenshot` | Capture webview screenshots |
64
- | `tauri_list_windows` | List all open webview windows |
61
+ | `tauri_driver_session` | Start/stop/status automation session |
65
62
  | `tauri_webview_find_element` | Find elements by selector |
66
- | `tauri_webview_interact` | Click, scroll, swipe, long-press |
63
+ | `tauri_read_logs` | Read console, Android, iOS, or system logs |
64
+ | `tauri_webview_interact` | Click, scroll, swipe, focus, long-press |
65
+ | `tauri_webview_screenshot` | Capture webview screenshots |
67
66
  | `tauri_webview_keyboard` | Type text or send key events |
68
67
  | `tauri_webview_wait_for` | Wait for elements, text, or events |
69
68
  | `tauri_webview_get_styles` | Get computed CSS styles |
70
69
  | `tauri_webview_execute_js` | Execute JavaScript in webview |
71
- | `tauri_webview_focus_element` | Focus on elements |
72
- | `tauri_driver_get_console_logs` | Get browser console logs |
73
- | `tauri_read_platform_logs` | Read Android/iOS/system logs |
74
- | `tauri_driver_session` | Start/stop automation session |
70
+ | `tauri_list_windows` | List all open webview windows |
75
71
 
76
72
  ### IPC & Plugin
77
73
 
78
74
  | Tool | Description |
79
75
  |------|-------------|
80
- | `tauri_plugin_execute_ipc` | Execute Tauri IPC commands |
81
- | `tauri_plugin_get_window_info` | Get window information |
82
- | `tauri_plugin_get_backend_state` | Get app metadata and state |
83
- | `tauri_plugin_ipc_monitor` | Start/stop IPC monitoring |
84
- | `tauri_plugin_ipc_get_events` | Get captured IPC events |
85
- | `tauri_plugin_emit_event` | Emit custom events |
76
+ | `tauri_ipc_execute_command` | Execute Tauri IPC commands |
77
+ | `tauri_ipc_get_backend_state` | Get app metadata and state |
78
+ | `tauri_ipc_monitor` | Start/stop IPC monitoring |
79
+ | `tauri_ipc_get_captured` | Get captured IPC traffic |
80
+ | `tauri_ipc_emit_event` | Emit custom events |
86
81
 
87
82
  ### Mobile Development
88
83
 
89
84
  | Tool | Description |
90
85
  |------|-------------|
91
86
  | `tauri_list_devices` | List Android devices and iOS simulators |
92
- | `tauri_launch_emulator` | Launch Android AVD or iOS Simulator |
93
-
94
- ### Project Management
95
-
96
- | Tool | Description |
97
- |------|-------------|
98
- | `tauri_run_command` | Run any Tauri CLI command |
99
- | `tauri_read_config` | Read Tauri config files |
100
- | `tauri_write_config` | Write config files with validation |
101
- | `tauri_get_docs` | Fetch Tauri documentation |
102
87
 
103
88
  ## Links
104
89
 
@@ -24,21 +24,6 @@ export async function executeIPCCommand(command, args = {}) {
24
24
  return JSON.stringify({ success: false, error: message });
25
25
  }
26
26
  }
27
- export const GetWindowInfoSchema = z.object({});
28
- export async function getWindowInfo() {
29
- try {
30
- const result = await executeIPCCommand('plugin:mcp-bridge|get_window_info');
31
- const parsed = JSON.parse(result);
32
- if (!parsed.success) {
33
- throw new Error(parsed.error || 'Unknown error');
34
- }
35
- return JSON.stringify(parsed.result);
36
- }
37
- catch (error) {
38
- const message = error instanceof Error ? error.message : String(error);
39
- throw new Error(`Failed to get window info: ${message}`);
40
- }
41
- }
42
27
  // Combined schema for managing IPC monitoring
43
28
  export const ManageIPCMonitoringSchema = z.object({
44
29
  action: z.enum(['start', 'stop']).describe('Action to perform: start or stop IPC monitoring'),
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { getDefaultHost, getDefaultPort } from '../config.js';
3
3
  import { AppDiscovery } from './app-discovery.js';
4
- import { resetPluginClient } from './plugin-client.js';
4
+ import { resetPluginClient, getPluginClient } from './plugin-client.js';
5
5
  import { resetInitialization } from './webview-executor.js';
6
6
  /**
7
7
  * Session Manager - Native IPC-based session management
@@ -18,7 +18,7 @@ import { resetInitialization } from './webview-executor.js';
18
18
  // Schemas
19
19
  // ============================================================================
20
20
  export const ManageDriverSessionSchema = z.object({
21
- action: z.enum(['start', 'stop']).describe('Action to perform: start or stop the session'),
21
+ action: z.enum(['start', 'stop', 'status']).describe('Action to perform: start or stop the session, or check status'),
22
22
  host: z.string().optional().describe('Host address to connect to (e.g., 192.168.1.100). Falls back to MCP_BRIDGE_HOST or TAURI_DEV_HOST env vars'),
23
23
  port: z.number().optional().describe('Port to connect to (default: 9223)'),
24
24
  });
@@ -26,7 +26,8 @@ export const ManageDriverSessionSchema = z.object({
26
26
  // Module State
27
27
  // ============================================================================
28
28
  // AppDiscovery instance - recreated when host changes
29
- let appDiscovery = null;
29
+ // Track current session info
30
+ let appDiscovery = null, currentSession = null;
30
31
  function getAppDiscovery(host) {
31
32
  if (!appDiscovery || appDiscovery.host !== host) {
32
33
  appDiscovery = new AppDiscovery(host);
@@ -63,6 +64,24 @@ async function tryConnect(host, port) {
63
64
  * @param port - Optional port number (defaults to 9223)
64
65
  */
65
66
  export async function manageDriverSession(action, host, port) {
67
+ // Handle status action
68
+ if (action === 'status') {
69
+ const client = getPluginClient();
70
+ if (client.isConnected() && currentSession) {
71
+ return JSON.stringify({
72
+ connected: true,
73
+ app: currentSession.name,
74
+ host: currentSession.host,
75
+ port: currentSession.port,
76
+ });
77
+ }
78
+ return JSON.stringify({
79
+ connected: false,
80
+ app: null,
81
+ host: null,
82
+ port: null,
83
+ });
84
+ }
66
85
  if (action === 'start') {
67
86
  // Reset any existing plugin client to ensure fresh connection
68
87
  resetPluginClient();
@@ -72,6 +91,7 @@ export async function manageDriverSession(action, host, port) {
72
91
  if (configuredHost !== 'localhost' && configuredHost !== '127.0.0.1') {
73
92
  try {
74
93
  const session = await tryConnect('localhost', configuredPort);
94
+ currentSession = session;
75
95
  return `Session started with app: ${session.name} (localhost:${session.port})`;
76
96
  }
77
97
  catch {
@@ -81,6 +101,7 @@ export async function manageDriverSession(action, host, port) {
81
101
  // Strategy 2: Try the configured/provided host
82
102
  try {
83
103
  const session = await tryConnect(configuredHost, configuredPort);
104
+ currentSession = session;
84
105
  return `Session started with app: ${session.name} (${session.host}:${session.port})`;
85
106
  }
86
107
  catch {
@@ -94,6 +115,7 @@ export async function manageDriverSession(action, host, port) {
94
115
  // Reset client again to connect to discovered port
95
116
  resetPluginClient();
96
117
  const session = await tryConnect('localhost', firstApp.port);
118
+ currentSession = session;
97
119
  return `Session started with app: ${session.name} (localhost:${session.port})`;
98
120
  }
99
121
  catch {
@@ -104,10 +126,12 @@ export async function manageDriverSession(action, host, port) {
104
126
  try {
105
127
  resetPluginClient();
106
128
  const session = await tryConnect(configuredHost, configuredPort);
129
+ currentSession = session;
107
130
  return `Session started with app: ${session.name} (${session.host}:${session.port})`;
108
131
  }
109
132
  catch {
110
133
  // All attempts failed
134
+ currentSession = null;
111
135
  return `Session started (native IPC mode - no Tauri app found at localhost or ${configuredHost}:${configuredPort})`;
112
136
  }
113
137
  }
@@ -117,5 +141,6 @@ export async function manageDriverSession(action, host, port) {
117
141
  }
118
142
  resetPluginClient();
119
143
  resetInitialization();
144
+ currentSession = null;
120
145
  return 'Session stopped';
121
146
  }
@@ -206,16 +206,41 @@ export async function clearConsoleLogs() {
206
206
  `;
207
207
  return executeInWebview(script);
208
208
  }
209
- function formatScreenshotResponse(dataUrl, windowContext) {
210
- let result = 'Webview screenshot captured (native)';
209
+ /**
210
+ * Parse a data URL to extract the base64 data and mime type.
211
+ */
212
+ function parseDataUrl(dataUrl) {
213
+ const match = dataUrl.match(/^data:(image\/(?:png|jpeg));base64,(.+)$/);
214
+ if (!match) {
215
+ return null;
216
+ }
217
+ return { mimeType: match[1], data: match[2] };
218
+ }
219
+ /**
220
+ * Build screenshot result with image content and optional text context.
221
+ */
222
+ function buildScreenshotResult(dataUrl, method, windowContext) {
223
+ const parsed = parseDataUrl(dataUrl);
224
+ if (!parsed) {
225
+ throw new Error(`Invalid data URL format: ${dataUrl.substring(0, 50)}...`);
226
+ }
227
+ const content = [];
228
+ // Add context text if there's window info or warnings
229
+ let contextText = `Screenshot captured via ${method}`;
211
230
  if (windowContext) {
212
- result += ` in window "${windowContext.windowLabel}"`;
231
+ contextText += ` in window "${windowContext.windowLabel}"`;
213
232
  if (windowContext.warning) {
214
- result += `\n\n⚠️ ${windowContext.warning}`;
233
+ contextText += `\n\n⚠️ ${windowContext.warning}`;
215
234
  }
216
235
  }
217
- result += `:\n\n![Screenshot](${dataUrl})`;
218
- return result;
236
+ content.push({ type: 'text', text: contextText });
237
+ // Add the image content
238
+ content.push({
239
+ type: 'image',
240
+ data: parsed.data,
241
+ mimeType: parsed.mimeType,
242
+ });
243
+ return { content };
219
244
  }
220
245
  /**
221
246
  * Prepares the html2canvas script for screenshot capture.
@@ -242,7 +267,7 @@ async function prepareHtml2canvasScript(format, quality) {
242
267
  * Capture a screenshot of the entire webview.
243
268
  *
244
269
  * @param options - Screenshot options (format, quality, windowId)
245
- * @returns Base64-encoded image data URL
270
+ * @returns Screenshot result with image content
246
271
  */
247
272
  export async function captureScreenshot(options = {}) {
248
273
  const { format = 'png', quality = 90, windowId } = options;
@@ -272,7 +297,7 @@ export async function captureScreenshot(options = {}) {
272
297
  throw new Error('Native screenshot returned invalid data');
273
298
  }
274
299
  // Build response with window context
275
- return formatScreenshotResponse(dataUrl, response.windowContext);
300
+ return buildScreenshotResult(dataUrl, 'native API', response.windowContext);
276
301
  }
277
302
  catch (nativeError) {
278
303
  // Log the native error for debugging, then fall back
@@ -343,7 +368,7 @@ export async function captureScreenshot(options = {}) {
343
368
  const result = await executeAsyncInWebview(html2canvasScript, undefined, 10000); // Longer timeout for library loading
344
369
  // Validate that we got a real data URL, not 'null' or empty
345
370
  if (result && result !== 'null' && result.startsWith('data:image/')) {
346
- return `Webview screenshot captured:\n\n![Screenshot](${result})`;
371
+ return buildScreenshotResult(result, 'html2canvas');
347
372
  }
348
373
  throw new Error(`html2canvas returned invalid result: ${result?.substring(0, 100) || 'null'}`);
349
374
  }
@@ -353,7 +378,7 @@ export async function captureScreenshot(options = {}) {
353
378
  const result = await executeAsyncInWebview(screenCaptureScript);
354
379
  // Validate that we got a real data URL
355
380
  if (result && result.startsWith('data:image/')) {
356
- return `Webview screenshot captured (via Screen Capture API):\n\n![Screenshot](${result})`;
381
+ return buildScreenshotResult(result, 'Screen Capture API');
357
382
  }
358
383
  throw new Error(`Screen Capture API returned invalid result: ${result?.substring(0, 50) || 'null'}`);
359
384
  }
@@ -1,4 +1,6 @@
1
1
  import { z } from 'zod';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { resolve } from 'node:path';
2
4
  import { executeInWebview, executeInWebviewWithContext, captureScreenshot, getConsoleLogs as getConsoleLogsFromCapture, } from './webview-executor.js';
3
5
  import { SCRIPTS, buildScript, buildTypeScript, buildKeyEventScript } from './scripts/index.js';
4
6
  // ============================================================================
@@ -15,7 +17,7 @@ export const WindowTargetSchema = z.object({
15
17
  // Schemas
16
18
  // ============================================================================
17
19
  export const InteractSchema = WindowTargetSchema.extend({
18
- action: z.enum(['click', 'double-click', 'long-press', 'scroll', 'swipe'])
20
+ action: z.enum(['click', 'double-click', 'long-press', 'scroll', 'swipe', 'focus'])
19
21
  .describe('Type of interaction to perform'),
20
22
  selector: z.string().optional().describe('CSS selector for the element to interact with'),
21
23
  x: z.number().optional().describe('X coordinate for direct coordinate interaction'),
@@ -32,6 +34,7 @@ export const InteractSchema = WindowTargetSchema.extend({
32
34
  export const ScreenshotSchema = WindowTargetSchema.extend({
33
35
  format: z.enum(['png', 'jpeg']).optional().default('png').describe('Image format'),
34
36
  quality: z.number().min(0).max(100).optional().describe('JPEG quality (0-100, only for jpeg format)'),
37
+ filePath: z.string().optional().describe('File path to save the screenshot to instead of returning as base64'),
35
38
  });
36
39
  export const KeyboardSchema = WindowTargetSchema.extend({
37
40
  action: z.enum(['type', 'press', 'down', 'up'])
@@ -76,6 +79,13 @@ export async function interact(options) {
76
79
  if (action === 'swipe') {
77
80
  return performSwipe({ fromX, fromY, toX, toY, duration, windowId });
78
81
  }
82
+ // Handle focus action
83
+ if (action === 'focus') {
84
+ if (!selector) {
85
+ throw new Error('Focus action requires a selector');
86
+ }
87
+ return focusElement({ selector, windowId });
88
+ }
79
89
  const script = buildScript(SCRIPTS.interact, {
80
90
  action,
81
91
  selector: selector ?? null,
@@ -108,9 +118,23 @@ async function performSwipe(options) {
108
118
  }
109
119
  }
110
120
  export async function screenshot(options = {}) {
111
- const { quality, format = 'png', windowId } = options;
121
+ const { quality, format = 'png', windowId, filePath } = options;
112
122
  // Use the native screenshot function from webview-executor
113
- return captureScreenshot({ format, quality, windowId });
123
+ const result = await captureScreenshot({ format, quality, windowId });
124
+ // If filePath is provided, write to file instead of returning base64
125
+ if (filePath) {
126
+ // Find the image content in the result
127
+ const imageContent = result.content.find((c) => { return c.type === 'image'; });
128
+ if (!imageContent || imageContent.type !== 'image') {
129
+ throw new Error('Screenshot capture failed: no image data');
130
+ }
131
+ // Decode base64 and write to file
132
+ const buffer = Buffer.from(imageContent.data, 'base64');
133
+ const resolvedPath = resolve(filePath);
134
+ await writeFile(resolvedPath, buffer);
135
+ return { filePath: resolvedPath, format };
136
+ }
137
+ return result;
114
138
  }
115
139
  export async function keyboard(options) {
116
140
  const { action, selectorOrKey, textOrModifiers, modifiers, windowId } = options;
package/dist/index.js CHANGED
@@ -52,6 +52,32 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
52
52
  }),
53
53
  };
54
54
  });
55
+ /**
56
+ * Convert a ToolResult to MCP content array.
57
+ * Handles string (legacy), single content, and content arrays.
58
+ */
59
+ function toolResultToContent(result) {
60
+ // Legacy string result - convert to text content
61
+ if (typeof result === 'string') {
62
+ return [{ type: 'text', text: result }];
63
+ }
64
+ // Array of content items
65
+ if (Array.isArray(result)) {
66
+ return result.map(contentToMcp);
67
+ }
68
+ // Single content item
69
+ return [contentToMcp(result)];
70
+ }
71
+ /**
72
+ * Convert a single ToolContent to MCP format.
73
+ */
74
+ function contentToMcp(content) {
75
+ if (content.type === 'text') {
76
+ return { type: 'text', text: content.text };
77
+ }
78
+ // Image content
79
+ return { type: 'image', data: content.data, mimeType: content.mimeType };
80
+ }
55
81
  // Tool call handler - generated from registry
56
82
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
57
83
  try {
@@ -60,7 +86,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
60
86
  throw new Error(`Unknown tool: ${request.params.name}`);
61
87
  }
62
88
  const output = await tool.handler(request.params.arguments);
63
- return { content: [{ type: 'text', text: output }] };
89
+ return { content: toolResultToContent(output) };
64
90
  }
65
91
  catch (error) {
66
92
  const message = error instanceof Error ? error.message : String(error);
@@ -37,47 +37,3 @@ export async function listDevices() {
37
37
  ]);
38
38
  return { android, ios };
39
39
  }
40
- export const LaunchEmulatorSchema = z.object({
41
- platform: z.enum(['android', 'ios']),
42
- name: z.string().describe('Name of the AVD or Simulator'),
43
- });
44
- export async function launchEmulator(platform, name) {
45
- if (platform === 'android') {
46
- try {
47
- // Launch Android Emulator - Try to spawn, but immediately await to catch ENOENT
48
- await execa('emulator', ['-avd', name], {
49
- detached: true,
50
- stdio: 'ignore',
51
- timeout: 1000, // Just check if it spawns
52
- });
53
- return `Launching Android AVD: ${name}`;
54
- }
55
- catch (error) {
56
- const message = error instanceof Error ? error.message : String(error);
57
- throw new Error(`Failed to launch Android emulator: ${message}`);
58
- }
59
- }
60
- else if (platform === 'ios') {
61
- // Check if we're on macOS first
62
- if (process.platform !== 'darwin') {
63
- throw new Error('iOS simulators are only available on macOS');
64
- }
65
- try {
66
- // Launch iOS Simulator
67
- await execa('xcrun', ['simctl', 'boot', name]);
68
- await execa('open', ['-a', 'Simulator']);
69
- return `Booted iOS Simulator: ${name}`;
70
- }
71
- catch (error) {
72
- const message = error instanceof Error ? error.message : String(error);
73
- // Provide more helpful error messages
74
- if (message.includes('xcrun: error')) {
75
- throw new Error('Xcode is not installed. Please install Xcode from the App Store to use iOS simulators.');
76
- }
77
- throw new Error(`Failed to launch iOS simulator: ${message}`);
78
- }
79
- }
80
- else {
81
- throw new Error(`Unsupported platform: ${platform}. Use 'android' or 'ios'.`);
82
- }
83
- }
@@ -1,15 +1,27 @@
1
1
  import { z } from 'zod';
2
2
  import { execa } from 'execa';
3
+ import { getConsoleLogs } from '../driver/webview-interactions.js';
3
4
  export const ReadLogsSchema = z.object({
4
- source: z.enum(['android', 'ios', 'system']),
5
+ source: z.enum(['console', 'android', 'ios', 'system'])
6
+ .describe('Log source: "console" for webview JS logs, "android" for logcat, "ios" for simulator, "system" for desktop'),
5
7
  lines: z.number().default(50),
6
8
  filter: z.string().optional().describe('Regex or keyword to filter logs'),
7
9
  since: z.string().optional().describe('ISO timestamp to filter logs since (e.g. 2023-10-27T10:00:00Z)'),
10
+ windowId: z.string().optional().describe('Window label for console logs (defaults to "main")'),
8
11
  });
9
- export async function readLogs(source, lines, filter, since) {
12
+ export async function readLogs(options) {
13
+ const { source, lines = 50, filter, since, windowId } = options;
10
14
  try {
11
15
  let output = '';
16
+ // Handle console logs (webview JS logs)
17
+ if (source === 'console') {
18
+ return await getConsoleLogs({ filter, since, windowId });
19
+ }
12
20
  if (source === 'android') {
21
+ // Find adb - check ANDROID_HOME first, then fall back to PATH
22
+ // eslint-disable-next-line no-process-env
23
+ const androidHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
24
+ const adbPath = androidHome ? `${androidHome}/platform-tools/adb` : 'adb';
13
25
  const args = ['logcat', '-d'];
14
26
  if (since) {
15
27
  // adb logcat -T expects "MM-DD HH:MM:SS.mmm"
@@ -26,7 +38,7 @@ export async function readLogs(source, lines, filter, since) {
26
38
  else {
27
39
  args.push('-t', lines.toString());
28
40
  }
29
- const { stdout } = await execa('logcat', ['-d', '-t', lines.toString()], { timeout: 5000 });
41
+ const { stdout } = await execa(adbPath, args, { timeout: 10000 });
30
42
  output = stdout;
31
43
  }
32
44
  else if (source === 'ios') {
@@ -8,7 +8,7 @@ Please follow these steps:
8
8
 
9
9
  1. **Start a session** - Use \`tauri_driver_session\` with action "start" to connect to the running Tauri app
10
10
 
11
- 2. **Get console logs** - Use \`tauri_driver_get_console_logs\` to retrieve any JavaScript errors or warnings from the webview console
11
+ 2. **Get console logs** - Use \`tauri_read_logs\` with source "console" to retrieve JavaScript errors or warnings
12
12
 
13
13
  3. **Analyze the errors** - Look at the error messages, stack traces, and identify:
14
14
  - What type of error it is (TypeError, ReferenceError, SyntaxError, etc.)
@@ -24,6 +24,96 @@ Please follow these steps:
24
24
  If no errors are found, let me know the app is running cleanly.
25
25
 
26
26
  If the session fails to start, help me troubleshoot the connection (is the app running? is the MCP bridge plugin installed?).`;
27
+ const SETUP_PROMPT = `Help me set up the MCP Bridge plugin in my Tauri project so I can use these AI development tools.
28
+
29
+ ## Prerequisites
30
+
31
+ - This is a **Tauri v2** project (check for \`src-tauri/\` directory and \`tauri.conf.json\`)
32
+ - If this is NOT a Tauri project, stop and let the user know this setup only applies to Tauri apps
33
+
34
+ ## Setup Steps
35
+
36
+ ### Step 1: Add the Rust Plugin
37
+
38
+ Add the plugin to \`src-tauri/Cargo.toml\` dependencies:
39
+
40
+ \`\`\`toml
41
+ [dependencies]
42
+ tauri-plugin-mcp-bridge = "0.2"
43
+ \`\`\`
44
+
45
+ Or run from the \`src-tauri\` directory:
46
+ \`\`\`bash
47
+ cargo add tauri-plugin-mcp-bridge
48
+ \`\`\`
49
+
50
+ ### Step 2: Register the Plugin
51
+
52
+ In the Tauri app's entry point (usually \`src-tauri/src/lib.rs\` or \`src-tauri/src/main.rs\`), register the plugin.
53
+
54
+ Find the \`tauri::Builder\` and add the plugin (only in debug builds):
55
+
56
+ \`\`\`rust
57
+ let mut builder = tauri::Builder::default();
58
+ // ... existing plugins ...
59
+
60
+ #[cfg(debug_assertions)]
61
+ {
62
+ builder = builder.plugin(tauri_plugin_mcp_bridge::init());
63
+ }
64
+
65
+ builder
66
+ .run(tauri::generate_context!())
67
+ .expect("error while running tauri application");
68
+ \`\`\`
69
+
70
+ ### Step 3: Enable Global Tauri (REQUIRED)
71
+
72
+ In \`src-tauri/tauri.conf.json\`, ensure \`withGlobalTauri\` is enabled:
73
+
74
+ \`\`\`json
75
+ {
76
+ "app": {
77
+ "withGlobalTauri": true
78
+ }
79
+ }
80
+ \`\`\`
81
+
82
+ **This is required** - without it, the MCP bridge cannot communicate with the webview.
83
+
84
+ ### Step 4: Add Plugin Permissions
85
+
86
+ Add the plugin permission to \`src-tauri/capabilities/default.json\` (create the file if it doesn't exist):
87
+
88
+ \`\`\`json
89
+ {
90
+ "$schema": "../gen/schemas/desktop-schema.json",
91
+ "identifier": "default",
92
+ "description": "Default capabilities",
93
+ "windows": ["main"],
94
+ "permissions": [
95
+ "mcp-bridge:default"
96
+ ]
97
+ }
98
+ \`\`\`
99
+
100
+ If the file already exists, just add \`"mcp-bridge:default"\` to the existing permissions array.
101
+
102
+ ## Verification
103
+
104
+ After setup:
105
+ 1. Run the Tauri app in development mode (\`cargo tauri dev\` or \`npm run tauri dev\`)
106
+ 2. The MCP bridge will start a WebSocket server on port 9223
107
+ 3. Use \`tauri_driver_session\` with action "start" to connect
108
+ 4. Use \`tauri_driver_session\` with action "status" to verify the connection
109
+
110
+ ## Notes
111
+
112
+ - The plugin only runs in debug builds (\`#[cfg(debug_assertions)]\`) so it won't affect production
113
+ - The WebSocket server binds to \`0.0.0.0\` by default to support mobile device testing
114
+ - For localhost-only access, use \`Builder::new().bind_address("127.0.0.1").build()\` instead of \`init()\`
115
+
116
+ Please examine the project structure and make the necessary changes to set up the MCP bridge plugin.`;
27
117
  /**
28
118
  * Complete registry of all available prompts
29
119
  */
@@ -47,6 +137,24 @@ export const PROMPTS = [
47
137
  ];
48
138
  },
49
139
  },
140
+ {
141
+ name: 'setup',
142
+ description: 'Set up the MCP Bridge plugin in a Tauri project. ' +
143
+ 'Guides through adding the Rust crate, registering the plugin, enabling withGlobalTauri, ' +
144
+ 'and adding permissions. Use this when starting with a new Tauri project.',
145
+ arguments: [],
146
+ handler: () => {
147
+ return [
148
+ {
149
+ role: 'user',
150
+ content: {
151
+ type: 'text',
152
+ text: SETUP_PROMPT,
153
+ },
154
+ },
155
+ ];
156
+ },
157
+ },
50
158
  ];
51
159
  /**
52
160
  * Create a Map for fast prompt lookup by name