auto validate tool arguments, improve validation messages (#200)

Marking this as closing https://github.com/dart-lang/ai/issues/197 - it should significantly help the LLM figure out what it did wrong. The error would now be something like "Value `<root>` is not of type `List<dynamic>` at path #root["roots"]".

I made a fair number of changes to the validation errors here, namely only emitting errors for the leaf node where the error actually happened and not the entire path up. I don't think that extra info was helpful.

cc @kenzieschmoll 
diff --git a/pkgs/dart_mcp/CHANGELOG.md b/pkgs/dart_mcp/CHANGELOG.md
index 700381a..008ac99 100644
--- a/pkgs/dart_mcp/CHANGELOG.md
+++ b/pkgs/dart_mcp/CHANGELOG.md
@@ -1,8 +1,18 @@
-## 0.2.3-wip
+## 0.3.0-wip
 
 - 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.
+- Add more detail to type validation errors.
+- Remove some duplicate validation errors, errors are only reported for the
+  leaf nodes and not all the way up the tree.
+  - Deprecated a few validation error types as a part of this, including
+    `propertyNamesInvalid`, `propertyValueInvalid`, `itemInvalid` and
+    `prefixItemInvalid`.
+- Added a `custom` validation error type.
+- Auto-validate schemas for all tools by default. This can be disabled by
+  passing `validateArguments: false` to `registerTool`.
+  - This is breaking since this method is overridden by the Dart MCP server.
 - 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.
diff --git a/pkgs/dart_mcp/lib/src/api/tools.dart b/pkgs/dart_mcp/lib/src/api/tools.dart
index 8ea339b..85a2c09 100644
--- a/pkgs/dart_mcp/lib/src/api/tools.dart
+++ b/pkgs/dart_mcp/lib/src/api/tools.dart
@@ -245,6 +245,9 @@
 /// Enum representing the types of validation failures when checking data
 /// against a schema.
 enum ValidationErrorType {
+  // For custom validation.
+  custom,
+
   // General
   typeMismatch,
 
@@ -259,7 +262,15 @@
   additionalPropertyNotAllowed,
   minPropertiesNotMet,
   maxPropertiesExceeded,
+  @Deprecated(
+    'These events are no longer emitted, just emit a single error for the '
+    'key itself',
+  )
   propertyNamesInvalid,
+  @Deprecated(
+    'These events are no longer emitted, just emit a single error for the '
+    'property itself',
+  )
   propertyValueInvalid,
   patternPropertyValueInvalid,
   unevaluatedPropertyNotAllowed,
@@ -268,7 +279,15 @@
   minItemsNotMet,
   maxItemsExceeded,
   uniqueItemsViolated,
+  @Deprecated(
+    'These events are no longer emitted, just emit a single error for the '
+    'item itself',
+  )
   itemInvalid,
+  @Deprecated(
+    'These events are no longer emitted, just emit a single error for the '
+    'prefix item itself',
+  )
   prefixItemInvalid,
   unevaluatedItemNotAllowed,
 
@@ -293,29 +312,38 @@
 extension type ValidationError.fromMap(Map<String, Object?> _value) {
   factory ValidationError(
     ValidationErrorType error, {
-    List<String>? path,
+    required List<String> path,
     String? details,
   }) => ValidationError.fromMap({
     'error': error.name,
-    if (path != null) 'path': path.toList(),
+    'path': path.toList(),
     if (details != null) 'details': details,
   });
 
-  /// The type of validation error that occurred.
-  ValidationErrorType? get error => ValidationErrorType.values.firstWhereOrNull(
-    (t) => t.name == _value['error'],
+  factory ValidationError.typeMismatch({
+    required List<String> path,
+    required Type expectedType,
+    required Object? actualValue,
+  }) => ValidationError(
+    ValidationErrorType.typeMismatch,
+    path: path,
+    details: 'Value `$actualValue` is not of type `$expectedType`',
   );
 
+  /// The type of validation error that occurred.
+  ValidationErrorType get error =>
+      ValidationErrorType.values.firstWhere((t) => t.name == _value['error']);
+
   /// The path to the object that had the error.
-  List<String>? get path => (_value['path'] as List?)?.cast<String>();
+  List<String> get path => (_value['path'] as List).cast<String>();
 
   /// Additional details about the error (optional).
   String? get details => _value['details'] as String?;
 
   String toErrorString() {
-    return '${error!.name} in object at '
-        '${path!.map((p) => '[$p]').join('')}'
-        '${details != null ? ' - $details' : ''}';
+    return '${details != null ? '$details' : error.name} at path '
+        '#root${path.map((p) => '["$p"]').join('')}'
+        '';
   }
 }
 
@@ -479,9 +507,10 @@
           if (data is! bool) {
             isValid = false;
             accumulatedFailures.add(
-              ValidationError(
-                ValidationErrorType.typeMismatch,
+              ValidationError.typeMismatch(
                 path: currentPath,
+                expectedType: bool,
+                actualValue: data,
               ),
             );
           }
@@ -489,9 +518,10 @@
           if (data != null) {
             isValid = false;
             accumulatedFailures.add(
-              ValidationError(
-                ValidationErrorType.typeMismatch,
+              ValidationError.typeMismatch(
                 path: currentPath,
+                expectedType: Null,
+                actualValue: data,
               ),
             );
           }
@@ -512,9 +542,9 @@
 
     // Validate data against the non-combinator keywords of the current schema
     // ('this').
-    if (!_performDirectValidation(data, currentPath, accumulatedFailures)) {
-      isValid = false;
-    }
+    isValid =
+        _performDirectValidation(data, currentPath, accumulatedFailures) &&
+        isValid;
 
     // Handle combinator keywords. Create the "base schema" from 'this' schema,
     // excluding combinator keywords. This base schema's constraints are
@@ -544,19 +574,16 @@
 
     if (allOf case final allOfList?) {
       var allSubSchemasAreValid = true;
-      final allOfDetailedSubFailures = <ValidationError>[];
 
       for (final subSchemaMember in allOfList) {
         final effectiveSubSchema = mergeWithBase(subSchemaMember);
-        final currentSubSchemaFailures = _createHashSet();
-        if (!effectiveSubSchema._validateSchema(
-          data,
-          currentPath,
-          currentSubSchemaFailures,
-        )) {
-          allSubSchemasAreValid = false;
-          allOfDetailedSubFailures.addAll(currentSubSchemaFailures);
-        }
+        allSubSchemasAreValid =
+            effectiveSubSchema._validateSchema(
+              data,
+              currentPath,
+              accumulatedFailures,
+            ) &&
+            allSubSchemasAreValid;
       }
       // `allOf` fails if any effective sub-schema (Base AND SubMember) failed.
       if (!allSubSchemasAreValid) {
@@ -564,7 +591,6 @@
         accumulatedFailures.add(
           ValidationError(ValidationErrorType.allOfNotMet, path: currentPath),
         );
-        accumulatedFailures.addAll(allOfDetailedSubFailures);
       }
     }
     if (anyOf case final anyOfList?) {
@@ -584,7 +610,11 @@
       if (!oneSubSchemaPassed) {
         isValid = false;
         accumulatedFailures.add(
-          ValidationError(ValidationErrorType.anyOfNotMet, path: currentPath),
+          ValidationError(
+            ValidationErrorType.anyOfNotMet,
+            path: currentPath,
+            details: 'No sub-schema passed validation for $data',
+          ),
         );
       }
     }
@@ -603,30 +633,35 @@
       if (matchingSubSchemaCount != 1) {
         isValid = false;
         accumulatedFailures.add(
-          ValidationError(ValidationErrorType.oneOfNotMet, path: currentPath),
+          ValidationError(
+            ValidationErrorType.oneOfNotMet,
+            path: currentPath,
+            details:
+                'Exactly one sub-schema must match $data but '
+                '$matchingSubSchemaCount did',
+          ),
         );
       }
     }
     if (not case final notList?) {
-      final notConditionViolatedBySubSchema = notList.any((subSchemaInNot) {
+      for (final subSchemaInNot in notList) {
         final effectiveSubSchemaForNot = mergeWithBase(subSchemaInNot);
         // 'not' is violated if data *validates* against the (Base AND
         // NotSubSchema).
-        return effectiveSubSchemaForNot._validateSchema(
+        if (effectiveSubSchemaForNot._validateSchema(
           data,
           currentPath,
           _createHashSet(),
-        );
-      });
-
-      if (notConditionViolatedBySubSchema) {
-        isValid = false;
-        accumulatedFailures.add(
-          ValidationError(
-            ValidationErrorType.notConditionViolated,
-            path: currentPath,
-          ),
-        );
+        )) {
+          isValid = false;
+          accumulatedFailures.add(
+            ValidationError(
+              ValidationErrorType.notConditionViolated,
+              path: currentPath,
+              details: '$data matched the schema $subSchemaInNot',
+            ),
+          );
+        }
       }
     }
 
@@ -872,7 +907,11 @@
   ) {
     if (data is! Map<String, Object?>) {
       accumulatedFailures.add(
-        ValidationError(ValidationErrorType.typeMismatch, path: currentPath),
+        ValidationError.typeMismatch(
+          path: currentPath,
+          expectedType: Map<String, Object?>,
+          actualValue: data,
+        ),
       );
       return false;
     }
@@ -887,7 +926,7 @@
           path: currentPath,
           details:
               'There should be at least $minProperties '
-              'properties. Only ${data.keys.length} were found.',
+              'properties. Only ${data.keys.length} were found',
         ),
       );
     }
@@ -900,7 +939,7 @@
           path: currentPath,
           details:
               'Exceeded maxProperties limit of $maxProperties '
-              '(${data.keys.length}).',
+              '(${data.keys.length})',
         ),
       );
     }
