Skip to content

Update spec to 2025-03-26 #70

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 13 commits into from
Apr 17, 2025
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
20 changes: 19 additions & 1 deletion pkgs/dart_mcp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,26 @@
## 0.1.1-wip
## 0.2.0-wip

- Support protocol version 2025-03-26.
- Adds support for `AudioContent`.
- Adds support for `ToolAnnotations`.
- Adds support for `ProgressNotification` messages.
- Save the `ServerCapabilities` object on the `ServerConnection` class to make
it easier to check the capabilities of the server.
- Add default version negotiation logic.
- Save the negotiated `ProtocolVersion` in a `protocolVersion` field for both
`MCPServer` and the `ServerConnection` classes.
- Automatically disconnect from servers if version negotiation fails.
- Added support for adding and listing `ResourceTemplate`s.
- Handlers have to handle their own matching of templates.
- **Breaking**: Fixed paginated result subtypes to use `nextCursor` instead of
`cursor` as the key for the next cursor.
- **Breaking**: Change the `ProgressNotification.progress` and
`ProgressNotification.total` types to `num` instead of `int` to align with the
spec.
- **Breaking**: Change the `protocolVersion` string to a `ProtocolVersion` enum,
which has all supported versions and whether or not they are supported.
- **Breaking**: Change `InitializeRequest` and `InitializeResult` to take a
`ProtocolVersion` instead of a string.

## 0.1.0

Expand Down
23 changes: 23 additions & 0 deletions pkgs/dart_mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,29 @@ Both the `MCPServer` and `MCPClient` support these.
| [Cancellation](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/utilities/cancellation/) | :x: | https://github.com/dart-lang/ai/issues/37 |
| [Progress](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/utilities/progress/) | :heavy_check_mark: | |

## Transport Mechanisms

This table describes the supported
[transport mechanisms](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports).

At its core this package is just built on streams, so any transport mechanism
can be used, but some are directly supported out of the box.

