Update to new MCP spec version (#193)

diff --git a/pkgs/dart_mcp/CHANGELOG.md b/pkgs/dart_mcp/CHANGELOG.md
index 08eb159..700381a 100644
--- a/pkgs/dart_mcp/CHANGELOG.md
+++ b/pkgs/dart_mcp/CHANGELOG.md
@@ -3,6 +3,10 @@
 - Added error checking to required fields of all `Request` subclasses so that
   they will throw helpful errors when accessed and not set.
 - Added enum support to Schema.
+- Updates to the latest MCP spec, [2025-06-08](https://modelcontextprotocol.io/specification/2025-06-18/changelog)
+  - Adds support for Elicitations to allow the server to ask the user questions.
+  - Adds `ResourceLink` as a tool return content type.
+  - Adds support for structured tool output.
 
 ## 0.2.2
 
diff --git a/pkgs/dart_mcp/lib/src/api/api.dart b/pkgs/dart_mcp/lib/src/api/api.dart
index 73fb647..2ead41d 100644
--- a/pkgs/dart_mcp/lib/src/api/api.dart
+++ b/pkgs/dart_mcp/lib/src/api/api.dart
@@ -2,7 +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.
 
-/// Interfaces are based on https://github.com/modelcontextprotocol/specification/blob/main/schema/2024-11-05/schema.json
+/// Interfaces are based on
+/// https://github.com/modelcontextprotocol/specification/blob/main/schema/2025-06-18/schema.ts
 library;
 
 import 'dart:collection';
@@ -11,6 +12,7 @@
 import 'package:json_rpc_2/json_rpc_2.dart';
 
 part 'completions.dart';
+part 'elicitation.dart';
 part 'initialization.dart';
 part 'logging.dart';
 part 'prompts.dart';
@@ -22,7 +24,8 @@
 /// Enum of the known protocol versions.
 enum ProtocolVersion {
   v2024_11_05('2024-11-05'),
-  v2025_03_26('2025-03-26');
+  v2025_03_26('2025-03-26'),
+  v2025_06_18('2025-06-18');
 
   const ProtocolVersion(this.versionString);
 
@@ -35,7 +38,7 @@
   static const oldestSupported = ProtocolVersion.v2024_11_05;
 
   /// The most recent version supported by the current API.
-  static const latestSupported = ProtocolVersion.v2025_03_26;
+  static const latestSupported = ProtocolVersion.v2025_06_18;
 
   /// The version string used over the wire to identify this version.
   final String versionString;
@@ -58,9 +61,65 @@
 /// An opaque token used to represent a cursor for pagination.
 extension type Cursor(String _) {}
 
-/// Generic metadata passed with most requests, can be anything.
+/// 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 (`-`). Any prefix beginning with zero or more valid labels,
+///   followed by `modelcontextprotocol` or `mcp`, followed by any valid
+///   label, is reserved for MCP use. For example: `modelcontextprotocol.io/`,
+///   `mcp.dev/`, `api.modelcontextprotocol.org/`, and `tools.mcp.com/` are
+///   all reserved.
+/// - Name: Unless empty, MUST begin and end with an alphanumeric character
+///   (`[a-z0-9A-Z]`). MAY contain hyphens (`-`), underscores (`_`), dots
+///   (`.`), and alphanumerics in between.
 extension type Meta.fromMap(Map<String, Object?> _value) {}
 
+/// 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)
+    implements Meta {
+  factory BaseMetadata({required String name, String? title}) =>
+      BaseMetadata.fromMap({'name': name, '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['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['title'] as String?;
+}
+
 /// A "mixin"-like extension type for any extension type that might contain a
 /// [ProgressToken] at the key "progressToken".
 ///
@@ -78,10 +137,22 @@
       MetaWithProgressToken.fromMap({'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['_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) {
+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).
   ///
@@ -273,15 +344,19 @@
 
 /// Text provided to or from an LLM.
 extension type TextContent.fromMap(Map<String, Object?> _value)
-    implements Content, Annotated {
+    implements Content, Annotated, WithMetadata {
   static const expectedType = 'text';
 
-  factory TextContent({required String text, Annotations? annotations}) =>
-      TextContent.fromMap({
-        'text': text,
-        'type': expectedType,
-        if (annotations != null) 'annotations': annotations,
-      });
+  factory TextContent({
+    required String text,
+    Annotations? annotations,
+    Meta? meta,
+  }) => TextContent.fromMap({
+    'text': text,
+    'type': expectedType,
+    if (annotations != null) 'annotations': annotations,
+    if (meta != null) '_meta': meta,
+  });
 
   String get type {
     final type = _value['type'] as String;
@@ -295,18 +370,20 @@
 
 /// An image provided to or from an LLM.
 extension type ImageContent.fromMap(Map<String, Object?> _value)
-    implements Content, Annotated {
+    implements Content, Annotated, WithMetadata {
   static const expectedType = 'image';
 
   factory ImageContent({
     required String data,
     required String mimeType,
     Annotations? annotations,
+    Meta? meta,
   }) => ImageContent.fromMap({
     'data': data,
     'mimeType': mimeType,
     'type': expectedType,
     if (annotations != null) 'annotations': annotations,
+    if (meta != null) '_meta': meta,
   });
 
   String get type {
@@ -328,18 +405,20 @@
 ///
 /// Only supported since version [ProtocolVersion.v2025_03_26].
 extension type AudioContent.fromMap(Map<String, Object?> _value)
-    implements Content, Annotated {
+    implements Content, Annotated, WithMetadata {
   static const expectedType = 'audio';
 
   factory AudioContent({
     required String data,
     required String mimeType,
     Annotations? annotations,
+    Meta? meta,
   }) => AudioContent.fromMap({
     'data': data,
     'mimeType': mimeType,
     'type': expectedType,
     if (annotations != null) 'annotations': annotations,
+    if (meta != null) '_meta': meta,
   });
 
   String get type {
@@ -362,16 +441,18 @@
 /// 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 {
+    implements Content, Annotated, WithMetadata {
   static const expectedType = 'resource';
 
   factory EmbeddedResource({
     required Content resource,
     Annotations? annotations,
+    Meta? meta,
   }) => EmbeddedResource.fromMap({
     'resource': resource,
     'type': expectedType,
     if (annotations != null) 'annotations': annotations,
+    if (meta != null) '_meta': meta,
   });
 
   String get type {
@@ -386,6 +467,76 @@
   String? get mimeType => _value['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,
+    required String description,
+    required String uri,
+    required String mimeType,
+    Annotations? annotations,
+    Meta? meta,
+  }) => ResourceLink.fromMap({
+    'name': name,
+    if (title != null) 'title': title,
+    'description': description,
+    'uri': uri,
+    'mimeType': mimeType,
+    'type': expectedType,
+    if (annotations != null) 'annotations': annotations,
+    if (meta != null) '_meta': meta,
+  });
+
+  String get type {
+    final type = _value['type'] as String;
+    assert(type == expectedType);
+    return type;
+  }
+
+  /// The name of the resource.
+  String get name {
+    final name = _value['name'] as String?;
+    if (name == null) {
+      throw ArgumentError('Missing name field in $ResourceLink.');
+    }
+    return name;
+  }
+
+  /// The description of the resource.
+  String get description {
+    final description = _value['description'] as String?;
+    if (description == null) {
+      throw ArgumentError('Missing description field in $ResourceLink.');
+    }
+    return description;
+  }
+
+  /// The URI of the resource.
+  String get uri {
+    final uri = _value['uri'] as String?;
+    if (uri == null) {
+      throw ArgumentError('Missing uri field in $ResourceLink.');
+    }
+    return uri;
+  }
+
+  /// The MIME type of the resource.
+  String get mimeType {
+    final mimeType = _value['mimeType'] as String?;
+    if (mimeType == null) {
+      throw ArgumentError('Missing mimeType field in $ResourceLink.');
+    }
+    return mimeType;
+  }
+}
+
 /// Base type for objects that include optional annotations for the client.
 ///
 /// The client can use annotations to inform how objects are used or displayed.
@@ -396,10 +547,15 @@
 
 /// The annotations for an [Annotated] object.
 extension type Annotations.fromMap(Map<String, Object?> _value) {
-  factory Annotations({List<Role>? audience, double? priority}) {
+  factory Annotations({
+    List<Role>? audience,
+    DateTime? lastModified,
+    double? priority,
+  }) {
     assert(priority == null || (priority >= 0 && priority <= 1));
     return Annotations.fromMap({
       if (audience != null) 'audience': [for (var role in audience) role.name],
+      if (lastModified != null) 'lastModified': lastModified.toIso8601String(),
       if (priority != null) 'priority': priority,
     });
   }
@@ -416,6 +572,18 @@
     ];
   }
 
+  /// 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['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
diff --git a/pkgs/dart_mcp/lib/src/api/completions.dart b/pkgs/dart_mcp/lib/src/api/completions.dart
index c5072f9..1d8b929 100644
--- a/pkgs/dart_mcp/lib/src/api/completions.dart
+++ b/pkgs/dart_mcp/lib/src/api/completions.dart
@@ -14,18 +14,20 @@
   factory CompleteRequest({
     required Reference ref,
     required CompletionArgument argument,
+    CompletionContext? context,
     MetaWithProgressToken? meta,
   }) => CompleteRequest.fromMap({
     'ref': ref,
     'argument': argument,
+    if (context != null) 'context': context,
     if (meta != null) '_meta': meta,
   });
 
   /// A reference to the thing to complete.
   ///
-  /// See the [PromptReference] and [ResourceReference] types.
+  /// See the [PromptReference] and [ResourceTemplateReference] types.
   ///
-  /// In the case of a [ResourceReference], it must refer to a
+  /// In the case of a [ResourceTemplateReference], it must refer to a
   /// [ResourceTemplate].
   Reference get ref {
     final ref = _value['ref'] as Reference?;
@@ -43,6 +45,9 @@
     }
     return argument;
   }
+
+  /// Additional, optional context for completions.
+  CompletionContext? get context => _value['context'] as CompletionContext?;
 }
 
 /// The server's response to a completion/complete request
@@ -105,7 +110,18 @@
   String get value => _value['value'] as String;
 }
 
-/// Union type for references, see [PromptReference] and [ResourceReference].
+/// A context passed to a [CompleteRequest].
+extension type CompletionContext.fromMap(Map<String, Object?> _value) {
+  factory CompletionContext({Map<String, String>? arguments}) =>
+      CompletionContext.fromMap({'arguments': arguments});
+
+  /// Previously-resolved variables in a URI template or prompt.
+  Map<String, String>? get arguments =>
+      (_value['arguments'] as Map?)?.cast<String, String>();
+}
+
+/// Union type for references, see [PromptReference] and
+/// [ResourceTemplateReference].
 extension type Reference._(Map<String, Object?> _value) {
   factory Reference.fromMap(Map<String, Object?> value) {
     assert(value.containsKey('type'));
@@ -115,8 +131,9 @@
   /// Whether or not this is a [PromptReference].
   bool get isPrompt => _value['type'] == PromptReference.expectedType;
 
-  /// Whether or not this is a [ResourceReference].
-  bool get isResource => _value['type'] == ResourceReference.expectedType;
+  /// Whether or not this is a [ResourceTemplateReference].
+  bool get isResource =>
+      _value['type'] == ResourceTemplateReference.expectedType;
 
   /// The type of reference.
   ///
@@ -127,12 +144,12 @@
 }
 
 /// A reference to a resource or resource template definition.
-extension type ResourceReference.fromMap(Map<String, Object?> _value)
+extension type ResourceTemplateReference.fromMap(Map<String, Object?> _value)
     implements Reference {
   static const expectedType = 'ref/resource';
 
-  factory ResourceReference({required String uri}) =>
-      ResourceReference.fromMap({'uri': uri, 'type': expectedType});
+  factory ResourceTemplateReference({required String uri}) =>
+      ResourceTemplateReference.fromMap({'uri': uri, 'type': expectedType});
 
   /// This should always be [expectedType].
   ///
@@ -148,13 +165,20 @@
   String get uri => _value['uri'] as String;
 }
 
+@Deprecated('Use ResourceTemplateReference instead')
+typedef ResourceReference = ResourceTemplateReference;
+
 /// Identifies a prompt.
 extension type PromptReference.fromMap(Map<String, Object?> _value)
-    implements Reference {
+    implements Reference, BaseMetadata {
   static const expectedType = 'ref/prompt';
 
-  factory PromptReference({required String name}) =>
-      PromptReference.fromMap({'name': name, 'type': expectedType});
+  factory PromptReference({required String name, String? title}) =>
+      PromptReference.fromMap({
+        'name': name,
+        'title': title,
+        'type': expectedType,
+      });
 
   /// This should always be [expectedType].
   ///
@@ -165,7 +189,4 @@
     assert(type == expectedType);
     return type;
   }
-
-  /// The name of the prompt or prompt template
-  String get name => _value['name'] as String;
 }
diff --git a/pkgs/dart_mcp/lib/src/api/elicitation.dart b/pkgs/dart_mcp/lib/src/api/elicitation.dart
new file mode 100644
index 0000000..edc83ee
--- /dev/null
+++ b/pkgs/dart_mcp/lib/src/api/elicitation.dart
@@ -0,0 +1,139 @@
+// 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.
+
+part of 'api.dart';
+
+/// The parameters for an `elicitation/create` request.
+extension type ElicitRequest._fromMap(Map<String, Object?> _value)
+    implements Request {
+  static const methodName = 'elicitation/create';
+
+  factory ElicitRequest({
+    required String message,
+    required Schema requestedSchema,
+  }) {
+    assert(
+      validateRequestedSchema(requestedSchema),
+      'Invalid requestedSchema. Must be a flat object of primitive values.',
+    );
+    return ElicitRequest._fromMap({
+      'message': message,
+      'requestedSchema': requestedSchema,
+    });
+  }
+
+  /// A message to display to the user when collecting the response.
+  String get message {
+    final message = _value['message'] as String?;
+    if (message == null) {
+      throw ArgumentError('Missing required message field in $ElicitRequest');
+    }
+    return message;
+  }
+
+  /// A JSON schema that describes the expected response.
+  ///
+  /// The content may only consist of a flat object (no nested maps or lists)
+  /// with primitive values (`String`, `num`, `bool`, `enum`).
+  ///
+  /// You can use [validateRequestedSchema] to validate that a schema conforms
+  /// to these limitations.
+  Schema get requestedSchema {
+    final requestedSchema = _value['requestedSchema'] as Schema?;
+    if (requestedSchema == null) {
+      throw ArgumentError(
+        'Missing required requestedSchema field in $ElicitRequest',
+      );
+    }
+    return requestedSchema;
+  }
+
+  /// Validates the [schema] to make sure that it conforms to the
+  /// limitations of the spec.
+  ///
+  /// See also: [requestedSchema] for a description of the spec limitations.
+  static bool validateRequestedSchema(Schema schema) {
+    if (schema.type != JsonType.object) {
+      return false;
+    }
+
+    final objectSchema = schema as ObjectSchema;
+    final properties = objectSchema.properties;
+
+    if (properties == null) {
+      return true; // No properties to validate.
+    }
+
+    for (final propertySchema in properties.values) {
+      // Combinators would mean it's not a simple primitive type.
+      if (propertySchema.allOf != null ||
+          propertySchema.anyOf != null ||
+          propertySchema.oneOf != null ||
+          propertySchema.not != null) {
+        return false;
+      }
+
+      switch (propertySchema.type) {
+        case JsonType.string:
+        case JsonType.num:
+        case JsonType.int:
+        case JsonType.bool:
+        case JsonType.enumeration:
+          break;
+        case JsonType.object:
+        case JsonType.list:
+        case JsonType.nil:
+        case null:
+          // Disallowed, or no type specified.
+          return false;
+      }
+    }
+
+    return true;
+  }
+}
+
+/// The client's response to an `elicitation/create` request.
+extension type ElicitResult.fromMap(Map<String, Object?> _value)
+    implements Result {
+  factory ElicitResult({
+    required ElicitationAction action,
+    Map<String, Object?>? content,
+  }) => ElicitResult.fromMap({'action': action.name, 'content': content});
+
+  /// The action taken by the user in response to an elicitation request.
+  ///
+  /// - [ElicitationAction.accept]: The user accepted the request and provided
+  ///   the requested information.
+  /// - [ElicitationAction.reject]: The user explicitly declined the action.
+  /// - [ElicitationAction.cancel]: The user dismissed without making an
+  ///   explicit choice.
+  ElicitationAction get action {
+    final action = _value['action'] as String?;
+    if (action == null) {
+      throw ArgumentError('Missing required action field in $ElicitResult');
+    }
+    return ElicitationAction.values.byName(action);
+  }
+
+  /// The content of the response, if the user accepted the request.
+  ///
+  /// Must be `null` if the user didn't accept the request.
+  ///
+  /// The content must conform to the [ElicitRequest]'s `requestedSchema`.
+  Map<String, Object?>? get content =>
+      _value['content'] as Map<String, Object?>?;
+}
+
+/// The action taken by the user in response to an elicitation request.
+enum ElicitationAction {
+  /// The user accepted the request and provided the requested information.
+  accept,
+
+  /// The user explicitly declined the action.
+  reject,
+
+  /// The user dismissed without making an explicit choice.
+  cancel,
+}
diff --git a/pkgs/dart_mcp/lib/src/api/initialization.dart b/pkgs/dart_mcp/lib/src/api/initialization.dart
index 80da37c..81d7b6b 100644
--- a/pkgs/dart_mcp/lib/src/api/initialization.dart
+++ b/pkgs/dart_mcp/lib/src/api/initialization.dart
@@ -113,10 +113,12 @@
     Map<String, Object?>? experimental,
     RootsCapabilities? roots,
     Map<String, Object?>? sampling,
+    ElicitationCapability? elicitation,
   }) => ClientCapabilities.fromMap({
     if (experimental != null) 'experimental': experimental,
     if (roots != null) 'roots': roots,
     if (sampling != null) 'sampling': sampling,
+    if (elicitation != null) 'elicitation': elicitation,
   });
 
   /// Experimental, non-standard capabilities that the client supports.
@@ -147,6 +149,16 @@
     assert(sampling == null);
     _value['sampling'] = value;
   }
+
+  /// Present if the client supports elicitation.
+  ElicitationCapability? get elicitation =>
+      _value['elicitation'] as ElicitationCapability?;
+
+  /// Sets [elicitation], asserting it is null first.
+  set elicitation(ElicitationCapability? value) {
+    assert(elicitation == null);
+    _value['elicitation'] = value;
+  }
 }
 
 /// Whether the client supports notifications for changes to the roots list.
@@ -165,6 +177,11 @@
   }
 }
 
+/// Whether the client supports elicitation.
+extension type ElicitationCapability.fromMap(Map<String, Object?> _value) {
+  factory ElicitationCapability() => ElicitationCapability.fromMap({});
+}
+
 /// Capabilities that a server may support.
 ///
 /// Known capabilities are defined here, in this schema, but this is not a
@@ -177,12 +194,14 @@
     Prompts? prompts,
     Resources? resources,
     Tools? tools,
+    Elicitation? elicitation,
   }) => ServerCapabilities.fromMap({
     if (experimental != null) 'experimental': experimental,
     if (logging != null) 'logging': logging,
     if (prompts != null) 'prompts': prompts,
     if (resources != null) 'resources': resources,
     if (tools != null) 'tools': tools,
+    if (elicitation != null) 'elicitation': elicitation,
   });
 
   /// Experimental, non-standard capabilities that the server supports.
@@ -240,6 +259,15 @@
     assert(tools == null);
     _value['tools'] = value;
   }
+
+  /// Present if the server supports elicitation.
+  Elicitation? get elicitation => _value['elicitation'] as Elicitation?;
+
+  /// Sets [elicitation] if it is null, otherwise asserts.
+  set elicitation(Elicitation? value) {
+    assert(elicitation == null);
+    _value['elicitation'] = value;
+  }
 }
 
 /// Completions parameter for [ServerCapabilities].
@@ -304,13 +332,31 @@
   }
 }
 
-/// Describes the name and version of an MCP implementation.
-extension type Implementation.fromMap(Map<String, Object?> _value) {
-  factory Implementation({required String name, required String version}) =>
-      Implementation.fromMap({'name': name, 'version': version});
+/// Elicitation parameter for [ServerCapabilities].
+extension type Elicitation.fromMap(Map<String, Object?> _value) {
+  factory Elicitation() => Elicitation.fromMap({});
+}
 
-  String get name => _value['name'] as String;
-  String get version => _value['version'] as String;
+/// Describes the name and version of an MCP implementation.
+extension type Implementation.fromMap(Map<String, Object?> _value)
+    implements BaseMetadata {
+  factory Implementation({
+    required String name,
+    required String version,
+    String? title,
+  }) => Implementation.fromMap({
+    'name': name,
+    'version': version,
+    if (title != null) 'title': title,
+  });
+
+  String get version {
+    final version = _value['version'] as String?;
+    if (version == null) {
+      throw ArgumentError('Missing version field in $Implementation.');
+    }
+    return version;
+  }
 }
 
 @Deprecated('Use Implementation instead.')
diff --git a/pkgs/dart_mcp/lib/src/api/prompts.dart b/pkgs/dart_mcp/lib/src/api/prompts.dart
index a658725..86fa48e 100644
--- a/pkgs/dart_mcp/lib/src/api/prompts.dart
+++ b/pkgs/dart_mcp/lib/src/api/prompts.dart
@@ -86,7 +86,8 @@
 }
 
 /// A prompt or prompt template that the server offers.
-extension type Prompt.fromMap(Map<String, Object?> _value) {
+extension type Prompt.fromMap(Map<String, Object?> _value)
+    implements BaseMetadata {
   factory Prompt({
     required String name,
     String? description,
@@ -97,9 +98,6 @@
     if (arguments != null) 'arguments': arguments,
   });
 
-  /// The name of the prompt or prompt template.
-  String get name => _value['name'] as String;
-
   /// An optional description of what this prompt provides.
   String? get description => _value['description'] as String?;
 
@@ -108,20 +106,20 @@
 }
 
 /// Describes an argument that a prompt can accept.
-extension type PromptArgument.fromMap(Map<String, Object?> _value) {
+extension type PromptArgument.fromMap(Map<String, Object?> _value)
+    implements BaseMetadata {
   factory PromptArgument({
     required String name,
+    String? title,
     String? description,
     bool? required,
   }) => PromptArgument.fromMap({
     'name': name,
+    if (title != null) 'title': title,
     if (description != null) 'description': description,
     if (required != null) 'required': required,
   });
 
-  /// The name of the argument.
-  String get name => _value['name'] as String;
-
   /// A human-readable description of the argument.
   String? get description => _value['description'] as String?;
 
diff --git a/pkgs/dart_mcp/lib/src/api/resources.dart b/pkgs/dart_mcp/lib/src/api/resources.dart
index 5b6da07..799502d 100644
--- a/pkgs/dart_mcp/lib/src/api/resources.dart
+++ b/pkgs/dart_mcp/lib/src/api/resources.dart
@@ -191,7 +191,7 @@
 
 /// A known resource that the server is capable of reading.
 extension type Resource.fromMap(Map<String, Object?> _value)
-    implements Annotated {
+    implements Annotated, BaseMetadata, WithMetadata {
   factory Resource({
     required String uri,
     required String name,
@@ -199,6 +199,7 @@
     String? description,
     String? mimeType,
     int? size,
+    Meta? meta,
   }) => Resource.fromMap({
     'uri': uri,
     'name': name,
@@ -206,16 +207,12 @@
     if (description != null) 'description': description,
     if (mimeType != null) 'mimeType': mimeType,
     if (size != null) 'size': size,
+    if (meta != null) '_meta': meta,
   });
 
   /// The URI of this resource.
   String get uri => _value['uri'] as String;
 
-  /// A human-readable name for this resource.
-  ///
-  /// This can be used by clients to populate UI elements.
-  String get name => _value['name'] as String;
-
   /// A description of what this resource represents.
   ///
   /// This can be used by clients to improve the LLM's understanding of
@@ -235,30 +232,29 @@
 
 /// A template description for resources available on the server.
 extension type ResourceTemplate.fromMap(Map<String, Object?> _value)
-    implements Annotated {
+    implements Annotated, BaseMetadata, WithMetadata {
   factory ResourceTemplate({
     required String uriTemplate,
     required String name,
-    Annotations? annotations,
+    String? title,
     String? description,
+    Annotations? annotations,
     String? mimeType,
+    Meta? meta,
   }) => ResourceTemplate.fromMap({
     'uriTemplate': uriTemplate,
     'name': name,
-    if (annotations != null) 'annotations': annotations,
+    if (title != null) 'title': title,
     if (description != null) 'description': description,
+    if (annotations != null) 'annotations': annotations,
     if (mimeType != null) 'mimeType': mimeType,
+    if (meta != null) '_meta': meta,
   });
 
   /// A URI template (according to RFC 6570) that can be used to construct
   /// resource URIs.
   String get uriTemplate => _value['uriTemplate'] as String;
 
-  /// A human-readable name for the type of resource this template refers to.
-  ///
-  /// This can be used by clients to populate UI elements.
-  String get name => _value['name'] as String;
-
   /// A description of what this template is for.
   ///
   /// This can be used by clients to improve the LLM's understanding of
@@ -276,7 +272,8 @@
 ///
 /// Could be either [TextResourceContents] or [BlobResourceContents],
 /// use [isText] and [isBlob] before casting to the more specific type.
-extension type ResourceContents.fromMap(Map<String, Object?> _value) {
+extension type ResourceContents.fromMap(Map<String, Object?> _value)
+    implements WithMetadata {
   /// Whether or not this represents [TextResourceContents].
   bool get isText => _value.containsKey('text');
 
@@ -297,10 +294,12 @@
     required String uri,
     required String text,
     String? mimeType,
+    Meta? meta,
   }) => TextResourceContents.fromMap({
     'uri': uri,
     'text': text,
     if (mimeType != null) 'mimeType': mimeType,
+    if (meta != null) '_meta': meta,
   });
 
   /// The text of the item.
@@ -317,10 +316,12 @@
     required String uri,
     required String blob,
     String? mimeType,
+    Meta? meta,
   }) => BlobResourceContents.fromMap({
     'uri': uri,
     'blob': blob,
     if (mimeType != null) 'mimeType': mimeType,
+    if (meta != null) '_meta': meta,
   });
 
   /// A base64-encoded string representing the binary data of the item.
diff --git a/pkgs/dart_mcp/lib/src/api/roots.dart b/pkgs/dart_mcp/lib/src/api/roots.dart
index f7194a3..94139e6 100644
--- a/pkgs/dart_mcp/lib/src/api/roots.dart
+++ b/pkgs/dart_mcp/lib/src/api/roots.dart
@@ -43,15 +43,26 @@
 }
 
 /// Represents a root directory or file that the server can operate on.
-extension type Root.fromMap(Map<String, Object?> _value) {
-  factory Root({required String uri, String? name}) =>
-      Root.fromMap({'uri': uri, if (name != null) 'name': name});
+extension type Root.fromMap(Map<String, Object?> _value)
+    implements WithMetadata {
+  factory Root({required String uri, String? name, Meta? meta}) =>
+      Root.fromMap({
+        'uri': uri,
+        if (name != null) 'name': name,
+        if (meta != null) '_meta': meta,
+      });
 
   /// The URI identifying the root.
   ///
   /// This *must* start with file:// for now. This restriction may be relaxed
   /// in future versions of the protocol to allow other URI schemes.
-  String get uri => _value['uri'] as String;
+  String get uri {
+    final uri = _value['uri'] as String?;
+    if (uri == null) {
+      throw ArgumentError('Missing uri field in $Root.');
+    }
+    return uri;
+  }
 
   /// An optional name for the root.
   ///
@@ -67,7 +78,7 @@
 /// This notification should be sent whenever the client adds, removes, or
 /// modifies any root.
 /// The server should then request an updated list of roots using the
-/// ListRootsRequest.
+/// [ListRootsRequest].
 extension type RootsListChangedNotification.fromMap(Map<String, Object?> _value)
     implements Notification {
   static const methodName = 'notifications/roots/list_changed';
diff --git a/pkgs/dart_mcp/lib/src/api/tools.dart b/pkgs/dart_mcp/lib/src/api/tools.dart
index 407566d..8ea339b 100644
--- a/pkgs/dart_mcp/lib/src/api/tools.dart
+++ b/pkgs/dart_mcp/lib/src/api/tools.dart
@@ -53,15 +53,17 @@
   factory CallToolResult({
     Meta? meta,
     required List<Content> content,
+    Map<String, Object?>? structuredContent,
     bool? isError,
   }) => CallToolResult.fromMap({
     'content': content,
+    if (structuredContent != null) 'structuredContent': structuredContent,
     if (isError != null) 'isError': isError,
     if (meta != null) '_meta': meta,
   });
 
-  /// The type of content, either [TextContent], [ImageContent],
-  /// or [EmbeddedResource],
+  /// The returned content, either [TextContent], [ImageContent],
+  /// [AudioContent], [ResourceLink] or [EmbeddedResource].
   List<Content> get content {
     final content = (_value['content'] as List?)?.cast<Content>();
     if (content == null) {
@@ -70,6 +72,11 @@
     return content;
   }
 
+  /// The content as structured output, if the [Tool] declared an
+  /// `outputSchema`.
+  Map<String, Object?>? get structuredContent =>
+      _value['structuredContent'] as Map<String, Object?>?;
+
   /// Whether the tool call ended in an error.
   ///
   /// If not set, this is assumed to be false (the call was successful).
@@ -119,18 +126,27 @@
 }
 
 /// Definition for a tool the client can call.
-extension type Tool.fromMap(Map<String, Object?> _value) {
+extension type Tool.fromMap(Map<String, Object?> _value)
+    implements BaseMetadata {
   factory Tool({
     required String name,
+    String? title,
     String? description,
     required ObjectSchema inputSchema,
+    // Only supported since version `ProtocolVersion.v2025_06_18`.
+    ObjectSchema? outputSchema,
     // Only supported since version `ProtocolVersion.v2025_03_26`.
     ToolAnnotations? annotations,
+    // Only supported since version `ProtocolVersion.v2025_03_26`.
+    Meta? meta,
   }) => Tool.fromMap({
     'name': name,
+    if (title != null) 'title': title,
     if (description != null) 'description': description,
     'inputSchema': inputSchema,
+    if (outputSchema != null) 'outputSchema': outputSchema,
     if (annotations != null) 'annotations': annotations,
+    if (meta != null) '_meta': meta,
   });
 
   /// Optional additional tool information.
@@ -140,15 +156,6 @@
       (_value['annotations'] as Map?)?.cast<String, Object?>()
           as ToolAnnotations?;
 
-  /// The name of the tool.
-  String get name {
-    final name = _value['name'] as String?;
-    if (name == null) {
-      throw ArgumentError('Missing name field in $Tool');
-    }
-    return name;
-  }
-
   /// A human-readable description of the tool.
   String? get description => _value['description'] as String?;
 
@@ -161,6 +168,13 @@
     }
     return inputSchema;
   }
+
+  /// An optional JSON [ObjectSchema] object defining the expected schema of the
+  /// tool output.
+  ///
+  /// If the `outputSchema` is specified, then the output from the tool must
+  /// conform to the schema.
+  ObjectSchema? get outputSchema => _value['outputSchema'] as ObjectSchema?;
 }
 
 /// Additional properties describing a Tool to clients.
diff --git a/pkgs/dart_mcp/lib/src/client/client.dart b/pkgs/dart_mcp/lib/src/client/client.dart
index cde9930..1b1a919 100644
--- a/pkgs/dart_mcp/lib/src/client/client.dart
+++ b/pkgs/dart_mcp/lib/src/client/client.dart
@@ -15,6 +15,7 @@
 import '../api/api.dart';
 import '../shared.dart';
 
+part 'elicitation_support.dart';
 part 'roots_support.dart';
 part 'sampling_support.dart';
 
@@ -104,6 +105,7 @@
       protocolLogSink: protocolLogSink,
       rootsSupport: self is RootsSupport ? self : null,
       samplingSupport: self is SamplingSupport ? self : null,
+      elicitationSupport: self is ElicitationSupport ? self : null,
     );
     connections.add(connection);
     channel.sink.done.then((_) => connections.remove(connection));
@@ -122,7 +124,7 @@
 
 /// An active server connection.
 base class ServerConnection extends MCPBase {
-  /// The version of the protocol that was negotiated during intialization.
+  /// The version of the protocol that was negotiated during initialization.
   ///
   /// Some APIs may error if you attempt to use them without first checking the
   /// protocol version.
@@ -201,6 +203,7 @@
     super.protocolLogSink,
     RootsSupport? rootsSupport,
     SamplingSupport? samplingSupport,
+    ElicitationSupport? elicitationSupport,
   }) {
     if (rootsSupport != null) {
       registerRequestHandler(
@@ -217,6 +220,12 @@
       );
     }
 
+    if (elicitationSupport != null) {
+      registerRequestHandler(ElicitRequest.methodName, (ElicitRequest request) {
+        return elicitationSupport.handleElicitation(request);
+      });
+    }
+
     registerNotificationHandler(
       PromptListChangedNotification.methodName,
       _promptListChangedController.sink.add,
@@ -277,8 +286,10 @@
     serverInfo = response.serverInfo;
     serverCapabilities = response.capabilities;
     final serverVersion = response.protocolVersion;
-    if (serverVersion?.isSupported != true) {
+    if (serverVersion == null || !serverVersion.isSupported) {
       await shutdown();
+    } else {
+      protocolVersion = serverVersion;
     }
     return response;
   }
diff --git a/pkgs/dart_mcp/lib/src/client/elicitation_support.dart b/pkgs/dart_mcp/lib/src/client/elicitation_support.dart
new file mode 100644
index 0000000..b847c3d
--- /dev/null
+++ b/pkgs/dart_mcp/lib/src/client/elicitation_support.dart
@@ -0,0 +1,22 @@
+// 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.
+
+part of 'client.dart';
+
+/// The interface for handling elicitation requests.
+///
+/// Any client using [ElicitationSupport] must implement this interface.
+abstract interface class WithElicitationHandler {
+  FutureOr<ElicitResult> handleElicitation(ElicitRequest request);
+}
+
+/// A mixin that adds support for the `elicitation` capability to an
+/// [MCPClient].
+base mixin ElicitationSupport on MCPClient implements WithElicitationHandler {
+  @override
+  void initialize() {
+    capabilities.elicitation ??= ElicitationCapability();
+    super.initialize();
+  }
+}
diff --git a/pkgs/dart_mcp/lib/src/server/elicitation_request_support.dart b/pkgs/dart_mcp/lib/src/server/elicitation_request_support.dart
new file mode 100644
index 0000000..6bdf0e1
--- /dev/null
+++ b/pkgs/dart_mcp/lib/src/server/elicitation_request_support.dart
@@ -0,0 +1,36 @@
+part of 'server.dart';
+
+/// A mixin that adds support for making `elicitation/create` requests to a
+/// [MCPServer].
+base mixin ElicitationRequestSupport on LoggingSupport {
+  /// Whether or not the connected client supports elicitation.
+  ///
+  /// Only safe to call after calling [initialize] on `super` since this is
+  /// based on the client capabilities.
+  bool get supportsElicitation => clientCapabilities.elicitation != null;
+
+  @override
+  FutureOr<InitializeResult> initialize(InitializeRequest request) {
+    initialized.then((_) {
+      if (!supportsElicitation) {
+        log(
+          LoggingLevel.warning,
+          'Client does not support the elicitation capability, some '
+          'functionality may be disabled.',
+        );
+      }
+    });
+    return super.initialize(request);
+  }
+
+  /// Sends an `elicitation/create` request to the client.
+  ///
+  /// This method will only succeed if the client has advertised the
+  /// `elicitation` capability.
+  Future<ElicitResult> elicit(ElicitRequest request) async {
+    if (!supportsElicitation) {
+      throw StateError('Client does not support elicitation');
+    }
+    return sendRequest(ElicitRequest.methodName, request);
+  }
+}
diff --git a/pkgs/dart_mcp/lib/src/server/server.dart b/pkgs/dart_mcp/lib/src/server/server.dart
index a0ab0e0..43260fd 100644
--- a/pkgs/dart_mcp/lib/src/server/server.dart
+++ b/pkgs/dart_mcp/lib/src/server/server.dart
@@ -12,6 +12,7 @@
 import '../shared.dart';
 
 part 'completions_support.dart';
+part 'elicitation_request_support.dart';
 part 'logging_support.dart';
 part 'prompts_support.dart';
 part 'resources_support.dart';
diff --git a/pkgs/dart_mcp/test/api/api_test.dart b/pkgs/dart_mcp/test/api/api_test.dart
index faddb6b..e26a237 100644
--- a/pkgs/dart_mcp/test/api/api_test.dart
+++ b/pkgs/dart_mcp/test/api/api_test.dart
@@ -61,6 +61,16 @@
 
   group('API object validation', () {
     test('throws when required fields are missing', () {
+      expect(() => Root.fromMap({}).uri, throwsA(isA<ArgumentError>()));
+      expect(
+        () => Implementation.fromMap({'name': 'test'}).version,
+        throwsA(isA<ArgumentError>()),
+      );
+      expect(
+        () => BaseMetadata.fromMap({}).name,
+        throwsA(isA<ArgumentError>()),
+      );
+
       final empty = <String, Object?>{};
 
       // Initialization
@@ -104,5 +114,14 @@
         throwsArgumentError,
       );
     });
+    test('meta field is parsed correctly', () {
+      final root = Root.fromMap({
+        'uri': 'file:///foo/bar',
+        '_meta': {'foo': 'bar'},
+      });
+      expect(root.meta, isNotNull);
+      final metaMap = root.meta as Map;
+      expect(metaMap['foo'], 'bar');
+    });
   });
 }
diff --git a/pkgs/dart_mcp/test/api/completions_test.dart b/pkgs/dart_mcp/test/api/completions_test.dart
index 04c542c..7a9730f 100644
--- a/pkgs/dart_mcp/test/api/completions_test.dart
+++ b/pkgs/dart_mcp/test/api/completions_test.dart
@@ -66,6 +66,27 @@
       TestMCPServerWithCompletions.packagePaths,
     );
   });
+
+  test('client can request resource completions with context', () async {
+    final environment = TestEnvironment(
+      TestMCPClient(),
+      TestMCPServerWithCompletions.new,
+    );
+    final initializeResult = await environment.initializeServer();
+    expect(initializeResult.capabilities.completions, Completions());
+
+    final serverConnection = environment.serverConnection;
+    expect(
+      (await serverConnection.requestCompletions(
+        CompleteRequest(
+          ref: TestMCPServerWithCompletions.packageUriTemplateRef,
+          argument: CompletionArgument(name: 'path', value: 'a'),
+          context: CompletionContext(arguments: {'package_name': 'async'}),
+        ),
+      )).completion.values,
+      ['async.dart'],
+    );
+  });
 }
 
 final class TestMCPServerWithCompletions extends TestMCPServer
@@ -84,15 +105,23 @@
           completion: Completion(values: cLanguages, hasMore: false),
         );
       case Reference(isResource: true)
