| // 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. |
| |
| /// Interfaces are based on the schema files under |
| /// https://github.com/modelcontextprotocol/modelcontextprotocol/tree/main/schema, |
| /// one directory per revision. See DEVELOPING.md. |
| library; |
| |
| import 'dart:collection'; |
| |
| import 'package:collection/collection.dart'; |
| |
| import '../utils/constants.dart'; |
| |
| part 'completions.dart'; |
| part 'elicitation.dart'; |
| part 'error_codes.dart'; |
| part 'icons.dart'; |
| part 'initialization.dart'; |
| part 'input_required.dart'; |
| part 'logging.dart'; |
| part 'prompts.dart'; |
| part 'resources.dart'; |
| part 'roots.dart'; |
| part 'sampling.dart'; |
| part 'subscriptions.dart'; |
| part 'tools.dart'; |
| |
| /// Enum of the known protocol versions. |
| enum ProtocolVersion { |
| v2024_11_05( |
| '2024-11-05', |
| supportsStreamableHttp: false, |
| addedMethods: { |
| CompleteRequest.methodName, |
| InitializeRequest.methodName, |
| SetLevelRequest.methodName, |
| CancelledNotification.methodName, |
| InitializedNotification.methodName, |
| LoggingMessageNotification.methodName, |
| ProgressNotification.methodName, |
| PromptListChangedNotification.methodName, |
| ResourceListChangedNotification.methodName, |
| ResourceUpdatedNotification.methodName, |
| RootsListChangedNotification.methodName, |
| ToolListChangedNotification.methodName, |
| PingRequest.methodName, |
| GetPromptRequest.methodName, |
| ListPromptsRequest.methodName, |
| ListResourcesRequest.methodName, |
| ReadResourceRequest.methodName, |
| SubscribeRequest.methodName, |
| ListResourceTemplatesRequest.methodName, |
| UnsubscribeRequest.methodName, |
| ListRootsRequest.methodName, |
| CreateMessageRequest.methodName, |
| CallToolRequest.methodName, |
| ListToolsRequest.methodName, |
| }, |
| ), |
| v2025_03_26('2025-03-26', supportsStreamableHttp: false), |
| v2025_06_18( |
| '2025-06-18', |
| supportsStreamableHttp: false, |
| addedMethods: {ElicitRequest.methodName}, |
| ), |
| v2025_11_25( |
| '2025-11-25', |
| supportsStreamableHttp: false, |
| addedMethods: { |
| ElicitationCompleteNotification.methodName, |
| 'notifications/tasks/status', |
| 'tasks/cancel', |
| 'tasks/get', |
| 'tasks/list', |
| 'tasks/result', |
| }, |
| ), |
| v2026_07_28( |
| '2026-07-28', |
| supportsStreamableHttp: true, |
| addedMethods: { |
| SubscriptionsAcknowledgedNotification.methodName, |
| DiscoverRequest.methodName, |
| SubscriptionsListenRequest.methodName, |
| }, |
| removedMethods: { |
| ElicitRequest.methodName, |
| InitializeRequest.methodName, |
| SetLevelRequest.methodName, |
| ElicitationCompleteNotification.methodName, |
| InitializedNotification.methodName, |
| RootsListChangedNotification.methodName, |
| 'notifications/tasks/status', |
| PingRequest.methodName, |
| SubscribeRequest.methodName, |
| UnsubscribeRequest.methodName, |
| ListRootsRequest.methodName, |
| CreateMessageRequest.methodName, |
| 'tasks/cancel', |
| 'tasks/get', |
| 'tasks/list', |
| 'tasks/result', |
| }, |
| ); |
| |
| const ProtocolVersion( |
| this.versionString, { |
| required this.supportsStreamableHttp, |
| this.addedMethods = const {}, |
| this.removedMethods = const {}, |
| }); |
| |
| /// Whether this package's Streamable HTTP transport supports this revision. |
| final bool supportsStreamableHttp; |
| |
| /// The methods this revision introduced. |
| final Set<String> addedMethods; |
| |
| /// The methods this revision stopped accepting. |
| /// |
| /// A method belongs here even when the revision kept its type, as long as it |
| /// took away the way to send it. 2026-07-28 keeps [ElicitRequest], |
| /// [ListRootsRequest] and [CreateMessageRequest] for the [InputRequest] an |
| /// [InputRequiredResult] carries, and none of the three can be sent on its |
| /// own there. |
| final Set<String> removedMethods; |
| |
| /// Whether [method] is one this revision accepts. |
| /// |
| /// Walks back from this revision to the first one, so a method holds until |
| /// some later revision lists it in [removedMethods]. A method no revision |
| /// ever added is not part of any of them. |
| bool methodIsValid(String method) { |
| for (var version = this; ; version = values[version.index - 1]) { |
| if (version.removedMethods.contains(method)) return false; |
| if (version.addedMethods.contains(method)) return true; |
| if (version.index == 0) return false; |
| } |
| } |
| |
| /// 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. |
| /// |
| /// This is the newest version the legacy `initialize` handshake negotiates. |
| /// A transport for a request-scoped protocol revision, such as |
| /// `handleStreamableHttpRequest` in `package:dart_mcp/streamable_http.dart`, |
| /// carries its own set of supported versions. |
| static const latestSupported = ProtocolVersion.v2025_11_25; |
| |
| /// 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. |
| extension type ProgressToken(/*String|int*/ Object _) {} |
| |
| /// An opaque token used to represent a cursor for pagination. |
| extension type Cursor(String _) {} |
| |
| /// Generic metadata passed with most requests. |
| /// |
| /// Metadata reserved by MCP to allow clients and servers to attach additional |
| /// metadata to their interactions. |
| /// |
| /// Certain key names are reserved by MCP for protocol-level metadata, as |
| /// specified below; implementations MUST NOT make assumptions about values at |
| /// these keys. |
| /// |
| /// Additionally, definitions in the schema may reserve particular names for |
| /// purpose-specific metadata, as declared in those definitions. |
| /// |
| /// Key name format: valid `_meta` key names have two segments: an optional |
| /// prefix, and a name. |
| /// |
| /// - Prefix: If specified, MUST be a series of labels separated by dots |
| /// (`.`), followed by a slash (`/`). Labels MUST start with a letter and |
| /// end with a letter or digit; interior characters can be letters, digits, |
| /// or hyphens (`-`). From the 2025-11-25 revision, implementations SHOULD |
| /// use reverse DNS notation (e.g., `com.example/` rather than |
| /// `example.com/`), and any prefix whose second label is |
| /// `modelcontextprotocol` or `mcp` is reserved for MCP use. For example: |
| /// `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, |
| /// and `com.mcp.tools/` are all reserved, while `com.example.mcp/` is not, |
| /// because its second label is `example`. The 2025-06-18 revision reserved |
| /// any prefix carrying `modelcontextprotocol` or `mcp` in a label other |
| /// than the last. |
| /// - Name: Unless empty, MUST begin and end with an alphanumeric character |
| /// (`[a-z0-9A-Z]`). MAY contain hyphens (`-`), underscores (`_`), dots |
| /// (`.`), and alphanumerics in between. |
| /// |
| /// From the 2026-07-28 revision, `traceparent`, `tracestate`, and `baggage` |
| /// are reserved for OpenTelemetry trace context, as an exception to the prefix |
| /// rule. When present, their values MUST follow the W3C Trace Context and |
| /// W3C Baggage formats respectively. |
| extension type Meta.fromMap(Map<String, Object?> _value) { |
| Object? operator [](String key) => _value[key]; |
| } |
| |
| /// Basic metadata required by multiple types. |
| /// |
| /// Not to be confused with the `_meta` property in the spec, which has a |
| /// different purpose. |
| extension type BaseMetadata.fromMap(Map<String, Object?> _value) { |
| factory BaseMetadata({required String name, String? title}) => |
| BaseMetadata.fromMap({ |
| Keys.name: name, |
| if (title != null) Keys.title: title, |
| }); |
| |
| /// Intended for programmatic or logical use, but used as a display name in |
| /// past specs for fallback (if title isn't present). |
| String get name { |
| final name = _value[Keys.name] as String?; |
| if (name == null) { |
| throw ArgumentError('Missing name field in $runtimeType'); |
| } |
| return name; |
| } |
| |
| /// A short title for this object. |
| /// |
| /// Intended for UI and end-user contexts — optimized to be human-readable and |
| /// easily understood, even by those unfamiliar with domain-specific |
| /// terminology. |
| /// |
| /// If not provided, the name should be used for display (except for Tool, |
| /// where `annotations.title` should be given precedence over using `name`, if |
| /// present). |
| String? get title => _value[Keys.title] as String?; |
| } |
| |
| /// A "mixin"-like extension type for any extension type that might contain a |
| /// [ProgressToken] at the key "progressToken". |
| /// |
| /// Should be "mixed in" by implementing this type from other extension types. |
| extension type WithProgressToken.fromMap(Map<String, Object?> _value) { |
| ProgressToken? get progressToken => |
| _value[Keys.progressToken] as ProgressToken?; |
| } |
| |
| /// A [Meta] object with a known progress token key. |
| /// |
| /// Has arbitrary other keys. |
| extension type MetaWithProgressToken.fromMap(Map<String, Object?> _value) |
| implements Meta, WithProgressToken { |
| factory MetaWithProgressToken({ProgressToken? progressToken}) => |
| MetaWithProgressToken.fromMap({ |
| if (progressToken != null) Keys.progressToken: progressToken, |
| }); |
| } |
| |
| /// Base interface for all types that can have arbitrary metadata attached. |
| /// |
| /// Should not be constructed directly, and has no public constructor. |
| extension type WithMetadata._fromMap(Map<String, Object?> _value) { |
| /// The `_meta` property/parameter is reserved by MCP to allow clients and |
| /// servers to attach additional metadata to their interactions. |
| /// |
| /// See [Meta] for more information about the format of these values. |
| Meta? get meta => _value[Keys.meta] as Meta?; |
| } |
| |
| /// Base interface for all request types. |
| /// |
| /// Should not be constructed directly, and has no public constructor. |
| extension type Request._fromMap(Map<String, Object?> _value) |
| implements WithMetadata { |
| /// If specified, the caller is requesting out-of-band progress notifications |
| /// for this request (as represented by notifications/progress). |
| /// |
| /// The value of this parameter is an opaque token that will be attached to |
| /// any subsequent notifications. The receiver is not obligated to provide |
| /// these notifications. |
| MetaWithProgressToken? get meta => |
| _value[Keys.meta] as MetaWithProgressToken?; |
| } |
| |
| /// Base interface for all notifications. |
| extension type Notification(Map<String, Object?> _value) { |
| /// This parameter name is reserved by MCP to allow clients and servers to |
| /// attach additional metadata to their notifications. |
| Meta? get meta => _value[Keys.meta] as Meta?; |
| } |
| |
| /// Base interface for all responses to requests. |
| extension type Result._(Map<String, Object?> _value) { |
| Meta? get meta => _value[Keys.meta] as Meta?; |
| |
| /// The type of this result, which determines how to parse it. |
| /// |
| /// Known types are "complete" and "input_required", but a server may send |
| /// any other string. Servers on protocol versions before 2026-07-28 omit |
| /// the field; the schema requires treating that as "complete", so an absent |
| /// value is reported here as "complete". |
| String get resultType => |
| _value[Keys.resultType] as String? ?? ResultTypes.complete; |
| |
| /// Whether the server asked for more input before it can answer, which makes |
| /// this an [InputRequiredResult]. |
| /// |
| /// An `is` check cannot tell that apart, because these are just extension |
| /// types and they share the runtime type `Map<String, Object?>`. |
| bool get isInputRequired => resultType == ResultTypes.inputRequired; |
| } |
| |
| /// A response that indicates success but carries no data. |
| extension type EmptyResult.fromMap(Map<String, Object?> _) implements Result { |
| factory EmptyResult() => EmptyResult.fromMap(const {}); |
| } |
| |
| /// This notification can be sent by either side to indicate that it is |
| /// cancelling a previously-issued request. |
| /// |
| /// The request SHOULD still be in-flight, but due to communication latency, it |
| /// is always possible that this notification MAY arrive after the request has |
| /// already finished. |
| /// |
| /// This notification indicates that the result will be unused, so any |
| /// associated processing SHOULD cease. |
| /// |
| /// A client MUST NOT attempt to cancel its `initialize` request. |
| extension type CancelledNotification.fromMap(Map<String, Object?> _value) |
| implements Notification { |
| static const methodName = 'notifications/cancelled'; |
| |
| factory CancelledNotification({ |
| required RequestId requestId, |
| String? reason, |
| Meta? meta, |
| }) { |
| return CancelledNotification.fromMap({ |
| Keys.requestId: requestId, |
| if (reason != null) Keys.reason: reason, |
| if (meta != null) Keys.meta: meta, |
| }); |
| } |
| |
| /// The ID of the request to cancel. |
| /// |
| /// This MUST correspond to the ID of a request previously issued in the same |
| /// direction. |
| RequestId? get requestId => _value[Keys.requestId] as RequestId?; |
| |
| /// An optional string describing the reason for the cancellation. This MAY be |
| /// logged or presented to the user. |
| String? get reason => _value[Keys.reason] as String?; |
| } |
| |
| /// An opaque request ID. |
| extension type RequestId(/*String|int*/ Object _) {} |
| |
| /// A ping, issued by either the server or the client, to check that the other |
| /// party is still alive. |
| /// |
| /// The receiver must promptly respond, or else may be disconnected. |
| /// |
| /// The request itself has no parameters. |
| extension type PingRequest._(Map<String, Object?> _) implements Request { |
| static const methodName = 'ping'; |
| |
| factory PingRequest({MetaWithProgressToken? meta}) => |
| PingRequest._({if (meta != null) Keys.meta: meta}); |
| } |
| |
| /// An out-of-band notification used to inform the receiver of a progress |
| /// update for a long-running request. |
| extension type ProgressNotification.fromMap(Map<String, Object?> _value) |
| implements Notification { |
| static const methodName = 'notifications/progress'; |
| |
| factory ProgressNotification({ |
| required ProgressToken progressToken, |
| required num progress, |
| num? total, |
| Meta? meta, |
| String? message, |
| }) => ProgressNotification.fromMap({ |
| Keys.progressToken: progressToken, |
| Keys.progress: progress, |
| if (total != null) Keys.total: total, |
| if (meta != null) Keys.meta: meta, |
| if (message != null) Keys.message: message, |
| }); |
| |
| /// The progress token which was given in the initial request, used to |
| /// associate this notification with the request that is proceeding. |
| ProgressToken get progressToken => |
| _value[Keys.progressToken] as ProgressToken; |
| |
| /// The progress thus far. |
| /// |
| /// This should increase every time progress is made, even if the total is |
| /// unknown. |
| num get progress => _value[Keys.progress] as num; |
| |
| /// Total number of items to process (or total progress required), if |
| /// known. |
| num? get total => _value[Keys.total] as num?; |
| |
| /// An optional message describing the current progress. |
| String? get message => _value[Keys.message] as String?; |
| } |
| |
| /// A "mixin"-like extension type for any request that contains a [Cursor] at |
| /// the key "cursor". |
| /// |
| /// Should be "mixed in" by implementing this type from other extension types. |
| /// |
| /// This type is not intended to be constructed directly and thus has no public |
| /// constructor. |
| extension type PaginatedRequest._fromMap(Map<String, Object?> _value) |
| implements Request { |
| /// An opaque token representing the current pagination position. |
| /// |
| /// If provided, the server should return results starting after this cursor. |
| Cursor? get cursor => _value[Keys.cursor] as Cursor?; |
| |
| /// A copy of this request that asks for the page at [cursor]. |
| /// |
| /// Copying keeps every entry the caller put on the request, including ones |
| /// this package does not read. |
| PaginatedRequest copyWithCursor(Cursor cursor) => |
| PaginatedRequest._fromMap({..._value, Keys.cursor: cursor}); |
| } |
| |
| /// A "mixin"-like extension type for any result type that contains a [Cursor] |
| /// at the key "cursor". |
| /// |
| /// Should be "mixed in" by implementing this type from other extension types. |
| /// |
| /// This type is not intended to be constructed directly and thus has no public |
| /// constructor. |
| extension type PaginatedResult._fromMap(Map<String, Object?> _value) |
| implements Result { |
| Cursor? get nextCursor => _value[Keys.nextCursor] as Cursor?; |
| } |
| |
| /// A "mixin"-like extension type for any result type that contains caching |
| /// hints at the keys "ttlMs" and "cacheScope". |
| /// |
| /// Should be "mixed in" by implementing this type from other extension types. |
| /// |
| /// This type is not intended to be constructed directly and thus has no public |
| /// constructor. |
| extension type CacheableResult._fromMap(Map<String, Object?> _value) |
| implements Result { |
| /// A hint for how long, in milliseconds, the client may cache this response |
| /// before re-fetching, analogous to HTTP `Cache-Control` max-age. |
| /// |
| /// A value of 0 marks the response as immediately stale. |
| /// |
| /// Servers on protocol versions before 2026-07-28 omit the field; the spec |
| /// says clients should assume 0 in that case, and should treat a negative |
| /// value from a non-conforming server as 0, so both are reported here as 0. |
| int get ttlMs { |
| final value = _value[Keys.ttlMs] as int? ?? 0; |
| return value < 0 ? 0 : value; |
| } |
| |
| /// The intended scope of the cached response, analogous to HTTP |
| /// `Cache-Control: public` vs `Cache-Control: private`. |
| /// |
| /// An absent or unrecognized value is reported here as `null` rather than |
| /// as a default. The schema comment says the field defaults to "public", |
| /// but the same SEP argues the field is required precisely "because there |
| /// is no safe default for older servers", and leaves a default to the |
| /// discretion of each SDK. Reporting `null` follows the second reading: |
| /// synthesizing "public" for a server that never declared a scope is the |
| /// one guess that can leak a private response into a shared cache. Servers |
| /// on protocol versions before 2026-07-28 omit the field, and their results |
| /// also carry no `ttlMs`, so they are already immediately stale. |
| CacheScope? get cacheScope { |
| final name = _value[Keys.cacheScope] as String?; |
| if (name == null) return null; |
| return CacheScope.values.firstWhereOrNull((scope) => scope.name == name); |
| } |
| } |
| |
| /// The intended scope of a cached [CacheableResult] response. |
| enum CacheScope { |
| /// The response contains no user-specific data; any client or intermediary |
| /// may cache it and serve it across authorization contexts. |
| public, |
| |
| /// The response may be cached and reused only within the same authorization |
| /// context; caches must not be shared across authorization contexts. |
| private, |
| } |
| |
| /// 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. |
| /// |
| /// Doing `is` checks does not work because these are just extension types, they |
| /// all have the same runtime type (`Map<String, Object?>`). |
| extension type Content._(Map<String, Object?> _value) { |
| factory Content.fromMap(Map<String, Object?> value) { |
| assert(value.containsKey(Keys.type)); |
| assert( |
| value[Keys.type] != ToolUseContent.expectedType && |
| value[Keys.type] != ToolResultContent.expectedType, |
| 'Sampling tool content cannot be read as Content.', |
| ); |
| // Tool use and tool result belong to sampling messages, where they read |
| // as a `SamplingMessageContentBlock`. A `tools/call` result carrying one |
| // is a mix-up the static types already refuse, and a map arriving here |
| // skips those types, so the assert covers that path. |
| 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[Keys.type] == TextContent.expectedType; |
| |
| /// Whether or not this is an [ImageContent]. |
| bool get isImage => _value[Keys.type] == ImageContent.expectedType; |
| |
| /// Whether or not this is an [AudioContent]. |
| bool get isAudio => _value[Keys.type] == AudioContent.expectedType; |
| |
| /// Whether or not this is an [EmbeddedResource]. |
| bool get isEmbeddedResource => |
| _value[Keys.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], [isAudio] and |
| /// [isEmbeddedResource] to determine the type and then do the cast. |
| String get type => _value[Keys.type] as String; |
| } |
| |
| /// Text provided to or from an LLM. |
| extension type TextContent.fromMap(Map<String, Object?> _value) |
| implements Content, SamplingMessageContentBlock, Annotated, WithMetadata { |
| static const expectedType = 'text'; |
| |
| factory TextContent({ |
| required String text, |
| Annotations? annotations, |
| Meta? meta, |
| }) => TextContent.fromMap({ |
| Keys.text: text, |
| Keys.type: expectedType, |
| if (annotations != null) Keys.annotations: annotations, |
| if (meta != null) Keys.meta: meta, |
| }); |
| |
| String get type { |
| final type = _value[Keys.type] as String; |
| assert(type == expectedType); |
| return type; |
| } |
| |
| /// The text content. |
| String get text => _value[Keys.text] as String; |
| } |
| |
| /// An image provided to or from an LLM. |
| extension type ImageContent.fromMap(Map<String, Object?> _value) |
| implements Content, SamplingMessageContentBlock, Annotated, WithMetadata { |
| static const expectedType = 'image'; |
| |
| factory ImageContent({ |
| required String data, |
| required String mimeType, |
| Annotations? annotations, |
| Meta? meta, |
| }) => ImageContent.fromMap({ |
| Keys.data: data, |
| Keys.mimeType: mimeType, |
| Keys.type: expectedType, |
| if (annotations != null) Keys.annotations: annotations, |
| if (meta != null) Keys.meta: meta, |
| }); |
| |
| String get type { |
| final type = _value[Keys.type] as String; |
| assert(type == expectedType); |
| return type; |
| } |
| |
| /// The base64 encoded image data. |
| String get data => _value[Keys.data] as String; |
| |
| /// The MIME type of the image. |
| /// |
| /// Different providers may support different image types. |
| String get mimeType => _value[Keys.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, SamplingMessageContentBlock, Annotated, WithMetadata { |
| static const expectedType = 'audio'; |
| |
| factory AudioContent({ |
| required String data, |
| required String mimeType, |
| Annotations? annotations, |
| Meta? meta, |
| }) => AudioContent.fromMap({ |
| Keys.data: data, |
| Keys.mimeType: mimeType, |
| Keys.type: expectedType, |
| if (annotations != null) Keys.annotations: annotations, |
| if (meta != null) Keys.meta: meta, |
| }); |
| |
| String get type { |
| final type = _value[Keys.type] as String; |
| assert(type == expectedType); |
| return type; |
| } |
| |
| /// The base64 encoded audio data. |
| String get data => _value[Keys.data] as String; |
| |
| /// The MIME type of the audio. |
| /// |
| /// Different providers may support different audio types. |
| String get mimeType => _value[Keys.mimeType] as String; |
| } |
| |
| /// The contents of a resource, embedded into a prompt or tool call result. |
| /// |
| /// It is up to the client how best to render embedded resources for the benefit |
| /// of the LLM and/or the user. |
| extension type EmbeddedResource.fromMap(Map<String, Object?> _value) |
| implements Content, Annotated, WithMetadata { |
| static const expectedType = 'resource'; |
| |
| factory EmbeddedResource({ |
| required ResourceContents resource, |
| Annotations? annotations, |
| Meta? meta, |
| }) => EmbeddedResource.fromMap({ |
| Keys.resource: resource, |
| Keys.type: expectedType, |
| if (annotations != null) Keys.annotations: annotations, |
| if (meta != null) Keys.meta: meta, |
| }); |
| |
| String get type { |
| final type = _value[Keys.type] as String; |
| assert(type == expectedType); |
| return type; |
| } |
| |
| /// Either [TextResourceContents] or [BlobResourceContents]. |
| ResourceContents get resource => _value[Keys.resource] as ResourceContents; |
| |
| @Deprecated('Use `.resource.mimeType`.') |
| String? get mimeType => _value[Keys.mimeType] as String?; |
| } |
| |
| /// A resource link returned from a tool. |
| /// |
| /// Resource links returned by tools are not guaranteed to appear in the results |
| /// of a `resources/list` request. |
| extension type ResourceLink.fromMap(Map<String, Object?> _value) |
| implements Content, Annotated, WithMetadata, BaseMetadata { |
| static const expectedType = 'resource_link'; |
| |
| factory ResourceLink({ |
| required String name, |
| String? title, |
| String? description, |
| required String uri, |
| String? mimeType, |
| List<Icon>? icons, |
| Annotations? annotations, |
| Meta? meta, |
| }) => ResourceLink.fromMap({ |
| Keys.name: name, |
| if (title != null) Keys.title: title, |
| if (description != null) Keys.description: description, |
| Keys.uri: uri, |
| if (mimeType != null) Keys.mimeType: mimeType, |
| if (icons != null) Keys.icons: icons, |
| Keys.type: expectedType, |
| if (annotations != null) Keys.annotations: annotations, |
| if (meta != null) Keys.meta: meta, |
| }); |
| |
| String get type { |
| final type = _value[Keys.type] as String; |
| assert(type == expectedType); |
| return type; |
| } |
| |
| /// The description of the resource. |
| String? get description => _value[Keys.description] as String?; |
| |
| /// The URI of the resource. |
| String get uri { |
| final uri = _value[Keys.uri] as String?; |
| if (uri == null) { |
| throw ArgumentError('Missing uri field in $ResourceLink.'); |
| } |
| return uri; |
| } |
| |
| /// The MIME type of the resource. |
| String? get mimeType => _value[Keys.mimeType] as String?; |
| |
| /// The size of the resource in bytes. |
| int? get size => _value[Keys.size] as int?; |
| |
| /// Optional set of sized icons that the client can display in a user |
| /// interface. |
| List<Icon>? get icons => (_value[Keys.icons] as List?)?.cast<Icon>(); |
| } |
| |
| /// Base type for objects that include optional annotations for the client. |
| /// |
| /// The client can use annotations to inform how objects are used or displayed. |
| extension type Annotated._fromMap(Map<String, Object?> _value) { |
| /// Annotations for this object. |
| Annotations? get annotations => _value[Keys.annotations] as Annotations?; |
| } |
| |
| /// The annotations for an [Annotated] object. |
| extension type Annotations.fromMap(Map<String, Object?> _value) { |
| factory Annotations({ |
| List<Role>? audience, |
| DateTime? lastModified, |
| double? priority, |
| }) { |
| assert(priority == null || (priority >= 0 && priority <= 1)); |
| return Annotations.fromMap({ |
| if (audience != null) |
| Keys.audience: [for (var role in audience) role.name], |
| if (lastModified != null) |
| Keys.lastModified: lastModified.toIso8601String(), |
| if (priority != null) Keys.priority: priority, |
| }); |
| } |
| |
| /// Describes who the intended customer of this object or data is. |
| /// |
| /// It can include multiple entries to indicate content useful for |
| /// multiple audiences (e.g., `[Role.user, Role.assistant]`). |
| List<Role>? get audience { |
| final audience = _value[Keys.audience] as List?; |
| if (audience == null) return null; |
| return [ |
| for (var role in audience) |
| Role.values.firstWhere((value) => value.name == role), |
| ]; |
| } |
| |
| /// Describes when this data was last modified. |
| /// |
| /// The moment the resource was last modified. |
| /// |
| /// Examples: last activity timestamp in an open file, timestamp when the |
| /// resource was attached, etc. |
| DateTime? get lastModified { |
| final lastModified = _value[Keys.lastModified] as String?; |
| if (lastModified == null) return null; |
| return DateTime.parse(lastModified); |
| } |
| |
| /// Describes how important this data is for operating the server. |
| /// |
| /// A value of 1 means "most important," and indicates that the data is |
| /// effectively required, while 0 means "least important," and indicates |
| /// that the data is entirely optional. |
| /// |
| /// Must be between 0 and 1. |
| double? get priority => (_value[Keys.priority] as num?)?.toDouble(); |
| } |