| Transport | Support | Notes |
| --- | --- | --- |
| [Stdio](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#stdio) | :heavy_check_mark: | |
| [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) | :x: | Unsupported at this time, may come in the future. |

## Batching Requests

Both the client and server support processing batch requests, but do not support
creating batch requests at this time.

## Authorization

Authorization is not supported at this time. This package is primarily targeted
at local MCP server usage for now.

## Server Capabilities

This table describes the state of implementation for the
Expand Down
6 changes: 3 additions & 3 deletions pkgs/dart_mcp/example/client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ void main() async {
print('initializing server');
final initializeResult = await server.initialize(
InitializeRequest(
protocolVersion: protocolVersion,
protocolVersion: ProtocolVersion.latestSupported,
capabilities: client.capabilities,
clientInfo: client.implementation,
),
);
print('initialized: $initializeResult');
if (initializeResult.protocolVersion != protocolVersion) {
if (initializeResult.protocolVersion != ProtocolVersion.latestSupported) {
throw StateError(
'Protocol version mismatch, expected $protocolVersion, '
'Protocol version mismatch, expected ${ProtocolVersion.latestSupported}, '
'got ${initializeResult.protocolVersion}',
);
}
Expand Down
11 changes: 6 additions & 5 deletions pkgs/dart_mcp/example/dash_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -224,15 +224,16 @@ final class DashClient extends MCPClient with RootsSupport {
for (var connection in serverConnections) {
final result = await connection.initialize(
InitializeRequest(
protocolVersion: protocolVersion,
protocolVersion: ProtocolVersion.latestSupported,
capabilities: capabilities,
clientInfo: implementation,
),
);
if (result.protocolVersion != protocolVersion) {
if (result.protocolVersion != ProtocolVersion.latestSupported) {
print(
'Protocol version mismatch, expected $protocolVersion, '
'got ${result.protocolVersion}, disconnecting from server',
'Protocol version mismatch, expected '
'${ProtocolVersion.latestSupported}, got ${result.protocolVersion}, '
'disconnecting from server',
);
await connection.shutdown();
serverConnections.remove(connection);
Expand Down Expand Up @@ -385,7 +386,7 @@ final class DashChatBotServer extends MCPServer with ToolsSupport {

final systemInstructions = gemini.Content.system('''
You are a developer assistant for Dart and Flutter apps. Your persona is a cute
blue humingbird named Dash, and you are also the mascot for the Dart and Flutter
blue hummingbird named Dash, and you are also the mascot for the Dart and Flutter
brands. Your personality is extremely cheery and bright, and your tone is always
positive.

Expand Down
114 changes: 99 additions & 15 deletions pkgs/dart_mcp/lib/src/api/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,37 @@ part 'roots.dart';
part 'sampling.dart';
part 'tools.dart';

/// The current protocol version
const protocolVersion = '2024-11-05';
/// Enum of the known protocol versions.
enum ProtocolVersion {
v2024_11_05('2024-11-05'),
v2025_03_26('2025-03-26');

const ProtocolVersion(this.versionString);

/// Returns the [ProtocolVersion] based on the [version] string, or `null` if
/// it was not recognized.
static ProtocolVersion? tryParse(String version) =>
values.firstWhereOrNull((v) => v.versionString == version);

/// The oldest version supported by the current API.
static const oldestSupported = ProtocolVersion.v2024_11_05;

/// The most recent version supported by the current API.
static const latestSupported = ProtocolVersion.v2025_03_26;

/// The version string used over the wire to identify this version.
final String versionString;

/// Whether or not this API is compatible with the current version.
///
/// **Note**: There may be extra fields included.
bool get isSupported => this >= oldestSupported && this <= latestSupported;

bool operator <(ProtocolVersion other) => index < other.index;
bool operator <=(ProtocolVersion other) => index <= other.index;
bool operator >(ProtocolVersion other) => index > other.index;
bool operator >=(ProtocolVersion other) => index >= other.index;
}

/// A progress token, used to associate progress notifications with the original
/// request.
Expand Down Expand Up @@ -136,14 +165,16 @@ extension type ProgressNotification.fromMap(Map<String, Object?> _value)

factory ProgressNotification({
required ProgressToken progressToken,
required int progress,
int? total,
required num progress,
num? total,
Meta? meta,
String? message,
}) => ProgressNotification.fromMap({
'progressToken': progressToken,
'progress': progress,
if (total != null) 'total': total,
if (meta != null) '_meta': meta,
if (message != null) 'message': message,
});

/// The progress token which was given in the initial request, used to
Expand All @@ -154,11 +185,14 @@ extension type ProgressNotification.fromMap(Map<String, Object?> _value)
///
/// This should increase every time progress is made, even if the total is
/// unknown.
int get progress => _value['progress'] as int;
num get progress => _value['progress'] as num;

/// Total number of items to process (or total progress required), if
/// known.
int? get total => _value['total'] as int?;
num? get total => _value['total'] as num?;

/// An optional message describing the current progress.
String? get message => _value['message'] as String?;
}

/// A "mixin"-like extension type for any request that contains a [Cursor] at
Expand All @@ -185,10 +219,11 @@ extension type PaginatedRequest._fromMap(Map<String, Object?> _value)
/// constructor.
extension type PaginatedResult._fromMap(Map<String, Object?> _value)
implements Result {
Cursor? get cursor => _value['cursor'] as Cursor?;
Cursor? get nextCursor => _value['nextCursor'] as Cursor?;
}

/// Could be either [TextContent], [ImageContent] or [EmbeddedResource].
/// Could be either [TextContent], [ImageContent], [AudioContent] or
/// [EmbeddedResource].
///
/// Use [isText], [isImage] and [isEmbeddedResource] before casting to the more
/// specific types, or switch on the [type] and then cast.
Expand All @@ -201,25 +236,40 @@ extension type Content._(Map<String, Object?> _value) {
return Content._(value);
}

/// Alias for [TextContent.new].
static const text = TextContent.new;

/// Alias for [ImageContent.new].
static const image = ImageContent.new;

/// Alias for [AudioContent.new].
static const audio = AudioContent.new;

/// Alias for [EmbeddedResource.new].
static const embeddedResource = EmbeddedResource.new;

/// Whether or not this is a [TextContent].
bool get isText => _value['type'] == TextContent.expectedType;

/// Whether or not this is a [ImageContent].
/// Whether or not this is an [ImageContent].
bool get isImage => _value['type'] == ImageContent.expectedType;

/// Whether or not this is an [AudioContent].
bool get isAudio => _value['type'] == AudioContent.expectedType;

/// Whether or not this is an [EmbeddedResource].
bool get isEmbeddedResource =>
_value['type'] == EmbeddedResource.expectedType;

/// The type of content.
///
/// You can use this in a switch to handle the various types (see the static
/// `expectedType` getters), or you can use [isText], [isImage], and
/// `expectedType` getters), or you can use [isText], [isImage], [isAudio] and
/// [isEmbeddedResource] to determine the type and then do the cast.
String get type => _value['type'] as String;
}

/// Text provided to an LLM.
/// Text provided to or from an LLM.
extension type TextContent.fromMap(Map<String, Object?> _value)
implements Content, Annotated {
static const expectedType = 'text';
Expand All @@ -241,7 +291,7 @@ extension type TextContent.fromMap(Map<String, Object?> _value)
String get text => _value['text'] as String;
}

/// An image provided to an LLM.
/// An image provided to or from an LLM.
extension type ImageContent.fromMap(Map<String, Object?> _value)
implements Content, Annotated {
static const expectedType = 'image';
Expand All @@ -263,11 +313,45 @@ extension type ImageContent.fromMap(Map<String, Object?> _value)
return type;
}

/// If the [type] is `image`, this is the base64 encoded image data.
/// The base64 encoded image data.
String get data => _value['data'] as String;

/// If the [type] is `image`, the MIME type of the image. Different providers
/// may support different image types.
/// The MIME type of the image.
///
/// Different providers may support different image types.
String get mimeType => _value['mimeType'] as String;
}

/// Audio provided to or from an LLM.
///
/// Only supported since version [ProtocolVersion.v2025_03_26].
extension type AudioContent.fromMap(Map<String, Object?> _value)
implements Content, Annotated {
static const expectedType = 'audio';

factory AudioContent({
required String data,
required String mimeType,
Annotations? annotations,
}) => AudioContent.fromMap({
'data': data,
'mimeType': mimeType,
'type': expectedType,
if (annotations != null) 'annotations': annotations,
});

String get type {
final type = _value['type'] as String;
assert(type == expectedType);
return type;
}

/// The base64 encoded audio data.
String get data => _value['data'] as String;

/// The MIME type of the audio.
///
/// Different providers may support different audio types.
String get mimeType => _value['mimeType'] as String;
}

Expand Down
2 changes: 2 additions & 0 deletions pkgs/dart_mcp/lib/src/api/completions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// 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.

/// Note that all features here are only supported since version
/// [ProtocolVersion.v2025_03_26].
part of 'api.dart';

/// A request from the client to the server, to ask for completion options.
Expand Down
29 changes: 23 additions & 6 deletions pkgs/dart_mcp/lib/src/api/initialization.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ extension type InitializeRequest._fromMap(Map<String, Object?> _value)
static const methodName = 'initialize';

factory InitializeRequest({
required String protocolVersion,
required ProtocolVersion protocolVersion,
required ClientCapabilities capabilities,
required ClientImplementation clientInfo,
MetaWithProgressToken? meta,
}) => InitializeRequest._fromMap({
'protocolVersion': protocolVersion,
'protocolVersion': protocolVersion.versionString,
'capabilities': capabilities,
'clientInfo': clientInfo,
if (meta != null) '_meta': meta,
Expand All @@ -25,9 +25,14 @@ extension type InitializeRequest._fromMap(Map<String, Object?> _value)
/// The latest version of the Model Context Protocol that the client supports.
///
/// The client MAY decide to support older versions as well.
String get protocolVersion => _value['protocolVersion'] as String;
///
/// May be `null` if the version is not recognized.
ProtocolVersion? get protocolVersion =>
ProtocolVersion.tryParse(_value['protocolVersion'] as String);

ClientCapabilities get capabilities =>
_value['capabilities'] as ClientCapabilities;

ClientImplementation get clientInfo =>
_value['clientInfo'] as ClientImplementation;
}
Expand All @@ -37,12 +42,12 @@ extension type InitializeRequest._fromMap(Map<String, Object?> _value)
extension type InitializeResult.fromMap(Map<String, Object?> _value)
implements Result {
factory InitializeResult({
required String protocolVersion,
required ProtocolVersion protocolVersion,
required ServerCapabilities serverCapabilities,
required ServerImplementation serverInfo,
required String instructions,
}) => InitializeResult.fromMap({
'protocolVersion': protocolVersion,
'protocolVersion': protocolVersion.versionString,
'capabilities': serverCapabilities,
'serverInfo': serverInfo,
'instructions': instructions,
Expand All @@ -52,7 +57,19 @@ extension type InitializeResult.fromMap(Map<String, Object?> _value)
///
/// This may not match the version that the client requested. If the client
/// cannot support this version, it MUST disconnect.
String get protocolVersion => _value['protocolVersion'] as String;
///
/// May be `null` if the version is not recognized.
ProtocolVersion? get protocolVersion =>
ProtocolVersion.tryParse(_value['protocolVersion'] as String);

/// Sets the protocol version, by default this is set for you, but you can
/// override it to a specific version if desired.
///
/// While this API is typed as nullable, `null` is not an allowed value.
set protocolVersion(ProtocolVersion? value) {
assert(value != null);
_value['protocolVersion'] = value!.versionString;
}

ServerCapabilities get capabilities =>
_value['capabilities'] as ServerCapabilities;
Expand Down
4 changes: 2 additions & 2 deletions pkgs/dart_mcp/lib/src/api/prompts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ extension type ListPromptsResult.fromMap(Map<String, Object?> _value)
implements PaginatedResult {
factory ListPromptsResult({
required List<Prompt> prompts,
Cursor? cursor,
Cursor? nextCursor,
Meta? meta,
}) => ListPromptsResult.fromMap({
'prompts': prompts,
if (cursor != null) 'cursor': cursor,
if (nextCursor != null) 'nextCursor': nextCursor,
if (meta != null) '_meta': meta,
});

Expand Down
Loading
Loading