Invoke dart/flutter in a more robust way (#143)

Closes https://github.com/dart-lang/ai/issues/33

Adds --dart-sdk and --flutter-sdk arguments to the CLI, falling back on DART_SDK and FLUTTER_SDK environment variables, and finally `Platform.resolvedExecutable`.

The flutter SDK will also be looked up relative to the Dart SDK, if a location is not given by command line arg or environment.
diff --git a/pkgs/dart_mcp_server/bin/main.dart b/pkgs/dart_mcp_server/bin/main.dart
index 6050251..351dc72 100644
--- a/pkgs/dart_mcp_server/bin/main.dart
+++ b/pkgs/dart_mcp_server/bin/main.dart
@@ -20,9 +20,14 @@
   }
 
   DartMCPServer? server;
-  await runZonedGuarded(
-    () async {
-      server = await DartMCPServer.connect(
+  final dartSdkPath =
+      parsedArgs.option(dartSdkOption) ?? io.Platform.environment['DART_SDK'];
+  final flutterSdkPath =
+      parsedArgs.option(flutterSdkOption) ??
+      io.Platform.environment['FLUTTER_SDK'];
+  runZonedGuarded(
+    () {
+      server = DartMCPServer(
         StreamChannel.withCloseGuarantee(io.stdin, io.stdout)
             .transform(StreamChannelTransformer.fromCodec(utf8))
             .transformStream(const LineSplitter())
@@ -34,6 +39,7 @@
               ),
             ),
         forceRootsFallback: parsedArgs.flag(forceRootsFallback),
+        sdk: Sdk.find(dartSdkPath: dartSdkPath, flutterSdkPath: flutterSdkPath),
       );
     },
     (e, s) {
@@ -65,6 +71,19 @@
 
 final argParser =
     ArgParser(allowTrailingOptions: false)
+      ..addOption(
+        dartSdkOption,
+        help:
+            'The path to the root of the desired Dart SDK. Defaults to the '
+            'DART_SDK environment variable.',
+      )
+      ..addOption(
+        flutterSdkOption,
+        help:
+            'The path to the root of the desired Flutter SDK. Defaults to '
+            'the FLUTTER_SDK environment variable, then searching up from the '
+            'Dart SDK.',
+      )
       ..addFlag(
         forceRootsFallback,
         negatable: true,
@@ -77,5 +96,7 @@
       )
       ..addFlag(help, abbr: 'h', help: 'Show usage text');
 
+const dartSdkOption = 'dart-sdk';
+const flutterSdkOption = 'flutter-sdk';
 const forceRootsFallback = 'force-roots-fallback';
 const help = 'help';
diff --git a/pkgs/dart_mcp_server/lib/dart_mcp_server.dart b/pkgs/dart_mcp_server/lib/dart_mcp_server.dart
index c1e1ead..badafe8 100644
--- a/pkgs/dart_mcp_server/lib/dart_mcp_server.dart
+++ b/pkgs/dart_mcp_server/lib/dart_mcp_server.dart
@@ -3,3 +3,4 @@
 // BSD-style license that can be found in the LICENSE file.
 
 export 'src/server.dart';
+export 'src/utils/sdk.dart' show Sdk;
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart b/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
index da870e7..10ae558 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
@@ -14,13 +14,15 @@
 
 import '../lsp/wire_format.dart';
 import '../utils/constants.dart';
+import '../utils/sdk.dart';
 
 /// Mix this in to any MCPServer to add support for analyzing Dart projects.
 ///
 /// The MCPServer must already have the [ToolsSupport] and [LoggingSupport]
 /// mixins applied.
 base mixin DartAnalyzerSupport
-    on ToolsSupport, LoggingSupport, RootsTrackingSupport {
+    on ToolsSupport, LoggingSupport, RootsTrackingSupport
+    implements SdkSupport {
   /// The LSP server connection for the analysis server.
   late final Peer _lspConnection;
 
@@ -52,9 +54,8 @@
       if (!supportsRoots)
         'Project analysis requires the "roots" capability which is not '
             'supported. Analysis tools have been disabled.',
-      if (Platform.environment['DART_SDK'] == null)
-        'Project analysis requires a "DART_SDK" environment variable to be set '
-            '(this should be the path to the root of the dart SDK). Analysis '
+      if (sdk.dartSdkPath == null)
+        'Project analysis requires a Dart SDK but none was given. Analysis '
             'tools have been disabled.',
     ];
 
@@ -90,7 +91,7 @@
   ///
   /// On failure, returns a reason for the failure.
   Future<String?> _initializeAnalyzerLspServer() async {
-    _lspServer = await Process.start('dart', [
+    _lspServer = await Process.start(sdk.dartExecutablePath, [
       'language-server',
       // Required even though it is documented as the default.
       // https://github.com/dart-lang/sdk/issues/60574
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
index 075540b..605ba1f 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
@@ -11,6 +11,7 @@
 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 Dart or Flutter CLI
 /// commands like `dart fix`, `dart format`, and `flutter test`.
@@ -18,14 +19,14 @@
 /// The MCPServer must already have the [ToolsSupport] and [LoggingSupport]
 /// mixins applied.
 base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
-    implements ProcessManagerSupport, FileSystemSupport {
+    implements ProcessManagerSupport, FileSystemSupport, SdkSupport {
   @override
   FutureOr<InitializeResult> initialize(InitializeRequest request) {
     try {
       return super.initialize(request);
     } finally {
-      // Can't call this until after `super.initialize`.
-      if (supportsRoots) {
+      // Can't call `supportsRoots` until after `super.initialize`.
+      if (supportsRoots && sdk.dartSdkPath != null) {
         registerTool(dartFixTool, _runDartFixTool);
         registerTool(dartFormatTool, _runDartFormatTool);
         registerTool(runTestsTool, _runTests);
@@ -38,12 +39,13 @@
   Future<CallToolResult> _runDartFixTool(CallToolRequest request) async {
     return runCommandInRoots(
       request,
-      commandForRoot: (_, _) => 'dart',
+      commandForRoot: (_, _, sdk) => sdk.dartExecutablePath,
       arguments: ['fix', '--apply'],
       commandDescription: 'dart fix',
       processManager: processManager,
       knownRoots: await roots,
       fileSystem: fileSystem,
+      sdk: sdk,
     );
   }
 
@@ -51,13 +53,14 @@
   Future<CallToolResult> _runDartFormatTool(CallToolRequest request) async {
     return runCommandInRoots(
       request,
-      commandForRoot: (_, _) => 'dart',
+      commandForRoot: (_, _, sdk) => sdk.dartExecutablePath,
       arguments: ['format'],
       commandDescription: 'dart format',
       processManager: processManager,
       defaultPaths: ['.'],
       knownRoots: await roots,
       fileSystem: fileSystem,
+      sdk: sdk,
     );
   }
 
@@ -70,6 +73,7 @@
       processManager: processManager,
       knownRoots: await roots,
       fileSystem: fileSystem,
+      sdk: sdk,
     );
   }
 
@@ -119,11 +123,19 @@
     return runCommandInRoot(
       request,
       arguments: commandArgs,
-      commandForRoot: (_, _) => projectType!,
+      commandForRoot:
+          (_, _, sdk) =>
+              switch (projectType) {
+                    'dart' => sdk.dartExecutablePath,
+                    'flutter' => sdk.flutterExecutablePath,
+                    _ => StateError('Unknown project type: $projectType'),
+                  }
+                  as String,
       commandDescription: '$projectType create',
       fileSystem: fileSystem,
       processManager: processManager,
       knownRoots: await roots,
+      sdk: sdk,
     );
   }
 
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
index eaca4bc..ab48500 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
@@ -10,6 +10,7 @@
 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`.
@@ -19,7 +20,7 @@
 /// The MCPServer must already have the [ToolsSupport] and [LoggingSupport]
 /// mixins applied.
 base mixin PubSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
-    implements ProcessManagerSupport, FileSystemSupport {
+    implements ProcessManagerSupport, FileSystemSupport, SdkSupport {
   @override
   FutureOr<InitializeResult> initialize(InitializeRequest request) {
     try {
@@ -79,6 +80,7 @@
       processManager: processManager,
       knownRoots: await roots,
       fileSystem: fileSystem,
+      sdk: sdk,
     );
   }
 
diff --git a/pkgs/dart_mcp_server/lib/src/server.dart b/pkgs/dart_mcp_server/lib/src/server.dart
index 072052e..d9d573d 100644
--- a/pkgs/dart_mcp_server/lib/src/server.dart
+++ b/pkgs/dart_mcp_server/lib/src/server.dart
@@ -2,14 +2,11 @@
 // 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:file/file.dart';
 import 'package:file/local.dart';
 import 'package:meta/meta.dart';
 import 'package:process/process.dart';
-import 'package:stream_channel/stream_channel.dart';
 
 import 'mixins/analyzer.dart';
 import 'mixins/dash_cli.dart';
@@ -19,6 +16,7 @@
 import 'mixins/roots_fallback_support.dart';
 import 'utils/file_system.dart';
 import 'utils/process_manager.dart';
+import 'utils/sdk.dart';
 
 /// An MCP server for Dart and Flutter tooling.
 final class DartMCPServer extends MCPServer
@@ -33,9 +31,10 @@
         PubSupport,
         PubDevSupport,
         DartToolingDaemonSupport
-    implements ProcessManagerSupport, FileSystemSupport {
+    implements ProcessManagerSupport, FileSystemSupport, SdkSupport {
   DartMCPServer(
     super.channel, {
+    required this.sdk,
     @visibleForTesting this.processManager = const LocalProcessManager(),
     @visibleForTesting this.fileSystem = const LocalFileSystem(),
     this.forceRootsFallback = false,
@@ -49,13 +48,6 @@
              'their development tools and running applications.',
        );
 
-  static Future<DartMCPServer> connect(
-    StreamChannel<String> mcpChannel, {
-    bool forceRootsFallback = false,
-  }) async {
-    return DartMCPServer(mcpChannel, forceRootsFallback: forceRootsFallback);
-  }
-
   @override
   final LocalProcessManager processManager;
 
@@ -64,4 +56,7 @@
 
   @override
   final bool forceRootsFallback;
+
+  @override
+  final Sdk sdk;
 }
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 4fce8b7..9310546 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
@@ -11,6 +11,7 @@
 import 'package:yaml/yaml.dart';
 
 import 'constants.dart';
+import 'sdk.dart';
 
 /// The supported kinds of projects.
 enum ProjectKind {
@@ -71,7 +72,7 @@
 /// root's 'paths'.
 Future<CallToolResult> runCommandInRoots(
   CallToolRequest request, {
-  FutureOr<String> Function(Root, FileSystem) commandForRoot =
+  FutureOr<String> Function(Root, FileSystem, Sdk) commandForRoot =
       defaultCommandForRoot,
   List<String> arguments = const [],
   required String commandDescription,
@@ -79,6 +80,7 @@
   required ProcessManager processManager,
   required List<Root> knownRoots,
   List<String> defaultPaths = const <String>[],
+  required Sdk sdk,
 }) async {
   var rootConfigs =
       (request.arguments?[ParameterNames.roots] as List?)
@@ -103,6 +105,7 @@
       processManager: processManager,
       knownRoots: knownRoots,
       defaultPaths: defaultPaths,
+      sdk: sdk,
     );
     if (result.isError == true) return result;
     outputs.addAll(result.content);
@@ -134,7 +137,7 @@
 Future<CallToolResult> runCommandInRoot(
   CallToolRequest request, {
   Map<String, Object?>? rootConfig,
-  FutureOr<String> Function(Root, FileSystem) commandForRoot =
+  FutureOr<String> Function(Root, FileSystem, Sdk) commandForRoot =
       defaultCommandForRoot,
   List<String> arguments = const [],
   required String commandDescription,
@@ -142,6 +145,7 @@
   required ProcessManager processManager,
   required List<Root> knownRoots,
   List<String> defaultPaths = const <String>[],
+  required Sdk sdk,
 }) async {
   rootConfig ??= request.arguments;
   final rootUriString = rootConfig?[ParameterNames.root] as String?;
@@ -185,7 +189,7 @@
   final projectRoot = fileSystem.directory(rootUri);
 
   final commandWithPaths = <String>[
-    await commandForRoot(root, fileSystem),
+    await commandForRoot(root, fileSystem, sdk),
     ...arguments,
   ];
   final paths =
@@ -240,18 +244,21 @@
 /// Returns 'dart' or 'flutter' based on the pubspec contents.
 ///
 /// Throws an [ArgumentError] if there is no pubspec.
-Future<String> defaultCommandForRoot(Root root, FileSystem fileSystem) async =>
-    switch (await inferProjectKind(root, fileSystem)) {
-      ProjectKind.dart => 'dart',
-      ProjectKind.flutter => 'flutter',
-      ProjectKind.unknown =>
-        throw ArgumentError.value(
-          root.uri,
-          'root.uri',
-          'Unknown project kind at root ${root.uri}. All projects must have a '
-              'pubspec.',
-        ),
-    };
+Future<String> defaultCommandForRoot(
+  Root root,
+  FileSystem fileSystem,
+  Sdk sdk,
+) async => switch (await inferProjectKind(root, fileSystem)) {
+  ProjectKind.dart => sdk.dartExecutablePath,
+  ProjectKind.flutter => sdk.flutterExecutablePath,
+  ProjectKind.unknown =>
+    throw ArgumentError.value(
+      root.uri,
+      'root.uri',
+      'Unknown project kind at root ${root.uri}. All projects must have a '
+          'pubspec.',
+    ),
+};
 
 /// Returns whether or not [rootUri] is an allowed root, either exactly matching
 /// or under on of the [knownRoots].
diff --git a/pkgs/dart_mcp_server/lib/src/utils/sdk.dart b/pkgs/dart_mcp_server/lib/src/utils/sdk.dart
new file mode 100644
index 0000000..f3f5523
--- /dev/null
+++ b/pkgs/dart_mcp_server/lib/src/utils/sdk.dart
@@ -0,0 +1,86 @@
+// 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:io';
+
+import 'package:path/path.dart' as p;
+
+/// An interface class that provides a single getter of type [Sdk].
+///
+/// This provides information about the Dart and Flutter sdks, if available.
+abstract interface class SdkSupport {
+  Sdk get sdk;
+}
+
+/// Information about the Dart and Flutter SDKs, if available.
+class Sdk {
+  /// The path to the root of the Dart SDK.
+  final String? dartSdkPath;
+
+  /// The path to the root of the Flutter SDK.
+  final String? flutterSdkPath;
+
+  Sdk({this.dartSdkPath, this.flutterSdkPath});
+
+  /// Creates an [Sdk] from the path to the Dart SDK.
+  ///
+  /// If no [dartSdkPath] is given, this will attempt to find one using
+  /// [Platform.resolvedExecutable], assuming that is the `dart` binary
+  /// under the `bin` dir of a Dart SDK.
+  ///
+  /// Validates that the path is valid by checking for the `version` file.
+  ///
+  /// If no [flutterSdkPath] is given, this will search up from the resolved
+  /// Dart SDK path to see if it is nested inside a Flutter SDK.
+  factory Sdk.find({String? dartSdkPath, String? flutterSdkPath}) {
+    // Assume that we are running from the Dart SDK bin dir if not given any
+    // other configuration.
+    dartSdkPath ??= p.dirname(p.dirname(Platform.resolvedExecutable));
+
+    final versionFile = dartSdkPath.child('version');
+    if (!File(versionFile).existsSync()) {
+      throw ArgumentError('Invalid Dart SDK path: $dartSdkPath');
+    }
+
+    // Check if this is nested inside a Flutter SDK.
+    if (dartSdkPath.parent case final cacheDir
+        when cacheDir.basename == 'cache' && flutterSdkPath == null) {
+      if (cacheDir.parent case final binDir when binDir.basename == 'bin') {
+        final flutterExecutable = binDir.child('flutter');
+        if (File(flutterExecutable).existsSync()) {
+          flutterSdkPath = binDir.parent;
+        }
+      }
+    }
+
+    return Sdk(dartSdkPath: dartSdkPath, flutterSdkPath: flutterSdkPath);
+  }
+
+  /// The path to the `dart` executable.
+  ///
+  /// Throws an [ArgumentError] if [dartSdkPath] is `null`.
+  String get dartExecutablePath =>
+      dartSdkPath?.child('bin').child('dart') ??
+      (throw ArgumentError(
+        'Dart SDK location unknown, try setting the DART_SDK environment '
+        'variable.',
+      ));
+
+  /// The path to the `flutter` executable.
+  ///
+  /// Throws an [ArgumentError] if [flutterSdkPath] is `null`.
+  String get flutterExecutablePath =>
+      flutterSdkPath?.child('bin').child('flutter') ??
+      (throw ArgumentError(
+        'Flutter SDK location unknown. To work on flutter projects, you must '
+        'spawn the server using `dart` from the flutter SDK and not a Dart '
+        'SDK, or set a FLUTTER_SDK environment variable.',
+      ));
+}
+
+extension on String {
+  String get basename => p.basename(this);
+  String child(String path) => p.join(this, path);
+  String get parent => p.dirname(this);
+}
diff --git a/pkgs/dart_mcp_server/test/test_harness.dart b/pkgs/dart_mcp_server/test/test_harness.dart
index 1a9bdae..a0c0f1c 100644
--- a/pkgs/dart_mcp_server/test/test_harness.dart
+++ b/pkgs/dart_mcp_server/test/test_harness.dart
@@ -11,6 +11,7 @@
 import 'package:dart_mcp_server/src/mixins/dtd.dart';
 import 'package:dart_mcp_server/src/server.dart';
 import 'package:dart_mcp_server/src/utils/constants.dart';
+import 'package:dart_mcp_server/src/utils/sdk.dart';
 import 'package:dtd/dtd.dart';
 import 'package:file/file.dart';
 import 'package:file/local.dart';
@@ -34,6 +35,7 @@
   final DartToolingMCPClient mcpClient;
   final ServerConnectionPair serverConnectionPair;
   final FileSystem fileSystem;
+  final Sdk sdk;
 
   ServerConnection get mcpServerConnection =>
       serverConnectionPair.serverConnection;
@@ -43,6 +45,7 @@
     this.serverConnectionPair,
     this.fakeEditorExtension,
     this.fileSystem,
+    this.sdk,
   );
 
   /// Starts a Dart Tooling Daemon as well as an MCP client and server, and
@@ -64,6 +67,10 @@
     bool inProcess = false,
     FileSystem? fileSystem,
   }) async {
+    final sdk = Sdk.find(
+      dartSdkPath: Platform.environment['DART_SDK'],
+      flutterSdkPath: Platform.environment['FLUTTER_SDK'],
+    );
     fileSystem ??= const LocalFileSystem();
 
     final mcpClient = DartToolingMCPClient();
@@ -73,13 +80,14 @@
       mcpClient,
       inProcess,
       fileSystem,
+      sdk,
     );
     final connection = serverConnectionPair.serverConnection;
     connection.onLog.listen((log) {
       printOnFailure('MCP Server Log: $log');
     });
 
-    final fakeEditorExtension = await FakeEditorExtension.connect();
+    final fakeEditorExtension = await FakeEditorExtension.connect(sdk);
     addTearDown(fakeEditorExtension.shutdown);
 
     return TestHarness._(
@@ -87,6 +95,7 @@
       serverConnectionPair,
       fakeEditorExtension,
       fileSystem,
+      sdk,
     );
   }
 
@@ -102,6 +111,7 @@
       appPath,
       isFlutter: isFlutter,
       args: args,
+      sdk: sdk,
     );
     await fakeEditorExtension.addDebugSession(session);
     final root = rootForPath(projectRoot);
@@ -192,15 +202,20 @@
     String appPath, {
     List<String> args = const [],
     required bool isFlutter,
+    required Sdk sdk,
   }) async {
-    final process = await TestProcess.start(isFlutter ? 'flutter' : 'dart', [
-      'run',
-      '--no${isFlutter ? '' : '-serve'}-devtools',
-      if (!isFlutter) '--enable-vm-service=0',
-      if (isFlutter) ...['-d', 'flutter-tester'],
-      appPath,
-      ...args,
-    ], workingDirectory: projectRoot);
+    final process = await TestProcess.start(
+      isFlutter ? sdk.flutterExecutablePath : sdk.dartExecutablePath,
+      [
+        'run',
+        '--no${isFlutter ? '' : '-serve'}-devtools',
+        if (!isFlutter) '--enable-vm-service=0',
+        if (isFlutter) ...['-d', 'flutter-tester'],
+        appPath,
+        ...args,
+      ],
+      workingDirectory: projectRoot,
+    );
 
     addTearDown(() async {
       await kill(process, isFlutter);
@@ -284,8 +299,10 @@
   static int get nextId => ++_nextId;
   static int _nextId = 0;
 
-  static Future<FakeEditorExtension> connect() async {
-    final dtdProcess = await TestProcess.start('dart', ['tooling-daemon']);
+  static Future<FakeEditorExtension> connect(Sdk sdk) async {
+    final dtdProcess = await TestProcess.start(sdk.dartExecutablePath, [
+      'tooling-daemon',
+    ]);
     final dtdUri = await _getDTDUri(dtdProcess);
     final dtd = await DartToolingDaemon.connect(Uri.parse(dtdUri));
     final extension = FakeEditorExtension._(dtd, dtdProcess, dtdUri);
@@ -369,6 +386,7 @@
   MCPClient client,
   bool inProcess,
   FileSystem fileSystem,
+  Sdk sdk,
 ) async {
   ServerConnection connection;
   DartMCPServer? server;
@@ -393,11 +411,12 @@
       serverChannel,
       processManager: TestProcessManager(),
       fileSystem: fileSystem,
+      sdk: sdk,
     );
     addTearDown(server.shutdown);
     connection = client.connectServer(clientChannel);
   } else {
-    connection = await client.connectStdioServer('dart', [
+    connection = await client.connectStdioServer(sdk.dartExecutablePath, [
       'pub', // Using `pub` gives us incremental compilation
       'run',
       'bin/main.dart',
@@ -470,12 +489,9 @@
     if (item.workingDirectory != value.workingDirectory) {
       return false;
     }
-    if (item.command.length != value.command.length) {
+    if (!equals(value.command).matches(item.command, matchState)) {
       return false;
     }
-    for (var i = 0; i < item.command.length; i++) {
-      if (item.command[i] != value.command[i]) return false;
-    }
     return true;
   }
 }
diff --git a/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart b/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
index 9bfdc93..98caf8d 100644
--- a/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/dart_cli_test.dart
@@ -76,7 +76,7 @@
       expect(result.isError, isNot(true));
       expect(testProcessManager.commandsRan, [
         equalsCommand((
-          command: ['dart', 'fix', '--apply'],
+          command: [endsWith('dart'), 'fix', '--apply'],
           workingDirectory: exampleFlutterAppRoot.path,
         )),
       ]);
@@ -98,7 +98,7 @@
       expect(result.isError, isNot(true));
       expect(testProcessManager.commandsRan, [
         equalsCommand((
-          command: ['dart', 'format', '.'],
+          command: [endsWith('dart'), 'format', '.'],
           workingDirectory: exampleFlutterAppRoot.path,
         )),
       ]);
@@ -123,7 +123,7 @@
       expect(result.isError, isNot(true));
       expect(testProcessManager.commandsRan, [
         equalsCommand((
-          command: ['dart', 'format', 'foo.dart', 'bar.dart'],
+          command: [endsWith('dart'), 'format', 'foo.dart', 'bar.dart'],
           workingDirectory: exampleFlutterAppRoot.path,
         )),
       ]);
@@ -154,11 +154,16 @@
       expect(result.isError, isNot(true));
       expect(testProcessManager.commandsRan, [
         equalsCommand((
-          command: ['flutter', 'test', 'foo_test.dart', 'bar_test.dart'],
+          command: [
+            endsWith('flutter'),
+            'test',
+            'foo_test.dart',
+            'bar_test.dart',
+          ],
           workingDirectory: exampleFlutterAppRoot.path,
         )),
         equalsCommand((
-          command: ['dart', 'test', 'zip_test.dart'],
+          command: [endsWith('dart'), 'test', 'zip_test.dart'],
           workingDirectory: dartCliAppRoot.path,
         )),
       ]);
@@ -180,7 +185,13 @@
 
         expect(testProcessManager.commandsRan, [
           equalsCommand((
-            command: ['dart', 'create', '--template', 'cli', 'new_app'],
+            command: [
+              endsWith('dart'),
+              'create',
+              '--template',
+              'cli',
+              'new_app',
+            ],
             workingDirectory: dartCliAppRoot.path,
           )),
         ]);
@@ -201,7 +212,13 @@
 
         expect(testProcessManager.commandsRan, [
           equalsCommand((
-            command: ['flutter', 'create', '--template', 'app', 'new_app'],
+            command: [
+              endsWith('flutter'),
+              'create',
+              '--template',
+              'app',
+              'new_app',
+            ],
             workingDirectory: exampleFlutterAppRoot.path,
           )),
         ]);
diff --git a/pkgs/dart_mcp_server/test/tools/pub_test.dart b/pkgs/dart_mcp_server/test/tools/pub_test.dart
index 8f19130..693b99e 100644
--- a/pkgs/dart_mcp_server/test/tools/pub_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/pub_test.dart
@@ -68,7 +68,7 @@
           expect(result.isError, isNot(true));
           expect(testProcessManager.commandsRan, [
             equalsCommand((
-              command: [appKind, 'pub', 'add', 'foo'],
+              command: [endsWith(appKind), 'pub', 'add', 'foo'],
               workingDirectory: fakeAppPath,
             )),
           ]);
@@ -91,7 +91,7 @@
           expect(result.isError, isNot(true));
           expect(testProcessManager.commandsRan, [
             equalsCommand((
-              command: [appKind, 'pub', 'remove', 'foo'],
+              command: [endsWith(appKind), 'pub', 'remove', 'foo'],
               workingDirectory: fakeAppPath,
             )),
           ]);
@@ -113,7 +113,7 @@
           expect(result.isError, isNot(true));
           expect(testProcessManager.commandsRan, [
             equalsCommand((
-              command: [appKind, 'pub', 'get'],
+              command: [endsWith(appKind), 'pub', 'get'],
               workingDirectory: fakeAppPath,
             )),
           ]);
@@ -135,7 +135,7 @@
           expect(result.isError, isNot(true));
           expect(testProcessManager.commandsRan, [
             equalsCommand((
-              command: [appKind, 'pub', 'upgrade'],
+              command: [endsWith(appKind), 'pub', 'upgrade'],
               workingDirectory: fakeAppPath,
             )),
           ]);
diff --git a/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart b/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
index 36b97e1..2392d30 100644
--- a/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
+++ b/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
@@ -5,6 +5,7 @@
 import 'package:dart_mcp/server.dart';
 import 'package:dart_mcp_server/src/utils/cli_utils.dart';
 import 'package:dart_mcp_server/src/utils/constants.dart';
+import 'package:dart_mcp_server/src/utils/sdk.dart';
 import 'package:file/memory.dart';
 import 'package:process/process.dart';
 import 'package:test/fake.dart';
@@ -35,12 +36,13 @@
             ],
           },
         ),
-        commandForRoot: (_, _) => 'testCommand',
+        commandForRoot: (_, _, _) => 'testCommand',
         arguments: ['a', 'b'],
         commandDescription: '',
         processManager: processManager,
         knownRoots: [Root(uri: 'file:///bar/')],
         fileSystem: fileSystem,
+        sdk: Sdk(),
       );
       expect(result.isError, isNot(true));
       expect(processManager.commandsRan, [
@@ -63,11 +65,12 @@
               ],
             },
           ),
-          commandForRoot: (_, _) => 'testCommand',
+          commandForRoot: (_, _, _) => 'testCommand',
           commandDescription: '',
           processManager: processManager,
           knownRoots: [Root(uri: 'file:///bar/')],
           fileSystem: fileSystem,
+          sdk: Sdk(),
         );
         expect(result.isError, isNot(true));
         expect(processManager.commandsRan, [
@@ -94,11 +97,12 @@
               ],
             },
           ),
-          commandForRoot: (_, _) => 'fake',
+          commandForRoot: (_, _, _) => 'fake',
           commandDescription: '',
           processManager: processManager,
           knownRoots: [Root(uri: 'file:///foo/')],
           fileSystem: fileSystem,
+          sdk: Sdk(),
         );
         expect(result.isError, isTrue);
         expect(
@@ -130,11 +134,12 @@
             ],
           },
         ),
-        commandForRoot: (_, _) => 'fake',
+        commandForRoot: (_, _, _) => 'fake',
         commandDescription: '',
         processManager: processManager,
         knownRoots: [Root(uri: 'file:///foo/')],
         fileSystem: fileSystem,
+        sdk: Sdk(),
       );
       expect(result.isError, isTrue);
       expect(