-          when (ref as ResourceReference).uri == packageUriTemplate.uriTemplate:
+          when (ref as ResourceTemplateReference).uri ==
+              packageUriTemplate.uriTemplate:
         return switch (request.argument) {
           CompletionArgument(name: 'package_name', value: 'a') =>
             CompleteResult(
               completion: Completion(values: aPackages, hasMore: false),
             ),
-          CompletionArgument(name: 'path', value: 'a') => CompleteResult(
-            completion: Completion(values: packagePaths, hasMore: false),
-          ),
+          CompletionArgument(name: 'path', value: 'a') => switch (request
+              .context
+              ?.arguments?['package_name']) {
+            'async' => CompleteResult(
+              completion: Completion(values: ['async.dart']),
+            ),
+            _ => CompleteResult(
+              completion: Completion(values: packagePaths, hasMore: false),
+            ),
+          },
           _ =>
             throw ArgumentError.value(
               request.argument,
@@ -124,7 +153,7 @@
   );
   static final cLanguages = ['c', 'c++', 'c#'];
 
-  static final packageUriTemplateRef = ResourceReference(
+  static final packageUriTemplateRef = ResourceTemplateReference(
     uri: packageUriTemplate.uriTemplate,
   );
   static final packageUriTemplate = ResourceTemplate(
diff --git a/pkgs/dart_mcp/test/api/elicitation_test.dart b/pkgs/dart_mcp/test/api/elicitation_test.dart
new file mode 100644
index 0000000..fa53940
--- /dev/null
+++ b/pkgs/dart_mcp/test/api/elicitation_test.dart
@@ -0,0 +1,74 @@
+// 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.
+
+import 'dart:async';
+
+import 'package:dart_mcp/client.dart';
+import 'package:dart_mcp/server.dart';
+import 'package:test/test.dart';
+
+import '../test_utils.dart';
+
+void main() {
+  group('elicitation', () {
+    test('server can elicit information from client', () async {
+      final elicitationCompleter = Completer<ElicitResult>();
+      final environment = TestEnvironment(
+        TestMCPClientWithElicitationSupport(
+          elicitationHandler: (request) {
+            return elicitationCompleter.future;
+          },
+        ),
+        TestMCPServerWithElicitationRequestSupport.new,
+      );
+      final server = environment.server;
+      unawaited(server.initialized);
+      await environment.serverConnection.initialize(
+        InitializeRequest(
+          protocolVersion: ProtocolVersion.latestSupported,
+          capabilities: environment.client.capabilities,
+          clientInfo: environment.client.implementation,
+        ),
+      );
+
+      final elicitationRequest = server.elicit(
+        ElicitRequest(
+          message: 'What is your name?',
+          requestedSchema: ObjectSchema(
+            properties: {'name': StringSchema(description: 'Your name')},
+            required: ['name'],
+          ),
+        ),
+      );
+
+      elicitationCompleter.complete(
+        ElicitResult(
+          action: ElicitationAction.accept,
+          content: {'name': 'John Doe'},
+        ),
+      );
+
+      final result = await elicitationRequest;
+      expect(result.action, ElicitationAction.accept);
+      expect(result.content, {'name': 'John Doe'});
+    });
+  });
+}
+
+final class TestMCPClientWithElicitationSupport extends TestMCPClient
+    with ElicitationSupport {
+  TestMCPClientWithElicitationSupport({required this.elicitationHandler});
+
+  FutureOr<ElicitResult> Function(ElicitRequest request) elicitationHandler;
+
+  @override
+  FutureOr<ElicitResult> handleElicitation(ElicitRequest request) {
+    return elicitationHandler(request);
+  }
+}
+
+base class TestMCPServerWithElicitationRequestSupport extends TestMCPServer
+    with LoggingSupport, ElicitationRequestSupport {
+  TestMCPServerWithElicitationRequestSupport(super.channel);
+}
diff --git a/pkgs/dart_mcp/test/api/tools_test.dart b/pkgs/dart_mcp/test/api/tools_test.dart
index 21bfb74..20f9a0d 100644
--- a/pkgs/dart_mcp/test/api/tools_test.dart
+++ b/pkgs/dart_mcp/test/api/tools_test.dart
@@ -4,9 +4,13 @@
 
 // ignore_for_file: lines_longer_than_80_chars
 
-import 'package:dart_mcp/src/api/api.dart';
+import 'dart:async';
+
+import 'package:dart_mcp/server.dart';
 import 'package:test/test.dart';
 
+import '../test_utils.dart';
+
 void main() {
   // Helper to strip path and details for comparison, keeping only the error
   // field. Assumes e.error is non-null for any valid error generated by
@@ -56,12 +60,12 @@
     // This relies on ValidationError's equality being based on its
     // underlying map (including the path if present).
     expect(
-      actualErrors.toSet(),
-      equals(expectedErrorsWithPaths.toSet()),
+      actualErrors.map((e) => e.toString()).toList()..sort(),
+      equals(expectedErrorsWithPaths.map((e) => e.toString()).toList()..sort()),
       reason:
           reason ??
-          'Data: $data. Expected (exact): $expectedErrorsWithPaths. '
-              'Actual (exact): $actualErrors',
+          'Data: $data. Expected (exact): ${expectedErrorsWithPaths.map((e) => e.toString()).toSet()}. '
+              'Actual (exact): ${actualErrors.map((e) => e.toString()).toSet()}',
     );
   }
 
@@ -1763,4 +1767,151 @@
       );
     });
   });
