diff --git a/pkgs/dart_mcp/example/sampling_client.dart b/pkgs/dart_mcp/example/sampling_client.dart new file mode 100644 index 00000000..825c251f --- /dev/null +++ b/pkgs/dart_mcp/example/sampling_client.dart @@ -0,0 +1,103 @@ +// 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 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 channel, { + Sink? 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; + } +} diff --git a/pkgs/dart_mcp/example/sampling_server.dart b/pkgs/dart_mcp/example/sampling_server.dart new file mode 100644 index 00000000..97cc3510 --- /dev/null +++ b/pkgs/dart_mcp/example/sampling_server.dart @@ -0,0 +1,63 @@ +// 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 server that makes sampling requests to a client. +library; + +import 'dart:async'; +import 'dart:io' as io; + +import 'package:dart_mcp/server.dart'; +import 'package:dart_mcp/stdio.dart'; + +void main() { + // Create the server and connect it to stdio. + MCPServerWithSampling(stdioChannel(input: io.stdin, output: io.stdout)); +} + +/// This server uses the [createMessage] function to make sampling requests +/// to the client. +base class MCPServerWithSampling extends MCPServer with LoggingSupport { + MCPServerWithSampling(super.channel) + : super.fromStreamChannel( + implementation: Implementation( + name: 'An example dart server which makes sampling requests', + version: '0.1.0', + ), + instructions: 'Just respond to the requests', + ) { + // You can't make requests until after we are fully initialized. + unawaited(initialized.then((_) => _makeSamplingRequest())); + } + + /// Makes a sampling request and logs the response. + void _makeSamplingRequest() async { + // Actually send the request. + final result = await createMessage( + CreateMessageRequest( + // All of the messages to be included in the context for the sampling + // request. + messages: [ + SamplingMessage( + // The role to be assigned the message in the context. + role: Role.user, + // The actual content of the message in the context. + content: Content.text(text: 'Hello'), + ), + ], + // The maximum response size in tokens. + maxTokens: 1000, + // This controls how much additional context to include from the + // original chat in the sampling request. Note that clients may not + // respect this argument. + includeContext: IncludeContext.none, + ), + ); + // Simply log the result, the client will print this to the console. + log( + LoggingLevel.warning, + '(${result.role}): ${(result.content as TextContent).text}', + ); + } +}