@@ -912,7 +951,7 @@
           ValidationError(
             ValidationErrorType.requiredPropertyMissing,
             path: currentPath,
-            details: 'Required property "$reqProp" is missing.',
+            details: 'Required property "$reqProp" is missing',
           ),
         );
       }
@@ -928,21 +967,13 @@
         if (data.containsKey(entry.key)) {
           currentPath.add(entry.key);
           evaluatedKeys.add(entry.key);
-          final propertySpecificFailures = _createHashSet();
-          if (!entry.value._validateSchema(
-            data[entry.key],
-            currentPath,
-            propertySpecificFailures,
-          )) {
-            isValid = false;
-            accumulatedFailures.add(
-              ValidationError(
-                ValidationErrorType.propertyValueInvalid,
-                path: currentPath,
-              ),
-            );
-            accumulatedFailures.addAll(propertySpecificFailures);
-          }
+          isValid =
+              entry.value._validateSchema(
+                data[entry.key],
+                currentPath,
+                accumulatedFailures,
+              ) &&
+              isValid;
           currentPath.removeLast();
         }
       }
@@ -959,21 +990,13 @@
           if (pattern.hasMatch(dataKey)) {
             currentPath.add(dataKey);
             evaluatedKeys.add(dataKey);
-            final patternPropertySpecificFailures = _createHashSet();
-            if (!entry.value._validateSchema(
-              data[dataKey],
-              currentPath,
-              patternPropertySpecificFailures,
-            )) {
-              isValid = false;
-              accumulatedFailures.add(
-                ValidationError(
-                  ValidationErrorType.patternPropertyValueInvalid,
-                  path: currentPath,
-                ),
-              );
-              accumulatedFailures.addAll(patternPropertySpecificFailures);
-            }
+            isValid =
+                entry.value._validateSchema(
+                  data[dataKey],
+                  currentPath,
+                  accumulatedFailures,
+                ) &&
+                isValid;
             currentPath.removeLast();
           }
         }
@@ -987,21 +1010,13 @@
     if (propertyNames case final propNamesSchema?) {
       for (final key in data.keys) {
         currentPath.add(key);
-        final propertyNameSpecificFailures = _createHashSet();
-        if (!propNamesSchema._validateSchema(
-          key,
-          currentPath,
-          propertyNameSpecificFailures,
-        )) {
-          isValid = false;
-          accumulatedFailures.addAll(propertyNameSpecificFailures);
-          accumulatedFailures.add(
-            ValidationError(
-              ValidationErrorType.propertyNamesInvalid,
-              path: currentPath,
-            ),
-          );
-        }
+        isValid =
+            propNamesSchema._validateSchema(
+              key,
+              currentPath,
+              accumulatedFailures,
+            ) &&
+            isValid;
         currentPath.removeLast();
       }
     }
@@ -1013,32 +1028,26 @@
     for (final dataKey in data.keys) {
       if (evaluatedKeys.contains(dataKey)) continue;
 
-      var isAdditionalPropertyAllowed = true;
       if (additionalProperties != null) {
         final ap = additionalProperties;
         currentPath.add(dataKey);
         if (ap is bool && !ap) {
-          isAdditionalPropertyAllowed = false;
-        } else if (ap is Schema) {
-          final additionalPropSchemaFailures = _createHashSet();
-          if (!ap._validateSchema(
-            data[dataKey],
-            currentPath,
-            additionalPropSchemaFailures,
-          )) {
-            isAdditionalPropertyAllowed = false;
-            // Add details why it failed
-            accumulatedFailures.addAll(additionalPropSchemaFailures);
-          }
-        }
-        if (!isAdditionalPropertyAllowed) {
           isValid = false;
           accumulatedFailures.add(
             ValidationError(
               ValidationErrorType.additionalPropertyNotAllowed,
               path: currentPath,
+              details: 'Additional property "$dataKey" is not allowed',
             ),
           );
+        } else if (ap is Schema) {
+          isValid =
+              ap._validateSchema(
+                data[dataKey],
+                currentPath,
+                accumulatedFailures,
+              ) &&
+              isValid;
         }
         currentPath.removeLast();
       } else if (unevaluatedProperties == false) {
@@ -1049,6 +1058,7 @@
           ValidationError(
             ValidationErrorType.unevaluatedPropertyNotAllowed,
             path: currentPath,
+            details: 'Unevaluated property "$dataKey" is not allowed',
           ),
         );
         currentPath.removeLast();
@@ -1092,7 +1102,11 @@
   ) {
     if (data is! String) {
       accumulatedFailures.add(
-        ValidationError(ValidationErrorType.typeMismatch, path: currentPath),
+        ValidationError.typeMismatch(
+          path: currentPath,
+          expectedType: String,
+          actualValue: data,
+        ),
       );
       return false;
     }
@@ -1103,7 +1117,7 @@
         ValidationError(
           ValidationErrorType.minLengthNotMet,
           path: currentPath,
-          details: 'String "$data" is not at least $minLen characters long.',
+          details: 'String "$data" is not at least $minLen characters long',
         ),
       );
     }
@@ -1113,7 +1127,7 @@
         ValidationError(
           ValidationErrorType.maxLengthExceeded,
           path: currentPath,
-          details: 'String "$data" is more than $maxLen characters long.',
+          details: 'String "$data" is more than $maxLen characters long',
         ),
       );
     }
@@ -1124,7 +1138,7 @@
         ValidationError(
           ValidationErrorType.patternMismatch,
           path: currentPath,
-          details: 'String "$data" doesn\'t match the pattern "$dataPattern".',
+          details: 'String "$data" doesn\'t match the pattern "$dataPattern"',
         ),
       );
     }
@@ -1172,7 +1186,11 @@
   ) {
     if (data is! String) {
       accumulatedFailures.add(
-        ValidationError(ValidationErrorType.typeMismatch, path: currentPath),
+        ValidationError.typeMismatch(
+          path: currentPath,
+          expectedType: String,
+          actualValue: data,
+        ),
       );
       return false;
     }
@@ -1236,7 +1254,11 @@
   ) {
     if (data is! num) {
       accumulatedFailures.add(
-        ValidationError(ValidationErrorType.typeMismatch, path: currentPath),
+        ValidationError.typeMismatch(
+          path: currentPath,
+          expectedType: num,
+          actualValue: data,
+        ),
       );
       return false;
     }
@@ -1248,7 +1270,7 @@
         ValidationError(
           ValidationErrorType.minimumNotMet,
           path: currentPath,
-          details: 'Value $data is not at least $min.',
+          details: 'Value $data is not at least $min',
         ),
       );
     }
@@ -1258,7 +1280,7 @@
         ValidationError(
           ValidationErrorType.maximumExceeded,
           path: currentPath,
-          details: 'Value $data is larger than $max.',
+          details: 'Value $data is larger than $max',
         ),
       );
     }
@@ -1269,7 +1291,7 @@
           ValidationErrorType.exclusiveMinimumNotMet,
           path: currentPath,
 
-          details: 'Value $data is not greater than $exclusiveMin.',
+          details: 'Value $data is not greater than $exclusiveMin',
         ),
       );
     }
@@ -1279,7 +1301,7 @@
         ValidationError(
           ValidationErrorType.exclusiveMaximumExceeded,
           path: currentPath,
-          details: 'Value $data is not less than $exclusiveMax.',
+          details: 'Value $data is not less than $exclusiveMax',
         ),
       );
     }
@@ -1291,7 +1313,7 @@
           ValidationError(
             ValidationErrorType.multipleOfInvalid,
             path: currentPath,
-            details: 'Value $data is not a multiple of $multipleOf.',
+            details: 'Value $data is not a multiple of $multipleOf',
           ),
         );
       }
@@ -1344,12 +1366,10 @@
   ) {
     if (data == null || (data is! int && data is! num)) {
       accumulatedFailures.add(
-        ValidationError(
-          ValidationErrorType.typeMismatch,
+        ValidationError.typeMismatch(
           path: currentPath,
-          details:
-              'Value $data has the type ${data.runtimeType}, which is not '
-              'an integer.',
+          expectedType: int,
+          actualValue: data,
         ),
       );
       return false;
@@ -1361,7 +1381,7 @@
           ValidationError(
             ValidationErrorType.typeMismatch,
             path: currentPath,
-            details: 'Value $data is a number, but is not an integer.',
+            details: 'Value $data is a number, but is not an integer',
           ),
         );
         return false;
@@ -1377,7 +1397,7 @@
         ValidationError(
           ValidationErrorType.minimumNotMet,
           path: currentPath,
-          details: 'Value $data is less than the minimum of $min.',
+          details: 'Value $data is less than the minimum of $min',
         ),
       );
     }
