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);