| // 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 '../utils/cli_utils.dart'; |
| import '../utils/constants.dart'; |
| import '../utils/file_system.dart'; |
| import '../utils/process_manager.dart'; |
| import '../utils/sdk.dart'; |
| |
| /// Mix this in to any MCPServer to add support for running Pub commands like |
| /// like `pub add` and `pub get`. |
| /// |
| /// See [SupportedPubCommand] for the set of currently supported pub commands. |
| /// |
| /// The MCPServer must already have the [ToolsSupport] and [LoggingSupport] |
| /// mixins applied. |
| base mixin PubSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport |
| implements ProcessManagerSupport, FileSystemSupport, SdkSupport { |
| @override |
| FutureOr<InitializeResult> initialize(InitializeRequest request) { |
| try { |
| return super.initialize(request); |
| } finally { |
| if (supportsRoots) { |
| registerTool(pubTool, _runDartPubTool); |
| } |
| } |
| } |
| |
| /// Implementation of the [pubTool]. |
| Future<CallToolResult> _runDartPubTool(CallToolRequest request) async { |
| final command = request.arguments![ParameterNames.command] as String; |
| final matchingCommand = SupportedPubCommand.fromName(command); |
| if (matchingCommand == null) { |
| return CallToolResult( |
| content: [ |
| TextContent( |
| text: |
| 'Unsupported pub command `$command`. Currently, the supported ' |
| 'commands are: ' |
| '${SupportedPubCommand.values.map((e) => e.name).join(', ')}', |
| ), |
| ], |
| isError: true, |
| ); |
| } |
| |
| final packageName = |
| request.arguments?[ParameterNames.packageName] as String?; |
| if (matchingCommand.requiresPackageName && packageName == null) { |
| return CallToolResult( |
| content: [ |
| TextContent( |
| text: |
| 'Missing required argument `packageName` for the `$command` ' |
| 'command.', |
| ), |
| ], |
| isError: true, |
| ); |
| } |
| |
| return runCommandInRoots( |
| request, |
| // TODO(https://github.com/dart-lang/ai/issues/81): conditionally use |
| // flutter when appropriate. |
| arguments: ['pub', command, if (packageName != null) packageName], |
| commandDescription: 'dart|flutter pub $command', |
| processManager: processManager, |
| knownRoots: await roots, |
| fileSystem: fileSystem, |
| sdk: sdk, |
| ); |
| } |
| |
| static final pubTool = Tool( |
| name: 'pub', |
| description: |
| 'Runs a pub command for the given project roots, like `dart pub ' |
| 'get` or `flutter pub add`.', |
| annotations: ToolAnnotations(title: 'pub', readOnlyHint: false), |
| inputSchema: Schema.object( |
| properties: { |
| ParameterNames.command: Schema.string( |
| title: 'The pub command to run.', |
| description: |
| 'Currently only ${SupportedPubCommand.listAll} are supported.', |
| ), |
| ParameterNames.packageName: Schema.string( |
| title: 'The package name to run the command for.', |
| description: |
| 'This is required for the ' |
| '${SupportedPubCommand.listAllThatRequirePackageName} commands.', |
| ), |
| ParameterNames.roots: rootsSchema(), |
| }, |
| required: [ParameterNames.command], |
| ), |
| ); |
| } |
| |
| /// The set of supported `dart pub` subcommands. |
| enum SupportedPubCommand { |
| // This is supported in a simplified form: `dart pub add <package-name>`. |
| // TODO(https://github.com/dart-lang/ai/issues/77): add support for adding |
| // dev dependencies. |
| add(requiresPackageName: true), |
| |
| get, |
| |
| // This is supported in a simplified form: `dart pub remove <package-name>`. |
| remove(requiresPackageName: true), |
| |
| upgrade; |
| |
| const SupportedPubCommand({this.requiresPackageName = false}); |
| |
| final bool requiresPackageName; |
| |
| static SupportedPubCommand? fromName(String name) { |
| for (final command in SupportedPubCommand.values) { |
| if (command.name == name) { |
| return command; |
| } |
| } |
| return null; |
| } |
| |
| static String get listAll { |
| return _writeCommandsAsList(SupportedPubCommand.values); |
| } |
| |
| static String get listAllThatRequirePackageName { |
| return _writeCommandsAsList( |
| SupportedPubCommand.values.where((c) => c.requiresPackageName).toList(), |
| ); |
| } |
| |
| static String _writeCommandsAsList(List<SupportedPubCommand> commands) { |
| final buffer = StringBuffer(); |
| for (var i = 0; i < commands.length; i++) { |
| final commandName = commands[i].name; |
| buffer.write('`$commandName`'); |
| if (i < commands.length - 2) { |
| buffer.write(', '); |
| } else if (i == commands.length - 2) { |
| buffer.write(', and '); |
| } |
| } |
| return buffer.toString(); |
| } |
| } |