Skip to content

Commit 5e93ef4

Browse files
authored
Deprecate EnumSchema and JsonType.enumeration (#236)
Enums are supposed to be represented as the "string" type with an "enum" entry. This was breaking Gemini CLI with the recent addition of some enum types for the flutter driver tool.
1 parent 66af21b commit 5e93ef4

File tree

10 files changed

+97
-90
lines changed

10 files changed

+97
-90
lines changed

mcp_examples/bin/workflow_client.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -512,11 +512,21 @@ final class WorkflowClient extends MCPClient with RootsSupport {
512512
properties: properties ?? {},
513513
nullable: nullable,
514514
);
515-
case JsonType.string:
515+
case JsonType.string
516+
when (inputSchema as StringSchema).enumValues == null:
516517
return gemini.Schema.string(
517518
description: inputSchema.description,
518519
nullable: nullable,
519520
);
521+
case JsonType.string
522+
when (inputSchema as StringSchema).enumValues != null:
523+
case JsonType.enumeration: // ignore: deprecated_member_use
524+
final schema = inputSchema as StringSchema;
525+
return gemini.Schema.enumString(
526+
enumValues: schema.enumValues!.toList(),
527+
description: description,
528+
nullable: nullable,
529+
);
520530
case JsonType.list:
521531
final listSchema = inputSchema as ListSchema;
522532
final itemSchema =
@@ -546,13 +556,6 @@ final class WorkflowClient extends MCPClient with RootsSupport {
546556
description: description,
547557
nullable: nullable,
548558
);
549-
case JsonType.enumeration:
550-
final schema = inputSchema as EnumSchema;
551-
return gemini.Schema.enumString(
552-
enumValues: schema.values.toList(),
553-
description: description,
554-
nullable: nullable,
555-
);
556559
default:
557560
throw UnimplementedError(
558561
'Unimplemented schema type ${inputSchema.type}',

pkgs/dart_mcp/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 0.3.2-wip
2+
3+
- Deprecate the `EnumSchema` type in favor of the `StringSchema` with an
4+
`enumValues` parameter. The `EnumSchema` type was not MCP spec compatible.
5+
- Also deprecated the associated JsonType.enumeration which doesn't exist
6+
in the JSON schema spec.
7+
18
## 0.3.1
29

310
- Fixes communication problem when a `MCPServer` is instantiated without

pkgs/dart_mcp/example/elicitations_client.dart

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ Do you want to accept (a), reject (r), or cancel (c) the elicitation?
8888
final name = property.key;
8989
final type = property.value.type;
9090
final allowedValues =
91-
type == JsonType.enumeration
92-
? ' (${(property.value as EnumSchema).values.join(', ')})'
91+
// ignore: deprecated_member_use_from_same_package
92+
(type == JsonType.enumeration || type == JsonType.string) &&
93+
(property.value as StringSchema).enumValues != null
94+
? ' (${(property.value as StringSchema).enumValues!.join(', ')})'
9395
: '';
9496
// Ask the user in a loop until the value provided matches the schema,
9597
// at which point we will `break` from the loop.
@@ -99,6 +101,7 @@ Do you want to accept (a), reject (r), or cancel (c) the elicitation?
99101
try {
100102
// Convert the value to the correct type.
101103
final convertedValue = switch (type) {
104+
// ignore: deprecated_member_use_from_same_package
102105
JsonType.string || JsonType.enumeration => userValue,
103106
JsonType.num => num.parse(userValue),
104107
JsonType.int => int.parse(userValue),

pkgs/dart_mcp/example/elicitations_server.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ base class MCPServerWithElicitation extends MCPServer
4242
properties: {
4343
'name': Schema.string(),
4444
'age': Schema.int(),
45-
'gender': Schema.enumeration(values: ['male', 'female', 'other']),
45+
'gender': Schema.string(enumValues: ['male', 'female', 'other']),
4646
},
4747
),
4848
),

pkgs/dart_mcp/lib/src/api/elicitation.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ extension type ElicitRequest._fromMap(Map<String, Object?> _value)
7777
case JsonType.num:
7878
case JsonType.int:
7979
case JsonType.bool:
80-
case JsonType.enumeration:
80+
case JsonType
81+
.enumeration: // ignore: deprecated_member_use_from_same_package
8182
break;
8283
case JsonType.object:
8384
case JsonType.list:

pkgs/dart_mcp/lib/src/api/tools.dart

Lines changed: 30 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ enum JsonType {
234234
num('number'),
235235
int('integer'),
236236
bool('boolean'),
237+
@Deprecated('Use JsonType.string')
237238
enumeration('enum'),
238239
nil('null');
239240

@@ -405,6 +406,7 @@ extension type Schema.fromMap(Map<String, Object?> _value) {
405406
static const object = ObjectSchema.new;
406407

407408
/// Alias for [EnumSchema.new].
409+
@Deprecated('Use Schema.string instead')
408410
static const enumeration = EnumSchema.new;
409411

410412
/// Alias for [NullSchema.new].
@@ -480,6 +482,8 @@ extension SchemaValidation on Schema {
480482
accumulatedFailures,
481483
);
482484
case JsonType.string:
485+
case JsonType
486+
.enumeration: // ignore: deprecated_member_use_from_same_package
483487
isValid = (this as StringSchema)._validateString(
484488
data,
485489
currentPath,
@@ -497,12 +501,6 @@ extension SchemaValidation on Schema {
497501
currentPath,
498502
accumulatedFailures,
499503
);
500-
case JsonType.enumeration:
501-
isValid = (this as EnumSchema)._validateEnum(
502-
data,
503-
currentPath,
504-
accumulatedFailures,
505-
);
506504
case JsonType.bool:
507505
if (data is! bool) {
508506
isValid = false;
@@ -1077,13 +1075,15 @@ extension type const StringSchema.fromMap(Map<String, Object?> _value)
10771075
int? minLength,
10781076
int? maxLength,
10791077
String? pattern,
1078+
Iterable<String>? enumValues,
10801079
}) => StringSchema.fromMap({
10811080
'type': JsonType.string.typeName,
10821081
if (title != null) 'title': title,
10831082
if (description != null) 'description': description,
10841083
if (minLength != null) 'minLength': minLength,
10851084
if (maxLength != null) 'maxLength': maxLength,
10861085
if (pattern != null) 'pattern': pattern,
1086+
if (enumValues != null) 'enum': enumValues,
10871087
});
10881088

10891089
/// The minimum allowed length of this String.
@@ -1095,6 +1095,16 @@ extension type const StringSchema.fromMap(Map<String, Object?> _value)
10951095
/// A regular expression pattern that the String must match.
10961096
String? get pattern => _value['pattern'] as String?;
10971097

1098+
/// The allowed values for this String, corresponds to the `enum` key.
1099+
Iterable<String>? get enumValues {
1100+
final values = (_value['enum'] as Iterable?)?.cast<String>();
1101+
assert(
1102+
values?.toSet().length == values?.length,
1103+
"The 'enum' property has duplicate entries.",
1104+
);
1105+
return values;
1106+
}
1107+
10981108
bool _validateString(
10991109
Object? data,
11001110
List<String> currentPath,
@@ -1142,13 +1152,27 @@ extension type const StringSchema.fromMap(Map<String, Object?> _value)
11421152
),
11431153
);
11441154
}
1155+
if (enumValues case final enumValues? when !enumValues.contains(data)) {
1156+
accumulatedFailures.add(
1157+
ValidationError(
1158+
ValidationErrorType.enumValueNotAllowed,
1159+
path: currentPath,
1160+
details:
1161+
'String "$data" is not one of the allowed values: '
1162+
'${enumValues.join(', ')}',
1163+
),
1164+
);
1165+
return false;
1166+
}
11451167
return isValid;
11461168
}
11471169
}
11481170

