diff --git a/mcp_server_dart/lib/src/capabilities/visual_capture/visual_capture.dart b/mcp_server_dart/lib/src/capabilities/visual_capture/visual_capture.dart index 00630cfd..3e862f54 100644 --- a/mcp_server_dart/lib/src/capabilities/visual_capture/visual_capture.dart +++ b/mcp_server_dart/lib/src/capabilities/visual_capture/visual_capture.dart @@ -105,10 +105,10 @@ final class DynamicRegistryAppPermissionBridgeGateway try { final decoded = jsonDecode(content); if (decoded is Map) { - return _asStringList(decoded['supportedKinds']); + return _supportedKindsFromObject(decoded); } if (decoded is Map) { - return _asStringList(decoded['supportedKinds']); + return _supportedKindsFromObject(decoded); } if (decoded is List) { return decoded.whereType().toList(growable: false); @@ -188,6 +188,15 @@ final class DynamicRegistryAppPermissionBridgeGateway } } +List _supportedKindsFromObject(final Map value) { + final direct = _asStringList(value['supportedKinds']); + if (direct.isNotEmpty) { + return direct; + } + + return _asStringList(_asObject(value['parameters'])['supportedKinds']); +} + final class WebVisualCapturePlatformAdapter implements VisualCapturePlatformAdapter { const WebVisualCapturePlatformAdapter(); diff --git a/mcp_server_dart/test/visual_capture_test.dart b/mcp_server_dart/test/visual_capture_test.dart index b8c8459f..3a86e1cf 100644 --- a/mcp_server_dart/test/visual_capture_test.dart +++ b/mcp_server_dart/test/visual_capture_test.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:flutter_mcp_toolkit_server/flutter_mcp_core.dart'; @@ -116,6 +117,40 @@ void main() { expect(result.appBridgeInstalled, isTrue); }); + test( + 'app bridge detects supported kinds from normalized resource parameters', + () async { + final broker = VisualCaptureBroker( + configuration: const CoreRuntimeConfiguration( + vmHost: 'localhost', + vmPort: 8181, + resourcesSupported: true, + imagesSupported: true, + dumpsSupported: false, + dynamicRegistrySupported: true, + saveImagesToFiles: false, + flutterDevice: 'ios', + ), + dynamicGateway: _FakeDynamicGateway( + resourceData: { + 'content': jsonEncode({ + 'parameters': { + 'supportedKinds': [ + PermissionKind.visualCapture.wireName, + ], + }, + }), + }, + ), + adapters: const [ + _PlaceholderAppBridgeAdapter(), + ], + ); + + expect(await broker.isAppBridgeInstalled(), isTrue); + }, + ); + test( 'prepareForCapture honors bridge-supported modes on app-owned targets', () async { @@ -204,6 +239,10 @@ final class _PlaceholderAppBridgeAdapter } final class _FakeDynamicGateway implements CoreDynamicGateway { + _FakeDynamicGateway({this.resourceData}); + + final Map? resourceData; + @override Future dynamicRegistryStats({ required final bool includeAppDetails, @@ -214,14 +253,20 @@ final class _FakeDynamicGateway implements CoreDynamicGateway { CoreResult.success(); @override - Future runClientResource(final String resourceUri) async => - CoreResult.success( - data: { - 'payload': { - 'supportedKinds': [PermissionKind.visualCapture.wireName], - }, + Future runClientResource(final String resourceUri) async { + final data = resourceData; + if (data != null) { + return CoreResult.success(data: data); + } + + return CoreResult.success( + data: { + 'payload': { + 'supportedKinds': [PermissionKind.visualCapture.wireName], }, - ); + }, + ); + } @override Future runClientTool(