Allow multiple package arguments to pub commands (#226)
diff --git a/pkgs/dart_mcp_server/CHANGELOG.md b/pkgs/dart_mcp_server/CHANGELOG.md
index 5c1088e..d9fa381 100644
--- a/pkgs/dart_mcp_server/CHANGELOG.md
+++ b/pkgs/dart_mcp_server/CHANGELOG.md
@@ -4,6 +4,7 @@
failure.
* Add failure reason field to analytics events so we can know why tool calls are
failing.
+* Allow for multiple package arguments to `pub add` and `pub remove`.
# 0.1.0 (Dart SDK 3.9.0)
@@ -54,3 +55,4 @@
* Screenshot tool disabled until
https://github.com/flutter/flutter/issues/170357 is resolved.
* Add `arg_parser.dart` public library with minimal deps to be used by the dart tool.
+
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
index 31abce9..e9bd90d 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
@@ -51,14 +51,15 @@
)..failureReason ??= CallToolFailureReason.noSuchCommand;
}
- final packageName =
- request.arguments?[ParameterNames.packageName] as String?;
- if (matchingCommand.requiresPackageName && packageName == null) {
+ final packageNames =
+ (request.arguments?[ParameterNames.packageNames] as List?)
+ ?.cast<String>();
+ if (matchingCommand.requiresPackageNames && packageNames == null) {
return CallToolResult(
content: [
TextContent(
text:
- 'Missing required argument `packageName` for the `$command` '
+ 'Missing required argument `packageNames` for the `$command` '
'command.',
),
],
@@ -68,9 +69,7 @@
return runCommandInRoots(
request,
- // TODO(https://github.com/dart-lang/ai/issues/81): conditionally use
- // flutter when appropriate.
- arguments: ['pub', command, if (packageName != null) packageName],
+ arguments: ['pub', command, if (packageNames != null) ...packageNames],
commandDescription: 'dart|flutter pub $command',
processManager: processManager,
knownRoots: await roots,
@@ -92,11 +91,17 @@
description:
'Currently only ${SupportedPubCommand.listAll} are supported.',
),
- ParameterNames.packageName: Schema.string(
- title: 'The package name to run the command for.',
+ ParameterNames.packageNames: Schema.list(
+ title: 'The package names to run the command for.',
description:
'This is required for the '
- '${SupportedPubCommand.listAllThatRequirePackageName} commands.',
+ '${SupportedPubCommand.listAllThatRequirePackageName} commands. ',
+ items: Schema.string(
+ title: 'A package to run the command for.',
+ description:
+ 'When used with "add", prefix with "dev:" to add the package '
+ 'as a dev dependency.',
+ ),
),
ParameterNames.roots: rootsSchema(),
},
@@ -110,18 +115,18 @@
// 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),
+ add(requiresPackageNames: true),
get,
// This is supported in a simplified form: `dart pub remove <package-name>`.
- remove(requiresPackageName: true),
+ remove(requiresPackageNames: true),
upgrade;
- const SupportedPubCommand({this.requiresPackageName = false});
+ const SupportedPubCommand({this.requiresPackageNames = false});
- final bool requiresPackageName;
+ final bool requiresPackageNames;
static SupportedPubCommand? fromName(String name) {
for (final command in SupportedPubCommand.values) {
@@ -138,7 +143,7 @@
static String get listAllThatRequirePackageName {
return _writeCommandsAsList(
- SupportedPubCommand.values.where((c) => c.requiresPackageName).toList(),
+ SupportedPubCommand.values.where((c) => c.requiresPackageNames).toList(),
);
}
diff --git a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
index 4b69d94..524f0e3 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
@@ -291,7 +291,7 @@
/// The schema for the `roots` parameter for any tool that accepts it.
ListSchema rootsSchema({bool supportsPaths = false}) => Schema.list(
- title: 'All projects roots to run this tool in.',
+ title: 'The project roots to run this tool in.',
items: Schema.object(
properties: {
ParameterNames.root: rootSchema,
diff --git a/pkgs/dart_mcp_server/lib/src/utils/constants.dart b/pkgs/dart_mcp_server/lib/src/utils/constants.dart
index 16b6453..eda9da1 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/constants.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/constants.dart
@@ -12,7 +12,7 @@
static const empty = 'empty';
static const line = 'line';
static const name = 'name';
- static const packageName = 'packageName';
+ static const packageNames = 'packageNames';
static const paths = 'paths';
static const platform = 'platform';
static const position = 'position';
diff --git a/pkgs/dart_mcp_server/test/tools/pub_test.dart b/pkgs/dart_mcp_server/test/tools/pub_test.dart
index 48fa6d1..a9658f7 100644
--- a/pkgs/dart_mcp_server/test/tools/pub_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/pub_test.dart
@@ -69,7 +69,7 @@
name: dartPubTool.name,
arguments: {
ParameterNames.command: 'add',
- ParameterNames.packageName: 'foo',
+ ParameterNames.packageNames: ['foo', 'bar'],
ParameterNames.roots: [
{ParameterNames.root: testRoot.uri},
],
@@ -81,7 +81,7 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: [endsWith(executableName), 'pub', 'add', 'foo'],
+ command: [endsWith(executableName), 'pub', 'add', 'foo', 'bar'],
workingDirectory: testRoot.path,
)),
]);
@@ -92,7 +92,7 @@
name: dartPubTool.name,
arguments: {
ParameterNames.command: 'remove',
- ParameterNames.packageName: 'foo',
+ ParameterNames.packageNames: ['foo', 'bar'],
ParameterNames.roots: [
{ParameterNames.root: testRoot.uri},
],
@@ -104,7 +104,13 @@
expect(result.isError, isNot(true));
expect(testProcessManager.commandsRan, [
equalsCommand((
- command: [endsWith(executableName), 'pub', 'remove', 'foo'],
+ command: [
+ endsWith(executableName),
+ 'pub',
+ 'remove',
+ 'foo',
+ 'bar',
+ ],
workingDirectory: testRoot.path,
)),
]);
@@ -214,7 +220,7 @@
});
for (final command in SupportedPubCommand.values.where(
- (c) => c.requiresPackageName,
+ (c) => c.requiresPackageNames,
)) {
test('for missing package name: $command', () async {
final request = CallToolRequest(
@@ -228,7 +234,7 @@
expect(
(result.content.single as TextContent).text,
- 'Missing required argument `packageName` for the '
+ 'Missing required argument `packageNames` for the '
'`${command.name}` command.',
);
expect(testProcessManager.commandsRan, isEmpty);