11491171
/// A JSON Schema definition for a set of allowed string values.
1172+
@Deprecated('Use StringSchema instead')
11501173
extension type EnumSchema.fromMap(Map<String, Object?> _value)
11511174
implements Schema {
1175+
@Deprecated('Use StringSchema instead')
11521176
factory EnumSchema({
11531177
String? title,
11541178
String? description,
@@ -1178,36 +1202,6 @@ extension type EnumSchema.fromMap(Map<String, Object?> _value)
11781202
);
11791203
return values;
11801204
}
1181-
1182-
bool _validateEnum(
1183-
Object? data,
1184-
List<String> currentPath,
1185-
HashSet<ValidationError> accumulatedFailures,
1186-
) {
1187-
if (data is! String) {
1188-
accumulatedFailures.add(
1189-
ValidationError.typeMismatch(
1190-
path: currentPath,
1191-
expectedType: String,
1192-
actualValue: data,
1193-
),
1194-
);
1195-
return false;
1196-
}
1197-
if (!values.contains(data)) {
1198-
accumulatedFailures.add(
1199-
ValidationError(
1200-
ValidationErrorType.enumValueNotAllowed,
1201-
path: currentPath,
1202-
details:
1203-
'String "$data" is not one of the allowed values: '
1204-
'${values.join(', ')}',
1205-
),
1206-
);
1207-
return false;
1208-
}
1209-
return true;
1210-
}
12111205
}
12121206

