From 07fba05db2763abaa21bd446dd51bc0c9890a530 Mon Sep 17 00:00:00 2001 From: "unicoderbot[bot]" <269805761+unicoderbot[bot]@users.noreply.github.com> Date: Fri, 30 Oct 2026 10:30:51 +0000 Subject: [PATCH] fix(mcp): validate check_ignore requires dart runner Closes #1779 Co-authored-by: marcossevilla --- lib/src/mcp/mcp_server.dart | 14 +++++ test/src/mcp/mcp_server_test.dart | 91 +++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/lib/src/mcp/mcp_server.dart b/lib/src/mcp/mcp_server.dart index 483494daa..f09158664 100644 --- a/lib/src/mcp/mcp_server.dart +++ b/lib/src/mcp/mcp_server.dart @@ -485,6 +485,20 @@ Only one value can be selected. Future _handleTest(CallToolRequest request) { final args = request.arguments ?? {}; + if (args['check_ignore'] == true && args['dart'] != true) { + return Future.value( + StructuredToolError( + toolName: 'test', + reason: + '"check_ignore" is only supported by the Dart test runner and ' + 'requires "dart": true. Flutter\'s test command does not accept ' + '--check-ignore; omit the argument or set "check_ignore": false ' + 'for Flutter runs.', + failureType: ToolFailureType.validation, + attemptedArguments: args, + ).toCallToolResult(), + ); + } final cliArgs = _parseTest(args); final timeoutSeconds = (args['timeout_seconds'] as num?)?.toInt(); return _runToolCommand( diff --git a/test/src/mcp/mcp_server_test.dart b/test/src/mcp/mcp_server_test.dart index 35085a3cc..6134dbe84 100644 --- a/test/src/mcp/mcp_server_test.dart +++ b/test/src/mcp/mcp_server_test.dart @@ -602,6 +602,97 @@ void main() { expect(capturedArgs, equals(['test'])); }); + group('check_ignore validation', () { + test( + 'rejects check_ignore: true without dart: true (dart omitted)', + () async { + // `--check-ignore` is only registered on `very_good dart test`. + // Forwarding it to `very_good test` (Flutter) exits with 64; the + // handler must reject the combination before invoking the runner. + final response = await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: {'check_ignore': true}, + ), + ), + ); + + expect(response['error'], isNull); + final result = CallToolResult.fromMap( + response['result'] as Map, + ); + expect(result.isError, isTrue); + final payload = _errorPayload(result); + expect(payload['failureType'], equals('validation')); + expect(payload['reason'], contains('check_ignore')); + expect(payload['reason'], contains('dart')); + verifyNever(() => mockCommandRunner.run(any())); + }, + ); + + test('rejects check_ignore: true with dart: false', () async { + final response = await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: {'check_ignore': true, 'dart': false}, + ), + ), + ); + + final result = CallToolResult.fromMap( + response['result'] as Map, + ); + expect(result.isError, isTrue); + final payload = _errorPayload(result); + expect(payload['failureType'], equals('validation')); + verifyNever(() => mockCommandRunner.run(any())); + }); + + test( + 'allows check_ignore: false on Flutter without validation error', + () async { + await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: {'check_ignore': false}, + ), + ), + ); + + final capturedArgs = + verify(() => mockCommandRunner.run(captureAny())).captured.first + as List; + expect(capturedArgs, equals(['test'])); + }, + ); + + test( + 'allows check_ignore: false on Dart without validation error', + () async { + await sendRequest( + CallToolRequest.methodName, + _params( + CallToolRequest( + name: 'test', + arguments: {'dart': true, 'check_ignore': false}, + ), + ), + ); + + final capturedArgs = + verify(() => mockCommandRunner.run(captureAny())).captured.first + as List; + expect(capturedArgs, equals(['dart', 'test'])); + }, + ); + }); + group('timeout_seconds (wall-clock)', () { // Mimics `_testCommand`'s SIGINT path: wait on // `ProcessSignalOverrides.current?.sigintWatch` and return