diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a10938e..36d592d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -107,6 +107,8 @@ and tool object fields. - Rejected non-JSON values in JSON-RPC envelope and remaining typed result metadata fields. +- Rejected non-JSON JSON-RPC error `data` values at parse and serialize + boundaries. - Prevented stateless MCP 2026 clients from sending core request and notification methods removed from that protocol revision. - Rejected server-initiated JSON-RPC requests received by stateless MCP 2026 diff --git a/lib/src/types/json_rpc.dart b/lib/src/types/json_rpc.dart index a4057710..4e1fa418 100644 --- a/lib/src/types/json_rpc.dart +++ b/lib/src/types/json_rpc.dart @@ -583,13 +583,15 @@ class JsonRpcErrorData { JsonRpcErrorData( code: json['code'] as int, message: json['message'] as String, - data: json['data'], + data: json.containsKey('data') + ? readJsonValue(json['data'], 'JsonRpcErrorData.data') + : null, ); Map toJson() => { 'code': code, 'message': message, - if (data != null) 'data': data, + if (data != null) 'data': readJsonValue(data, 'JsonRpcErrorData.data'), }; } diff --git a/test/types_edge_cases_test.dart b/test/types_edge_cases_test.dart index ea4723ee..b28556b8 100644 --- a/test/types_edge_cases_test.dart +++ b/test/types_edge_cases_test.dart @@ -53,6 +53,33 @@ void main() { final restored = JsonRpcErrorData.fromJson(json); expect(restored.data['nested']['level'], equals(2)); }); + + test('JsonRpcErrorData rejects non-JSON data values', () { + expect( + () => const JsonRpcErrorData( + code: -32600, + message: 'Bad data', + data: {'bad': Object()}, + ).toJson(), + throwsA(isA()), + ); + expect( + () => const JsonRpcErrorData( + code: -32600, + message: 'Bad number', + data: {'score': double.infinity}, + ).toJson(), + throwsA(isA()), + ); + expect( + () => JsonRpcErrorData.fromJson({ + 'code': -32600, + 'message': 'Bad data', + 'data': {'bad': Object()}, + }), + throwsA(isA()), + ); + }); }); group('JsonRpcCancelledNotification Edge Cases', () {