Skip to content

Commit 7347a3b

Browse files
author
GitLab CI
committed
fix: bridge tools missing from MCP server + Tauri eval fix
- Add press_key, scroll, find_element, get_text to bridge handlers - Screenshot fallback to native when bridge returns _needs_native - Tauri: fix get_registered_tools/call_tool to use eval_js_with_result - reset_app: graceful fallback for non-Flutter bridge platforms - .NET MAUI test app: Thread.Sleep instead of Console.ReadLine
1 parent 9f4f1a9 commit 7347a3b

File tree

6 files changed

+99
-21
lines changed

6 files changed

+99
-21
lines changed

lib/src/cli/tool_handlers/bf_interaction.dart

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,64 @@ extension _BfInteraction on FlutterMcpServer {
176176
key: args['key'], text: args['text'], timeout: timeout);
177177
return {"gone": gone};
178178

179+
case 'press_key':
180+
final key = args['key'] as String? ?? '';
181+
if (key.isEmpty) {
182+
return {"success": false, "error": "Missing 'key' argument"};
183+
}
184+
if (client is BridgeDriver) {
185+
await client.callMethod('press_key', {'key': key});
186+
return {"success": true, "message": "Key pressed: $key"};
187+
}
188+
return {"success": false, "error": "press_key not supported on this platform. Use CDP mode or a bridge SDK."};
189+
190+
case 'scroll':
191+
final direction = args['direction'] as String? ?? 'down';
192+
final amount = (args['amount'] as num?)?.toDouble() ?? 300;
193+
if (client is BridgeDriver) {
194+
await client.callMethod('scroll', {
195+
'direction': direction,
196+
'amount': amount,
197+
});
198+
return {"success": true, "message": "Scrolled $direction by $amount"};
199+
}
200+
// Flutter: use scrollTo with key/text if provided
201+
if (args['key'] != null || args['text'] != null) {
202+
final fc = _asFlutterClient(client!, 'scroll');
203+
await fc.scrollTo(key: args['key'] as String?, text: args['text'] as String?);
204+
return {"success": true, "message": "Scrolled to element"};
205+
}
206+
return {"success": false, "error": "scroll on Flutter requires 'key' or 'text' argument"};
207+
208+
case 'find_element':
209+
final text = args['text'] as String?;
210+
final key = args['key'] as String?;
211+
if (text == null && key == null) {
212+
return {"success": false, "error": "Provide 'text' or 'key'"};
213+
}
214+
if (client is BridgeDriver) {
215+
final result = await client.callMethod('find_element', {
216+
if (text != null) 'text': text,
217+
if (key != null) 'key': key,
218+
});
219+
return result;
220+
}
221+
final fc2 = _asFlutterClient(client!, 'find_element');
222+
final elements = await fc2.findByType(text ?? key ?? '');
223+
return {"found": elements.isNotEmpty, "count": elements.length};
224+
225+
case 'get_text':
226+
if (client is BridgeDriver) {
227+
final result = await client.callMethod('get_text', {
228+
if (args['ref'] != null) 'ref': args['ref'],
229+
if (args['key'] != null) 'key': args['key'],
230+
});
231+
return result;
232+
}
233+
final fc3 = _asFlutterClient(client!, 'get_text');
234+
final textContent = await fc3.getTextContent();
235+
return {"text": textContent};
236+
179237
// Screenshot
180238
default:
181239
return null;

