diff --git a/lib/src/mcp/mcp_server.dart b/lib/src/mcp/mcp_server.dart index 7d698ac5c..78d6fbc56 100644 --- a/lib/src/mcp/mcp_server.dart +++ b/lib/src/mcp/mcp_server.dart @@ -174,6 +174,14 @@ If is omitted, then core will be selected. 'Target directory path (defaults to current directory). ' 'Can be absolute or relative path to project root.', ), + 'paths': ListSchema( + description: + 'Test files or directories to run, relative to the project ' + "root (e.g. ['test/src/foo_test.dart', 'test/widgets']). " + 'When omitted, the whole suite runs. Note that targeting ' + 'specific paths disables the test optimization step.', + items: StringSchema(), + ), 'dart': BooleanSchema( description: '''Whether to run Dart tests. If not specified, Flutter tests will be run if a Flutter project is detected.''', @@ -452,6 +460,13 @@ Only one value can be selected. ]); } + // Positional test targets go last, after every option, so that they are + // parsed as `rest` rather than as a value for the preceding option. + final paths = args['paths'] as List?; + if (paths != null) { + cliArgs.addAll(paths.cast()); + } + return cliArgs; } diff --git a/test/src/mcp/mcp_server_test.dart b/test/src/mcp/mcp_server_test.dart index 6c36ce9a3..43af7d632 100644 --- a/test/src/mcp/mcp_server_test.dart +++ b/test/src/mcp/mcp_server_test.dart @@ -502,6 +502,69 @@ void main() { as List; expect(capturedArgs, equals(['test', '--timeout', '120'])); }); + + test('passes paths as positional test targets', () async { + await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: { + 'paths': ['test/src/foo_test.dart', 'test/widgets'], + }, + ), + ), + ); + + final capturedArgs = + verify(() => mockCommandRunner.run(captureAny())).captured.first + as List; + expect( + capturedArgs, + equals(['test', 'test/src/foo_test.dart', 'test/widgets']), + ); + }); + + test('passes paths after options so they are parsed as rest', () async { + await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: { + 'dart': true, + 'concurrency': '8', + 'paths': ['test/src/foo_test.dart'], + }, + ), + ), + ); + + final capturedArgs = + verify(() => mockCommandRunner.run(captureAny())).captured.first + as List; + expect( + capturedArgs, + equals(['dart', 'test', '-j', '8', 'test/src/foo_test.dart']), + ); + }); + + test('adds no positional targets when paths is empty', () async { + await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: {'paths': []}, + ), + ), + ); + + final capturedArgs = + verify(() => mockCommandRunner.run(captureAny())).captured.first + as List; + expect(capturedArgs, equals(['test'])); + }); }); group('Tool: packages_get', () {