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)