Skip to content

Commit 8ae03ea

Browse files
Copilotstephentoub
andcommitted
Address code review feedback: rename EnumOption to EnumSchemaOption and split EnumItemsSchema
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 27e403a commit 8ae03ea

File tree

3 files changed

+95
-104
lines changed

3 files changed

+95
-104
lines changed

docs/concepts/elicitation/samples/server/Tools/InteractiveTools.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,10 @@ CancellationToken token
170170
Description = "Select the issue severity level",
171171
OneOf =
172172
[
173-
new EnumOption { Const = "p0", Title = "P0 - Critical (Immediate attention required)" },
174-
new EnumOption { Const = "p1", Title = "P1 - High (Urgent, within 24 hours)" },
175-
new EnumOption { Const = "p2", Title = "P2 - Medium (Within a week)" },
176-
new EnumOption { Const = "p3", Title = "P3 - Low (As time permits)" }
173+
new EnumSchemaOption { Const = "p0", Title = "P0 - Critical (Immediate attention required)" },
174+
new EnumSchemaOption { Const = "p1", Title = "P1 - High (Urgent, within 24 hours)" },
175+
new EnumSchemaOption { Const = "p2", Title = "P2 - Medium (Within a week)" },
176+
new EnumSchemaOption { Const = "p3", Title = "P3 - Low (As time permits)" }
177177
],
178178
Default = "p2"
179179
}
@@ -204,7 +204,7 @@ CancellationToken token
204204
Description = "Select one or more tags",
205205
MinItems = 1,
206206
MaxItems = 3,
207-
Items = new EnumItemsSchema
207+
Items = new UntitledEnumItemsSchema
208208
{
209209
Type = "string",
210210
Enum = ["bug", "feature", "documentation", "enhancement", "question"]
@@ -239,14 +239,14 @@ CancellationToken token
239239
{
240240
Title = "Features",
241241
Description = "Select desired features",
242-
Items = new EnumItemsSchema
242+
Items = new TitledEnumItemsSchema
243243
{
244244
AnyOf =
245245
[
246-
new EnumOption { Const = "auth", Title = "Authentication & Authorization" },
247-
new EnumOption { Const = "api", Title = "RESTful API" },
248-
new EnumOption { Const = "ui", Title = "Modern UI Components" },
249-
new EnumOption { Const = "db", Title = "Database Integration" }
246+
new EnumSchemaOption { Const = "auth", Title = "Authentication & Authorization" },
247+
new EnumSchemaOption { Const = "api", Title = "RESTful API" },
248+
new EnumSchemaOption { Const = "ui", Title = "Modern UI Components" },
249+
new EnumSchemaOption { Const = "db", Title = "Database Integration" }
250250
]
251251
}
252252
}

src/ModelContextProtocol.Core/Protocol/ElicitRequestParams.cs

Lines changed: 77 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,10 @@ public class Converter : JsonConverter<PrimitiveSchemaDefinition>
120120
IList<string>? defaultStringArray = null;
121121
IList<string>? enumValues = null;
122122
IList<string>? enumNames = null;
123-
IList<EnumOption>? oneOf = null;
123+
IList<EnumSchemaOption>? oneOf = null;
124124
int? minItems = null;
125125
int? maxItems = null;
126-
EnumItemsSchema? items = null;
126+
object? items = null; // Can be UntitledEnumItemsSchema or TitledEnumItemsSchema
127127

128128
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
129129
{
@@ -278,30 +278,27 @@ public class Converter : JsonConverter<PrimitiveSchemaDefinition>
278278
break;
279279

280280
case "array":
281-
if (items is not null)
281+
if (items is TitledEnumItemsSchema titledItems)
282282
{
283-
if (items.AnyOf is not null)
283+
// TitledMultiSelectEnumSchema
284+
psd = new TitledMultiSelectEnumSchema
284285
{
285-
// TitledMultiSelectEnumSchema
286-
psd = new TitledMultiSelectEnumSchema
287-
{
288-
MinItems = minItems,
289-
MaxItems = maxItems,
290-
Items = items,
291-
Default = defaultStringArray,
292-
};
293-
}
294-
else if (items.Enum is not null)
286+
MinItems = minItems,
287+
MaxItems = maxItems,
288+
Items = titledItems,
289+
Default = defaultStringArray,
290+
};
291+
}
292+
else if (items is UntitledEnumItemsSchema untitledItems)
293+
{
294+
// UntitledMultiSelectEnumSchema
295+
psd = new UntitledMultiSelectEnumSchema
295296
{
296-
// UntitledMultiSelectEnumSchema
297-
psd = new UntitledMultiSelectEnumSchema
298-
{
299-
MinItems = minItems,
300-
MaxItems = maxItems,
301-
Items = items,
302-
Default = defaultStringArray,
303-
};
304-
}
297+
MinItems = minItems,
298+
MaxItems = maxItems,
299+
Items = untitledItems,
300+
Default = defaultStringArray,
301+
};
305302
}
306303
break;
307304

