diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e4829db..3a37f452 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,6 +82,8 @@ the 2026 request metadata builder while preserving legacy metadata parsing. - Rejected negative cacheable-result `ttlMs` values during parsing instead of clamping malformed wire values to zero. +- Validated MRTR `inputResponses` as `CreateMessageResult`, `ListRootsResult`, + or `ElicitResult` instead of accepting arbitrary result objects. ## 2.2.0 diff --git a/lib/src/types/json_rpc.dart b/lib/src/types/json_rpc.dart index 747b5a15..b2098250 100644 --- a/lib/src/types/json_rpc.dart +++ b/lib/src/types/json_rpc.dart @@ -758,6 +758,12 @@ class InputResponse { } factory InputResponse.fromJson(Map json) { + if (!_isValidInputResponse(json)) { + throw const FormatException( + 'InputResponse must be a CreateMessageResult, ListRootsResult, ' + 'or ElicitResult', + ); + } return InputResponse.raw(Map.from(json)); } @@ -785,6 +791,28 @@ class InputResponse { Map toJson() => Map.from(value); } +bool _isValidInputResponse(Map json) { + return _canParseInputResponse(CreateMessageResult.fromJson, json) || + _canParseInputResponse(ListRootsResult.fromJson, json) || + _canParseInputResponse(ElicitResult.fromJson, json); +} + +bool _canParseInputResponse( + BaseResultData Function(Map json) parser, + Map json, +) { + try { + parser(json); + return true; + } on FormatException { + return false; + } on ArgumentError { + return false; + } on TypeError { + return false; + } +} + /// Result returned when a request needs extra client input before retry. class InputRequiredResult implements BaseResultData { /// Server-to-client requests the client must fulfill before retry. diff --git a/test/mcp_2026_07_28_test.dart b/test/mcp_2026_07_28_test.dart index 4620dada..93338b16 100644 --- a/test/mcp_2026_07_28_test.dart +++ b/test/mcp_2026_07_28_test.dart @@ -603,6 +603,17 @@ void main() { ), throwsFormatException, ); + expect( + () => ReadResourceRequest.fromJson( + const { + 'uri': 'file:///repo/README.md', + 'inputResponses': { + 'unknown': {'unexpected': true}, + }, + }, + ), + throwsFormatException, + ); }); test('server acknowledges subscriptions/listen with subscription id',