don't bail early when running in multiple roots (#218)
Previously if a tool was ran in multiple roots and one of them failed, it would immediately return with just that failure output.
diff --git a/pkgs/dart_mcp_server/CHANGELOG.md b/pkgs/dart_mcp_server/CHANGELOG.md
index 4b9e419..9ae564a 100644
--- a/pkgs/dart_mcp_server/CHANGELOG.md
+++ b/pkgs/dart_mcp_server/CHANGELOG.md
@@ -1,4 +1,9 @@
-# 0.1.0 (Dart SDK 3.8.0) - WP
+# 0.1.1 (Dart SDK 3.10.0) - WIP
+
+* Change tools that accept multiple roots to not return immediately on the first
+ failure.
+
+# 0.1.0 (Dart SDK 3.9.0)
* Add documentation/homepage/repository links to pub results.
* Handle relative paths under roots without trailing slashes.
diff --git a/pkgs/dart_mcp_server/lib/src/server.dart b/pkgs/dart_mcp_server/lib/src/server.dart
index b874182..27374ed 100644
--- a/pkgs/dart_mcp_server/lib/src/server.dart
+++ b/pkgs/dart_mcp_server/lib/src/server.dart
@@ -58,7 +58,7 @@
}) : super.fromStreamChannel(
implementation: Implementation(
name: 'dart and flutter tooling',
- version: '0.1.0',
+ version: '0.1.1',
),
instructions:
'This server helps to connect Dart and Flutter developers to '
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..af74733 100644
--- a/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
+++ b/pkgs/dart_mcp_server/lib/src/utils/cli_utils.dart
@@ -96,6 +96,7 @@
}
final outputs = <Content>[];
+ var isError = false;
for (var rootConfig in rootConfigs) {
final result = await runCommandInRoot(
request,
@@ -109,10 +110,10 @@
defaultPaths: defaultPaths,
sdk: sdk,
);
- if (result.isError == true) return result;
+ isError = isError || result.isError == true;
outputs.addAll(result.content);
}
- return CallToolResult(content: outputs);
+ return CallToolResult(content: outputs, isError: isError);
}
/// Runs [commandForRoot] in a single project root specified in the
@@ -233,7 +234,7 @@
TextContent(
text:
'$commandDescription failed in ${projectRoot.path}:\n'
- '$output\n\nErrors\n$errors',
+ '$output${errors.isEmpty ? '' : '\nErrors:\n$errors'}',
),
],
isError: true,
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 0ee5168..b38f092 100644
--- a/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
+++ b/pkgs/dart_mcp_server/test/utils/cli_utils_test.dart
@@ -195,39 +195,49 @@
});
group('cannot run commands', () {
- final processManager = FakeProcessManager();
-
test('with roots outside of known roots', () async {
- for (var invalidRoot in ['file:///bar/', 'file:///foo/../bar/']) {
- final result = await runCommandInRoots(
- CallToolRequest(
- name: 'foo',
- arguments: {
- ParameterNames.roots: [
- {ParameterNames.root: invalidRoot},
- ],
- },
- ),
- commandForRoot: (_, _, _) => 'fake',
- commandDescription: '',
- processManager: processManager,
- knownRoots: [Root(uri: 'file:///foo/')],
- fileSystem: fileSystem,
- sdk: Sdk(),
- );
- expect(result.isError, isTrue);
- expect(
- result.content.single,
- isA<TextContent>().having(
- (t) => t.text,
- 'text',
- allOf(contains('Invalid root $invalidRoot')),
- ),
- );
- }
+ final processManager = TestProcessManager();
+ final invalidRoots = ['file:///bar/', 'file:///foo/../bar/'];
+ final allRoots = ['file:///foo/', ...invalidRoots];
+ final result = await runCommandInRoots(
+ CallToolRequest(
+ name: 'foo',
+ arguments: {
+ ParameterNames.roots: [
+ for (var root in allRoots) {ParameterNames.root: root},
+ ],
+ },
+ ),
+ commandForRoot: (_, _, _) => 'testProcess',
+ commandDescription: 'Test process',
+ processManager: processManager,
+ knownRoots: [Root(uri: 'file:///foo/')],
+ fileSystem: fileSystem,
+ sdk: Sdk(),
+ );
+ expect(result.isError, isTrue);
+ expect(
+ result.content,
+ unorderedEquals([
+ for (var root in invalidRoots)
+ isA<TextContent>().having(
+ (t) => t.text,
+ 'text',
+ contains('Invalid root $root'),
+ ),
+ for (var root in allRoots)
+ if (!invalidRoots.contains(root))
+ isA<TextContent>().having(
+ (t) => t.text,
+ 'text',
+ allOf(contains('Test process'), contains(Uri.parse(root).path)),
+ ),
+ ]),
+ );
});
test('with paths outside of known roots', () async {
+ final processManager = FakeProcessManager();
final result = await runCommandInRoots(
CallToolRequest(
name: 'foo',