@@ -333,14 +330,14 @@ public class Converter : JsonConverter<PrimitiveSchemaDefinition>
333330
return psd;
334331
}
335332

336-
private static IList<EnumOption> DeserializeEnumOptions(ref Utf8JsonReader reader)
333+
private static IList<EnumSchemaOption> DeserializeEnumOptions(ref Utf8JsonReader reader)
337334
{
338335
if (reader.TokenType != JsonTokenType.StartArray)
339336
{
340337
throw new JsonException("Expected array for oneOf property.");
341338
}
342339

343-
var options = new List<EnumOption>();
340+
var options = new List<EnumSchemaOption>();
344341
while (reader.Read() && reader.TokenType != JsonTokenType.EndArray)
345342
{
346343
if (reader.TokenType != JsonTokenType.StartObject)
@@ -378,20 +375,23 @@ private static IList<EnumOption> DeserializeEnumOptions(ref Utf8JsonReader reade
378375
throw new JsonException("Each option in oneOf must have both 'const' and 'title' properties.");
379376
}
380377

381-
options.Add(new EnumOption { Const = constValue, Title = titleValue });
378+
options.Add(new EnumSchemaOption { Const = constValue, Title = titleValue });
382379
}
383380

384381
return options;
385382
}
386383

387-
private static EnumItemsSchema DeserializeEnumItemsSchema(ref Utf8JsonReader reader)
384+
private static object DeserializeEnumItemsSchema(ref Utf8JsonReader reader)
388385
{
389386
if (reader.TokenType != JsonTokenType.StartObject)
390387
{
391388
throw new JsonException("Expected object for items property.");
392389
}
393390

394-
var itemsSchema = new EnumItemsSchema();
391+
string? type = null;
392+
IList<string>? enumValues = null;
393+
IList<EnumSchemaOption>? anyOf = null;
394+
395395
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
396396
{
397397
if (reader.TokenType == JsonTokenType.PropertyName)
@@ -402,13 +402,13 @@ private static EnumItemsSchema DeserializeEnumItemsSchema(ref Utf8JsonReader rea
402402
switch (propertyName)
403403
{
404404
case "type":
405-
itemsSchema.Type = reader.GetString() ?? "string";
405+
type = reader.GetString();
406406
break;
407407
case "enum":
408-
itemsSchema.Enum = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.IListString);
408+
enumValues = JsonSerializer.Deserialize(ref reader, McpJsonUtilities.JsonContext.Default.IListString);
409409
break;
410410
case "anyOf":
411-
itemsSchema.AnyOf = DeserializeEnumOptions(ref reader);
411+
anyOf = DeserializeEnumOptions(ref reader);
412412
break;
413413
default:
414414
reader.Skip();
@@ -417,7 +417,19 @@ private static EnumItemsSchema DeserializeEnumItemsSchema(ref Utf8JsonReader rea
417417
}
418418
}
419419

420-
return itemsSchema;
420+
// Determine which type to create based on the properties
421+
if (anyOf is not null)
422+
{
423+
return new TitledEnumItemsSchema { AnyOf = anyOf };
424+
}
425+
else if (enumValues is not null)
426+
{
427+
return new UntitledEnumItemsSchema { Type = type ?? "string", Enum = enumValues };
428+
}
429+
else
430+
{
431+
throw new JsonException("Items schema must have either 'enum' or 'anyOf' property.");
432+
}
421433
}
422434

423435
/// <inheritdoc/>
@@ -517,11 +529,8 @@ public override void Write(Utf8JsonWriter writer, PrimitiveSchemaDefinition valu
517529
{
518530
writer.WriteNumber("maxItems", untitledMultiSelect.MaxItems.Value);
519531
}
520-
if (untitledMultiSelect.Items is not null)
521-
{
522-
writer.WritePropertyName("items");
523-
SerializeEnumItemsSchema(writer, untitledMultiSelect.Items);
524-
}
532+
writer.WritePropertyName("items");
533+
SerializeUntitledEnumItemsSchema(writer, untitledMultiSelect.Items);
525534
if (untitledMultiSelect.Default is not null)
526535
{
527536
writer.WritePropertyName("default");
@@ -538,11 +547,8 @@ public override void Write(Utf8JsonWriter writer, PrimitiveSchemaDefinition valu
538547
{
539548
writer.WriteNumber("maxItems", titledMultiSelect.MaxItems.Value);
540549
}
541-
if (titledMultiSelect.Items is not null)
542-
{
543-
writer.WritePropertyName("items");
544-
SerializeEnumItemsSchema(writer, titledMultiSelect.Items);
545-
}
550+
writer.WritePropertyName("items");
551+
SerializeTitledEnumItemsSchema(writer, titledMultiSelect.Items);
546552
if (titledMultiSelect.Default is not null)
547553
{
548554
writer.WritePropertyName("default");
@@ -576,7 +582,7 @@ public override void Write(Utf8JsonWriter writer, PrimitiveSchemaDefinition valu
576582
writer.WriteEndObject();
577583
}
578584

579-
private static void SerializeEnumOptions(Utf8JsonWriter writer, IList<EnumOption> options)
585+
private static void SerializeEnumOptions(Utf8JsonWriter writer, IList<EnumSchemaOption> options)
580586
{
581587
writer.WriteStartArray();
582588
foreach (var option in options)
@@ -589,23 +595,20 @@ private static void SerializeEnumOptions(Utf8JsonWriter writer, IList<EnumOption
589595
writer.WriteEndArray();
590596
}
591597

592-
private static void SerializeEnumItemsSchema(Utf8JsonWriter writer, EnumItemsSchema itemsSchema)
598+
private static void SerializeUntitledEnumItemsSchema(Utf8JsonWriter writer, UntitledEnumItemsSchema itemsSchema)
593599
{
594600
writer.WriteStartObject();
595601
writer.WriteString("type", itemsSchema.Type);
596-
597-
if (itemsSchema.Enum is not null)
598-
{
599-
writer.WritePropertyName("enum");
600-
JsonSerializer.Serialize(writer, itemsSchema.Enum, McpJsonUtilities.JsonContext.Default.IListString);
601-
}
602-
603-
if (itemsSchema.AnyOf is not null && itemsSchema.AnyOf.Count > 0)
604-
{
605-
writer.WritePropertyName("anyOf");
606-
SerializeEnumOptions(writer, itemsSchema.AnyOf);
607-
}
608-
602+
writer.WritePropertyName("enum");
603+
JsonSerializer.Serialize(writer, itemsSchema.Enum, McpJsonUtilities.JsonContext.Default.IListString);
604+
writer.WriteEndObject();
605+
}
606+
607+
private static void SerializeTitledEnumItemsSchema(Utf8JsonWriter writer, TitledEnumItemsSchema itemsSchema)
608+
{
609+
writer.WriteStartObject();
610+
writer.WritePropertyName("anyOf");
611+
SerializeEnumOptions(writer, itemsSchema.AnyOf);
609612
writer.WriteEndObject();
610613
}
611614
}
@@ -816,9 +819,9 @@ public IList<string> Enum
816819
}
817820

818821
/// <summary>
819-
/// Represents a single option in a titled enum schema.
822+
/// Represents a single option in a titled enum schema with a constant value and display title.
820823
/// </summary>
821-
public sealed class EnumOption
824+
public sealed class EnumSchemaOption
822825
{
823826
/// <summary>Gets or sets the constant value for this option.</summary>
824827
[JsonPropertyName("const")]
@@ -851,7 +854,7 @@ public override string Type
851854
/// <summary>Gets or sets the list of enum options with their constant values and display titles.</summary>
852855
[JsonPropertyName("oneOf")]
853856
[field: MaybeNull]
854-
public IList<EnumOption> OneOf
857+
public IList<EnumSchemaOption> OneOf
855858
{
856859
get => field ??= [];
857860
set
@@ -867,21 +870,27 @@ public IList<EnumOption> OneOf
867870
}
868871

869872
/// <summary>
870-
/// Represents the items schema for multi-select enum arrays.
873+
/// Represents the items schema for untitled multi-select enum arrays.
871874
/// </summary>
872-
public sealed class EnumItemsSchema
875+
public sealed class UntitledEnumItemsSchema
873876
{
874877
/// <summary>Gets or sets the type of the items.</summary>
875878
[JsonPropertyName("type")]
876879
public string Type { get; set; } = "string";
877880

878-
/// <summary>Gets or sets the list of allowed string values for untitled multi-select enums.</summary>
881+
/// <summary>Gets or sets the list of allowed string values.</summary>
879882
[JsonPropertyName("enum")]
880-
public IList<string>? Enum { get; set; }
883+
public required IList<string> Enum { get; set; }
884+
}
881885

882-
/// <summary>Gets or sets the list of enum options for titled multi-select enums.</summary>
886+
/// <summary>
887+
/// Represents the items schema for titled multi-select enum arrays.
888+
/// </summary>
889+
public sealed class TitledEnumItemsSchema
890+
{
891+
/// <summary>Gets or sets the list of enum options with constant values and display titles.</summary>
883892
[JsonPropertyName("anyOf")]
884-
public IList<EnumOption>? AnyOf { get; set; }
893+
public required IList<EnumSchemaOption> AnyOf { get; set; }
885894
}
886895

887896
/// <summary>
@@ -913,16 +922,7 @@ public override string Type
913922

914923
/// <summary>Gets or sets the schema for items in the array.</summary>
915924
[JsonPropertyName("items")]
916-
[field: MaybeNull]
917-
public EnumItemsSchema Items
918-
{
919-
get => field ??= new EnumItemsSchema();
920-
set
921-
{
922-
Throw.IfNull(value);
923-
field = value;
924-
}
925-
}
925+
public required UntitledEnumItemsSchema Items { get; set; }
926926

927927
/// <summary>Gets or sets the default values for the enum.</summary>
928928
[JsonPropertyName("default")]
@@ -958,16 +958,7 @@ public override string Type
958958

959959
/// <summary>Gets or sets the schema for items in the array.</summary>
960960
[JsonPropertyName("items")]
961-
[field: MaybeNull]
962-
public EnumItemsSchema Items
963-
{
964-
get => field ??= new EnumItemsSchema();
965-
set
966-
{
967-
Throw.IfNull(value);
968-
field = value;
969-
}
970-
}
961+
public required TitledEnumItemsSchema Items { get; set; }
971962

972963
/// <summary>Gets or sets the default values for the enum.</summary>
973964
[JsonPropertyName("default")]

tests/ModelContextProtocol.Tests/Protocol/EnumSchemaTests.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,9 @@ public void TitledSingleSelectEnumSchema_Serializes_Correctly()
4747
Description = "Issue severity",
4848
OneOf =
4949
[
50-
new ElicitRequestParams.EnumOption { Const = "critical", Title = "Critical" },
51-
new ElicitRequestParams.EnumOption { Const = "high", Title = "High Priority" },
52-
new ElicitRequestParams.EnumOption { Const = "low", Title = "Low Priority" }
50+
new ElicitRequestParams.EnumSchemaOption { Const = "critical", Title = "Critical" },
51+
new ElicitRequestParams.EnumSchemaOption { Const = "high", Title = "High Priority" },
52+
new ElicitRequestParams.EnumSchemaOption { Const = "low", Title = "Low Priority" }
5353
],
5454
Default = "high"
5555
};
@@ -85,7 +85,7 @@ public void UntitledMultiSelectEnumSchema_Serializes_Correctly()
8585
Description = "Select multiple tags",
8686
MinItems = 1,
8787
MaxItems = 3,
88-
Items = new ElicitRequestParams.EnumItemsSchema
88+
Items = new ElicitRequestParams.UntitledEnumItemsSchema
8989
{
9090
Type = "string",
9191
Enum = ["bug", "feature", "documentation", "enhancement"]
@@ -125,13 +125,13 @@ public void TitledMultiSelectEnumSchema_Serializes_Correctly()
125125
Title = "Features",
126126
Description = "Select desired features",
127127
MinItems = 2,
128-
Items = new ElicitRequestParams.EnumItemsSchema
128+
Items = new ElicitRequestParams.TitledEnumItemsSchema
129129
{
130130
AnyOf =
131131
[
132-
new ElicitRequestParams.EnumOption { Const = "auth", Title = "Authentication" },
133-
new ElicitRequestParams.EnumOption { Const = "api", Title = "REST API" },
134-
new ElicitRequestParams.EnumOption { Const = "ui", Title = "User Interface" }
132+
new ElicitRequestParams.EnumSchemaOption { Const = "auth", Title = "Authentication" },
133+
new ElicitRequestParams.EnumSchemaOption { Const = "api", Title = "REST API" },
134+
new ElicitRequestParams.EnumSchemaOption { Const = "ui", Title = "User Interface" }
135135
]
136136
},
137137
Default = ["auth", "api"]

0 commit comments

Comments
 (0)