12131207
/// A JSON Schema definition for a [num].

pkgs/dart_mcp/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: dart_mcp
2-
version: 0.3.1
2+
version: 0.3.2-wip
33
description: A package for making MCP servers and clients.
44
repository: https://github.com/dart-lang/ai/tree/main/pkgs/dart_mcp
55
issue_tracker: https://github.com/dart-lang/ai/issues?q=is%3Aissue+is%3Aopen+label%3Apackage%3Adart_mcp

pkgs/dart_mcp/test/api/tools_test.dart

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ void main() {
205205
});
206206

207207
test('EnumSchema', () {
208+
// ignore: deprecated_member_use_from_same_package
208209
final schema = EnumSchema(
209210
title: 'Foo',
210211
description: 'Bar',
@@ -860,30 +861,6 @@ void main() {
860861
});
861862
});
862863

863-
group('Enum Specific', () {
864-
test('enumValueNotAllowed', () {
865-
final schema = EnumSchema(values: {'a', 'b'});
866-
expectFailuresMatch(schema, 'c', [
867-
ValidationError(
868-
ValidationErrorType.enumValueNotAllowed,
869-
path: const [],
870-
),
871-
]);
872-
});
873-
874-
test('valid enum value', () {
875-
final schema = EnumSchema(values: {'a', 'b'});
876-
expectFailuresMatch(schema, 'a', []);
877-
});
878-
879-
test('enum with non-string data', () {
880-
final schema = EnumSchema(values: {'a', 'b'});
881-
expectFailuresMatch(schema, 1, [
882-
ValidationError(ValidationErrorType.typeMismatch, path: const []),
883-
]);
884-
});
885-
});
886-
887864
group('Schema Combinators', () {
888865
test('allOfNotMet - one sub-schema fails', () {
889866
final schema = Schema.combined(
@@ -1340,6 +1317,28 @@ void main() {
13401317
ValidationError(ValidationErrorType.patternMismatch, path: const []),
13411318
]);
13421319
});
1320+
1321+
test('enumValueNotAllowed', () {
1322+
final schema = StringSchema(enumValues: {'a', 'b'});
1323+
expectFailuresMatch(schema, 'c', [
1324+
ValidationError(
1325+
ValidationErrorType.enumValueNotAllowed,
1326+
path: const [],
1327+
),
1328+
]);
1329+
});
1330+
1331+
test('valid enum value', () {
1332+
final schema = StringSchema(enumValues: {'a', 'b'});
1333+
expectFailuresMatch(schema, 'a', []);
1334+
});
1335+
1336+
test('enum with non-string data', () {
1337+
final schema = StringSchema(enumValues: {'a', 'b'});
1338+
expectFailuresMatch(schema, 1, [
1339+
ValidationError(ValidationErrorType.typeMismatch, path: const []),
1340+
]);
1341+
});
13431342
});
13441343

