Retain compatibility with 3.7 SDKs (#163)

The code will only ship with newer SDKs but keep compatibility for
easier testing from source with older SDKs.

Remove use of null aware elements.
diff --git a/pkgs/dart_mcp_server/bin/main.dart b/pkgs/dart_mcp_server/bin/main.dart
index 4fec4f0..351dc72 100644
--- a/pkgs/dart_mcp_server/bin/main.dart
+++ b/pkgs/dart_mcp_server/bin/main.dart
@@ -69,31 +69,32 @@
   );
 }
 
-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,
-    defaultsTo: false,
-    help:
-        'Forces a behavior for project roots which uses MCP tools instead '
-        'of the native MCP roots. This can be helpful for clients like '
-        'cursor which claim to have roots support but do not actually '
-        'support it.',
-  )
-  ..addFlag(help, abbr: 'h', help: 'Show usage text');
+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,
+        defaultsTo: false,
+        help:
+            'Forces a behavior for project roots which uses MCP tools instead '
+            'of the native MCP roots. This can be helpful for clients like '
+            'cursor which claim to have roots support but do not actually '
+            'support it.',
+      )
+      ..addFlag(help, abbr: 'h', help: 'Show usage text');
 
 const dartSdkOption = 'dart-sdk';
 const flutterSdkOption = 'flutter-sdk';
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart b/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
index e39ae44..10ae558 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
@@ -109,18 +109,19 @@
           log(LoggingLevel.warning, line, logger: 'DartLanguageServer');
         });
 
-    _lspConnection = Peer(lspChannel(_lspServer.stdout, _lspServer.stdin))
-      ..registerMethod(
-        lsp.Method.textDocument_publishDiagnostics.toString(),
-        _handleDiagnostics,
-      )
-      ..registerMethod(r'$/analyzerStatus', _handleAnalyzerStatus)
-      ..registerFallback((Parameters params) {
-        log(
-          LoggingLevel.debug,
-          () => 'Unhandled LSP message: ${params.method} - ${params.asMap}',
-        );
-      });
+    _lspConnection =
+        Peer(lspChannel(_lspServer.stdout, _lspServer.stdin))
+          ..registerMethod(
+            lsp.Method.textDocument_publishDiagnostics.toString(),
+            _handleDiagnostics,
+          )
+          ..registerMethod(r'$/analyzerStatus', _handleAnalyzerStatus)
+          ..registerFallback((Parameters params) {
+            log(
+              LoggingLevel.debug,
+              () => 'Unhandled LSP message: ${params.method} - ${params.asMap}',
+            );
+          });
 
     unawaited(_lspConnection.listen());
 
@@ -354,9 +355,8 @@
     diagnostics[diagnosticParams.uri] = diagnosticParams.diagnostics;
     log(LoggingLevel.debug, {
       ParameterNames.uri: diagnosticParams.uri,
-      'diagnostics': diagnosticParams.diagnostics
-          .map((d) => d.toJson())
-          .toList(),
+      'diagnostics':
+          diagnosticParams.diagnostics.map((d) => d.toJson()).toList(),
     });
   }
 
@@ -368,18 +368,16 @@
       final newRoots = await roots;
 
       final oldWorkspaceFolders = _currentWorkspaceFolders;
