blob: 825c251f2e76240d0c452391df8bd2cf79bc8dba [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.
/// A client that connects to a server and uses the [SamplingSupport] mixin to
/// responds to sampling requests.
library;
import 'dart:async';
import 'dart:io';
import 'package:dart_mcp/client.dart';
import 'package:dart_mcp/stdio.dart';
import 'package:stream_channel/stream_channel.dart';
void main() async {
// Create a client, which is the top level object that manages all
// server connections.
final client = MCPClientWithSamplingSupport(
Implementation(name: 'example dart client', version: '0.1.0'),
);
print('connecting to server');
// Start the server as a separate process.
final process = await Process.start('dart', [
'run',
'example/sampling_server.dart',
]);
// Connect the client to the server.
final server = client.connectServer(
stdioChannel(input: process.stdout, output: process.stdin),
);
// When the server connection is closed, kill the process.
unawaited(server.done.then((_) => process.kill()));
print('server started');
// Initialize the server and let it know our capabilities.
print('initializing server');
final initializeResult = await server.initialize(
InitializeRequest(
protocolVersion: ProtocolVersion.latestSupported,
capabilities: client.capabilities,
clientInfo: client.implementation,
),
);
print('initialized: $initializeResult');
// Notify the server that we are initialized.
server.notifyInitialized();
print('sent initialized notification');
print('waiting for the server to send sampling requests');
}
/// A client which implements sampling support by mixing in [SamplingSupport].
///
/// This implementation just echos back the message to confirm it was received.
final class MCPClientWithSamplingSupport extends MCPClient
with SamplingSupport {
MCPClientWithSamplingSupport(super.implementation);
@override
/// To handle sampling requests, you must implement this function.
FutureOr<CreateMessageResult> handleCreateMessage(
CreateMessageRequest request,
Implementation serverInfo,
) {
// Simply echo back the message to avoid the need for API keys and actual
// model interactions.
//
// Note that in a real client, you should also ask the user to approve the
// elicitation request.
print('Received sampling request: $request');
return CreateMessageResult(
role: Role.assistant,
content: Content.text(
text:
'You asked '
'"${(request.messages.single.content as TextContent).text}"',
),
model: 'Echo bot',
);
}
/// Whenever connecting to a server, we also listen for log messages.
///
/// The server will log the responses it gets to sampling messages.
@override
ServerConnection connectServer(
StreamChannel<String> channel, {
Sink<String>? protocolLogSink,
}) {
final connection = super.connectServer(
channel,
protocolLogSink: protocolLogSink,
);
// Whenever a log message is received, print it to the console.
connection.onLog.listen((message) {
print('[${message.level}]: ${message.data}');
});
return connection;
}
}