13451344
group('Number Specific', () {

pkgs/dart_mcp_server/lib/src/mixins/dtd.dart

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -653,10 +653,10 @@ base mixin DartToolingDaemonSupport
653653
'figure out how to find the widget instead of just guessing tooltip '
654654
'text or other things.',
655655
properties: {
656-
'command': Schema.enumeration(
656+
'command': Schema.string(
657657
// Commented out values are flutter_driver commands that are not
658658
// supported, but may be in the future.
659-
values: [
659+
enumValues: [
660660
'get_health',
661661
'get_layer_tree',
662662
'get_render_tree',
@@ -707,13 +707,13 @@ base mixin DartToolingDaemonSupport
707707
'The frequency in Hz of the generated move events. '
708708
'Required for the scroll command',
709709
),
710-
'finderType': Schema.enumeration(
710+
'finderType': Schema.string(
711711
description:
712712
'The kind of finder to use, if required for the command. '
713713
'Required for get_text, scroll, scroll_into_view, tap, waitFor, '
714714
'waitForAbsent, waitForTappable, get_offset, and '
715715
'get_diagnostics_tree',
716-
values: [
716+
enumValues: [
717717
'ByType',
718718
'ByValueKey',
719719
'ByTooltipMessage',
@@ -728,8 +728,8 @@ base mixin DartToolingDaemonSupport
728728
description:
729729
'Required for the ByValueKey finder, the String value of the key',
730730
),
731-
'keyValueType': Schema.enumeration(
732-
values: ['int', 'String'],
731+
'keyValueType': Schema.string(
732+
enumValues: ['int', 'String'],
733733
description:
734734
'Required for the ByValueKey finder, the type of the key',
735735
),
@@ -767,25 +767,25 @@ base mixin DartToolingDaemonSupport
767767
additionalProperties: true,
768768
),
769769
// This is a boolean but uses the `true` and `false` strings.
770-
'matchRoot': Schema.enumeration(
770+
'matchRoot': Schema.string(
771771
description:
772772
'Required by the Descendent and Ancestor finders. '
773773
'Whether the widget matching `of` will be considered for a '
774774
'match',
775-
values: ['true', 'false'],
775+
enumValues: ['true', 'false'],
776776
),
777777
// This is a boolean but uses the `true` and `false` strings.
778-
'firstMatchOnly': Schema.enumeration(
778+
'firstMatchOnly': Schema.string(
779779
description:
780780
'Required by the Descendent and Ancestor finders. '
781781
'If true then only the first ancestor or descendent matching '
782782
'`matching` will be returned.',
783-
values: ['true', 'false'],
783+
enumValues: ['true', 'false'],
784784
),
785-
'action': Schema.enumeration(
785+
'action': Schema.string(
786786
description:
787787
'Required for send_text_input_action, the input action to send',
788-
values: [
788+
enumValues: [
789789
'none',
790790
'unspecified',
791791
'done',
@@ -806,23 +806,23 @@ base mixin DartToolingDaemonSupport
806806
'Maximum time in milliseconds to wait for the command to '
807807
'complete. Defaults to $_defaultTimeoutMs.',
808808
),
809-
'offsetType': Schema.enumeration(
809+
'offsetType': Schema.string(
810810
description:
811811
'Offset types that can be requested by get_offset. '
812812
'Required for get_offset.',
813-
values: [
813+
enumValues: [
814814
'topLeft',
815815
'topRight',
816816
'bottomLeft',
817817
'bottomRight',
818818
'center',
819819
],
820820
),
821-
'diagnosticsType': Schema.enumeration(
821+
'diagnosticsType': Schema.string(
822822
description:
823823
'The type of diagnostics tree to request. '
824824
'Required for get_diagnostics_tree',
825-
values: ['renderObject', 'widget'],
825+
enumValues: ['renderObject', 'widget'],
826826
),
827827
'subtreeDepth': Schema.int(
828828
description:

0 commit comments

Comments
 (0)