blob: 9294eb60138c5325573ff74c4df47251706b6edb [file] [edit]
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'package:dart_mcp/client.dart';
import 'package:dart_mcp/server.dart';
import 'package:stream_channel/stream_channel.dart';
import 'package:test/test.dart';
class TestEnvironment<Client extends MCPClient, Server extends MCPServer> {
/// The client side of the communication channel - the stream is the incoming
/// data and the sink is outgoing data.
final clientController = StreamController<String>();
/// The server side of the communication channel - the stream is the incoming
/// data and the sink is outgoing data.
final serverController = StreamController<String>();
late final clientChannel = StreamChannel<String>.withCloseGuarantee(
serverController.stream,
clientController.sink,
);
late final serverChannel = StreamChannel<String>.withCloseGuarantee(
clientController.stream,
serverController.sink,
);
final Client client;
late final Server server;
late final ServerConnection serverConnection;
/// Creates a [TestEnvironment], and adds a [tearDown] to shut it down
/// automatically.
///
/// You may manually shut down the environment by calling [shutdown].
TestEnvironment(
this.client,
Server Function(StreamChannel<String>) createServer, {
Sink<String>? protocolLogSink,
}) {
server = createServer(serverChannel);
serverConnection = client.connectServer(
clientChannel,
protocolLogSink: protocolLogSink,
);
addTearDown(shutdown);
}
/// Initializes the server and waits for it to receive the initialization
/// notification, then returns the original [InitializeResult] for tests
/// to inspect if desired.
Future<InitializeResult> initializeServer({
ProtocolVersion protocolVersion = ProtocolVersion.latestSupported,
}) async {
final initializeResult = await serverConnection.initialize(
InitializeRequest(
protocolVersion: protocolVersion,
capabilities: client.capabilities,
clientInfo: client.implementation,
),
);
/// Only notify initialized if we got a supported protocol version
if (initializeResult.protocolVersion?.isSupported == true) {
serverConnection.notifyInitialized(InitializedNotification());
await server.initialized;
}
return initializeResult;
}
Future<void> shutdown() async {
await client.shutdown();
await server.shutdown();
}
}
base class TestMCPClient extends MCPClient {
TestMCPClient()
: super(Implementation(name: 'test client', version: '0.1.0'));
}
base class TestMCPServer extends MCPServer {
TestMCPServer(super.channel, {super.protocolLogSink})
: super.fromStreamChannel(
implementation: Implementation(name: 'test server', version: '0.1.0'),
instructions: 'A test server',
);
}
/// Can be passed to the [TestEnvironment] as the `protocolLogSink`, to log
/// all protocol messages for debugging.
class PrintOnErrorSink implements Sink<String> {
@override
void add(String data) {
printOnFailure(data);
}
@override
void close() {}
}