Skip to content

Commit 817b63c

Browse files
authored
Merge branch 'main' into fix/skill-assets-worktree-root
2 parents 3ea5433 + 3667891 commit 817b63c

3 files changed

Lines changed: 102 additions & 4 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# flutter-mcp-toolkit
44

5+
[![MCP Toplist](https://mcptoplist.com/badge/glama%2FArenukvern%2Fmcp_flutter.svg)](https://mcptoplist.com/server/glama%2FArenukvern%2Fmcp_flutter)
6+
57
_Inspect and drive a running Flutter app from your AI assistant._
68

79
[![skills.sh](https://skills.sh/b/arenukvern/mcp_flutter)](https://skills.sh/arenukvern/mcp_flutter)

packages/server_capability_core/lib/src/tools/inspection_tools.dart

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,12 @@ void registerInspectionTools(final CapabilityContext context) {
156156
// ---------------------------------------------------------------------------
157157
// capture_ui_snapshot
158158
// ---------------------------------------------------------------------------
159-
// The "bundle" described in docs is JSON inside a single TextContent.
160-
// Legacy resource_handler.dart captureUiSnapshot (lines 472-474) returns
161-
// CallToolResult(content: [TextContent(text: jsonEncode(result.data))]).
162-
// No multi-content transform required — standard runCommand with no onSuccess.
159+
// The bundle travels as JSON in a TextContent, but captured images are lifted
160+
// out of `screenshots.images` into ImageContent blocks (same artifact contract
161+
// as get_screenshots). Base64 inlined into the JSON counts against the client
162+
// response budget as text, which overflows it for a single desktop frame.
163+
// `imageSummaries` keeps the per-image ids/hashes, so the bundle stays
164+
// self-describing with the payload removed.
163165
context.registerTool(
164166
ToolRegistration(
165167
name: 'capture_ui_snapshot',
@@ -188,6 +190,33 @@ void registerInspectionTools(final CapabilityContext context) {
188190
screenshotMode: parseScreenshotMode(args['screenshotMode']),
189191
permissionPolicy: parsePermissionPolicy(args['permissionPolicy']),
190192
),
193+
onSuccess: (final data) {
194+
final bundle = _asMap(data);
195+
final screenshots = _asMap(bundle['screenshots']);
196+
final images = _stringList(screenshots['images']);
197+
if (images.isEmpty) {
198+
return AgentResult.success(
199+
artifacts: [AgentArtifact.text(jsonEncode(bundle))],
200+
);
201+
}
202+
return AgentResult.success(
203+
artifacts: [
204+
AgentArtifact.text(
205+
jsonEncode(<String, Object?>{
206+
...bundle,
207+
'screenshots': <String, Object?>{
208+
...screenshots,
209+
'images': const <String>[],
210+
},
211+
}),
212+
),
213+
...images.map(
214+
(final image) =>
215+
AgentArtifact.text(image, mimeType: 'image/png'),
216+
),
217+
],
218+
);
219+
},
191220
);
192221
},
193222
),

packages/server_capability_core/test/tools/inspection_tools_test.dart

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,73 @@ void main() {
591591
},
592592
);
593593

594+
test(
595+
'capture_ui_snapshot onSuccess: images move to ImageContent blocks '
596+
'and leave the bundle JSON',
597+
() async {
598+
final runner = FakeCommandRunner()
599+
..nextExecuteResult = CoreResult.success(
600+
data: {
601+
'message': 'Captured UI snapshot bundle.',
602+
'screenshots': {
603+
'images': ['base64dataA', 'base64dataB'],
604+
'fileUrls': <String>[],
605+
'captureMode': 'flutter_layer',
606+
},
607+
'imageSummaries': [
608+
{'id': 'image_1', 'source': 'inline_base64'},
609+
{'id': 'image_2', 'source': 'inline_base64'},
610+
],
611+
},
612+
);
613+
final ctx = _registeredCtx(runner: runner);
614+
final reg = ctx.registrationFor('capture_ui_snapshot')!;
615+
final result = await reg.handler(const <String, Object?>{});
616+
expect(result.ok, isTrue);
617+
expect(result.artifacts, hasLength(3));
618+
619+
final bundle =
620+
jsonDecode(result.artifacts.first.text!) as Map<String, Object?>;
621+
final screenshots = bundle['screenshots']! as Map<String, Object?>;
622+
expect(screenshots['images'], isEmpty);
623+
expect(screenshots['captureMode'], 'flutter_layer');
624+
expect(bundle['imageSummaries'], hasLength(2));
625+
626+
final images = result.artifacts
627+
.where((final a) => a.mimeType == 'image/png')
628+
.toList();
629+
expect(images, hasLength(2));
630+
expect(images[0].text, 'base64dataA');
631+
expect(images[1].text, 'base64dataB');
632+
},
633+
);
634+
635+
test(
636+
'capture_ui_snapshot onSuccess: fileUrls bundle stays a single '
637+
'TextContent',
638+
() async {
639+
final runner = FakeCommandRunner()
640+
..nextExecuteResult = CoreResult.success(
641+
data: {
642+
'screenshots': {
643+
'images': <String>[],
644+
'fileUrls': ['file:///tmp/screen0.png'],
645+
},
646+
},
647+
);
648+
final ctx = _registeredCtx(runner: runner);
649+
final reg = ctx.registrationFor('capture_ui_snapshot')!;
650+
final result = await reg.handler(const <String, Object?>{});
651+
expect(result.ok, isTrue);
652+
expect(result.artifacts, hasLength(1));
653+
expect(result.artifacts.single.mimeType, 'text/plain');
654+
final bundle =
655+
jsonDecode(result.artifacts.single.text!) as Map<String, Object?>;
656+
final screenshots = bundle['screenshots']! as Map<String, Object?>;
657+
expect(screenshots['fileUrls'], ['file:///tmp/screen0.png']);
658+
},
659+
);
660+
594661
test(
595662
'capture_ui_snapshot handler short-circuits on override failure',
596663
() async {

0 commit comments

Comments
 (0)