-      final newWorkspaceFolders = _currentWorkspaceFolders =
-          HashSet<lsp.WorkspaceFolder>(
+      final newWorkspaceFolders =
+          _currentWorkspaceFolders = HashSet<lsp.WorkspaceFolder>(
             equals: (a, b) => a.uri == b.uri,
             hashCode: (a) => a.uri.hashCode,
           )..addAll(newRoots.map((r) => r.asWorkspaceFolder));
 
-      final added = newWorkspaceFolders
-          .difference(oldWorkspaceFolders)
-          .toList();
-      final removed = oldWorkspaceFolders
-          .difference(newWorkspaceFolders)
-          .toList();
+      final added =
+          newWorkspaceFolders.difference(oldWorkspaceFolders).toList();
+      final removed =
+          oldWorkspaceFolders.difference(newWorkspaceFolders).toList();
 
       // This can happen in the case of multiple notifications in quick
       // succession, the `roots` future will complete only after the state has
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 a4337d9..a6f8bfc 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
@@ -109,9 +109,10 @@
       // Platforms are ignored for Dart, so no need to validate them.
       final invalidPlatforms = platforms.difference(_allowedFlutterPlatforms);
       if (invalidPlatforms.isNotEmpty) {
-        final plural = invalidPlatforms.length > 1
-            ? 'are not valid platforms'
-            : 'is not a valid platform';
+        final plural =
+            invalidPlatforms.length > 1
+                ? 'are not valid platforms'
+                : 'is not a valid platform';
         errors.add(
           ValidationError(
             ValidationErrorType.itemInvalid,
@@ -152,13 +153,14 @@
     return runCommandInRoot(
       request,
       arguments: commandArgs,
-      commandForRoot: (_, _, sdk) =>
-          switch (projectType) {
-                'dart' => sdk.dartExecutablePath,
-                'flutter' => sdk.flutterExecutablePath,
-                _ => StateError('Unknown project type: $projectType'),
-              }
-              as String,
+      commandForRoot:
+          (_, _, sdk) =>
+              switch (projectType) {
+                    'dart' => sdk.dartExecutablePath,
+                    'flutter' => sdk.flutterExecutablePath,
+                    _ => StateError('Unknown project type: $projectType'),
+                  }
+                  as String,
       commandDescription: '$projectType create',
       fileSystem: fileSystem,
       processManager: processManager,
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
index d94f25d..1dfffe4 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
@@ -96,8 +96,10 @@
         continue;
       }
       if (debugSession.vmServiceUri case final vmServiceUri?) {
-        final vmServiceFuture = activeVmServices[debugSession.id] =
-            vmServiceConnectUri(vmServiceUri);
+        final vmServiceFuture =
+            activeVmServices[debugSession.id] = vmServiceConnectUri(
+              vmServiceUri,
+            );
         final vmService = await vmServiceFuture;
         // Start listening for and collecting errors immediately.
         final errorService = await _AppErrorsListener.forVmService(
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/pub_dev_search.dart b/pkgs/dart_mcp_server/lib/src/mixins/pub_dev_search.dart
index 8bb836f..5b16eb4 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/pub_dev_search.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/pub_dev_search.dart
@@ -43,9 +43,11 @@
     try {
       result = jsonDecode(await _client.read(searchUrl));
 
-      final packageNames = dig<List>(result, [
-        'packages',
-      ]).take(_resultsLimit).map((p) => dig<String>(p, ['package'])).toList();
+      final packageNames =
+          dig<List>(result, ['packages'])
+              .take(_resultsLimit)
+              .map((p) => dig<String>(p, ['package']))
+              .toList();
 
       if (packageNames.isEmpty) {
         return CallToolResult(
@@ -69,17 +71,18 @@
       }
 
       // Retrieve information about all the packages in parallel.
-      final subQueryFutures = packageNames
-          .map(
-            (packageName) => (
-              versionListing: retrieve('api/packages/$packageName'),
-              score: retrieve('api/packages/$packageName/score'),
-              docIndex: retrieve(
-                'documentation/$packageName/latest/index.json',
-              ),
-            ),
-          )
-          .toList();
+      final subQueryFutures =
+          packageNames
+              .map(
+                (packageName) => (
+                  versionListing: retrieve('api/packages/$packageName'),
+                  score: retrieve('api/packages/$packageName/score'),
+                  docIndex: retrieve(
+                    'documentation/$packageName/latest/index.json',
+                  ),
+                ),
+              )
+              .toList();
 
       // Aggregate the retrieved information about each package into a
       // TextContent.
@@ -94,10 +97,11 @@
                       ?.cast<Map<String, Object?>>() ??
                   <Map<String, Object?>>[])
             if (!object.containsKey('enclosedBy'))
-              object['name'] as String: Uri.https(
-                'pub.dev',
-                'documentation/$packageName/latest/${object['href']}',
-              ).toString(),
+              object['name'] as String:
+                  Uri.https(
+                    'pub.dev',
+                    'documentation/$packageName/latest/${object['href']}',
+                  ).toString(),
         };
         results.add(
           TextContent(
@@ -108,26 +112,34 @@
                   'latest',
                   'version',
                 ]),
-                'description': ?dig<String?>(versionListing, [
-                  'latest',
-                  'pubspec',
-                  'description',
-                ]),
-                'homepage': ?dig<String?>(versionListing, [
-                  'latest',
-                  'pubspec',
-                  'homepage',
-                ]),
-                'repository': ?dig<String?>(versionListing, [
-                  'latest',
-                  'pubspec',
-                  'repository',
-                ]),
-                'documentation': ?dig<String?>(versionListing, [
-                  'latest',
-                  'pubspec',
-                  'documentation',
-                ]),
+                if (dig<String?>(versionListing, [
+                      'latest',
+                      'pubspec',
+                      'description',
+                    ])
+                    case final description?)
+                  'description': description,
+                if (dig<String?>(versionListing, [
+                      'latest',
+                      'pubspec',
+                      'homepage',
+                    ])
+                    case final homepage?)
+                  'homepage': homepage,
+                if (dig<String?>(versionListing, [
+                      'latest',
+                      'pubspec',
+                      'repository',
+                    ])
+                    case final repository?)
+                  'repository': repository,
+                if (dig<String?>(versionListing, [
+                      'latest',
+                      'pubspec',
+                      'documentation',
+                    ])
+                    case final documentation?)
+                  'documentation': documentation,
               },
               if (libraryDocs.isNotEmpty) ...{'libraries': libraryDocs},
               if (scoreResult != null) ...{
@@ -139,15 +151,19 @@
                     'downloadCount30Days',
                   ]),
                 },
-                'topics': dig<List>(scoreResult, [
-                  'tags',
-                ]).where((t) => (t as String).startsWith('topic:')).toList(),
-                'licenses': dig<List>(scoreResult, [
-                  'tags',
-                ]).where((t) => (t as String).startsWith('license')).toList(),
-                'publisher': dig<List>(scoreResult, ['tags'])
-                    .where((t) => (t as String).startsWith('publisher:'))
-                    .firstOrNull,
+                'topics':
+                    dig<List>(
+                      scoreResult,
+                      ['tags'],
+                    ).where((t) => (t as String).startsWith('topic:')).toList(),
+                'licenses':
+                    dig<List>(scoreResult, ['tags'])
+                        .where((t) => (t as String).startsWith('license'))
+                        .toList(),
+                'publisher':
+                    dig<List>(scoreResult, ['tags'])
+                        .where((t) => (t as String).startsWith('publisher:'))
+                        .firstOrNull,
               },
             }),
           ),
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/roots_fallback_support.dart b/pkgs/dart_mcp_server/lib/src/mixins/roots_fallback_support.dart
index 1c2562a..a47f1b9 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/roots_fallback_support.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/roots_fallback_support.dart
@@ -50,8 +50,8 @@
       // If the client supports roots, just use their stream (or lack thereof).
       // If they don't, use our own stream.
       _fallbackEnabled
