Skip to content

Commit 7fbe88a

Browse files
authored
refine the prompt and fix the extension listener (#244)
Fixes an issue where we would think flutter_driver wasn't registered sometimes, because those events come on a different stream. Refine the prompt to have it also run the actual test. Also added an optional user_journey argument to the prompt to avoid an extra back and forth with the agent.
1 parent 8d2384b commit 7fbe88a

File tree

4 files changed

+107
-25
lines changed

4 files changed

+107
-25
lines changed

pkgs/dart_mcp_server/lib/src/mixins/dtd.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,7 +1136,7 @@ class _AppListener {
11361136
}),
11371137
);
11381138

1139-
subscriptions.add(
1139+
subscriptions.addAll([
11401140
vmService.onServiceEvent.listen((Event e) {
11411141
switch (e.kind) {
11421142
case EventKind.kServiceRegistered:
@@ -1145,10 +1145,17 @@ class _AppListener {
11451145
registeredServices.remove(e.service!);
11461146
}
11471147
}),
1148-
);
1148+
vmService.onIsolateEvent.listen((e) {
1149+
switch (e.kind) {
1150+
case EventKind.kServiceExtensionAdded:
1151+
registeredServices.add(e.extensionRPC!);
1152+
}
1153+
}),
1154+
]);
11491155

11501156
await [
11511157
vmService.streamListen(EventStreams.kExtension),
1158+
vmService.streamListen(EventStreams.kIsolate),
11521159
vmService.streamListen(EventStreams.kStderr),
11531160
vmService.streamListen(EventStreams.kService),
11541161
].wait;
@@ -1176,6 +1183,7 @@ class _AppListener {
11761183
await Future.wait(_subscriptions.map((s) => s.cancel()));
11771184
try {
11781185
await _vmService.streamCancel(EventStreams.kExtension);
1186+
await _vmService.streamCancel(EventStreams.kIsolate);
11791187
await _vmService.streamCancel(EventStreams.kStderr);
11801188
await _vmService.streamCancel(EventStreams.kService);
11811189
} on RPCError catch (_) {

pkgs/dart_mcp_server/lib/src/mixins/prompts.dart

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'dart:async';
77
import 'package:dart_mcp/server.dart';
88
import 'package:meta/meta.dart';
99

10+
import '../utils/constants.dart';
11+
1012
/// A mixin which adds support for various dart and flutter specific prompts.
1113
base mixin DashPrompts on PromptsSupport {
1214
@override
@@ -17,12 +19,19 @@ base mixin DashPrompts on PromptsSupport {
1719

1820
/// Creates the flutter driver user journey prompt based on a request.
1921
GetPromptResult _flutterDriverUserJourneyPrompt(GetPromptRequest request) {
22+
final userJourney =
23+
request.arguments?[ParameterNames.userJourney] as String?;
2024
return GetPromptResult(
2125
messages: [
2226
PromptMessage(
2327
role: Role.user,
2428
content: flutterDriverUserJourneyPromptContent,
2529
),
30+
if (userJourney != null)
31+
PromptMessage(
32+
role: Role.user,
33+
content: Content.text(text: 'The user journey is:\n$userJourney'),
34+
),
2635
],
2736
);
2837
}
@@ -36,6 +45,14 @@ Prompts the LLM to attempt to accomplish a user journey in the running app using
3645
flutter driver. If successful, it will then translate the steps it followed into
3746
a flutter driver test and write that to disk.
3847
''',
48+
arguments: [
49+
PromptArgument(
50+
name: ParameterNames.userJourney,
51+
title: 'User Journey',
52+
description: 'The user journey to perform and write a test for.',
53+
required: false,
54+
),
55+
],
3956
);
4057

4158
@visibleForTesting
@@ -52,9 +69,18 @@ Perform the following tasks in order:
5269
reading in files to accomplish this task, just inspect the live state of the
5370
app and widget tree. If you get stuck, feel free to ask the user for help.
5471
- If you are able to successfully complete the journey, then create a flutter
55-
driver based test with an appropriate name, which performs all the same
56-
actions that you performed. Include the original user journey as a comment
57-
in the test file.
72+
driver based test with an appropriate name under the integration_test
73+
directory. The test should perform all the successful actions that you
74+
performed to complete the task and validate the result. Include the
75+
original user journey as a comment above the test function, or reference the
76+
file the user journey is defined in if it came from a file. Note that
77+
flutter_driver tests are NOT allowed to import package:flutter_test, they MUST
78+
use package:test. Importing package:flutter_test will cause very confusing
79+
errors and waste my time. Also, when creating variables that you will assign
80+
in a setUp or setUpAll function, they must be late (preferred) or nullable.
81+
- After writing the test, first analyze the project for errors, and format it.
82+
- Next, execute the test using the command `flutter drive --driver <test-path>`
83+
and verify that it passes.
5884
''',
5985
);
6086
}

pkgs/dart_mcp_server/lib/src/utils/constants.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ extension ParameterNames on Never {
2424
static const testRunnerArgs = 'testRunnerArgs';
2525
static const uri = 'uri';
2626
static const uris = 'uris';
27+
static const userJourney = 'user_journey';
2728
}
2829

2930
/// A shared success response for tools.

pkgs/dart_mcp_server/test/tools/prompts_test.dart

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'package:dart_mcp/server.dart';
66
import 'package:dart_mcp_server/src/mixins/prompts.dart';
7+
import 'package:dart_mcp_server/src/utils/constants.dart';
78
import 'package:test/test.dart';
89

910
import '../test_harness.dart';
@@ -23,29 +24,75 @@ void main() {
2324
expect(
2425
promptsResult.prompts,
2526
equals([
26-
isA<GetPromptRequest>().having(
27-
(p) => p.name,
28-
'name',
29-
DashPrompts.flutterDriverUserJourneyTest.name,
30-
),
27+
isA<Prompt>()
28+
.having(
29+
(p) => p.name,
30+
'name',
31+
DashPrompts.flutterDriverUserJourneyTest.name,
32+
)
33+
.having(
34+
(p) => p.arguments,
35+
'arguments',
36+
equals([
37+
isA<PromptArgument>()
38+
.having(
39+
(arg) => arg.name,
40+
'name',
41+
ParameterNames.userJourney,
42+
)
43+
.having((arg) => arg.required, 'required', false),
44+
]),
45+
),
3146
]),
3247
);
3348
});
3449

35-
test('can get the flutter driver user journey prompt', () async {
36-
final server = testHarness.mcpServerConnection;
37-
final prompt = await server.getPrompt(
38-
GetPromptRequest(name: DashPrompts.flutterDriverUserJourneyTest.name),
39-
);
40-
expect(
41-
prompt.messages.single,
42-
isA<PromptMessage>()
43-
.having((m) => m.role, 'role', Role.user)
44-
.having(
45-
(m) => m.content,
46-
'content',
47-
equals(DashPrompts.flutterDriverUserJourneyPromptContent),
48-
),
49-
);
50+
group('Can get the flutter driver user journey prompt ', () {
51+
test(' with no arguments', () async {
52+
final server = testHarness.mcpServerConnection;
53+
final prompt = await server.getPrompt(
54+
GetPromptRequest(name: DashPrompts.flutterDriverUserJourneyTest.name),
55+
);
56+
expect(
57+
prompt.messages.single,
58+
isA<PromptMessage>()
59+
.having((m) => m.role, 'role', Role.user)
60+
.having(
61+
(m) => m.content,
62+
'content',
63+
equals(DashPrompts.flutterDriverUserJourneyPromptContent),
64+
),
65+
);
66+
});
67+
68+
test('with a user journey arguments', () async {
69+
final server = testHarness.mcpServerConnection;
70+
final userJourney = 'A really sick user journey';
71+
final prompt = await server.getPrompt(
72+
GetPromptRequest(
73+
name: DashPrompts.flutterDriverUserJourneyTest.name,
74+
arguments: {ParameterNames.userJourney: userJourney},
75+
),
76+
);
77+
expect(
78+
prompt.messages,
79+
equals([
80+
isA<PromptMessage>()
81+
.having((m) => m.role, 'role', Role.user)
82+
.having(
83+
(m) => m.content,
84+
'content',
85+
equals(DashPrompts.flutterDriverUserJourneyPromptContent),
86+
),
87+
isA<PromptMessage>()
88+
.having((m) => m.role, 'role', Role.user)
89+
.having(
90+
(m) => (m.content as TextContent).text,
91+
'content.text',
92+
contains(userJourney),
93+
),
94+
]),
95+
);
96+
});
5097
});
5198
}

0 commit comments

Comments
 (0)