@@ -1387,7 +1407,7 @@
         ValidationError(
           ValidationErrorType.maximumExceeded,
           path: currentPath,
-          details: 'Value $data is more than the maximum of $max.',
+          details: 'Value $data is more than the maximum of $max',
         ),
       );
     }
@@ -1397,7 +1417,7 @@
         ValidationError(
           ValidationErrorType.exclusiveMinimumNotMet,
           path: currentPath,
-          details: 'Value $data is not greater than $exclusiveMin.',
+          details: 'Value $data is not greater than $exclusiveMin',
         ),
       );
     }
@@ -1407,7 +1427,7 @@
         ValidationError(
           ValidationErrorType.exclusiveMaximumExceeded,
           path: currentPath,
-          details: 'Value $data is not less than $exclusiveMax.',
+          details: 'Value $data is not less than $exclusiveMax',
         ),
       );
     }
@@ -1418,7 +1438,7 @@
         ValidationError(
           ValidationErrorType.multipleOfInvalid,
           path: currentPath,
-          details: 'Value $data is not a multiple of $multOf.',
+          details: 'Value $data is not a multiple of $multOf',
         ),
       );
     }
@@ -1584,7 +1604,11 @@
   ) {
     if (data is! List<dynamic>) {
       accumulatedFailures.add(
-        ValidationError(ValidationErrorType.typeMismatch, path: currentPath),
+        ValidationError.typeMismatch(
+          path: currentPath,
+          expectedType: List<dynamic>,
+          actualValue: data,
+        ),
       );
       return false;
     }
@@ -1599,7 +1623,7 @@
           path: currentPath,
           details:
               'List has ${data.length} items, but must have at least '
-              '$min.',
+              '$min',
         ),
       );
     }
@@ -1612,7 +1636,7 @@
           path: currentPath,
           details:
               'List has ${data.length} items, but must have less than '
-              '$max.',
+              '$max',
         ),
       );
     }
@@ -1632,7 +1656,7 @@
         ValidationError(
           ValidationErrorType.uniqueItemsViolated,
           path: currentPath,
-          details: 'List contains duplicate items: ${duplicates.join(', ')}.',
+          details: 'List contains duplicate items: ${duplicates.join(', ')}',
         ),
       );
     }
@@ -1642,21 +1666,13 @@
       for (var i = 0; i < pItems.length && i < data.length; i++) {
         evaluatedItems[i] = true;
         currentPath.add(i.toString());
-        final prefixItemSpecificFailures = _createHashSet();
-        if (!pItems[i]._validateSchema(
-          data[i],
-          currentPath,
-          prefixItemSpecificFailures,
-        )) {
-          isValid = false;
-          accumulatedFailures.add(
-            ValidationError(
-              ValidationErrorType.prefixItemInvalid,
-              path: currentPath,
-            ),
-          );
-          accumulatedFailures.addAll(prefixItemSpecificFailures);
-        }
+        isValid =
+            pItems[i]._validateSchema(
+              data[i],
+              currentPath,
+              accumulatedFailures,
+            ) &&
+            isValid;
         currentPath.removeLast();
       }
     }
@@ -1665,18 +1681,13 @@
       for (var i = startIndex; i < data.length; i++) {
         evaluatedItems[i] = true;
         currentPath.add(i.toString());
-        final itemSpecificFailures = _createHashSet();
-        if (!itemSchema._validateSchema(
-          data[i],
-          currentPath,
-          itemSpecificFailures,
-        )) {
-          isValid = false;
-          accumulatedFailures.add(
-            ValidationError(ValidationErrorType.itemInvalid, path: currentPath),
-          );
-          accumulatedFailures.addAll(itemSpecificFailures);
-        }
+        isValid =
+            itemSchema._validateSchema(
+              data[i],
+              currentPath,
+              accumulatedFailures,
+            ) &&
+            isValid;
         currentPath.removeLast();
       }
     }
@@ -1713,11 +1724,7 @@
           a.error == b.error;
     },
     hashCode: (ValidationError error) {
-      return Object.hashAll([
-        ...error.path ?? const [],
-        error.details,
-        error.error,
-      ]);
+      return Object.hashAll([...error.path, error.details, error.error]);
     },
   );
 }
diff --git a/pkgs/dart_mcp/lib/src/server/tools_support.dart b/pkgs/dart_mcp/lib/src/server/tools_support.dart
index e255019..6521b86 100644
--- a/pkgs/dart_mcp/lib/src/server/tools_support.dart
+++ b/pkgs/dart_mcp/lib/src/server/tools_support.dart
@@ -43,17 +43,38 @@
   ///
   /// Throws a [StateError] if there is already a [Tool] registered with the
   /// same name.
