From 911dda0b2ef9cf7ec8d1746848894f66267f2215 Mon Sep 17 00:00:00 2001 From: d-markey Date: Sun, 26 Oct 2025 18:42:53 +0100 Subject: [PATCH] Attempt at fixing https://github.com/dart-lang/tools/issues/2202. I experienced an issue when using a MCP server (using `dart_mcp` which uses this package). Worked well on VM, but when I switched to Web, MCP initialization never returned. As it turns out, the target MCP server does not support CORS (eg. https://api.githubcopilot.com/mcp/) and it seems the error never flows back to the code that initializes the MCP server, so the corresponding future never completes. I've added a test "can send a message and receive an error" that mimics the behavior I observed: the test times out without this fix. The fix consists in forwarding the error to both the client and the server. --- pkgs/json_rpc_2/CHANGELOG.md | 6 +++ pkgs/json_rpc_2/dart_test.yaml | 7 +++ pkgs/json_rpc_2/lib/src/peer.dart | 81 ++++++++++++++++------------- pkgs/json_rpc_2/pubspec.yaml | 2 +- pkgs/json_rpc_2/test/peer_test.dart | 21 ++++++++ 5 files changed, 79 insertions(+), 38 deletions(-) create mode 100644 pkgs/json_rpc_2/dart_test.yaml diff --git a/pkgs/json_rpc_2/CHANGELOG.md b/pkgs/json_rpc_2/CHANGELOG.md index 0ab99dab8..01a65dd1c 100644 --- a/pkgs/json_rpc_2/CHANGELOG.md +++ b/pkgs/json_rpc_2/CHANGELOG.md @@ -1,3 +1,9 @@ +## 4.0.0-tentative + +* Enable tests for Web-platforms. +* Added a test for peer failure in "like a client" scenario. Without this fix, `peer.sendRequest()` never completes and the test times out. +* The fix consists in forwarding errors to both the client and the server. + ## 4.0.0 * Add custom ID generator option to clients, which allows for `String` ids. diff --git a/pkgs/json_rpc_2/dart_test.yaml b/pkgs/json_rpc_2/dart_test.yaml new file mode 100644 index 000000000..e4a0bbbc2 --- /dev/null +++ b/pkgs/json_rpc_2/dart_test.yaml @@ -0,0 +1,7 @@ +platforms: + - vm + - chrome + +compilers: + - dart2js + - dart2wasm diff --git a/pkgs/json_rpc_2/lib/src/peer.dart b/pkgs/json_rpc_2/lib/src/peer.dart index 71d9093df..e4965ed16 100644 --- a/pkgs/json_rpc_2/lib/src/peer.dart +++ b/pkgs/json_rpc_2/lib/src/peer.dart @@ -69,10 +69,11 @@ class Peer implements Client, Server { bool strictProtocolChecks = true, Object Function()? idGenerator, }) : this.withoutJson( - jsonDocument.bind(channel).transform(respondToFormatExceptions), - onUnhandledError: onUnhandledError, - strictProtocolChecks: strictProtocolChecks, - idGenerator: idGenerator); + jsonDocument.bind(channel).transform(respondToFormatExceptions), + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks, + idGenerator: idGenerator, + ); /// Creates a [Peer] that communicates using decoded messages over [_channel]. /// @@ -93,20 +94,21 @@ class Peer implements Client, Server { /// If [idGenerator] is passed, it will be called to generate an ID for each /// request. Defaults to an auto-incrementing `int`. The value returned must /// be either an `int` or `String`. - Peer.withoutJson(this._channel, - {ErrorCallback? onUnhandledError, - bool strictProtocolChecks = true, - Object Function()? idGenerator}) { + Peer.withoutJson( + this._channel, { + ErrorCallback? onUnhandledError, + bool strictProtocolChecks = true, + Object Function()? idGenerator, + }) { _server = Server.withoutJson( - StreamChannel(_serverIncomingForwarder.stream, _channel.sink), - onUnhandledError: onUnhandledError, - strictProtocolChecks: strictProtocolChecks); + StreamChannel(_serverIncomingForwarder.stream, _channel.sink), + onUnhandledError: onUnhandledError, + strictProtocolChecks: strictProtocolChecks, + ); _client = Client.withoutJson( - StreamChannel( - _clientIncomingForwarder.stream, - _channel.sink, - ), - idGenerator: idGenerator); + StreamChannel(_clientIncomingForwarder.stream, _channel.sink), + idGenerator: idGenerator, + ); } // Client methods. @@ -138,30 +140,35 @@ class Peer implements Client, Server { Future listen() { _client.listen(); _server.listen(); - _channel.stream.listen((message) { - if (message is Map) { - if (message.containsKey('result') || message.containsKey('error')) { - _clientIncomingForwarder.add(message); - } else { - _serverIncomingForwarder.add(message); - } - } else if (message is List && - message.isNotEmpty && - message.first is Map) { - if (message.first.containsKey('result') || - message.first.containsKey('error')) { - _clientIncomingForwarder.add(message); + _channel.stream.listen( + (message) { + if (message is Map) { + if (message.containsKey('result') || message.containsKey('error')) { + _clientIncomingForwarder.add(message); + } else { + _serverIncomingForwarder.add(message); + } + } else if (message is List && + message.isNotEmpty && + message.first is Map) { + if (message.first.containsKey('result') || + message.first.containsKey('error')) { + _clientIncomingForwarder.add(message); + } else { + _serverIncomingForwarder.add(message); + } } else { + // Non-Map and -List messages are ill-formed, so we pass them to the + // server since it knows how to send error responses. _serverIncomingForwarder.add(message); } - } else { - // Non-Map and -List messages are ill-formed, so we pass them to the - // server since it knows how to send error responses. - _serverIncomingForwarder.add(message); - } - }, onError: (Object error, StackTrace stackTrace) { - _serverIncomingForwarder.addError(error, stackTrace); - }, onDone: close); + }, + onError: (Object error, StackTrace stackTrace) { + _serverIncomingForwarder.addError(error, stackTrace); + _clientIncomingForwarder.addError(error, stackTrace); + }, + onDone: close, + ); return done; } diff --git a/pkgs/json_rpc_2/pubspec.yaml b/pkgs/json_rpc_2/pubspec.yaml index ad4e839c5..681a43129 100644 --- a/pkgs/json_rpc_2/pubspec.yaml +++ b/pkgs/json_rpc_2/pubspec.yaml @@ -1,5 +1,5 @@ name: json_rpc_2 -version: 4.0.0 +version: 4.0.0-tentative description: >- Utilities to write a client or server using the JSON-RPC 2.0 spec. repository: https://github.com/dart-lang/tools/tree/main/pkgs/json_rpc_2 diff --git a/pkgs/json_rpc_2/test/peer_test.dart b/pkgs/json_rpc_2/test/peer_test.dart index 0df605619..8f00dc863 100644 --- a/pkgs/json_rpc_2/test/peer_test.dart +++ b/pkgs/json_rpc_2/test/peer_test.dart @@ -45,6 +45,27 @@ void main() { peer.sendRequest('foo', {'bar': 'baz'}), completion(equals('qux'))); }); + test('can send a message and receive an error', () { + expect(outgoing.first.then((request) { + expect( + request, + equals({ + 'jsonrpc': '2.0', + 'method': 'foo', + 'params': {'bar': 'baz'}, + 'id': 0 + })); + incoming.addError(Exception('failure')); + }), completes); + + peer.listen().ignore(); + + expect( + peer.sendRequest('foo', {'bar': 'baz'}), + throwsA(isA().having(($) => $.message, 'message', + contains('client closed with pending request "foo"')))); + }); + test('can send a batch of messages and receive a batch of responses', () { expect(outgoing.first.then((request) { expect(