Skip to content

Add some flags to example, fix version issue #127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Add server name to log messages, flush messages
  • Loading branch information
gspencergoog committed May 13, 2025
commit 5405a0eba30854c812df8886444babd8ac92a1c1
7 changes: 3 additions & 4 deletions pkgs/dart_mcp/example/workflow_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -162,13 +162,16 @@ final class WorkflowClient extends MCPClient with RootsSupport {
StreamSinkTransformer.fromHandlers(
handleData: (String data, EventSink<List<int>> innerSink) {
innerSink.add(utf8.encode(data));
// It's a log, so we want to make sure it's always up-to-date.
fileByteSink.flush();
},
handleError: (
Object error,
StackTrace stackTrace,
EventSink<List<int>> innerSink,
) {
innerSink.addError(error, stackTrace);
fileByteSink.flush();
},
handleDone: (EventSink<List<int>> innerSink) {
innerSink.close();
Expand Down Expand Up @@ -377,10 +380,6 @@ final class WorkflowClient extends MCPClient with RootsSupport {
/// Invokes a function and adds the result as context to the chat history.
Future<void> _handleFunctionCall(gemini.FunctionCall functionCall) async {
chatHistory.add(gemini.Content.model([functionCall]));
logSink?.add(
'+++ Calling function ${functionCall.name} with args: '
'${functionCall.args}\n',
);
final connection = connectionForFunction[functionCall.name]!;
final result = await connection.callTool(
CallToolRequest(name: functionCall.name, arguments: functionCall.args),
Expand Down
4 changes: 2 additions & 2 deletions pkgs/dart_mcp/lib/src/api/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ extension type Tools.fromMap(Map<String, Object?> _value) {
}
}

/// Describes the name and version of an MCP implementation.
/// Describes the name and version of an MCP client implementation.
extension type ClientImplementation.fromMap(Map<String, Object?> _value) {
factory ClientImplementation({
required String name,
Expand All @@ -306,7 +306,7 @@ extension type ClientImplementation.fromMap(Map<String, Object?> _value) {
String get version => _value['version'] as String;
}

/// Describes the name and version of an MCP implementation.
/// Describes the name and version of an MCP server implementation.
extension type ServerImplementation.fromMap(Map<String, Object?> _value) {
factory ServerImplementation({
required String name,
Expand Down
9 changes: 6 additions & 3 deletions pkgs/dart_mcp/lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,17 @@ base class ServerConnection extends MCPBase {

/// The [ServerImplementation] returned from the [initialize] request.
///
/// Only assigned after [initialize] has successfully completed.
late ServerImplementation serverInfo;
/// Only non-null after [initialize] has successfully completed.
ServerImplementation? serverInfo;

/// The [ServerCapabilities] returned from the [initialize] request.
///
/// Only assigned after [initialize] has successfully completed.
late ServerCapabilities serverCapabilities;

@override
String get name => serverInfo?.name ?? super.name;

/// Emits an event any time the server notifies us of a change to the list of
/// prompts it supports.
///
Expand Down Expand Up @@ -212,7 +215,7 @@ base class ServerConnection extends MCPBase {
registerRequestHandler(
CreateMessageRequest.methodName,
(CreateMessageRequest request) =>
samplingSupport.handleCreateMessage(request, serverInfo),
samplingSupport.handleCreateMessage(request, serverInfo!),
);
}

Expand Down
3 changes: 3 additions & 0 deletions pkgs/dart_mcp/lib/src/server/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ abstract base class MCPServer extends MCPBase {
/// Only assigned after `initialize` has been called.
late ClientCapabilities clientCapabilities;

@override
String get name => implementation.name;

/// Emits an event any time the client notifies us of a change to the list of
/// roots it supports.
///
Expand Down
80 changes: 46 additions & 34 deletions pkgs/dart_mcp/lib/src/shared.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,33 @@
// 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.

/// @docImport 'client/client.dart';
/// @docImport 'server/server.dart';
library;

import 'dart:async';

import 'package:async/async.dart' show StreamSinkTransformer;
import 'package:json_rpc_2/json_rpc_2.dart';
import 'package:meta/meta.dart';
import 'package:stream_channel/stream_channel.dart';

import 'api/api.dart';

/// Base class for both client and server implementations.
/// Base class for MCP server-related implementations.
///
/// Handles registering method and notification handlers, sending requests and
/// notifications, progress support, and any other shared functionality.
///
/// See also:
/// - [MCPServer] A base class to extend when implementing an MCP server.
/// - [ServerConnection] A class that represents an active server connection.
base class MCPBase {
final Peer _peer;
late final Peer _peer;

/// The name of the associated server.
///
/// Used to identify log messages.
String get name => 'unknown';

/// Progress controllers by token.
///
Expand All @@ -27,8 +39,8 @@ base class MCPBase {
/// Whether the connection with the peer is active.
bool get isActive => !_peer.isClosed;

MCPBase(StreamChannel<String> channel, {Sink<String>? protocolLogSink})
: _peer = Peer(_maybeForwardMessages(channel, protocolLogSink)) {
MCPBase(StreamChannel<String> channel, {Sink<String>? protocolLogSink}) {
_peer = Peer(_maybeForwardMessages(channel, protocolLogSink));
registerNotificationHandler(
ProgressNotification.methodName,
_handleProgress,
Expand Down Expand Up @@ -150,34 +162,34 @@ base class MCPBase {
PingRequest.methodName,
null,
).then((_) => true).timeout(timeout, onTimeout: () => false);
}

/// If [protocolLogSink] is non-null, emits messages to it for all messages
/// sent over [channel].
///
/// This is intended to be written to a file or emitted to a user to aid in
/// debugging protocol messages between the client and server.
StreamChannel<String> _maybeForwardMessages(
StreamChannel<String> channel,
Sink<String>? protocolLogSink,
) {
if (protocolLogSink == null) return channel;

return channel
.transformStream(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
protocolLogSink.add('<<< $data\n');
sink.add(data);
},
),
)
.transformSink(
StreamSinkTransformer.fromHandlers(
handleData: (data, sink) {
protocolLogSink.add('>>> $data\n');
sink.add(data);
},
),
);
/// If [protocolLogSink] is non-null, emits messages to it for all messages
/// sent over [channel].
///
/// This is intended to be written to a file or emitted to a user to aid in
/// debugging protocol messages between the client and server.
StreamChannel<String> _maybeForwardMessages(
StreamChannel<String> channel,
Sink<String>? protocolLogSink,
) {
if (protocolLogSink == null) return channel;

return channel
.transformStream(
StreamTransformer.fromHandlers(
handleData: (data, sink) {
protocolLogSink.add('<<< ($name) $data\n');
sink.add(data);
},
),
)
.transformSink(
StreamSinkTransformer.fromHandlers(
handleData: (data, sink) {
protocolLogSink.add('>>> ($name) $data\n');
sink.add(data);
},
),
);
}
}