Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion mcp_examples/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ dependencies:
args: ^2.7.0
async: ^2.13.0
cli_util: ^0.4.2
dart_mcp: ^0.2.0
dart_mcp: ^0.4.0
google_generative_ai: ^0.4.7
path: ^1.9.1
stream_channel: ^2.1.4
Expand Down
8 changes: 4 additions & 4 deletions pkgs/dart_mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
## 0.3.4-wip
## 0.4.0-wip

- Update the tool calling example to include progress notifications.
- Remove a reference to "screenshot" for a generic error that occurs for more
than just screenshots.
- Mark the "root" parameter for create_project required.
- **Breaking**: Update APIs to accept nullable parameters when the parameters
are not required for that method. This is only breaking if you override these
methods.

## 0.3.3

Expand Down
22 changes: 11 additions & 11 deletions pkgs/dart_mcp/lib/src/client/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -136,30 +136,30 @@ base class ServerConnection extends MCPBase {
///
/// This is a broadcast stream, events are not buffered and only future events
/// are given.
Stream<PromptListChangedNotification> get promptListChanged =>
Stream<PromptListChangedNotification?> get promptListChanged =>
_promptListChangedController.stream;
final _promptListChangedController =
StreamController<PromptListChangedNotification>.broadcast();
StreamController<PromptListChangedNotification?>.broadcast();

/// Emits an event any time the server notifies us of a change to the list of
/// tools it supports.
///
/// This is a broadcast stream, events are not buffered and only future events
/// are given.
Stream<ToolListChangedNotification> get toolListChanged =>
Stream<ToolListChangedNotification?> get toolListChanged =>
_toolListChangedController.stream;
final _toolListChangedController =
StreamController<ToolListChangedNotification>.broadcast();
StreamController<ToolListChangedNotification?>.broadcast();

/// Emits an event any time the server notifies us of a change to the list of
/// resources it supports.
///
/// This is a broadcast stream, events are not buffered and only future events
/// are given.
Stream<ResourceListChangedNotification> get resourceListChanged =>
Stream<ResourceListChangedNotification?> get resourceListChanged =>
_resourceListChangedController.stream;
final _resourceListChangedController =
StreamController<ResourceListChangedNotification>.broadcast();
StreamController<ResourceListChangedNotification?>.broadcast();

/// Emits an event any time the server notifies us of a change to a resource
/// that this client has subscribed to.
Expand Down Expand Up @@ -291,7 +291,7 @@ base class ServerConnection extends MCPBase {
sendRequest(CallToolRequest.methodName, request);

/// Lists all the [Resource]s from this server.
Future<ListResourcesResult> listResources(ListResourcesRequest request) =>
Future<ListResourcesResult> listResources([ListResourcesRequest? request]) =>
sendRequest(ListResourcesRequest.methodName, request);

/// Reads a [Resource] returned from the [ListResourcesResult] or matching
Expand All @@ -300,12 +300,12 @@ base class ServerConnection extends MCPBase {
sendRequest(ReadResourceRequest.methodName, request);

/// Lists all the [ResourceTemplate]s from this server.
Future<ListResourceTemplatesResult> listResourceTemplates(
ListResourceTemplatesRequest request,
) => sendRequest(ListResourceTemplatesRequest.methodName, request);
Future<ListResourceTemplatesResult> listResourceTemplates([
ListResourceTemplatesRequest? request,
]) => sendRequest(ListResourceTemplatesRequest.methodName, request);

/// Lists all the prompts from this server.
Future<ListPromptsResult> listPrompts(ListPromptsRequest request) =>
Future<ListPromptsResult> listPrompts([ListPromptsRequest? request]) =>
sendRequest(ListPromptsRequest.methodName, request);

/// Gets the requested [Prompt] from the server.
Expand Down
2 changes: 1 addition & 1 deletion pkgs/dart_mcp/lib/src/client/roots_support.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,6 @@ base mixin RootsSupport on MCPClient {
}

/// Handler for [ListRootsRequest]s - returns the available [Root]s.
FutureOr<ListRootsResult> handleListRoots(ListRootsRequest request) =>
FutureOr<ListRootsResult> handleListRoots([ListRootsRequest? request]) =>
ListRootsResult(roots: _roots.toList());
}
2 changes: 1 addition & 1 deletion pkgs/dart_mcp/lib/src/server/prompts_support.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ base mixin PromptsSupport on MCPServer {
}

/// Lists the available prompts.
ListPromptsResult _listPrompts(ListPromptsRequest request) =>
ListPromptsResult _listPrompts([ListPromptsRequest? request]) =>
ListPromptsResult(prompts: _prompts.values.toList());

/// Gets the response for a given prompt.
Expand Down
8 changes: 4 additions & 4 deletions pkgs/dart_mcp/lib/src/server/resources_support.dart
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,9 @@ base mixin ResourcesSupport on MCPServer {
}

/// Lists all the [ResourceTemplate]s currently available.
ListResourceTemplatesResult _listResourceTemplates(
ListResourceTemplatesRequest request,
) {
ListResourceTemplatesResult _listResourceTemplates([
ListResourceTemplatesRequest? request,
]) {
return ListResourceTemplatesResult(
resourceTemplates: [
for (var descriptor in _resourceTemplates) descriptor.template,
Expand Down Expand Up @@ -191,7 +191,7 @@ base mixin ResourcesSupport on MCPServer {
}

/// Lists all the resources currently available.
ListResourcesResult _listResources(ListResourcesRequest request) {
ListResourcesResult _listResources(ListResourcesRequest? request) {
return ListResourcesResult(resources: _resources.values.toList());
}

Expand Down
16 changes: 8 additions & 8 deletions pkgs/dart_mcp/lib/src/server/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ part 'tools_support.dart';
abstract base class MCPServer extends MCPBase {
/// Completes when this server has finished initialization and gotten the
/// final ack from the client.
Future<void> get initialized => _initialized.future;
final Completer<void> _initialized = Completer<void>();
Future<InitializedNotification?> get initialized => _initialized.future;
final Completer<InitializedNotification?> _initialized = Completer();

/// Whether this server is still active and has completed initialization.
bool get ready => isActive && _initialized.isCompleted;
Expand Down Expand Up @@ -65,9 +65,9 @@ abstract base class MCPServer extends MCPBase {
///
/// This is a broadcast stream, events are not buffered and only future events
/// are given.
Stream<RootsListChangedNotification>? get rootsListChanged =>
Stream<RootsListChangedNotification?>? get rootsListChanged =>
_rootsListChangedController?.stream;
StreamController<RootsListChangedNotification>? _rootsListChangedController;
StreamController<RootsListChangedNotification?>? _rootsListChangedController;

MCPServer.fromStreamChannel(
super.channel, {
Expand Down Expand Up @@ -106,7 +106,7 @@ abstract base class MCPServer extends MCPBase {
clientCapabilities = request.capabilities;
if (clientCapabilities.roots?.listChanged == true) {
_rootsListChangedController =
StreamController<RootsListChangedNotification>.broadcast();
StreamController<RootsListChangedNotification?>.broadcast();
registerNotificationHandler(
RootsListChangedNotification.methodName,
_rootsListChangedController!.sink.add,
Expand All @@ -128,12 +128,12 @@ abstract base class MCPServer extends MCPBase {
///
/// The server should not respond.
@mustCallSuper
void handleInitialized(InitializedNotification notification) {
_initialized.complete();
void handleInitialized([InitializedNotification? notification]) {
_initialized.complete(notification);
}

/// Lists all the root URIs from the client.
Future<ListRootsResult> listRoots(ListRootsRequest request) =>
Future<ListRootsResult> listRoots([ListRootsRequest? request]) =>
sendRequest(ListRootsRequest.methodName, request);

/// A request to prompt the LLM owned by the client with a message.
Expand Down
4 changes: 1 addition & 3 deletions pkgs/dart_mcp/lib/src/shared.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ base class MCPBase {
void Function(T) impl,
) => _peer.registerMethod(
name,
(Parameters p) => impl(
(p.value as Map? ?? <String, Object?>{}).cast<String, Object?>() as T,
),
(Parameters? p) => impl((p?.value as Map?)?.cast<String, Object?>() as T),
);

/// Sends a notification to the peer.
Expand Down
2 changes: 1 addition & 1 deletion pkgs/dart_mcp/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: dart_mcp
version: 0.3.4-wip
version: 0.4.0-wip
description: A package for making MCP servers and clients.
repository: https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp
issue_tracker: https://github.com/dart-lang/ai/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Adart_mcp
Expand Down
6 changes: 3 additions & 3 deletions pkgs/dart_mcp/test/api/prompts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ void main() {

final serverConnection = environment.serverConnection;

final promptsResult = await serverConnection.listPrompts(
ListPromptsRequest(),
);
final promptsResult = await serverConnection.listPrompts();
expect(promptsResult.prompts, [TestMCPServerWithPrompts.greeting]);

final greetingResult = await serverConnection.getPrompt(
Expand Down Expand Up @@ -58,6 +56,7 @@ void main() {
emitsInOrder([
PromptListChangedNotification(),
PromptListChangedNotification(),
null,
]),
reason: 'We should get a notification for new and removed prompts',
);
Expand All @@ -68,6 +67,7 @@ void main() {
(_) => GetPromptResult(messages: []),
);
server.removePrompt('new prompt');
server.sendNotification(PromptListChangedNotification.methodName);
// Give the notifications a chance to propagate.
await pumpEventQueue();

Expand Down
7 changes: 6 additions & 1 deletion pkgs/dart_mcp/test/api/roots_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void main() {
final server = environment.server;
final events = StreamQueue(server.rootsListChanged!);

expect((await server.listRoots(ListRootsRequest())).roots, isEmpty);
expect((await server.listRoots()).roots, isEmpty);

final a = Root(uri: 'test://a', name: 'a');
final a2 = Root(uri: 'test://a', name: 'a2');
Expand All @@ -41,6 +41,11 @@ void main() {

expect(await events.take(2), hasLength(2));

environment.serverConnection.sendNotification(
RootsListChangedNotification.methodName,
);
expect(await events.next, isNull);

expect(
(await server.listRoots(ListRootsRequest())).roots,
unorderedEquals([a, b]),
Expand Down
52 changes: 28 additions & 24 deletions pkgs/dart_mcp/test/client_and_server_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,31 +143,35 @@ void main() {
});

test(
'server can handle initialized notification with null parameters',
'server can handle initialized notification with null or actual parameters',
() async {
final serverLog = StreamController<String>();
final environment = TestEnvironment(
TestMCPClient(),
(c) => TestMCPServer(c, protocolLogSink: serverLog.sink),
);
await environment.serverConnection.initialize(
InitializeRequest(
protocolVersion: ProtocolVersion.latestSupported,
capabilities: environment.client.capabilities,
clientInfo: environment.client.implementation,
),
);
// Send a notification that doesn't have any parameters.
environment.serverConnection.notifyInitialized();
await environment.server.initialized;
expect(
serverLog.stream,
emitsInOrder([
allOf(startsWith('<<<'), contains('initialize')),
allOf(startsWith('>>>'), contains('serverInfo')),
allOf(startsWith('<<<'), contains('notifications/initialized')),
]),
);
for (final initializedMessage in [null, InitializedNotification()]) {
final serverLog = StreamController<String>();
final environment = TestEnvironment(
TestMCPClient(),
(c) => TestMCPServer(c, protocolLogSink: serverLog.sink),
);
await environment.serverConnection.initialize(
InitializeRequest(
protocolVersion: ProtocolVersion.latestSupported,
capabilities: environment.client.capabilities,
clientInfo: environment.client.implementation,
),
);
// Send a notification that doesn't have any parameters.
environment.serverConnection.notifyInitialized(initializedMessage);
final result = await environment.server.initialized;
expect(result, initializedMessage);
expect(
serverLog.stream,
emitsInOrder([
allOf(startsWith('<<<'), contains('initialize')),
allOf(startsWith('>>>'), contains('serverInfo')),
allOf(startsWith('<<<'), contains('notifications/initialized')),
]),
);
await environment.client.shutdown();
}
},
);

Expand Down
58 changes: 30 additions & 28 deletions pkgs/dart_mcp/test/server/resources_support_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ void main() {

final serverConnection = environment.serverConnection;

final resourcesResult = await serverConnection.listResources(
ListResourcesRequest(),
);
final resourcesResult = await serverConnection.listResources();
expect(resourcesResult.resources.length, 1);

final resource = resourcesResult.resources.single;
Expand Down Expand Up @@ -127,6 +125,9 @@ void main() {
ResourceListChangedNotification(),
);

server.sendNotification(ResourceListChangedNotification.methodName);
expect(await resourceListChangedQueue.next, null);

expect(resourceListChangedQueue.hasNext, completion(false));

/// We need to manually shut down to so that the `hasNext` futures can
Expand Down Expand Up @@ -199,35 +200,36 @@ void main() {
await environment.shutdown();
});

test('Resource templates can be listed and queried', () async {
final environment = TestEnvironment(
TestMCPClient(),
(channel) => TestMCPServerWithResources(
channel,
fileContents: {'package:foo/foo.dart': 'hello world!'},
),
);
await environment.initializeServer();
test(
'Resource templates can be listed, queried, and subscribed to',
() async {
final environment = TestEnvironment(
TestMCPClient(),
(channel) => TestMCPServerWithResources(
channel,
fileContents: {'package:foo/foo.dart': 'hello world!'},
),
);
await environment.initializeServer();

final serverConnection = environment.serverConnection;
final serverConnection = environment.serverConnection;

final templatesResponse = await serverConnection.listResourceTemplates(
ListResourceTemplatesRequest(),
);
final templatesResponse = await serverConnection.listResourceTemplates();

expect(
templatesResponse.resourceTemplates.single,
TestMCPServerWithResources.packageUriTemplate,
);
expect(
templatesResponse.resourceTemplates.single,
TestMCPServerWithResources.packageUriTemplate,
);

final readResourceResponse = await serverConnection.readResource(
ReadResourceRequest(uri: 'package:foo/foo.dart'),
);
expect(
(readResourceResponse.contents.single as TextResourceContents).text,
'hello world!',
);
});
final readResourceResponse = await serverConnection.readResource(
ReadResourceRequest(uri: 'package:foo/foo.dart'),
);
expect(
(readResourceResponse.contents.single as TextResourceContents).text,
'hello world!',
);
},
);
}

final class TestMCPServerWithResources extends TestMCPServer
Expand Down
2 changes: 1 addition & 1 deletion pkgs/dart_mcp/test/server/roots_tracking_support_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ final class TestMCPClientWithRoots extends TestMCPClient with RootsSupport {
Future<void>? waitToRespond;

@override
FutureOr<ListRootsResult> handleListRoots(ListRootsRequest request) async {
FutureOr<ListRootsResult> handleListRoots([ListRootsRequest? request]) async {
await waitToRespond;
return super.handleListRoots(request);
}
Expand Down
Loading