lib/src/cli/tool_handlers/bf_screenshot.dart

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,29 @@ extension _BfScreenshot on FlutterMcpServer {
1111
final saveToFile =
1212
args['save_to_file'] ?? false; // Return base64 by default for speed
1313

14-
final imageBase64 =
14+
var imageBase64 =
1515
await client!.takeScreenshot(quality: quality, maxWidth: maxWidth);
1616

17+
// Fallback to native screenshot if bridge returns null
18+
// (e.g. React Native returns _needs_native flag)
19+
if (imageBase64 == null) {
20+
try {
21+
final nDriver = await _getNativeDriver(args);
22+
if (nDriver != null) {
23+
final nResult = await nDriver.screenshot(saveToFile: false);
24+
imageBase64 = nResult.base64Image;
25+
}
26+
} catch (_) {
27+
// Native fallback failed, continue to error
28+
}
29+
}
30+
1731
if (imageBase64 == null) {
1832
return {
1933
"success": false,
2034
"error": "Failed to capture screenshot",
21-
"message": "Screenshot returned null"
35+
"message":
36+
"Screenshot returned null. Try 'native_screenshot' tool instead."
2237
};
2338
}
2439

lib/src/cli/tool_handlers/dev_tool_handlers.dart

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -114,17 +114,20 @@ extension _DevToolHandlers on FlutterMcpServer {
114114
});
115115
return {'success': true, 'platform': 'bridge', 'result': result};
116116
} catch (_) {
117-
// Fallback: hot restart if bridge doesn't support reset
117+
// Fallback: try go_back to home, then re-initialize
118118
try {
119-
await client.callMethod('hot_restart');
120-
return {
121-
'success': true,
122-
'platform': 'bridge',
123-
'fallback': 'hot_restart'
124-
};
125-
} catch (e) {
126-
return {'success': false, 'error': e.toString()};
127-
}
119+
await client.callMethod('go_back');
120+
} catch (_) {}
121+
try {
122+
await client.callMethod('initialize', {'client_name': 'reset'});
123+
} catch (_) {}
124+
return {
125+
'success': true,
126+
'platform': 'bridge',
127+
'fallback': 'go_back+reinitialize',
128+
'note':
129+
'Full reset not supported on this platform; navigated back and re-initialized',
130+
};
128131
}
129132
}
130133

lib/src/drivers/bridge_driver.dart

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ class BridgeDriver implements AppDriver {
159159
'quality': quality,
160160
if (maxWidth != null) 'maxWidth': maxWidth,
161161
});
162+
// Some SDKs (e.g. React Native) return _needs_native flag when
163+
// in-app screenshot is not available; caller should fall back to
164+
// native_screenshot in that case.
165+
if (result['_needs_native'] == true) return null;
162166
return result['image'] as String?;
163167
}
164168

sdks/tauri/src/lib.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -793,11 +793,10 @@ async fn handle_method<R: Runtime>(
793793
var tools = (window.__flutter_skill_tools__ || []).map(function(t) {
794794
return { name: t.name, description: t.description || '', params: t.params || {}, source: t.source || 'js-registered' };
795795
});
796-
return JSON.stringify({ tools: tools, count: tools.length });
796+
return { tools: tools, count: tools.length };
797797
})()
798798
"#;
799-
let res = window.eval(js).map_err(|e| e.to_string())?;
800-
Ok(serde_json::from_str(&res.to_string()).unwrap_or(json!({"tools": [], "count": 0})))
799+
eval_js_with_result(window, js, 5000, result_tx).await
801800
}
802801

803802
"call_tool" => {
@@ -807,14 +806,13 @@ async fn handle_method<R: Runtime>(
807806
(function() {{
808807
var tools = window.__flutter_skill_tools__ || [];
809808
var tool = tools.find(function(t) {{ return t.name === '{}'; }});
810-
if (!tool) return JSON.stringify({{ error: 'Tool not found: {}' }});
811-
if (!tool.handler) return JSON.stringify({{ error: 'Tool has no handler' }});
809+
if (!tool) return {{ error: 'Tool not found: {}' }};
810+
if (!tool.handler) return {{ error: 'Tool has no handler' }};
812811
var result = tool.handler({});
813-
return JSON.stringify({{ success: true, tool: '{}', result: result }});
812+
return {{ success: true, tool: '{}', result: result }};
814813
}})()
815814
"#, name, name, serde_json::to_string(&args).unwrap_or_default(), name);
816-
let res = window.eval(&js).map_err(|e| e.to_string())?;
817-
Ok(serde_json::from_str(&res.to_string()).unwrap_or(json!({"error": "parse error"})))
815+
eval_js_with_result(window, &js, 5000, result_tx).await
818816
}
819817

820818
_ => Err(format!("Unknown method: {method}")),

test/e2e/dotnet_test_app/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ static async Task Main(string[] args)
466466
var bridge = new TestBridge(port);
467467
bridge.Start();
468468
Console.WriteLine($"[flutter-skill-dotnet] Test bridge on port {port}. Press Enter to stop.");
469-
Console.ReadLine();
469+
System.Threading.Thread.Sleep(System.Threading.Timeout.Infinite);
470470
bridge.Stop();
471471
}
472472
}

0 commit comments

Comments
 (0)