Skip to content
Merged
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
12 changes: 10 additions & 2 deletions pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,7 @@ class _AppListener {
}),
);

subscriptions.add(
subscriptions.addAll([
vmService.onServiceEvent.listen((Event e) {
switch (e.kind) {
case EventKind.kServiceRegistered:
Expand All @@ -1145,10 +1145,17 @@ class _AppListener {
registeredServices.remove(e.service!);
}
}),
);
vmService.onIsolateEvent.listen((e) {
switch (e.kind) {
case EventKind.kServiceExtensionAdded:
registeredServices.add(e.extensionRPC!);
}
}),
]);

await [
vmService.streamListen(EventStreams.kExtension),
vmService.streamListen(EventStreams.kIsolate),
vmService.streamListen(EventStreams.kStderr),
vmService.streamListen(EventStreams.kService),
].wait;
Expand Down Expand Up @@ -1176,6 +1183,7 @@ class _AppListener {
await Future.wait(_subscriptions.map((s) => s.cancel()));
try {
await _vmService.streamCancel(EventStreams.kExtension);
await _vmService.streamCancel(EventStreams.kIsolate);
await _vmService.streamCancel(EventStreams.kStderr);
await _vmService.streamCancel(EventStreams.kService);
} on RPCError catch (_) {
Expand Down
32 changes: 29 additions & 3 deletions pkgs/dart_mcp_server/lib/src/mixins/prompts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import 'dart:async';
import 'package:dart_mcp/server.dart';
import 'package:meta/meta.dart';

import '../utils/constants.dart';

/// A mixin which adds support for various dart and flutter specific prompts.
base mixin DashPrompts on PromptsSupport {
@override
Expand All @@ -17,12 +19,19 @@ base mixin DashPrompts on PromptsSupport {

/// Creates the flutter driver user journey prompt based on a request.
GetPromptResult _flutterDriverUserJourneyPrompt(GetPromptRequest request) {
final userJourney =
request.arguments?[ParameterNames.userJourney] as String?;
return GetPromptResult(
messages: [
PromptMessage(
role: Role.user,
content: flutterDriverUserJourneyPromptContent,
),
if (userJourney != null)
PromptMessage(
role: Role.user,
content: Content.text(text: 'The user journey is:\n$userJourney'),
),
],
);
}
Expand All @@ -36,6 +45,14 @@ Prompts the LLM to attempt to accomplish a user journey in the running app using
flutter driver. If successful, it will then translate the steps it followed into
a flutter driver test and write that to disk.
''',
arguments: [
PromptArgument(
name: ParameterNames.userJourney,
title: 'User Journey',
description: 'The user journey to perform and write a test for.',
required: false,
),
],
);

@visibleForTesting
Expand All @@ -52,9 +69,18 @@ Perform the following tasks in order:
reading in files to accomplish this task, just inspect the live state of the
app and widget tree. If you get stuck, feel free to ask the user for help.
- If you are able to successfully complete the journey, then create a flutter
driver based test with an appropriate name, which performs all the same
actions that you performed. Include the original user journey as a comment
in the test file.
driver based test with an appropriate name under the integration_test
directory. The test should perform all the successful actions that you
performed to complete the task and validate the result. Include the
original user journey as a comment above the test function, or reference the
file the user journey is defined in if it came from a file. Note that
flutter_driver tests are NOT allowed to import package:flutter_test, they MUST
use package:test. Importing package:flutter_test will cause very confusing
errors and waste my time. Also, when creating variables that you will assign
in a setUp or setUpAll function, they must be late (preferred) or nullable.
- After writing the test, first analyze the project for errors, and format it.
- Next, execute the test using the command `flutter drive --driver <test-path>`
and verify that it passes.
''',
);
}
1 change: 1 addition & 0 deletions pkgs/dart_mcp_server/lib/src/utils/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ extension ParameterNames on Never {
static const testRunnerArgs = 'testRunnerArgs';
static const uri = 'uri';
static const uris = 'uris';
static const userJourney = 'user_journey';
}

/// A shared success response for tools.
Expand Down
87 changes: 67 additions & 20 deletions pkgs/dart_mcp_server/test/tools/prompts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import 'package:dart_mcp/server.dart';
import 'package:dart_mcp_server/src/mixins/prompts.dart';
import 'package:dart_mcp_server/src/utils/constants.dart';
import 'package:test/test.dart';

import '../test_harness.dart';
Expand All @@ -23,29 +24,75 @@ void main() {
expect(
promptsResult.prompts,
equals([
isA<GetPromptRequest>().having(
(p) => p.name,
'name',
DashPrompts.flutterDriverUserJourneyTest.name,
),
isA<Prompt>()
.having(
(p) => p.name,
'name',
DashPrompts.flutterDriverUserJourneyTest.name,
)
.having(
(p) => p.arguments,
'arguments',
equals([
isA<PromptArgument>()
.having(
(arg) => arg.name,
'name',
ParameterNames.userJourney,
)
.having((arg) => arg.required, 'required', false),
]),
),
]),
);
});

test('can get the flutter driver user journey prompt', () async {
final server = testHarness.mcpServerConnection;
final prompt = await server.getPrompt(
GetPromptRequest(name: DashPrompts.flutterDriverUserJourneyTest.name),
);
expect(
prompt.messages.single,
isA<PromptMessage>()
.having((m) => m.role, 'role', Role.user)
.having(
(m) => m.content,
'content',
equals(DashPrompts.flutterDriverUserJourneyPromptContent),
),
);
group('Can get the flutter driver user journey prompt ', () {
test(' with no arguments', () async {
final server = testHarness.mcpServerConnection;
final prompt = await server.getPrompt(
GetPromptRequest(name: DashPrompts.flutterDriverUserJourneyTest.name),
);
expect(
prompt.messages.single,
isA<PromptMessage>()
.having((m) => m.role, 'role', Role.user)
.having(
(m) => m.content,
'content',
equals(DashPrompts.flutterDriverUserJourneyPromptContent),
),
);
});

test('with a user journey arguments', () async {
final server = testHarness.mcpServerConnection;
final userJourney = 'A really sick user journey';
final prompt = await server.getPrompt(
GetPromptRequest(
name: DashPrompts.flutterDriverUserJourneyTest.name,
arguments: {ParameterNames.userJourney: userJourney},
),
);
expect(
prompt.messages,
equals([
isA<PromptMessage>()
.having((m) => m.role, 'role', Role.user)
.having(
(m) => m.content,
'content',
equals(DashPrompts.flutterDriverUserJourneyPromptContent),
),
isA<PromptMessage>()
.having((m) => m.role, 'role', Role.user)
.having(
(m) => (m.content as TextContent).text,
'content.text',
contains(userJourney),
),
]),
);
});
});
}