Skip to content

Commit 405a120

Browse files
authored
add test for server closing early to validate behavior (#105)
Related to #96 - Tests the current behavior, ensures we aren't leaking exceptions beyond what is expected. - Also documents explicitly the behavior in initialize to encourage exception handling, and helping you to debug.
1 parent 3d7b99d commit 405a120

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

pkgs/dart_mcp/lib/src/client/client.dart

+5
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,11 @@ base class ServerConnection extends MCPBase {
264264
///
265265
/// The client must call [notifyInitialized] after receiving and accepting
266266
/// this response.
267+
///
268+
/// Throws a [StateError] if initialization fails for unknown reasons (usually
269+
/// the server connection closes prematurely due to misconfiguration). To
270+
/// debug these errors you should pass a `protocolLogSink` when creating these
271+
/// connections.
267272
Future<InitializeResult> initialize(InitializeRequest request) async {
268273
final response = await sendRequest<InitializeResult>(
269274
InitializeRequest.methodName,

pkgs/dart_mcp/pubspec.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ environment:
1010
dependencies:
1111
async: ^2.13.0
1212
collection: ^1.19.1
13-
json_rpc_2: ^3.0.3
13+
json_rpc_2: '>=3.0.3 <5.0.0'
1414
meta: ^1.16.0
1515
stream_channel: ^2.1.4
1616
stream_transform: ^2.1.1

pkgs/dart_mcp/test/client_and_server_test.dart

+43
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:dart_mcp/client.dart';
99
import 'package:dart_mcp/server.dart';
1010
import 'package:json_rpc_2/error_code.dart';
1111
import 'package:json_rpc_2/json_rpc_2.dart';
12+
import 'package:stream_channel/stream_channel.dart';
1213
import 'package:test/test.dart';
1314

1415
import 'test_utils.dart';
@@ -287,6 +288,48 @@ void main() {
287288
);
288289
expect(environment.initializeServer(), completes);
289290
});
291+
292+
test('server exits before initialization', () {
293+
final client = TestMCPClient();
294+
final clientController = StreamController<String>();
295+
final serverController = StreamController<String>();
296+
final clientChannel = StreamChannel<String>.withGuarantees(
297+
clientController.stream,
298+
serverController.sink,
299+
);
300+
final serverChannel = StreamChannel<String>.withGuarantees(
301+
serverController.stream,
302+
clientController.sink,
303+
);
304+
final connection = client.connectServer(clientChannel);
305+
306+
expect(
307+
connection.initialize(
308+
InitializeRequest(
309+
protocolVersion: ProtocolVersion.latestSupported,
310+
capabilities: ClientCapabilities(),
311+
clientInfo: ClientImplementation(name: '', version: ''),
312+
),
313+
),
314+
throwsA(
315+
isA<StateError>().having(
316+
(e) => e.message,
317+
'message',
318+
'The client closed with pending request "initialize".',
319+
),
320+
),
321+
);
322+
323+
// This shuts down the channel between the client and server, so it
324+
// happens during the initialization request (which the server never)
325+
// responds to.
326+
serverChannel.sink.close();
327+
328+
addTearDown(() {
329+
expect(connection.isActive, false);
330+
expect(client.connections, isEmpty);
331+
});
332+
});
290333
});
291334
}
292335

0 commit comments

Comments
 (0)