+  ///
+  /// If [validateArguments] is true, then request arguments are automatically
+  /// validated against the [tool]s input schema.
   void registerTool(
     Tool tool,
-    FutureOr<CallToolResult> Function(CallToolRequest) impl,
-  ) {
+    FutureOr<CallToolResult> Function(CallToolRequest) impl, {
+    bool validateArguments = true,
+  }) {
     if (_registeredTools.containsKey(tool.name)) {
       throw StateError(
         'Failed to register tool ${tool.name}, it is already registered',
       );
     }
     _registeredTools[tool.name] = tool;
-    _registeredToolImpls[tool.name] = impl;
+    _registeredToolImpls[tool.name] =
+        validateArguments
+            ? (request) {
+              final errors = tool.inputSchema.validate(
+                request.arguments ?? const <String, Object?>{},
+              );
+              if (errors.isNotEmpty) {
+                return CallToolResult(
+                  content: [
+                    for (final error in errors)
+                      Content.text(text: error.toErrorString()),
+                  ],
+                  isError: true,
+                );
+              }
+              return impl(request);
+            }
+            : impl;
 
     if (ready) {
       _notifyToolListChanged();
diff --git a/pkgs/dart_mcp/pubspec.yaml b/pkgs/dart_mcp/pubspec.yaml
index 3a98683..0e48b2a 100644
--- a/pkgs/dart_mcp/pubspec.yaml
+++ b/pkgs/dart_mcp/pubspec.yaml
@@ -1,5 +1,5 @@
 name: dart_mcp
-version: 0.2.3-wip
+version: 0.3.0-wip
 description: A package for making MCP servers and clients.
 repository: https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp
 issue_tracker: https://github.com/dart-lang/ai/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Adart_mcp
diff --git a/pkgs/dart_mcp/test/api/tools_test.dart b/pkgs/dart_mcp/test/api/tools_test.dart
index 20f9a0d..d3ba813 100644
--- a/pkgs/dart_mcp/test/api/tools_test.dart
+++ b/pkgs/dart_mcp/test/api/tools_test.dart
@@ -17,7 +17,8 @@
   // validate().
   ValidationError onlyKeepError(ValidationError e) {
     return ValidationError(
-      e.error!, // The factory requires a non-nullable error.
+      e.error, // The factory requires a non-nullable error.
+      path: const [],
     );
   }
 
@@ -255,49 +256,49 @@
     group('Type Mismatch', () {
       test('object schema with non-map data', () {
         expectFailuresMatch(Schema.object(), 'not a map', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('list schema with non-list data', () {
         expectFailuresMatch(Schema.list(), 'not a list', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('string schema with non-string data', () {
         expectFailuresMatch(Schema.string(), 123, [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('number schema with non-num data', () {
         expectFailuresMatch(Schema.num(), 'not a number', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('integer schema with non-int data', () {
         expectFailuresMatch(Schema.int(), 'not an int', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('integer schema with non-integer num data', () {
         expectFailuresMatch(Schema.int(), 10.5, [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('boolean schema with non-bool data', () {
         expectFailuresMatch(Schema.bool(), 'not a bool', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('null schema with non-null data', () {
         expectFailuresMatch(Schema.nil(), 'not null', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('integer schema with integer-like num data (e.g. 10.0)', () {
         // This test expects minimumNotMet because 10.0 is converted to int 10,
         // which is less than the minimum of 11.
         expectFailuresMatch(IntegerSchema(minimum: 11), 10.0, [
-          ValidationError(ValidationErrorType.minimumNotMet),
+          ValidationError(ValidationErrorType.minimumNotMet, path: const []),
         ]);
       });
     });
@@ -310,8 +311,8 @@
         // 'hi' fails minLength: 3.
         // The allOf combinator fails, and the specific sub-failure is also reported.
         expectFailuresMatch(schema, 'hi', [
-          ValidationError(ValidationErrorType.allOfNotMet),
-          ValidationError(ValidationErrorType.minLengthNotMet),
+          ValidationError(ValidationErrorType.allOfNotMet, path: const []),
+          ValidationError(ValidationErrorType.minLengthNotMet, path: const []),
         ]);
       });
 
@@ -324,9 +325,9 @@
         );
         // 'Short123' fails minLength and pattern.
         expectFailuresMatch(schema, 'Short123', [
-          ValidationError(ValidationErrorType.allOfNotMet),
-          ValidationError(ValidationErrorType.minLengthNotMet),
-          ValidationError(ValidationErrorType.patternMismatch),
+          ValidationError(ValidationErrorType.allOfNotMet, path: const []),
+          ValidationError(ValidationErrorType.minLengthNotMet, path: const []),
+          ValidationError(ValidationErrorType.patternMismatch, path: const []),
         ]);
       });
 
@@ -336,7 +337,7 @@
         );
         // `true` will cause typeMismatch for both StringSchema and NumberSchema.
         expectFailuresMatch(schema, true, [
-          ValidationError(ValidationErrorType.anyOfNotMet),
+          ValidationError(ValidationErrorType.anyOfNotMet, path: const []),
           // The specific type mismatches from sub-schemas might also be reported
           // depending on how _validateSchema handles anyOf error aggregation.
           // Current tools.dart only adds anyOfNotMet for anyOf.
@@ -353,7 +354,7 @@
         // "Hi1" fails minLength: 5 and pattern: '^[a-z]+$'.
         // Since both sub-schemas fail, anyOfNotMet is reported.
         expectFailuresMatch(schema, 'Hi1', [
-          ValidationError(ValidationErrorType.anyOfNotMet),
+          ValidationError(ValidationErrorType.anyOfNotMet, path: const []),
         ]);
       });
 
@@ -366,7 +367,7 @@
         );
         // `true` matches neither sub-schema.
         expectFailuresMatch(s, true, [
-          ValidationError(ValidationErrorType.oneOfNotMet),
+          ValidationError(ValidationErrorType.oneOfNotMet, path: const []),
         ]);
       });
 
@@ -376,7 +377,7 @@
         );
         // 'test' matches both maxLength: 10 and pattern: 'test'.
         expectFailuresMatch(schema, 'test', [
-          ValidationError(ValidationErrorType.oneOfNotMet),
+          ValidationError(ValidationErrorType.oneOfNotMet, path: const []),
         ]);
       });
 
@@ -386,7 +387,10 @@
         );
         // 'test' matches the second sub-schema in the "not" list.
         expectFailuresMatch(schema, 'test', [
-          ValidationError(ValidationErrorType.notConditionViolated),
+          ValidationError(
+            ValidationErrorType.notConditionViolated,
+            path: const [],
+          ),
         ]);
       });
     });
@@ -400,7 +404,8 @@
           [
             ValidationError(
               ValidationErrorType.requiredPropertyMissing,
-              details: 'Required property "name" is missing.',
+              path: const [],
+              details: 'Required property "name" is missing',
             ),
           ],
         );
@@ -415,7 +420,12 @@
         expectFailuresMatch(
           schema,
           {'name': 'test', 'age': 30},
-          [ValidationError(ValidationErrorType.additionalPropertyNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.additionalPropertyNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -429,9 +439,9 @@
           schema,
           {'name': 'test', 'extra': 'abc'},
           [
-            ValidationError(ValidationErrorType.additionalPropertyNotAllowed),
             ValidationError(
               ValidationErrorType.minLengthNotMet,
+              path: const [],
             ), // Sub-failure from additionalProperties schema
           ],
         );
@@ -442,7 +452,12 @@
         expectFailuresMatch(
           schema,
           {'a': 1},
-          [ValidationError(ValidationErrorType.minPropertiesNotMet)],
+          [
+            ValidationError(
+              ValidationErrorType.minPropertiesNotMet,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -451,7 +466,12 @@
         expectFailuresMatch(
           schema,
           {'a': 1, 'b': 2},
-          [ValidationError(ValidationErrorType.maxPropertiesExceeded)],
+          [
+            ValidationError(
+              ValidationErrorType.maxPropertiesExceeded,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -462,9 +482,9 @@
           schema,
           {'ab': 1, 'abc': 2},
           [
-            ValidationError(ValidationErrorType.propertyNamesInvalid),
             ValidationError(
               ValidationErrorType.minLengthNotMet,
+              path: const [],
             ), // Sub-failure from propertyNames schema
           ],
         );
@@ -479,10 +499,7 @@
         expectFailuresMatch(
           schema,
           {'age': 10},
-          [
-            ValidationError(ValidationErrorType.propertyValueInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
       });
 
@@ -494,10 +511,7 @@
         expectFailuresMatch(
           schema,
           {'x-custom': 5},
-          [
-            ValidationError(ValidationErrorType.patternPropertyValueInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
       });
 
@@ -510,7 +524,12 @@
         expectFailuresMatch(
           schema,
           {'name': 'test', 'age': 30},
-          [ValidationError(ValidationErrorType.unevaluatedPropertyNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.unevaluatedPropertyNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
     });
@@ -521,7 +540,7 @@
         expectFailuresMatch(
           schema,
           [1],
-          [ValidationError(ValidationErrorType.minItemsNotMet)],
+          [ValidationError(ValidationErrorType.minItemsNotMet, path: const [])],
         );
       });
 
@@ -530,7 +549,12 @@
         expectFailuresMatch(
           schema,
           [1, 2],
-          [ValidationError(ValidationErrorType.maxItemsExceeded)],
+          [
+            ValidationError(
+              ValidationErrorType.maxItemsExceeded,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -539,7 +563,12 @@
         expectFailuresMatch(
           schema,
           [1, 2, 1],
-          [ValidationError(ValidationErrorType.uniqueItemsViolated)],
+          [
+            ValidationError(
+              ValidationErrorType.uniqueItemsViolated,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -549,10 +578,7 @@
         expectFailuresMatch(
           schema,
           [10, 5, 12],
-          [
-            ValidationError(ValidationErrorType.itemInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
       });
 
@@ -564,18 +590,17 @@
         expectFailuresMatch(
           schema,
           [5], // Not enough items for all prefixItems, but first one is checked
-          [
-            ValidationError(ValidationErrorType.prefixItemInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
         // Second prefix item 'hi' fails StringSchema(minLength: 3).
         expectFailuresMatch(
           schema,
           [10, 'hi'],
           [
-            ValidationError(ValidationErrorType.prefixItemInvalid),
-            ValidationError(ValidationErrorType.minLengthNotMet),
+            ValidationError(
+              ValidationErrorType.minLengthNotMet,
+              path: const [],
+            ),
           ],
         );
       });
@@ -591,7 +616,12 @@
           expectFailuresMatch(
             schema,
             [10, 'extra'],
-            [ValidationError(ValidationErrorType.unevaluatedItemNotAllowed)],
+            [
+              ValidationError(
+                ValidationErrorType.unevaluatedItemNotAllowed,
+                path: const [],
+              ),
+            ],
           );
         },
       );
@@ -601,7 +631,7 @@
       test('minLengthNotMet', () {
         final schema = StringSchema(minLength: 3);
         expectFailuresMatch(schema, 'hi', [
-          ValidationError(ValidationErrorType.minLengthNotMet),
+          ValidationError(ValidationErrorType.minLengthNotMet, path: const []),
         ]);
       });
       // ... other string specific tests using expectFailuresMatch
@@ -611,7 +641,7 @@
       test('minimumNotMet', () {
         final schema = NumberSchema(minimum: 10);
         expectFailuresMatch(schema, 5, [
-          ValidationError(ValidationErrorType.minimumNotMet),
+          ValidationError(ValidationErrorType.minimumNotMet, path: const []),
         ]);
       });
       // ... other number specific tests using expectFailuresMatch
@@ -621,7 +651,7 @@
       test('minimumNotMet', () {
         final schema = IntegerSchema(minimum: 10);
         expectFailuresMatch(schema, 5, [
-          ValidationError(ValidationErrorType.minimumNotMet),
+          ValidationError(ValidationErrorType.minimumNotMet, path: const []),
         ]);
       });
       // ... other integer specific tests using expectFailuresMatch
@@ -632,7 +662,11 @@
     // Tests specifically for path validation will use expectFailuresExact
     test('typeMismatch at root has empty path', () {
       expectFailuresExact(Schema.string(), 123, [
-        ValidationError(ValidationErrorType.typeMismatch, path: []),
+        ValidationError.typeMismatch(
+          path: [],
+          expectedType: String,
+          actualValue: 123,
+        ),
       ]);
     });
 
@@ -646,7 +680,7 @@
             ValidationErrorType.requiredPropertyMissing,
             path:
                 [], // Missing property is checked at the current object level (root in this case)
-            details: 'Required property "name" is missing.',
+            details: 'Required property "name" is missing',
           ),
         ],
       );
@@ -661,13 +695,9 @@
         {'age': 10},
         [
           ValidationError(
-            ValidationErrorType.propertyValueInvalid,
-            path: ['age'],
-          ),
-          ValidationError(
             ValidationErrorType.minimumNotMet,
             path: ['age'],
-            details: 'Value 10 is less than the minimum of 18.',
+            details: 'Value 10 is less than the minimum of 18',
           ), // Sub-failure also has the path
         ],
       );
@@ -694,23 +724,12 @@
         }, // 'street' is missing
         [
           ValidationError(
-            ValidationErrorType.propertyValueInvalid,
-            path: [
-              'user',
-              'address',
-            ], // Path to the object where 'street' is missing
-          ),
-          ValidationError(
-            ValidationErrorType.propertyValueInvalid,
-            path: ['user'], // Path to the object where 'street' is missing
-          ),
-          ValidationError(
             ValidationErrorType.requiredPropertyMissing,
             path: [
               'user',
               'address',
             ], // Path to the object where 'street' is missing
-            details: 'Required property "street" is missing.',
+            details: 'Required property "street" is missing',
           ),
         ],
       );
@@ -722,11 +741,10 @@
         schema,
         [101, 50, 200], // Item at index 1 (value 50) is invalid
         [
-          ValidationError(ValidationErrorType.itemInvalid, path: ['1']),
           ValidationError(
             ValidationErrorType.minimumNotMet,
             path: ['1'],
-            details: 'Value 50 is less than the minimum of 100.',
+            details: 'Value 50 is less than the minimum of 100',
           ),
         ],
       );
@@ -740,17 +758,15 @@
         schema,
         ['ok', 20], // Item at index 1 (value 20) fails prefixItem schema
         [
-          ValidationError(ValidationErrorType.prefixItemInvalid, path: ['0']),
           ValidationError(
             ValidationErrorType.minLengthNotMet,
             path: ['0'],
-            details: 'String "ok" is not at least 3 characters long.',
+            details: 'String "ok" is not at least 3 characters long',
           ),
-          ValidationError(ValidationErrorType.prefixItemInvalid, path: ['1']),
           ValidationError(
             ValidationErrorType.maximumExceeded,
             path: ['1'],
-            details: 'Value 20 is more than the maximum of 10.',
+            details: 'Value 20 is more than the maximum of 10',
           ),
         ],
       );
@@ -766,12 +782,12 @@
         ValidationError(
           ValidationErrorType.minLengthNotMet,
           path: [],
-          details: 'String "hi" is not at least 3 characters long.',
+          details: 'String "hi" is not at least 3 characters long',
         ), // from first sub-schema
         ValidationError(
           ValidationErrorType.maxLengthExceeded,
           path: [],
-          details: 'String "hi" is more than 1 characters long.',
+          details: 'String "hi" is more than 1 characters long',
         ), // from second sub-schema
       ]);
     });
@@ -786,13 +802,9 @@
         {'name': 'test', 'extra': 'abc'},
         [
           ValidationError(
-            ValidationErrorType.additionalPropertyNotAllowed,
-            path: ['extra'],
-          ),
-          ValidationError(
             ValidationErrorType.minLengthNotMet,
             path: ['extra'],
-            details: 'String "abc" is not at least 5 characters long.',
+            details: 'String "abc" is not at least 5 characters long',
           ),
         ],
       );
@@ -803,47 +815,47 @@
     group('Type Mismatch', () {
       test('object schema with non-map data', () {
         expectFailuresMatch(Schema.object(), 'not a map', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('list schema with non-list data', () {
         expectFailuresMatch(Schema.list(), 'not a list', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('string schema with non-string data', () {
         expectFailuresMatch(Schema.string(), 123, [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('number schema with non-num data', () {
         expectFailuresMatch(Schema.num(), 'not a number', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('integer schema with non-int data', () {
         expectFailuresMatch(Schema.int(), 'not an int', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('integer schema with non-integer num data', () {
         expectFailuresMatch(Schema.int(), 10.5, [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('boolean schema with non-bool data', () {
         expectFailuresMatch(Schema.bool(), 'not a bool', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('null schema with non-null data', () {
         expectFailuresMatch(Schema.nil(), 'not null', [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
       test('integer schema with integer-like num data (e.g. 10.0)', () {
         expectFailuresMatch(IntegerSchema(minimum: 11), 10.0, [
-          ValidationError(ValidationErrorType.minimumNotMet),
+          ValidationError(ValidationErrorType.minimumNotMet, path: const []),
         ]);
       });
     });
@@ -852,7 +864,10 @@
       test('enumValueNotAllowed', () {
         final schema = EnumSchema(values: {'a', 'b'});
         expectFailuresMatch(schema, 'c', [
-          ValidationError(ValidationErrorType.enumValueNotAllowed),
+          ValidationError(
+            ValidationErrorType.enumValueNotAllowed,
+            path: const [],
+          ),
         ]);
       });
 
@@ -864,7 +879,7 @@
       test('enum with non-string data', () {
         final schema = EnumSchema(values: {'a', 'b'});
         expectFailuresMatch(schema, 1, [
-          ValidationError(ValidationErrorType.typeMismatch),
+          ValidationError(ValidationErrorType.typeMismatch, path: const []),
         ]);
       });
     });
@@ -875,8 +890,8 @@
           allOf: [StringSchema(minLength: 3), StringSchema(maxLength: 5)],
         );
         expectFailuresMatch(schema, 'hi', [
-          ValidationError(ValidationErrorType.allOfNotMet),
-          ValidationError(ValidationErrorType.minLengthNotMet),
+          ValidationError(ValidationErrorType.allOfNotMet, path: const []),
+          ValidationError(ValidationErrorType.minLengthNotMet, path: const []),
         ]);
       });
 
@@ -888,9 +903,9 @@
           ],
         );
         expectFailuresMatch(schema, 'Short123', [
-          ValidationError(ValidationErrorType.allOfNotMet),
-          ValidationError(ValidationErrorType.minLengthNotMet),
-          ValidationError(ValidationErrorType.patternMismatch),
+          ValidationError(ValidationErrorType.allOfNotMet, path: const []),
+          ValidationError(ValidationErrorType.minLengthNotMet, path: const []),
+          ValidationError(ValidationErrorType.patternMismatch, path: const []),
         ]);
       });
 
@@ -904,7 +919,7 @@
         // NumberSchema(minimum: 100).validate(true) -> [typeMismatch]
         // So anyOf fails.
         expectFailuresMatch(schema, true, [
-          ValidationError(ValidationErrorType.anyOfNotMet),
+          ValidationError(ValidationErrorType.anyOfNotMet, path: const []),
         ]);
       });
 
@@ -920,7 +935,7 @@
         // StringSchema(pattern: '^[a-z]+$').validate("Hi1") -> [patternMismatch]
         // Since both fail, anyOf fails.
         expectFailuresMatch(schema, 'Hi1', [
-          ValidationError(ValidationErrorType.anyOfNotMet),
+          ValidationError(ValidationErrorType.anyOfNotMet, path: const []),
         ]);
       });
 
@@ -937,7 +952,7 @@
           ],
         );
         expectFailuresMatch(s, true, [
-          ValidationError(ValidationErrorType.oneOfNotMet),
+          ValidationError(ValidationErrorType.oneOfNotMet, path: const []),
         ]);
       });
 
@@ -946,7 +961,7 @@
           oneOf: [StringSchema(maxLength: 10), StringSchema(pattern: 'test')],
         );
         expectFailuresMatch(schema, 'test', [
-          ValidationError(ValidationErrorType.oneOfNotMet),
+          ValidationError(ValidationErrorType.oneOfNotMet, path: const []),
         ]);
       });
 
@@ -955,7 +970,10 @@
           not: [StringSchema(maxLength: 2), StringSchema(pattern: 'test')],
         );
         expectFailuresMatch(schema, 'test', [
-          ValidationError(ValidationErrorType.notConditionViolated),
+          ValidationError(
+            ValidationErrorType.notConditionViolated,
+            path: const [],
+          ),
         ]);
       });
 
@@ -964,7 +982,14 @@
           not: [StringSchema(maxLength: 10), StringSchema(pattern: 'test')],
         );
         expectFailuresMatch(schema, 'test', [
-          ValidationError(ValidationErrorType.notConditionViolated),
+          ValidationError(
+            ValidationErrorType.notConditionViolated,
+            path: const [],
+          ),
+          ValidationError(
+            ValidationErrorType.notConditionViolated,
+            path: const [],
+          ),
         ]);
       });
     });
@@ -975,7 +1000,12 @@
         expectFailuresMatch(
           schema,
           {'foo': 1},
-          [ValidationError(ValidationErrorType.requiredPropertyMissing)],
+          [
+            ValidationError(
+              ValidationErrorType.requiredPropertyMissing,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -987,7 +1017,12 @@
         expectFailuresMatch(
           schema,
           {'name': 'test', 'age': 30},
-          [ValidationError(ValidationErrorType.additionalPropertyNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.additionalPropertyNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1000,8 +1035,10 @@
           schema,
           {'name': 'test', 'extra': 'abc'},
           [
-            ValidationError(ValidationErrorType.minLengthNotMet),
-            ValidationError(ValidationErrorType.additionalPropertyNotAllowed),
+            ValidationError(
+              ValidationErrorType.minLengthNotMet,
+              path: const [],
+            ),
           ],
         );
       });
@@ -1011,7 +1048,12 @@
         expectFailuresMatch(
           schema,
           {'a': 1},
-          [ValidationError(ValidationErrorType.minPropertiesNotMet)],
+          [
+            ValidationError(
+              ValidationErrorType.minPropertiesNotMet,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1020,7 +1062,12 @@
         expectFailuresMatch(
           schema,
           {'a': 1, 'b': 2},
-          [ValidationError(ValidationErrorType.maxPropertiesExceeded)],
+          [
+            ValidationError(
+              ValidationErrorType.maxPropertiesExceeded,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1030,8 +1077,10 @@
           schema,
           {'ab': 1, 'abc': 2},
           [
-            ValidationError(ValidationErrorType.minLengthNotMet),
-            ValidationError(ValidationErrorType.propertyNamesInvalid),
+            ValidationError(
+              ValidationErrorType.minLengthNotMet,
+              path: const [],
+            ),
           ],
         );
       });
@@ -1043,10 +1092,7 @@
         expectFailuresMatch(
           schema,
           {'age': 10},
-          [
-            ValidationError(ValidationErrorType.propertyValueInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
       });
 
@@ -1057,10 +1103,7 @@
         expectFailuresMatch(
           schema,
           {'x-custom': 5},
-          [
-            ValidationError(ValidationErrorType.patternPropertyValueInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
       });
 
@@ -1073,7 +1116,12 @@
         expectFailuresMatch(
           schema,
           {'name': 'test', 'age': 30},
-          [ValidationError(ValidationErrorType.unevaluatedPropertyNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.unevaluatedPropertyNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1107,7 +1155,7 @@
         expectFailuresMatch(
           schema,
           [1],
-          [ValidationError(ValidationErrorType.minItemsNotMet)],
+          [ValidationError(ValidationErrorType.minItemsNotMet, path: const [])],
         );
       });
 
@@ -1116,7 +1164,12 @@
         expectFailuresMatch(
           schema,
           [1, 2],
-          [ValidationError(ValidationErrorType.maxItemsExceeded)],
+          [
+            ValidationError(
+              ValidationErrorType.maxItemsExceeded,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1125,7 +1178,12 @@
         expectFailuresMatch(
           schema,
           [1, 2, 1],
-          [ValidationError(ValidationErrorType.uniqueItemsViolated)],
+          [
+            ValidationError(
+              ValidationErrorType.uniqueItemsViolated,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1136,10 +1194,7 @@
         expectFailuresMatch(
           schema,
           [10, 5, 12],
-          [
-            ValidationError(ValidationErrorType.itemInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
       });
 
@@ -1150,17 +1205,16 @@
         expectFailuresMatch(
           schema,
           [5],
-          [
-            ValidationError(ValidationErrorType.prefixItemInvalid),
-            ValidationError(ValidationErrorType.minimumNotMet),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
         expectFailuresMatch(
           schema,
           [10, 'hi'],
           [
-            ValidationError(ValidationErrorType.prefixItemInvalid),
-            ValidationError(ValidationErrorType.minLengthNotMet),
+            ValidationError(
+              ValidationErrorType.minLengthNotMet,
+              path: const [],
+            ),
           ],
         );
       });
@@ -1175,7 +1229,12 @@
           expectFailuresMatch(
             schema,
             [10, 'extra'],
-            [ValidationError(ValidationErrorType.unevaluatedItemNotAllowed)],
+            [
+              ValidationError(
+                ValidationErrorType.unevaluatedItemNotAllowed,
+                path: const [],
+              ),
+            ],
           );
         },
       );
@@ -1185,7 +1244,12 @@
         expectFailuresMatch(
           schema,
           ['extra'],
-          [ValidationError(ValidationErrorType.unevaluatedItemNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.unevaluatedItemNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1208,8 +1272,10 @@
           schemaWithItems,
           [10, 'a'],
           [
-            ValidationError(ValidationErrorType.itemInvalid),
-            ValidationError(ValidationErrorType.minLengthNotMet),
+            ValidationError(
+              ValidationErrorType.minLengthNotMet,
+              path: const [],
+            ),
           ],
         );
 
@@ -1222,7 +1288,12 @@
         expectFailuresMatch(
           schemaNoItems,
           [10, 'extra string'],
-          [ValidationError(ValidationErrorType.unevaluatedItemNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.unevaluatedItemNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1236,10 +1307,7 @@
         expectFailuresMatch(
           schema,
           [10, 'hello', true], // `true` is unevaluated but allowed
-          [
-            ValidationError(ValidationErrorType.itemInvalid),
-            ValidationError(ValidationErrorType.typeMismatch),
-          ],
+          [ValidationError(ValidationErrorType.typeMismatch, path: const [])],
           reason:
               'Item `true` at index 2 is evaluated by `items: StringSchema()` '
               'and fails. `unevaluatedItems` (defaulting to true) does not apply '
@@ -1252,21 +1320,24 @@
       test('minLengthNotMet', () {
         final schema = StringSchema(minLength: 3);
         expectFailuresMatch(schema, 'hi', [
-          ValidationError(ValidationErrorType.minLengthNotMet),
+          ValidationError(ValidationErrorType.minLengthNotMet, path: const []),
         ]);
       });
 
       test('maxLengthExceeded', () {
         final schema = StringSchema(maxLength: 3);
         expectFailuresMatch(schema, 'hello', [
-          ValidationError(ValidationErrorType.maxLengthExceeded),
+          ValidationError(
+            ValidationErrorType.maxLengthExceeded,
+            path: const [],
+          ),
         ]);
       });
 
       test('patternMismatch', () {
         final schema = StringSchema(pattern: r'^\d+$');
         expectFailuresMatch(schema, 'abc', [
-          ValidationError(ValidationErrorType.patternMismatch),
+          ValidationError(ValidationErrorType.patternMismatch, path: const []),
         ]);
       });
     });
@@ -1275,53 +1346,71 @@
       test('minimumNotMet', () {
         final schema = NumberSchema(minimum: 10);
         expectFailuresMatch(schema, 5, [
-          ValidationError(ValidationErrorType.minimumNotMet),
+          ValidationError(ValidationErrorType.minimumNotMet, path: const []),
         ]);
       });
 
       test('maximumExceeded', () {
         final schema = NumberSchema(maximum: 10);
         expectFailuresMatch(schema, 15, [
-          ValidationError(ValidationErrorType.maximumExceeded),
+          ValidationError(ValidationErrorType.maximumExceeded, path: const []),
         ]);
       });
 
       test('exclusiveMinimumNotMet - equal value', () {
         final schema = NumberSchema(exclusiveMinimum: 10);
         expectFailuresMatch(schema, 10, [
-          ValidationError(ValidationErrorType.exclusiveMinimumNotMet),
+          ValidationError(
+            ValidationErrorType.exclusiveMinimumNotMet,
+            path: const [],
+          ),
         ]);
       });
       test('exclusiveMinimumNotMet - smaller value', () {
         final schema = NumberSchema(exclusiveMinimum: 10);
         expectFailuresMatch(schema, 9, [
-          ValidationError(ValidationErrorType.exclusiveMinimumNotMet),
+          ValidationError(
+            ValidationErrorType.exclusiveMinimumNotMet,
+            path: const [],
+          ),
         ]);
       });
 
       test('exclusiveMaximumExceeded - equal value', () {
         final schema = NumberSchema(exclusiveMaximum: 10);
         expectFailuresMatch(schema, 10, [
-          ValidationError(ValidationErrorType.exclusiveMaximumExceeded),
+          ValidationError(
+            ValidationErrorType.exclusiveMaximumExceeded,
+            path: const [],
+          ),
         ]);
       });
       test('exclusiveMaximumExceeded - larger value', () {
         final schema = NumberSchema(exclusiveMaximum: 10);
         expectFailuresMatch(schema, 11, [
-          ValidationError(ValidationErrorType.exclusiveMaximumExceeded),
+          ValidationError(
+            ValidationErrorType.exclusiveMaximumExceeded,
+            path: const [],
+          ),
         ]);
       });
 
       test('multipleOfInvalid', () {
         final schema = NumberSchema(multipleOf: 3);
         expectFailuresMatch(schema, 10, [
-          ValidationError(ValidationErrorType.multipleOfInvalid),
+          ValidationError(
+            ValidationErrorType.multipleOfInvalid,
+            path: const [],
+          ),
         ]);
       });
       test('multipleOfInvalid - floating point', () {
         final schema = NumberSchema(multipleOf: 0.1);
         expectFailuresMatch(schema, 0.25, [
-          ValidationError(ValidationErrorType.multipleOfInvalid),
+          ValidationError(
+            ValidationErrorType.multipleOfInvalid,
+            path: const [],
+          ),
         ]);
       });
       test('multipleOfInvalid - valid floating point', () {
@@ -1347,47 +1436,62 @@
       test('minimumNotMet', () {
         final schema = IntegerSchema(minimum: 10);
         expectFailuresMatch(schema, 5, [
-          ValidationError(ValidationErrorType.minimumNotMet),
+          ValidationError(ValidationErrorType.minimumNotMet, path: const []),
         ]);
       });
 
       test('maximumExceeded', () {
         final schema = IntegerSchema(maximum: 10);
         expectFailuresMatch(schema, 15, [
-          ValidationError(ValidationErrorType.maximumExceeded),
+          ValidationError(ValidationErrorType.maximumExceeded, path: const []),
         ]);
       });
 
       test('exclusiveMinimumNotMet - equal value', () {
         final schema = IntegerSchema(exclusiveMinimum: 10);
         expectFailuresMatch(schema, 10, [
-          ValidationError(ValidationErrorType.exclusiveMinimumNotMet),
+          ValidationError(
+            ValidationErrorType.exclusiveMinimumNotMet,
+            path: const [],
+          ),
         ]);
       });
       test('exclusiveMinimumNotMet - smaller value', () {
         final schema = IntegerSchema(exclusiveMinimum: 10);
         expectFailuresMatch(schema, 9, [
-          ValidationError(ValidationErrorType.exclusiveMinimumNotMet),
+          ValidationError(
+            ValidationErrorType.exclusiveMinimumNotMet,
+            path: const [],
+          ),
         ]);
       });
 
       test('exclusiveMaximumExceeded - equal value', () {
         final schema = IntegerSchema(exclusiveMaximum: 10);
         expectFailuresMatch(schema, 10, [
-          ValidationError(ValidationErrorType.exclusiveMaximumExceeded),
+          ValidationError(
+            ValidationErrorType.exclusiveMaximumExceeded,
+            path: const [],
+          ),
         ]);
       });
       test('exclusiveMaximumExceeded - larger value', () {
         final schema = IntegerSchema(exclusiveMaximum: 10);
         expectFailuresMatch(schema, 11, [
-          ValidationError(ValidationErrorType.exclusiveMaximumExceeded),
+          ValidationError(
+            ValidationErrorType.exclusiveMaximumExceeded,
+            path: const [],
+          ),
         ]);
       });
 
       test('multipleOfInvalid', () {
         final schema = IntegerSchema(multipleOf: 3);
         expectFailuresMatch(schema, 10, [
-          ValidationError(ValidationErrorType.multipleOfInvalid),
+          ValidationError(
+            ValidationErrorType.multipleOfInvalid,
+            path: const [],
+          ),
         ]);
       });
 
@@ -1421,12 +1525,12 @@
 
         final schemaAnyOfEmpty = Schema.combined(anyOf: []);
         expectFailuresMatch(schemaAnyOfEmpty, 'any data', [
-          ValidationError(ValidationErrorType.anyOfNotMet),
+          ValidationError(ValidationErrorType.anyOfNotMet, path: const []),
         ]);
 
         final schemaOneOfEmpty = Schema.combined(oneOf: []);
         expectFailuresMatch(schemaOneOfEmpty, 'any data', [
-          ValidationError(ValidationErrorType.oneOfNotMet),
+          ValidationError(ValidationErrorType.oneOfNotMet, path: const []),
         ]);
 
         // If 'not' is a list of schemas, and the list is empty,
@@ -1458,16 +1562,16 @@
 
         // Fails minLength (from parent StringSchema) and pattern (from allOf)
         expectFailuresExact(schema, 'A', [
+          ValidationError(ValidationErrorType.allOfNotMet, path: []),
           ValidationError(
             ValidationErrorType.minLengthNotMet,
             path: [],
-            details: 'String "A" is not at least 2 characters long.',
+            details: 'String "A" is not at least 2 characters long',
           ),
-          ValidationError(ValidationErrorType.allOfNotMet, path: []),
           ValidationError(
             ValidationErrorType.patternMismatch,
             path: [],
-            details: 'String "A" doesn\'t match the pattern "^[a-z]+\$".',
+            details: 'String "A" doesn\'t match the pattern "^[a-z]+\$"',
           ),
         ]);
 
@@ -1477,7 +1581,7 @@
           ValidationError(
             ValidationErrorType.maxLengthExceeded,
             path: [],
-            details: 'String "abcdef" is more than 5 characters long.',
+            details: 'String "abcdef" is more than 5 characters long',
           ),
         ]);
       });
@@ -1501,17 +1605,9 @@
           },
           [
             ValidationError(
-              ValidationErrorType.propertyValueInvalid,
-              path: ['user'],
-            ),
-            ValidationError(
-              ValidationErrorType.propertyValueInvalid,
-              path: ['user', 'name'],
-            ),
-            ValidationError(
               ValidationErrorType.minLengthNotMet,
               path: ['user', 'name'],
-              details: 'String "hi" is not at least 5 characters long.',
+              details: 'String "hi" is not at least 5 characters long',
             ),
           ],
         );
@@ -1531,15 +1627,10 @@
             {'id': 10}, // This item is invalid
           ],
           [
-            ValidationError(ValidationErrorType.itemInvalid, path: ['1']),
-            ValidationError(
-              ValidationErrorType.propertyValueInvalid,
-              path: ['1', 'id'],
-            ),
             ValidationError(
               ValidationErrorType.minimumNotMet,
               path: ['1', 'id'],
-              details: 'Value 10 is less than the minimum of 100.',
+              details: 'Value 10 is less than the minimum of 100',
             ),
           ],
         );
@@ -1556,19 +1647,13 @@
         expectFailuresMatch(
           schema,
           {'known': 'yes', 'extraNum': -5},
-          [
-            ValidationError(ValidationErrorType.minimumNotMet),
-            ValidationError(ValidationErrorType.additionalPropertyNotAllowed),
-          ],
+          [ValidationError(ValidationErrorType.minimumNotMet, path: const [])],
         );
         // Invalid: additional property is wrong type for its schema
         expectFailuresMatch(
           schema,
           {'known': 'yes', 'extraStr': 'text'},
-          [
-            ValidationError(ValidationErrorType.typeMismatch),
-            ValidationError(ValidationErrorType.additionalPropertyNotAllowed),
-          ],
+          [ValidationError(ValidationErrorType.typeMismatch, path: const [])],
         );
       });
 
@@ -1583,7 +1668,12 @@
         expectFailuresMatch(
           schema,
           {'y-foo': 'bar'},
-          [ValidationError(ValidationErrorType.unevaluatedPropertyNotAllowed)],
+          [
+            ValidationError(
+              ValidationErrorType.unevaluatedPropertyNotAllowed,
+              path: const [],
+            ),
+          ],
         );
       });
 
@@ -1631,8 +1721,10 @@
             schema,
             {'name': 'test', 'age': 30},
             [
-              ValidationError(ValidationErrorType.additionalPropertyNotAllowed),
-              ValidationError(ValidationErrorType.minimumNotMet),
+              ValidationError(
+                ValidationErrorType.minimumNotMet,
+                path: const [],
+              ),
             ],
           );
         },
@@ -1650,10 +1742,7 @@
         expectFailuresMatch(
           schema,
           [1, 'b', 3],
-          [
-            ValidationError(ValidationErrorType.itemInvalid),
-            ValidationError(ValidationErrorType.typeMismatch),
-          ],
+          [ValidationError(ValidationErrorType.typeMismatch, path: const [])],
         );
       });
 
@@ -1671,19 +1760,13 @@
           expectFailuresMatch(
             schema,
             ['a', 1, 'c'],
-            [
-              ValidationError(ValidationErrorType.itemInvalid),
-              ValidationError(ValidationErrorType.typeMismatch),
-            ],
+            [ValidationError(ValidationErrorType.typeMismatch, path: const [])],
           );
           // Invalid: prefixItem fails StringSchema
           expectFailuresMatch(
             schema,
             [10, 1, 2],
-            [
-              ValidationError(ValidationErrorType.prefixItemInvalid),
-              ValidationError(ValidationErrorType.typeMismatch),
-            ],
+            [ValidationError(ValidationErrorType.typeMismatch, path: const [])],
           );
         },
       );
@@ -1720,13 +1803,13 @@
             ValidationError(
               ValidationErrorType.minLengthNotMet,
               path: [],
-              details: 'String "Hi" is not at least 5 characters long.',
+              details: 'String "Hi" is not at least 5 characters long',
             ),
             ValidationError(ValidationErrorType.allOfNotMet, path: []),
             ValidationError(
               ValidationErrorType.patternMismatch,
               path: [],
-              details: 'String "Hi" doesn\'t match the pattern "^[a-z]+\$".',
+              details: 'String "Hi" doesn\'t match the pattern "^[a-z]+\$"',
             ),
           ]);
 
@@ -1738,7 +1821,7 @@
             ValidationError(
               ValidationErrorType.patternMismatch,
               path: [],
-              details: 'String "Hiall" doesn\'t match the pattern "^[a-z]+\$".',
+              details: 'String "Hiall" doesn\'t match the pattern "^[a-z]+\$"',
             ),
           ]);
 
@@ -1760,7 +1843,7 @@
               ValidationErrorType.patternMismatch,
               path: [],
               details:
-                  'String "LongEnoughButCAPS" doesn\'t match the pattern "^[a-z]+\$".',
+                  'String "LongEnoughButCAPS" doesn\'t match the pattern "^[a-z]+\$"',
             ),
           ]);
         },
diff --git a/pkgs/dart_mcp/test/server/tools_support_test.dart b/pkgs/dart_mcp/test/server/tools_support_test.dart
index 75721eb..337082a 100644
--- a/pkgs/dart_mcp/test/server/tools_support_test.dart
+++ b/pkgs/dart_mcp/test/server/tools_support_test.dart
@@ -24,9 +24,11 @@
     final serverConnection = environment.serverConnection;
 
     final toolsResult = await serverConnection.listTools();
-    expect(toolsResult.tools.length, 1);
+    expect(toolsResult.tools.length, 2);
 
-    final tool = toolsResult.tools.single;
+    final tool = toolsResult.tools.firstWhere(
+      (tool) => tool.name == TestMCPServerWithTools.helloWorld.name,
+    );
 
     final result = await serverConnection.callTool(
       CallToolRequest(name: tool.name),
@@ -72,6 +74,46 @@
     // Need to manually close so the stream matchers can complete.
     await environment.shutdown();
   });
+
+  test('schema validation failure returns an error', () async {
+    final environment = TestEnvironment(
+      TestMCPClient(),
+      TestMCPServerWithTools.new,
+    );
+    await environment.initializeServer();
+
+    final serverConnection = environment.serverConnection;
+
+    // Call with no arguments, should fail because 'message' is required.
+    var result = await serverConnection.callTool(
+      CallToolRequest(
+        name: TestMCPServerWithTools.echo.name,
+        arguments: const {},
+      ),
+    );
+    expect(result.isError, isTrue);
+    expect(result.content.single, isA<TextContent>());
+    final textContent = result.content.single as TextContent;
+    expect(
+      textContent.text,
+      contains('Required property "message" is missing at path #root'),
+    );
+
+    // Call with wrong type for 'message'.
+    result = await serverConnection.callTool(
+      CallToolRequest(
+        name: TestMCPServerWithTools.echo.name,
+        arguments: {'message': 123},
+      ),
+    );
+    expect(result.isError, isTrue);
+    expect(result.content.single, isA<TextContent>());
+    final textContent2 = result.content.single as TextContent;
+    expect(
+      textContent2.text,
+      contains('Value `123` is not of type `String` at path #root["message"]'),
+    );
+  });
 }
 
 final class TestMCPServerWithTools extends TestMCPServer with ToolsSupport {
@@ -83,9 +125,24 @@
       helloWorld,
       (_) => CallToolResult(content: [helloWorldContent]),
     );
+    registerTool(TestMCPServerWithTools.echo, TestMCPServerWithTools.echoImpl);
     return super.initialize(request);
   }
 
+  static final echo = Tool(
+    name: 'echo',
+    description: 'Echoes the input',
+    inputSchema: ObjectSchema(
+      properties: {'message': StringSchema(description: 'The message to echo')},
+      required: ['message'],
+    ),
+  );
+
+  static CallToolResult echoImpl(CallToolRequest request) {
+    final message = request.arguments!['message'] as String;
+    return CallToolResult(content: [TextContent(text: message)]);
+  }
+
   static final helloWorld = Tool(
     name: 'hello_world',
     description: 'Says hello world!',
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
index 70fc5a6..ef4bb55 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dash_cli.dart
@@ -96,7 +96,7 @@
     if (projectType != 'dart' && projectType != 'flutter') {
       errors.add(
         ValidationError(
-          ValidationErrorType.itemInvalid,
+          ValidationErrorType.custom,
           path: [ParameterNames.projectType],
           details: 'Only `dart` and `flutter` are allowed values.',
         ),
@@ -106,7 +106,7 @@
     if (p.isAbsolute(directory)) {
       errors.add(
         ValidationError(
-          ValidationErrorType.itemInvalid,
+          ValidationErrorType.custom,
           path: [ParameterNames.directory],
           details: 'Directory must be a relative path.',
         ),
@@ -125,7 +125,7 @@
                 : 'is not a valid platform';
         errors.add(
           ValidationError(
-            ValidationErrorType.itemInvalid,
+            ValidationErrorType.custom,
             path: [ParameterNames.platform],
             details:
                 '${invalidPlatforms.join(',')} $plural. Platforms '
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
index c390004..8fe2f8e 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/dtd.dart
@@ -187,15 +187,6 @@
       return _dtdAlreadyConnected;
     }
 
-    if (request.arguments?[ParameterNames.uri] == null) {
-      return CallToolResult(
-        isError: true,
-        content: [
-          TextContent(text: 'Required parameter "uri" was not provided.'),
-        ],
-      );
-    }
-
     try {
       _dtd = await DartToolingDaemon.connect(
         Uri.parse(request.arguments![ParameterNames.uri] as String),
diff --git a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
index ab48500..9059a10 100644
--- a/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
+++ b/pkgs/dart_mcp_server/lib/src/mixins/pub.dart
@@ -34,13 +34,7 @@
 
   /// Implementation of the [pubTool].
   Future<CallToolResult> _runDartPubTool(CallToolRequest request) async {
-    final command = request.arguments?[ParameterNames.command] as String?;
-    if (command == null) {
-      return CallToolResult(
-        content: [TextContent(text: 'Missing required argument `command`.')],
-        isError: true,
-      );
-    }
+    final command = request.arguments![ParameterNames.command] as String;
     final matchingCommand = SupportedPubCommand.fromName(command);
     if (matchingCommand == null) {
       return CallToolResult(
diff --git a/pkgs/dart_mcp_server/lib/src/server.dart b/pkgs/dart_mcp_server/lib/src/server.dart
index f90a80f..04af79d 100644
--- a/pkgs/dart_mcp_server/lib/src/server.dart
+++ b/pkgs/dart_mcp_server/lib/src/server.dart
@@ -161,8 +161,9 @@
   /// if [analytics] is not `null`.
   void registerTool(
     Tool tool,
-    FutureOr<CallToolResult> Function(CallToolRequest) impl,
-  ) {
+    FutureOr<CallToolResult> Function(CallToolRequest) impl, {
+    bool validateArguments = true,
+  }) {
     // For type promotion.
     final analytics = this.analytics;
 
@@ -196,6 +197,7 @@
               }
             }
           },
+      validateArguments: validateArguments,
     );
   }
 
diff --git a/pkgs/dart_mcp_server/pubspec.yaml b/pkgs/dart_mcp_server/pubspec.yaml
index 4df5932..0f37a2b 100644
--- a/pkgs/dart_mcp_server/pubspec.yaml
+++ b/pkgs/dart_mcp_server/pubspec.yaml
@@ -13,7 +13,7 @@
   args: ^2.7.0
   async: ^2.13.0
   collection: ^1.19.1
-  dart_mcp: ^0.2.2
+  dart_mcp: ^0.3.0
   dds_service_extensions: ^2.0.1
   devtools_shared: ^11.2.0
   dtd: ^2.4.0
diff --git a/pkgs/dart_mcp_server/test/tools/dtd_test.dart b/pkgs/dart_mcp_server/test/tools/dtd_test.dart
index 713fd1a..bf92e23 100644
--- a/pkgs/dart_mcp_server/test/tools/dtd_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/dtd_test.dart
@@ -607,7 +607,7 @@
         expect(missingArgResult.isError, isTrue);
         expect(
           (missingArgResult.content.first as TextContent).text,
-          'Required parameter "enabled" was not provided or is not a boolean.',
+          'Required property "enabled" is missing at path #root',
         );
 
         // Clean up
diff --git a/pkgs/dart_mcp_server/test/tools/pub_test.dart b/pkgs/dart_mcp_server/test/tools/pub_test.dart
index 841a60d..e349db1 100644
--- a/pkgs/dart_mcp_server/test/tools/pub_test.dart
+++ b/pkgs/dart_mcp_server/test/tools/pub_test.dart
@@ -192,7 +192,7 @@
 
             expect(
               (result.content.single as TextContent).text,
-              'Missing required argument `command`.',
+              'Required property "command" is missing at path #root',
             );
             expect(testProcessManager.commandsRan, isEmpty);
           });
diff --git a/pkgs/dart_mcp_server/test_fixtures/counter_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/pkgs/dart_mcp_server/test_fixtures/counter_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
new file mode 100644
index 0000000..539ab02
--- /dev/null
+++ b/pkgs/dart_mcp_server/test_fixtures/counter_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java
@@ -0,0 +1,19 @@
+package io.flutter.plugins;
+
+import androidx.annotation.Keep;
+import androidx.annotation.NonNull;
+import io.flutter.Log;
+
+import io.flutter.embedding.engine.FlutterEngine;
+
+/**
+ * Generated file. Do not edit.
+ * This file is generated by the Flutter tool based on the
+ * plugins that support the Android platform.
+ */
+@Keep
+public final class GeneratedPluginRegistrant {
+  private static final String TAG = "GeneratedPluginRegistrant";
+  public static void registerWith(@NonNull FlutterEngine flutterEngine) {
+  }
+}
diff --git a/pkgs/dart_mcp_server/test_fixtures/counter_app/android/local.properties b/pkgs/dart_mcp_server/test_fixtures/counter_app/android/local.properties
new file mode 100644
index 0000000..be68df0
--- /dev/null
+++ b/pkgs/dart_mcp_server/test_fixtures/counter_app/android/local.properties
@@ -0,0 +1 @@
+flutter.sdk=/usr/local/google/home/jakemac/flutter
\ No newline at end of file