Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
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
2 changes: 2 additions & 0 deletions pkgs/dart_mcp/test/server/tools_support_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ void main() {
emitsInOrder([
ToolListChangedNotification(),
ToolListChangedNotification(),
null,
]),
);

Expand All @@ -67,6 +68,7 @@ void main() {
);

server.unregisterTool('foo');
server.sendNotification(ToolListChangedNotification.methodName);

// Give the notifications time to be received.
await pumpEventQueue();
Expand Down
Loading