Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,10 @@ final class DynamicRegistryAppPermissionBridgeGateway
try {
final decoded = jsonDecode(content);
if (decoded is Map<String, Object?>) {
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<String>().toList(growable: false);
Expand Down Expand Up @@ -188,6 +188,15 @@ final class DynamicRegistryAppPermissionBridgeGateway
}
}

List<String> _supportedKindsFromObject(final Map<dynamic, dynamic> value) {
final direct = _asStringList(value['supportedKinds']);
if (direct.isNotEmpty) {
return direct;
}

return _asStringList(_asObject(value['parameters'])['supportedKinds']);
}

final class WebVisualCapturePlatformAdapter
implements VisualCapturePlatformAdapter {
const WebVisualCapturePlatformAdapter();
Expand Down
59 changes: 52 additions & 7 deletions mcp_server_dart/test/visual_capture_test.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io';

import 'package:flutter_mcp_toolkit_server/flutter_mcp_core.dart';
Expand Down Expand Up @@ -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: <String, Object?>{
'content': jsonEncode(<String, Object?>{
'parameters': <String, Object?>{
'supportedKinds': <String>[
PermissionKind.visualCapture.wireName,
],
},
}),
},
),
adapters: const <VisualCapturePlatformAdapter>[
_PlaceholderAppBridgeAdapter(),
],
);

expect(await broker.isAppBridgeInstalled(), isTrue);
},
);

test(
'prepareForCapture honors bridge-supported modes on app-owned targets',
() async {
Expand Down Expand Up @@ -204,6 +239,10 @@ final class _PlaceholderAppBridgeAdapter
}

final class _FakeDynamicGateway implements CoreDynamicGateway {
_FakeDynamicGateway({this.resourceData});

final Map<String, Object?>? resourceData;

@override
Future<CoreResult> dynamicRegistryStats({
required final bool includeAppDetails,
Expand All @@ -214,14 +253,20 @@ final class _FakeDynamicGateway implements CoreDynamicGateway {
CoreResult.success();

@override
Future<CoreResult> runClientResource(final String resourceUri) async =>
CoreResult.success(
data: <String, Object?>{
'payload': <String, Object?>{
'supportedKinds': <String>[PermissionKind.visualCapture.wireName],
},
Future<CoreResult> runClientResource(final String resourceUri) async {
final data = resourceData;
if (data != null) {
return CoreResult.success(data: data);
}

return CoreResult.success(
data: <String, Object?>{
'payload': <String, Object?>{
'supportedKinds': <String>[PermissionKind.visualCapture.wireName],
},
);
},
);
}

@override
Future<CoreResult> runClientTool(
Expand Down