diff --git a/pkgs/dart_mcp/CHANGELOG.md b/pkgs/dart_mcp/CHANGELOG.md index a7ab42a9..80ad8db7 100644 --- a/pkgs/dart_mcp/CHANGELOG.md +++ b/pkgs/dart_mcp/CHANGELOG.md @@ -7,7 +7,7 @@ - This package still allows `reject` and treats it as an alias for`decline`. - The old `reject` enum value was replaced with a static constant equal exactly to `decline`, so switches are not affected. - +- Add `title` parameter to `Prompt` constructor. ## 0.3.2 diff --git a/pkgs/dart_mcp/lib/src/api/api.dart b/pkgs/dart_mcp/lib/src/api/api.dart index bdb86474..8e0b6e2a 100644 --- a/pkgs/dart_mcp/lib/src/api/api.dart +++ b/pkgs/dart_mcp/lib/src/api/api.dart @@ -503,15 +503,6 @@ extension type ResourceLink.fromMap(Map _value) return type; } - /// The name of the resource. - String get name { - final name = _value['name'] as String?; - if (name == null) { - throw ArgumentError('Missing name field in $ResourceLink.'); - } - return name; - } - /// The description of the resource. String get description { final description = _value['description'] as String?; diff --git a/pkgs/dart_mcp/lib/src/api/prompts.dart b/pkgs/dart_mcp/lib/src/api/prompts.dart index 90126eca..80618d55 100644 --- a/pkgs/dart_mcp/lib/src/api/prompts.dart +++ b/pkgs/dart_mcp/lib/src/api/prompts.dart @@ -90,10 +90,12 @@ extension type Prompt.fromMap(Map _value) implements BaseMetadata { factory Prompt({ required String name, + String? title, String? description, List? arguments, }) => Prompt.fromMap({ 'name': name, + if (title != null) 'title': title, if (description != null) 'description': description, if (arguments != null) 'arguments': arguments, }); diff --git a/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart b/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart new file mode 100644 index 00000000..2a2efd98 --- /dev/null +++ b/pkgs/dart_mcp_server/lib/src/mixins/prompts.dart @@ -0,0 +1,60 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:dart_mcp/server.dart'; +import 'package:meta/meta.dart'; + +/// A mixin which adds support for various dart and flutter specific prompts. +base mixin DashPrompts on PromptsSupport { + @override + FutureOr initialize(InitializeRequest request) { + addPrompt(flutterDriverUserJourneyTest, _flutterDriverUserJourneyPrompt); + return super.initialize(request); + } + + /// Creates the flutter driver user journey prompt based on a request. + GetPromptResult _flutterDriverUserJourneyPrompt(GetPromptRequest request) { + return GetPromptResult( + messages: [ + PromptMessage( + role: Role.user, + content: flutterDriverUserJourneyPromptContent, + ), + ], + ); + } + + @visibleForTesting + static final flutterDriverUserJourneyTest = Prompt( + name: 'flutter_driver_user_journey_test', + title: 'User journey flutter driver test', + description: ''' +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. +''', + ); + + @visibleForTesting + static final flutterDriverUserJourneyPromptContent = Content.text( + text: ''' +Perform the following tasks in order: + +- Prompt the user to navigate to the home page of the app. +- Prompt the user for a user journey that they would like to write a test for. +- Attempt to complete the given user journey using flutter driver to inspect the + widget tree and interact with the application. Only durable interactions + should be performed, do not use temporary IDs to select or interact with + widgets, but instead select them based on text, type, tooltip, etc. Avoid + 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. +''', + ); +} diff --git a/pkgs/dart_mcp_server/lib/src/server.dart b/pkgs/dart_mcp_server/lib/src/server.dart index 382730fa..7cc33980 100644 --- a/pkgs/dart_mcp_server/lib/src/server.dart +++ b/pkgs/dart_mcp_server/lib/src/server.dart @@ -19,6 +19,7 @@ import 'arg_parser.dart'; import 'mixins/analyzer.dart'; import 'mixins/dash_cli.dart'; import 'mixins/dtd.dart'; +import 'mixins/prompts.dart'; import 'mixins/pub.dart'; import 'mixins/pub_dev_search.dart'; import 'mixins/roots_fallback_support.dart'; @@ -39,7 +40,9 @@ final class DartMCPServer extends MCPServer DashCliSupport, PubSupport, PubDevSupport, - DartToolingDaemonSupport + DartToolingDaemonSupport, + PromptsSupport, + DashPrompts implements AnalyticsSupport, ProcessManagerSupport, diff --git a/pkgs/dart_mcp_server/pubspec.yaml b/pkgs/dart_mcp_server/pubspec.yaml index 40f054d7..c7346639 100644 --- a/pkgs/dart_mcp_server/pubspec.yaml +++ b/pkgs/dart_mcp_server/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: args: ^2.7.0 async: ^2.13.0 collection: ^1.19.1 - dart_mcp: ^0.3.2 + dart_mcp: ^0.3.3 dds_service_extensions: ^2.0.1 devtools_shared: ^12.0.0 dtd: ^4.0.0 diff --git a/pkgs/dart_mcp_server/test/tools/prompts_test.dart b/pkgs/dart_mcp_server/test/tools/prompts_test.dart new file mode 100644 index 00000000..3f4b6bca --- /dev/null +++ b/pkgs/dart_mcp_server/test/tools/prompts_test.dart @@ -0,0 +1,51 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:dart_mcp/server.dart'; +import 'package:dart_mcp_server/src/mixins/prompts.dart'; +import 'package:test/test.dart'; + +import '../test_harness.dart'; + +void main() { + late TestHarness testHarness; + + // TODO: Use setUpAll, currently this fails due to an apparent TestProcess + // issue. + setUp(() async { + testHarness = await TestHarness.start(); + }); + + test('can list prompts', () async { + final server = testHarness.mcpServerConnection; + final promptsResult = await server.listPrompts(ListPromptsRequest()); + expect( + promptsResult.prompts, + equals([ + isA().having( + (p) => p.name, + 'name', + DashPrompts.flutterDriverUserJourneyTest.name, + ), + ]), + ); + }); + + 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() + .having((m) => m.role, 'role', Role.user) + .having( + (m) => m.content, + 'content', + equals(DashPrompts.flutterDriverUserJourneyPromptContent), + ), + ); + }); +}