+
+  group('Tool Communication', () {
+    test('can call a tool', () async {
+      final environment = TestEnvironment(
+        TestMCPClient(),
+        (channel) => TestMCPServerWithTools(
+          channel,
+          tools: [
+            Tool(
+              name: 'foo',
+              inputSchema: ObjectSchema(properties: {'bar': StringSchema()}),
+            ),
+          ],
+          toolHandlers: {
+            'foo': (CallToolRequest request) {
+              return CallToolResult(
+                content: [
+                  TextContent(
+                    text: (request.arguments as Map)['bar'] as String,
+                  ),
+                ],
+              );
+            },
+          },
+        ),
+      );
+      final serverConnection = environment.serverConnection;
+      await serverConnection.initialize(
+        InitializeRequest(
+          protocolVersion: ProtocolVersion.latestSupported,
+          capabilities: environment.client.capabilities,
+          clientInfo: environment.client.implementation,
+        ),
+      );
+      final request = CallToolRequest(name: 'foo', arguments: {'bar': 'baz'});
+      final result = await serverConnection.callTool(request);
+      expect(result.content, hasLength(1));
+      expect(result.content.first, isA<TextContent>());
+      final textContent = result.content.first as TextContent;
+      expect(textContent.text, 'baz');
+    });
+
+    test('can return a resource link', () async {
+      final environment = TestEnvironment(
+        TestMCPClient(),
+        (channel) => TestMCPServerWithTools(
+          channel,
+          tools: [Tool(name: 'foo', inputSchema: ObjectSchema())],
+          toolHandlers: {
+            'foo': (request) {
+              return CallToolResult(
+                content: [
+                  ResourceLink(
+                    name: 'foo',
+                    description: 'a description',
+                    uri: 'https://google.com',
+                    mimeType: 'text/html',
+                  ),
+                ],
+              );
+            },
+          },
+        ),
+      );
+      final serverConnection = environment.serverConnection;
+      await serverConnection.initialize(
+        InitializeRequest(
+          protocolVersion: ProtocolVersion.latestSupported,
+          capabilities: environment.client.capabilities,
+          clientInfo: environment.client.implementation,
+        ),
+      );
+      final request = CallToolRequest(name: 'foo', arguments: {});
+      final result = await serverConnection.callTool(request);
+      expect(result.content, hasLength(1));
+      expect(result.content.first, isA<ResourceLink>());
+      final resourceLink = result.content.first as ResourceLink;
+      expect(resourceLink.name, 'foo');
+      expect(resourceLink.description, 'a description');
+      expect(resourceLink.uri, 'https://google.com');
+      expect(resourceLink.mimeType, 'text/html');
+    });
+
+    test('can return structured content', () async {
+      final environment = TestEnvironment(
+        TestMCPClient(),
+        (channel) => TestMCPServerWithTools(
+          channel,
+          tools: [
+            Tool(
+              name: 'foo',
+              inputSchema: ObjectSchema(),
+              outputSchema: ObjectSchema(properties: {'bar': StringSchema()}),
+            ),
+          ],
+          toolHandlers: {
+            'foo': (request) {
+              return CallToolResult(
+                content: [],
+                structuredContent: {'bar': 'baz'},
+              );
+            },
+          },
+        ),
+      );
+      final serverConnection = environment.serverConnection;
+      await serverConnection.initialize(
+        InitializeRequest(
+          protocolVersion: ProtocolVersion.latestSupported,
+          capabilities: environment.client.capabilities,
+          clientInfo: environment.client.implementation,
+        ),
+      );
+      final request = CallToolRequest(name: 'foo', arguments: {});
+      final result = await serverConnection.callTool(request);
+      expect(result.structuredContent, {'bar': 'baz'});
+    });
+  });
+}
+
+base class TestMCPServerWithTools extends TestMCPServer with ToolsSupport {
+  final List<Tool> _initialTools;
+  final Map<String, FutureOr<CallToolResult> Function(CallToolRequest)>
+  _initialToolHandlers;
+
+  TestMCPServerWithTools(
+    super.channel, {
+    List<Tool> tools = const [],
+    Map<String, FutureOr<CallToolResult> Function(CallToolRequest)>
+        toolHandlers =
+        const {},
+  }) : _initialTools = tools,
+       _initialToolHandlers = toolHandlers;
+
+  @override
+  FutureOr<InitializeResult> initialize(InitializeRequest request) async {
+    final result = await super.initialize(request);
+    for (final tool in _initialTools) {
+      final handler = _initialToolHandlers[tool.name];
+      if (handler != null) {
+        registerTool(tool, handler);
+      } else {
+        throw StateError('No handler provided for tool: ${tool.name}');
+      }
+    }
+    return result;
+  }
 }