-      ? _rootsListChangedFallbackController?.stream
-      : super.rootsListChanged;
+          ? _rootsListChangedFallbackController?.stream
+          : super.rootsListChanged;
 
   StreamController<RootsListChangedNotification>?
   _rootsListChangedFallbackController;
@@ -76,8 +76,8 @@
   @override
   Future<ListRootsResult> listRoots(ListRootsRequest request) async =>
       _fallbackEnabled
-      ? ListRootsResult(roots: _customRoots.toList())
-      : super.listRoots(request);
+          ? ListRootsResult(roots: _customRoots.toList())
+          : super.listRoots(request);
 
   /// Adds the roots in [request] the custom roots and calls [updateRoots].
   ///
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 593627d..392fc4e 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
@@ -85,8 +85,9 @@
   List<String> defaultPaths = const <String>[],
   required Sdk sdk,
 }) async {
-  var rootConfigs = (request.arguments?[ParameterNames.roots] as List?)
-      ?.cast<Map<String, Object?>>();
+  var rootConfigs =
+      (request.arguments?[ParameterNames.roots] as List?)
+          ?.cast<Map<String, Object?>>();
 
   // Default to use the known roots if none were specified.
   if (rootConfigs == null || rootConfigs.isEmpty) {
@@ -256,12 +257,13 @@
 ) async => switch (await inferProjectKind(rootUri, fileSystem)) {
   ProjectKind.dart => sdk.dartExecutablePath,
   ProjectKind.flutter => sdk.flutterExecutablePath,
-  ProjectKind.unknown => throw ArgumentError.value(
-    rootUri,
-    'rootUri',
-    'Unknown project kind at root $rootUri. All projects must have a '
-        'pubspec.',
-  ),
+  ProjectKind.unknown =>
+    throw ArgumentError.value(
+      rootUri,
+      'rootUri',
+      'Unknown project kind at root $rootUri. All projects must have a '
+          'pubspec.',
+    ),
 };
 
 /// Returns whether [uri] is under or exactly equal to [root].
diff --git a/pkgs/dart_mcp_server/pubspec.yaml b/pkgs/dart_mcp_server/pubspec.yaml
index cea8e92..b560d11 100644
--- a/pkgs/dart_mcp_server/pubspec.yaml
+++ b/pkgs/dart_mcp_server/pubspec.yaml
@@ -6,7 +6,7 @@
 publish_to: none
 
 environment:
-  sdk: ^3.8.0
+  sdk: ^3.7.0
 
 executables:
   dart_mcp_server: main
diff --git a/pkgs/dart_mcp_server/test/test_harness.dart b/pkgs/dart_mcp_server/test/test_harness.dart
index e5901c8..6e9f577 100644
--- a/pkgs/dart_mcp_server/test/test_harness.dart
+++ b/pkgs/dart_mcp_server/test/test_harness.dart
@@ -226,9 +226,8 @@
     final stdout = StreamQueue(process.stdoutStream());
     while (vmServiceUri == null && await stdout.hasNext) {
       final line = await stdout.next;
-      final serviceString = isFlutter
-          ? 'A Dart VM Service'
-          : 'The Dart VM service';
+      final serviceString =
+          isFlutter ? 'A Dart VM Service' : 'The Dart VM service';
       if (line.contains(serviceString)) {
         vmServiceUri = line
             .substring(line.indexOf('http:'))
@@ -377,10 +376,8 @@
   return dtdUri;
 }
 
-typedef ServerConnectionPair = ({
-  ServerConnection serverConnection,
-  DartMCPServer? server,
-});
+typedef ServerConnectionPair =
+    ({ServerConnection serverConnection, DartMCPServer? server});
 
 /// Starts up the [DartMCPServer] and connects [client] to it.
 ///
diff --git a/pkgs/dart_mcp_server/test/tools/dtd_test.dart b/pkgs/dart_mcp_server/test/tools/dtd_test.dart
index 0961ee9..ba07aa8 100644
--- a/pkgs/dart_mcp_server/test/tools/dtd_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/dtd_test.dart
@@ -405,14 +405,16 @@
 
             final stdin = debugSession.appProcess.stdin;
             stdin.writeln('');
-            var resources = (await serverConnection.listResources(
-              ListResourcesRequest(),
-            )).resources;
+            var resources =
+                (await serverConnection.listResources(
+                  ListResourcesRequest(),
+                )).resources;
             if (resources.runtimeErrors.isEmpty) {
               await onResourceListChanged;
-              resources = (await serverConnection.listResources(
-                ListResourcesRequest(),
-              )).resources;
+              resources =
+                  (await serverConnection.listResources(
+                    ListResourcesRequest(),
+                  )).resources;
             }
             final resource = resources.runtimeErrors.single;
 
@@ -422,9 +424,10 @@
             await serverConnection.subscribeResource(
               SubscribeRequest(uri: resource.uri),
             );
-            var originalContents = (await serverConnection.readResource(
-              ReadResourceRequest(uri: resource.uri),
-            )).contents;
+            var originalContents =
+                (await serverConnection.readResource(
+                  ReadResourceRequest(uri: resource.uri),
+                )).contents;
             final errorMatcher = isA<TextResourceContents>().having(
               (c) => c.text,
               'text',
@@ -434,9 +437,10 @@
             // re-read the resource.
             if (originalContents.isEmpty) {
               await resourceUpdatedQueue.next;
-              originalContents = (await serverConnection.readResource(
-                ReadResourceRequest(uri: resource.uri),
-              )).contents;
+              originalContents =
+                  (await serverConnection.readResource(
+                    ReadResourceRequest(uri: resource.uri),
+                  )).contents;
             }
             expect(
               originalContents.length,
@@ -456,9 +460,10 @@
             );
 
             // Should now have another error.
-            final newContents = (await serverConnection.readResource(
-              ReadResourceRequest(uri: resource.uri),
-            )).contents;
+            final newContents =
+                (await serverConnection.readResource(
+                  ReadResourceRequest(uri: resource.uri),
+                )).contents;
             expect(newContents.length, 2);
             expect(newContents.last, errorMatcher);
 
@@ -470,9 +475,10 @@
               ),
             );
 
-            final finalContents = (await serverConnection.readResource(
-              ReadResourceRequest(uri: resource.uri),
-            )).contents;
+            final finalContents =
+                (await serverConnection.readResource(
+                  ReadResourceRequest(uri: resource.uri),
+                )).contents;
             expect(finalContents, isEmpty);
           },
           onPlatform: {
diff --git a/pkgs/dart_mcp_server/test/tools/pub_dev_search_test.dart b/pkgs/dart_mcp_server/test/tools/pub_dev_search_test.dart
index 436ac6c..11feb5e 100644
--- a/pkgs/dart_mcp_server/test/tools/pub_dev_search_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/pub_dev_search_test.dart
@@ -213,8 +213,10 @@
   _FixedResponseClient(this.handler);
 
   _FixedResponseClient.withMappedResponses(Map<String, String> responses)
-    : handler = ((url) =>
-          responses[url.toString()] ?? (throw ClientException('No internet')));
+    : handler =
+          ((url) =>
+              responses[url.toString()] ??
+              (throw ClientException('No internet')));
 
   @override
   Future<String> read(Uri url, {Map<String, String>? headers}) async {
diff --git a/pkgs/dart_mcp_server/test/tools/pub_test.dart b/pkgs/dart_mcp_server/test/tools/pub_test.dart
index eea837a..841a60d 100644
--- a/pkgs/dart_mcp_server/test/tools/pub_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/pub_test.dart
@@ -28,17 +28,18 @@
     final executableName =
         '$appKind${Platform.isWindows
             ? appKind == 'dart'
-                  ? '.exe'
-                  : '.bat'
+                ? '.exe'
+                : '.bat'
             : ''}';
     group('$appKind app', () {
       // TODO: Use setUpAll, currently this fails due to an apparent TestProcess
       // issue.
       setUp(() async {
         fileSystem = MemoryFileSystem(
-          style: Platform.isWindows
-              ? FileSystemStyle.windows
-              : FileSystemStyle.posix,
+          style:
+              Platform.isWindows
+                  ? FileSystemStyle.windows
+                  : FileSystemStyle.posix,
         );
         fileSystem.file(p.join(fakeAppPath, 'pubspec.yaml'))
           ..createSync(recursive: true)