Skip to content

Retain compatibility with 3.7 SDKs #163

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Merged
merged 2 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 26 additions & 25 deletions pkgs/dart_mcp_server/bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,31 +69,32 @@ void main(List<String> args) async {
);
}

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';
Expand Down
44 changes: 21 additions & 23 deletions pkgs/dart_mcp_server/lib/src/mixins/analyzer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,18 +109,19 @@ base mixin DartAnalyzerSupport
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());

Expand Down Expand Up @@ -354,9 +355,8 @@ base mixin DartAnalyzerSupport
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(),
});
}

Expand All @@ -368,18 +368,16 @@ base mixin DartAnalyzerSupport
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
Expand Down
22 changes: 12 additions & 10 deletions pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,10 @@ base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
// 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,
Expand Down Expand Up @@ -152,13 +153,14 @@ base mixin DashCliSupport on ToolsSupport, LoggingSupport, RootsTrackingSupport
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,
Expand Down
6 changes: 4 additions & 2 deletions pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ base mixin DartToolingDaemonSupport
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(
Expand Down
110 changes: 63 additions & 47 deletions pkgs/dart_mcp_server/lib/src/mixins/pub_dev_search.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ base mixin PubDevSupport on ToolsSupport {
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(
Expand All @@ -69,17 +71,18 @@ base mixin PubDevSupport on ToolsSupport {
}

// 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.
Expand All @@ -94,10 +97,11 @@ base mixin PubDevSupport on ToolsSupport {
?.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(
Expand All @@ -108,26 +112,34 @@ base mixin PubDevSupport on ToolsSupport {
'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) ...{
Expand All @@ -139,15 +151,19 @@ base mixin PubDevSupport on ToolsSupport {
'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,
},
}),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ base mixin RootsFallbackSupport on ToolsSupport, RootsTrackingSupport {
// 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;
Expand All @@ -76,8 +76,8 @@ base mixin RootsFallbackSupport on ToolsSupport, RootsTrackingSupport {
@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].
///
Expand Down
18 changes: 10 additions & 8 deletions pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ Future<CallToolResult> runCommandInRoots(
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) {
Expand Down Expand Up @@ -256,12 +257,13 @@ Future<String> defaultCommandForRoot(
) 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].
Expand Down
2 changes: 1 addition & 1 deletion pkgs/dart_mcp_server/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: >-
publish_to: none

environment:
sdk: ^3.8.0
sdk: ^3.7.0

executables:
dart_mcp_server: main
Expand Down
Loading