diff --git a/pkgs/dart_mcp/test/client_and_server_test.dart b/pkgs/dart_mcp/test/client_and_server_test.dart
index 3aa3faa..f2cbb8a 100644
--- a/pkgs/dart_mcp/test/client_and_server_test.dart
+++ b/pkgs/dart_mcp/test/client_and_server_test.dart
@@ -261,6 +261,19 @@
   });
 
   group('version negotiation', () {
+    test('client and server respect negotiated protocol version', () async {
+      final environment = TestEnvironment(TestMCPClient(), TestMCPServer.new);
+      final serverConnection = environment.serverConnection;
+      final initializeResult = await serverConnection.initialize(
+        InitializeRequest(
+          protocolVersion: ProtocolVersion.oldestSupported,
+          capabilities: environment.client.capabilities,
+          clientInfo: environment.client.implementation,
+        ),
+      );
+      expect(initializeResult.protocolVersion, ProtocolVersion.oldestSupported);
+      expect(serverConnection.protocolVersion, ProtocolVersion.oldestSupported);
+    });
     test('server can downgrade the version', () async {
       final environment = TestEnvironment(
         TestMCPClient(),
diff --git a/pkgs/dart_mcp_server/pubspec.yaml b/pkgs/dart_mcp_server/pubspec.yaml
index b78cbf7..4df5932 100644
--- a/pkgs/dart_mcp_server/pubspec.yaml
+++ b/pkgs/dart_mcp_server/pubspec.yaml
@@ -3,7 +3,6 @@
   An MCP server for Dart projects, exposing various developer tools to AI
   models.
 publish_to: none
-
 environment:
   sdk: ^3.7.0
 
@@ -25,8 +24,7 @@
   language_server_protocol:
     git:
       url: https://github.com/dart-lang/sdk.git
-      path:
-        third_party/pkg/language_server_protocol
+      path: third_party/pkg/language_server_protocol
   meta: ^1.16.0
   path: ^1.9.1
   pool: ^1.5.1