@hypothesi/tauri-mcp-server 0.10.0 → 0.11.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.
@@ -99,13 +99,13 @@ async function cleanupPickerHighlights(windowId, appIdentifier) {
99
99
  * Capture a screenshot of a specific element using html2canvas.
100
100
  * Returns the base64 data URL of the cropped element image, or null on failure.
101
101
  */
102
- async function captureElementScreenshot(cssSelector, windowId) {
102
+ async function captureElementScreenshot(cssSelector, windowId, appIdentifier) {
103
103
  // Ensure html2canvas is loaded in the webview
104
104
  try {
105
- const isRegistered = await isScriptRegistered(HTML2CANVAS_SCRIPT_ID);
105
+ const isRegistered = await isScriptRegistered(HTML2CANVAS_SCRIPT_ID, appIdentifier);
106
106
  if (!isRegistered) {
107
107
  const source = getHtml2CanvasSource();
108
- await registerScript(HTML2CANVAS_SCRIPT_ID, 'inline', source);
108
+ await registerScript(HTML2CANVAS_SCRIPT_ID, 'inline', source, windowId, appIdentifier);
109
109
  }
110
110
  }
111
111
  catch {
@@ -149,7 +149,7 @@ async function captureElementScreenshot(cssSelector, windowId) {
149
149
  return dataUrl;
150
150
  `;
151
151
  try {
152
- const dataUrl = await executeAsyncInWebview(captureScript, windowId, 10000);
152
+ const dataUrl = await executeAsyncInWebview(captureScript, windowId, 10000, appIdentifier);
153
153
  if (!dataUrl || !dataUrl.startsWith('data:image/')) {
154
154
  return null;
155
155
  }
@@ -221,7 +221,7 @@ export async function selectElement(options) {
221
221
  // Add formatted metadata
222
222
  content.push({ type: 'text', text: formatElementMetadata(element) });
223
223
  // Capture element-only screenshot (no picker overlays visible)
224
- const screenshot = await captureElementScreenshot(element.cssSelector, windowId);
224
+ const screenshot = await captureElementScreenshot(element.cssSelector, windowId, appIdentifier);
225
225
  if (screenshot) {
226
226
  content.push(screenshot);
227
227
  }
@@ -261,7 +261,7 @@ export async function getPointedElement(options) {
261
261
  // Add formatted metadata
262
262
  content.push({ type: 'text', text: formatElementMetadata(element) });
263
263
  // Capture element-only screenshot (no overlays)
264
- const screenshot = await captureElementScreenshot(element.cssSelector, windowId);
264
+ const screenshot = await captureElementScreenshot(element.cssSelector, windowId, appIdentifier);
265
265
  if (screenshot) {
266
266
  content.push(screenshot);
267
267
  }
@@ -59,7 +59,7 @@ interface GetScriptsResponse {
59
59
  * @param windowLabel - Optional window label to target
60
60
  * @returns Promise resolving to registration result
61
61
  */
62
- export declare function registerScript(id: string, type: ScriptType, content: string, windowLabel?: string): Promise<RegisterScriptResponse>;
62
+ export declare function registerScript(id: string, type: ScriptType, content: string, windowLabel?: string, appIdentifier?: string | number): Promise<RegisterScriptResponse>;
63
63
  /**
64
64
  * Removes a script from the registry and DOM.
65
65
  *
@@ -67,25 +67,25 @@ export declare function registerScript(id: string, type: ScriptType, content: st
67
67
  * @param windowLabel - Optional window label to target
68
68
  * @returns Promise resolving to removal result
69
69
  */
70
- export declare function removeScript(id: string, windowLabel?: string): Promise<RemoveScriptResponse>;
70
+ export declare function removeScript(id: string, windowLabel?: string, appIdentifier?: string | number): Promise<RemoveScriptResponse>;
71
71
  /**
72
72
  * Clears all registered scripts from the registry and DOM.
73
73
  *
74
74
  * @param windowLabel - Optional window label to target
75
75
  * @returns Promise resolving to the number of scripts cleared
76
76
  */
77
- export declare function clearScripts(windowLabel?: string): Promise<ClearScriptsResponse>;
77
+ export declare function clearScripts(windowLabel?: string, appIdentifier?: string | number): Promise<ClearScriptsResponse>;
78
78
  /**
79
79
  * Gets all registered scripts.
80
80
  *
81
81
  * @returns Promise resolving to the list of registered scripts
82
82
  */
83
- export declare function getScripts(): Promise<GetScriptsResponse>;
83
+ export declare function getScripts(appIdentifier?: string | number): Promise<GetScriptsResponse>;
84
84
  /**
85
85
  * Checks if a script with the given ID is registered.
86
86
  *
87
87
  * @param id - The script ID to check
88
88
  * @returns Promise resolving to true if the script is registered
89
89
  */
90
- export declare function isScriptRegistered(id: string): Promise<boolean>;
90
+ export declare function isScriptRegistered(id: string, appIdentifier?: string | number): Promise<boolean>;
91
91
  export {};
@@ -19,8 +19,8 @@ import { ensureSessionAndConnect } from './plugin-client.js';
19
19
  * @param windowLabel - Optional window label to target
20
20
  * @returns Promise resolving to registration result
21
21
  */
22
- export async function registerScript(id, type, content, windowLabel) {
23
- const client = await ensureSessionAndConnect();
22
+ export async function registerScript(id, type, content, windowLabel, appIdentifier) {
23
+ const client = await ensureSessionAndConnect(appIdentifier);
24
24
  const response = await client.sendCommand({
25
25
  command: 'register_script',
26
26
  args: { id, type, content, windowLabel },
@@ -37,8 +37,8 @@ export async function registerScript(id, type, content, windowLabel) {
37
37
  * @param windowLabel - Optional window label to target
38
38
  * @returns Promise resolving to removal result
39
39
  */
40
- export async function removeScript(id, windowLabel) {
41
- const client = await ensureSessionAndConnect();
40
+ export async function removeScript(id, windowLabel, appIdentifier) {
41
+ const client = await ensureSessionAndConnect(appIdentifier);
42
42
  const response = await client.sendCommand({
43
43
  command: 'remove_script',
44
44
  args: { id, windowLabel },
@@ -54,8 +54,8 @@ export async function removeScript(id, windowLabel) {
54
54
  * @param windowLabel - Optional window label to target
55
55
  * @returns Promise resolving to the number of scripts cleared
56
56
  */
57
- export async function clearScripts(windowLabel) {
58
- const client = await ensureSessionAndConnect();
57
+ export async function clearScripts(windowLabel, appIdentifier) {
58
+ const client = await ensureSessionAndConnect(appIdentifier);
59
59
  const response = await client.sendCommand({
60
60
  command: 'clear_scripts',
61
61
  args: { windowLabel },
@@ -70,8 +70,8 @@ export async function clearScripts(windowLabel) {
70
70
  *
71
71
  * @returns Promise resolving to the list of registered scripts
72
72
  */
73
- export async function getScripts() {
74
- const client = await ensureSessionAndConnect();
73
+ export async function getScripts(appIdentifier) {
74
+ const client = await ensureSessionAndConnect(appIdentifier);
75
75
  const response = await client.sendCommand({
76
76
  command: 'get_scripts',
77
77
  args: {},
@@ -87,7 +87,7 @@ export async function getScripts() {
87
87
  * @param id - The script ID to check
88
88
  * @returns Promise resolving to true if the script is registered
89
89
  */
90
- export async function isScriptRegistered(id) {
91
- const { scripts } = await getScripts();
90
+ export async function isScriptRegistered(id, appIdentifier) {
91
+ const { scripts } = await getScripts(appIdentifier);
92
92
  return scripts.some((s) => { return s.id === id; });
93
93
  }
@@ -56,8 +56,24 @@ export function buildTypeScript(selector, text, strategy) {
56
56
  if (!element) throw new Error('Element not found: ' + selector);
57
57
 
58
58
  element.focus();
59
- element.value = text;
60
- element.dispatchEvent(new Event('input', { bubbles: true }));
59
+
60
+ // Use native prototype setter to bypass React's value tracker
61
+ var proto = element.tagName === 'TEXTAREA'
62
+ ? HTMLTextAreaElement.prototype
63
+ : HTMLInputElement.prototype;
64
+ var descriptor = Object.getOwnPropertyDescriptor(proto, 'value');
65
+
66
+ if (descriptor && descriptor.set) {
67
+ descriptor.set.call(element, text);
68
+ } else {
69
+ element.value = text;
70
+ }
71
+
72
+ // Reset React's internal value tracker so it detects the change
73
+ if (element._valueTracker) element._valueTracker.setValue('');
74
+
75
+ // Dispatch proper InputEvent (not generic Event) for React compatibility
76
+ element.dispatchEvent(new InputEvent('input', { bubbles: true, inputType: 'insertText', data: text }));
61
77
  element.dispatchEvent(new Event('change', { bubbles: true }));
62
78
 
63
79
  var msg = 'Typed "' + text + '" into ' + selector;
@@ -47,9 +47,22 @@
47
47
  }
48
48
 
49
49
  if (strategy === 'text') {
50
+ // First try: match element text content
50
51
  var xpath = xpathForText(selectorOrRef);
51
52
  var result = document.evaluate(xpath, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
52
- return result.singleNodeValue;
53
+ if (result.singleNodeValue) return result.singleNodeValue;
54
+
55
+ // Fallback: search placeholder, aria-label, and title attributes
56
+ var attrSelectors = [
57
+ '[placeholder*="' + selectorOrRef.replace(/"/g, '\\"') + '"]',
58
+ '[aria-label*="' + selectorOrRef.replace(/"/g, '\\"') + '"]',
59
+ '[title*="' + selectorOrRef.replace(/"/g, '\\"') + '"]',
60
+ ];
61
+ for (var i = 0; i < attrSelectors.length; i++) {
62
+ var el = document.querySelector(attrSelectors[i]);
63
+ if (el) return el;
64
+ }
65
+ return null;
53
66
  }
54
67
 
55
68
  if (strategy === 'xpath') {
@@ -78,12 +91,25 @@
78
91
  }
79
92
 
80
93
  if (strategy === 'text') {
94
+ // First try: match element text content
81
95
  var xpath = xpathForText(selector);
82
96
  var snapshot = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
83
97
  var results = [];
84
98
  for (var i = 0; i < snapshot.snapshotLength; i++) {
85
99
  results.push(snapshot.snapshotItem(i));
86
100
  }
101
+ if (results.length > 0) return results;
102
+
103
+ // Fallback: search placeholder, aria-label, and title attributes
104
+ var attrSelectors = [
105
+ '[placeholder*="' + selector.replace(/"/g, '\\"') + '"]',
106
+ '[aria-label*="' + selector.replace(/"/g, '\\"') + '"]',
107
+ '[title*="' + selector.replace(/"/g, '\\"') + '"]',
108
+ ];
109
+ for (var i = 0; i < attrSelectors.length; i++) {
110
+ var found = Array.from(document.querySelectorAll(attrSelectors[i]));
111
+ if (found.length > 0) return results.concat(found);
112
+ }
87
113
  return results;
88
114
  }
89
115
 
@@ -12,7 +12,7 @@ import { z } from 'zod';
12
12
  *
13
13
  * @throws Error if no session is active (driver_session must be called first)
14
14
  */
15
- export declare function ensureReady(): Promise<void>;
15
+ export declare function ensureReady(windowId?: string, appIdentifier?: string | number): Promise<void>;
16
16
  /**
17
17
  * Reset initialization state (useful for testing or reconnecting).
18
18
  */
@@ -48,7 +48,7 @@ export declare function executeInWebviewWithContext(script: string, windowId?: s
48
48
  * @param timeout - Timeout in milliseconds (default: 5000)
49
49
  * @returns Result of the script execution
50
50
  */
51
- export declare function executeAsyncInWebview(script: string, windowId?: string, timeout?: number): Promise<string>;
51
+ export declare function executeAsyncInWebview(script: string, windowId?: string, timeout?: number, appIdentifier?: string | number): Promise<string>;
52
52
  /**
53
53
  * Initialize console log capture in the webview.
54
54
  * This intercepts console methods and stores logs in memory.
@@ -1,6 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { connectPlugin } from './plugin-client.js';
3
- import { hasActiveSession, getDefaultSession, resolveTargetApp } from './session-manager.js';
2
+ import { hasActiveSession, resolveTargetApp, manageDriverSession } from './session-manager.js';
4
3
  import { createMcpLogger } from '../logger.js';
5
4
  import { buildScreenshotScript, buildScreenshotCaptureScript, getHtml2CanvasSource, HTML2CANVAS_SCRIPT_ID, } from './scripts/html2canvas-loader.js';
6
5
  import { registerScript, isScriptRegistered } from './script-manager.js';
@@ -17,7 +16,7 @@ import { getResolveRefSource, RESOLVE_REF_SCRIPT_ID } from './scripts/index.js';
17
16
  // ============================================================================
18
17
  // Auto-Initialization System
19
18
  // ============================================================================
20
- let isInitialized = false;
19
+ const initializedTargets = new Set();
21
20
  const driverLogger = createMcpLogger('DRIVER');
22
21
  /**
23
22
  * Ensures the MCP server is fully initialized and ready to use.
@@ -32,28 +31,52 @@ const driverLogger = createMcpLogger('DRIVER');
32
31
  *
33
32
  * @throws Error if no session is active (driver_session must be called first)
34
33
  */
35
- export async function ensureReady() {
36
- if (isInitialized) {
37
- return;
38
- }
39
- // Require an active session to prevent connecting to wrong app
34
+ export async function ensureReady(windowId, appIdentifier) {
35
+ // Auto-connect if no active session
40
36
  if (!hasActiveSession()) {
41
- throw new Error('No active session. Call driver_session with action "start" first to connect to a Tauri app.');
37
+ const result = await manageDriverSession('start');
38
+ if (!hasActiveSession()) {
39
+ throw new Error('Auto-connect failed: ' + result + '. Call driver_session with action "start" to connect manually.');
40
+ }
42
41
  }
43
- // Get default session for initial connection
44
- const session = getDefaultSession();
45
- if (session) {
46
- await connectPlugin(session.host, session.port);
42
+ const session = resolveTargetApp(appIdentifier);
43
+ if (!session.client.isConnected()) {
44
+ await session.client.connect();
47
45
  }
48
- // Register the resolve-ref helper so ref-based selectors work in all tools
49
- await registerScript(RESOLVE_REF_SCRIPT_ID, 'inline', getResolveRefSource());
50
- isInitialized = true;
46
+ const targetKey = `${session.host}:${session.port}:${windowId ?? 'main'}`;
47
+ if (initializedTargets.has(targetKey)) {
48
+ return;
49
+ }
50
+ // Register the resolve-ref helper in the target window
51
+ // so ref-based selectors work there.
52
+ await registerScript(RESOLVE_REF_SCRIPT_ID, 'inline', getResolveRefSource(), windowId, appIdentifier);
53
+ await waitForResolveRefHelper(session, windowId);
54
+ initializedTargets.add(targetKey);
51
55
  }
52
56
  /**
53
57
  * Reset initialization state (useful for testing or reconnecting).
54
58
  */
55
59
  export function resetInitialization() {
56
- isInitialized = false;
60
+ initializedTargets.clear();
61
+ }
62
+ async function waitForResolveRefHelper(session, windowId) {
63
+ if (!session) {
64
+ throw new Error('No active session available while registering resolve-ref helper.');
65
+ }
66
+ for (let attempt = 0; attempt < 20; attempt++) {
67
+ const response = await session.client.sendCommand({
68
+ command: 'execute_js',
69
+ args: {
70
+ script: 'return !!(window.__MCP__ && typeof window.__MCP__.resolveRef === "function")',
71
+ windowLabel: windowId,
72
+ },
73
+ }, 2000);
74
+ if (response.success && response.data === true) {
75
+ return;
76
+ }
77
+ await new Promise((resolve) => { return setTimeout(resolve, 50); });
78
+ }
79
+ throw new Error('Resolve-ref helper was not available in the webview after registration.');
57
80
  }
58
81
  /**
59
82
  * Execute JavaScript in the Tauri webview using native IPC via WebSocket.
@@ -78,7 +101,7 @@ export async function executeInWebview(script, windowId, appIdentifier) {
78
101
  export async function executeInWebviewWithContext(script, windowId, appIdentifier) {
79
102
  try {
80
103
  // Ensure we're fully initialized
81
- await ensureReady();
104
+ await ensureReady(windowId, appIdentifier);
82
105
  // Resolve target session
83
106
  const session = resolveTargetApp(appIdentifier);
84
107
  const client = session.client;
@@ -124,11 +147,12 @@ export async function executeInWebviewWithContext(script, windowId, appIdentifie
124
147
  * @param timeout - Timeout in milliseconds (default: 5000)
125
148
  * @returns Result of the script execution
126
149
  */
127
- export async function executeAsyncInWebview(script, windowId, timeout = 5000) {
150
+ export async function executeAsyncInWebview(script, windowId, timeout, appIdentifier) {
151
+ const resolvedTimeout = timeout ?? 5000;
128
152
  const wrappedScript = `
129
153
  return (async () => {
130
154
  const timeoutPromise = new Promise((_, reject) => {
131
- setTimeout(() => reject(new Error('Script execution timeout')), ${timeout});
155
+ setTimeout(() => reject(new Error('Script execution timeout')), ${resolvedTimeout});
132
156
  });
133
157
 
134
158
  const scriptPromise = (async () => {
@@ -138,7 +162,7 @@ export async function executeAsyncInWebview(script, windowId, timeout = 5000) {
138
162
  return await Promise.race([scriptPromise, timeoutPromise]);
139
163
  })();
140
164
  `;
141
- return executeInWebview(wrappedScript, windowId);
165
+ return executeInWebview(wrappedScript, windowId, appIdentifier);
142
166
  }
143
167
  // ============================================================================
144
168
  // Console Log Capture System
@@ -268,14 +292,14 @@ function buildScreenshotResult(dataUrl, method, windowContext) {
268
292
  * Prepares the html2canvas script for screenshot capture.
269
293
  * Tries to use the script manager for persistence, falls back to inline injection.
270
294
  */
271
- async function prepareHtml2canvasScript(format, quality) {
295
+ async function prepareHtml2canvasScript(format, quality, windowId, appIdentifier) {
272
296
  try {
273
297
  // Check if html2canvas is already registered
274
- const isRegistered = await isScriptRegistered(HTML2CANVAS_SCRIPT_ID);
298
+ const isRegistered = await isScriptRegistered(HTML2CANVAS_SCRIPT_ID, appIdentifier);
275
299
  if (!isRegistered) {
276
300
  // Register html2canvas via script manager for persistence across navigations
277
301
  const html2canvasSource = getHtml2CanvasSource();
278
- await registerScript(HTML2CANVAS_SCRIPT_ID, 'inline', html2canvasSource);
302
+ await registerScript(HTML2CANVAS_SCRIPT_ID, 'inline', html2canvasSource, windowId, appIdentifier);
279
303
  }
280
304
  // Use the capture-only script since html2canvas is now registered
281
305
  return buildScreenshotCaptureScript(format, quality);
@@ -299,7 +323,7 @@ export async function captureScreenshot(options = {}) {
299
323
  // - Linux: Chromium/WebKit screenshot APIs
300
324
  try {
301
325
  // Ensure we're fully initialized
302
- await ensureReady();
326
+ await ensureReady(windowId, appIdentifier);
303
327
  // Resolve target session
304
328
  const session = resolveTargetApp(appIdentifier);
305
329
  const client = session.client;
@@ -331,7 +355,7 @@ export async function captureScreenshot(options = {}) {
331
355
  }
332
356
  // Fallback 1: Use html2canvas library for high-quality DOM rendering
333
357
  // Try to use the script manager to register html2canvas for persistence
334
- const html2canvasScript = await prepareHtml2canvasScript(format, quality);
358
+ const html2canvasScript = await prepareHtml2canvasScript(format, quality, windowId, appIdentifier);
335
359
  // Fallback: Try Screen Capture API if available
336
360
  // Note: This script is wrapped by executeAsyncInWebview, so we don't need an IIFE
337
361
  const screenCaptureScript = `
@@ -390,7 +414,7 @@ export async function captureScreenshot(options = {}) {
390
414
  `;
391
415
  try {
392
416
  // Try html2canvas second (after native APIs)
393
- const result = await executeAsyncInWebview(html2canvasScript, undefined, 10000); // Longer timeout for library loading
417
+ const result = await executeAsyncInWebview(html2canvasScript, windowId, 10000, appIdentifier);
394
418
  // Validate that we got a real data URL, not 'null' or empty
395
419
  if (result && result !== 'null' && result.startsWith('data:image/')) {
396
420
  return buildScreenshotResult(result, 'html2canvas');
@@ -400,7 +424,7 @@ export async function captureScreenshot(options = {}) {
400
424
  catch (html2canvasError) {
401
425
  try {
402
426
  // Fallback to Screen Capture API
403
- const result = await executeAsyncInWebview(screenCaptureScript);
427
+ const result = await executeAsyncInWebview(screenCaptureScript, windowId, 5000, appIdentifier);
404
428
  // Validate that we got a real data URL
405
429
  if (result && result.startsWith('data:image/')) {
406
430
  return buildScreenshotResult(result, 'Screen Capture API');
@@ -22,7 +22,8 @@ export const WindowTargetSchema = z.object({
22
22
  * Defaults to 'css' for backward compatibility.
23
23
  */
24
24
  const selectorStrategyField = z.enum(['css', 'xpath', 'text']).default('css').describe('Selector strategy: "css" (default) for CSS selectors, "xpath" for XPath expressions, ' +
25
- '"text" to find elements containing the given text. Ref IDs (e.g., "ref=e3") work with any strategy.');
25
+ '"text" to find elements by text content, with fallback to placeholder, aria-label, ' +
26
+ 'and title attributes. Ref IDs (e.g., "ref=e3") work with any strategy.');
26
27
  // ============================================================================
27
28
  // Schemas
28
29
  // ============================================================================
@@ -154,10 +155,8 @@ export async function screenshot(options = {}) {
154
155
  if (!imageContent || imageContent.type !== 'image') {
155
156
  throw new Error('Screenshot capture failed: no image data');
156
157
  }
157
- // Decode base64 and write to file
158
- const buffer = Buffer.from(imageContent.data, 'base64');
159
158
  const resolvedPath = resolve(filePath);
160
- await writeFile(resolvedPath, buffer);
159
+ await writeFile(resolvedPath, imageContent.data, 'base64');
161
160
  return { filePath: resolvedPath, format };
162
161
  }
163
162
  return result;
@@ -295,7 +294,7 @@ export async function domSnapshot(options) {
295
294
  const { type, selector, strategy, windowId, appIdentifier } = options;
296
295
  // Only load aria-api for accessibility snapshots
297
296
  if (type === 'accessibility') {
298
- await ensureAriaApiLoaded(windowId);
297
+ await ensureAriaApiLoaded(windowId, appIdentifier);
299
298
  }
300
299
  // Then execute the snapshot script
301
300
  const script = buildScript(SCRIPTS.domSnapshot, { type, selector: selector ?? null, strategy: strategy ?? 'css' });
@@ -311,11 +310,11 @@ export async function domSnapshot(options) {
311
310
  * Ensure aria-api library is loaded in the webview.
312
311
  * Uses the script manager to inject the library if not already present.
313
312
  */
314
- async function ensureAriaApiLoaded(windowId) {
313
+ async function ensureAriaApiLoaded(windowId, appIdentifier) {
315
314
  const { getAriaApiSource, ARIA_API_SCRIPT_ID: ariaApiScriptId } = await import('./scripts/aria-api-loader.js');
316
315
  const { registerScript, isScriptRegistered } = await import('./script-manager.js');
317
- if (await isScriptRegistered(ariaApiScriptId)) {
316
+ if (await isScriptRegistered(ariaApiScriptId, appIdentifier)) {
318
317
  return;
319
318
  }
320
- await registerScript(ariaApiScriptId, 'inline', getAriaApiSource(), windowId);
319
+ await registerScript(ariaApiScriptId, 'inline', getAriaApiSource(), windowId, appIdentifier);
321
320
  }
@@ -171,6 +171,7 @@ export const TOOLS = [
171
171
  name: 'webview_find_element',
172
172
  description: '[Tauri Apps Only] Find DOM elements in a running Tauri app\'s webview. ' +
173
173
  'Supports CSS selectors (default), XPath expressions, and text content matching via the strategy parameter. ' +
174
+ 'The "text" strategy first searches element text content, then falls back to placeholder, aria-label, and title attributes. ' +
174
175
  'Returns the element\'s HTML. ' +
175
176
  'Requires active driver_session. ' +
176
177
  MULTI_APP_DESC + ' ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypothesi/tauri-mcp-server",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "mcpName": "io.github.hypothesi/mcp-server-tauri",
5
5
  "description": "A Model Context Protocol server for use with Tauri v2 applications",
6
6
  "type": "module",