Skip to content

Commit 7103d14

Browse files
Copilotstephentoub
andcommitted
Add comprehensive tests for new enum schema types and update documentation
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent 44572d5 commit 7103d14

2 files changed

Lines changed: 317 additions & 1 deletion

File tree

docs/concepts/elicitation/elicitation.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ The C# SDK registers an instance of <xref:ModelContextProtocol.Server.McpServer>
1616
so tools can simply add a parameter of type <xref:ModelContextProtocol.Server.McpServer> to their method signature to access it.
1717

1818
The MCP Server must specify the schema of each input value it is requesting from the user.
19-
Only primitive types (string, number, boolean) are supported for elicitation requests.
19+
Primitive types (string, number, boolean) and enum types are supported for elicitation requests.
2020
The schema may include a description to help the user understand what is being requested.
2121

22+
For enum types, the SDK supports several schema formats:
23+
- **UntitledSingleSelectEnumSchema**: A single-select enum where the enum values serve as both the value and display text
24+
- **TitledSingleSelectEnumSchema**: A single-select enum with separate display titles for each option (using JSON Schema `oneOf` with `const` and `title`)
25+
- **UntitledMultiSelectEnumSchema**: A multi-select enum allowing multiple values to be selected
26+
- **TitledMultiSelectEnumSchema**: A multi-select enum with display titles for each option
27+
- **LegacyTitledEnumSchema** (deprecated): The legacy enum schema using `enumNames` for backward compatibility
28+
2229
The server can request a single input or multiple inputs at once.
2330
To help distinguish multiple inputs, each input has a unique name.
2431

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,309 @@
1+
using ModelContextProtocol.Protocol;
2+
using System.Text.Json;
3+
4+
#pragma warning disable CS0618 // Type or member is obsolete
5+
6+
namespace ModelContextProtocol.Tests.Protocol;
7+
8+
public class EnumSchemaTests
9+
{
10+
[Fact]
11+
public void UntitledSingleSelectEnumSchema_Serializes_Correctly()
12+
{
13+
// Arrange
14+
var schema = new ElicitRequestParams.UntitledSingleSelectEnumSchema
15+
{
16+
Title = "Priority",
17+
Description = "Task priority level",
18+
Enum = ["low", "medium", "high"],
19+
Default = "medium"
20+
};
21+
22+
// Act
23+
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
24+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
25+
26+
// Assert
27+
Assert.NotNull(deserialized);
28+
var result = Assert.IsType<ElicitRequestParams.UntitledSingleSelectEnumSchema>(deserialized);
29+
Assert.Equal("string", result.Type);
30+
Assert.Equal("Priority", result.Title);
31+
Assert.Equal("Task priority level", result.Description);
32+
Assert.Equal(["low", "medium", "high"], result.Enum);
33+
Assert.Equal("medium", result.Default);
34+
Assert.Contains("\"type\":\"string\"", json);
35+
Assert.Contains("\"enum\":[\"low\",\"medium\",\"high\"]", json);
36+
Assert.DoesNotContain("enumNames", json);
37+
Assert.DoesNotContain("oneOf", json);
38+
}
39+
40+
[Fact]
41+
public void TitledSingleSelectEnumSchema_Serializes_Correctly()
42+
{
43+
// Arrange
44+
var schema = new ElicitRequestParams.TitledSingleSelectEnumSchema
45+
{
46+
Title = "Severity",
47+
Description = "Issue severity",
48+
OneOf =
49+
[
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" }
53+
],
54+
Default = "high"
55+
};
56+
57+
// Act
58+
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
59+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
60+
61+
// Assert
62+
Assert.NotNull(deserialized);
63+
var result = Assert.IsType<ElicitRequestParams.TitledSingleSelectEnumSchema>(deserialized);
64+
Assert.Equal("string", result.Type);
65+
Assert.Equal("Severity", result.Title);
66+
Assert.Equal("Issue severity", result.Description);
67+
Assert.Equal(3, result.OneOf.Count);
68+
Assert.Equal("critical", result.OneOf[0].Const);
69+
Assert.Equal("Critical", result.OneOf[0].Title);
70+
Assert.Equal("high", result.Default);
71+
Assert.Contains("\"oneOf\":", json);
72+
Assert.Contains("\"const\":\"critical\"", json);
73+
Assert.Contains("\"title\":\"Critical\"", json);
74+
Assert.DoesNotContain("enum\":", json);
75+
Assert.DoesNotContain("enumNames", json);
76+
}
77+
78+
[Fact]
79+
public void UntitledMultiSelectEnumSchema_Serializes_Correctly()
80+
{
81+
// Arrange
82+
var schema = new ElicitRequestParams.UntitledMultiSelectEnumSchema
83+
{
84+
Title = "Tags",
85+
Description = "Select multiple tags",
86+
MinItems = 1,
87+
MaxItems = 3,
88+
Items = new ElicitRequestParams.EnumItemsSchema
89+
{
90+
Type = "string",
91+
Enum = ["bug", "feature", "documentation", "enhancement"]
92+
},
93+
Default = ["bug", "feature"]
94+
};
95+
96+
// Act
97+
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
98+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
99+
100+
// Assert
101+
Assert.NotNull(deserialized);
102+
var result = Assert.IsType<ElicitRequestParams.UntitledMultiSelectEnumSchema>(deserialized);
103+
Assert.Equal("array", result.Type);
104+
Assert.Equal("Tags", result.Title);
105+
Assert.Equal("Select multiple tags", result.Description);
106+
Assert.Equal(1, result.MinItems);
107+
Assert.Equal(3, result.MaxItems);
108+
Assert.NotNull(result.Items);
109+
Assert.Equal("string", result.Items.Type);
110+
Assert.Equal(["bug", "feature", "documentation", "enhancement"], result.Items.Enum);
111+
Assert.Equal(["bug", "feature"], result.Default);
112+
Assert.Contains("\"type\":\"array\"", json);
113+
Assert.Contains("\"minItems\":1", json);
114+
Assert.Contains("\"maxItems\":3", json);
115+
Assert.Contains("\"items\":", json);
116+
Assert.DoesNotContain("anyOf", json);
117+
}
118+
119+
[Fact]
120+
public void TitledMultiSelectEnumSchema_Serializes_Correctly()
121+
{
122+
// Arrange
123+
var schema = new ElicitRequestParams.TitledMultiSelectEnumSchema
124+
{
125+
Title = "Features",
126+
Description = "Select desired features",
127+
MinItems = 2,
128+
Items = new ElicitRequestParams.EnumItemsSchema
129+
{
130+
AnyOf =
131+
[
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" }
135+
]
136+
},
137+
Default = ["auth", "api"]
138+
};
139+
140+
// Act
141+
string json = JsonSerializer.Serialize<ElicitRequestParams.PrimitiveSchemaDefinition>(schema, McpJsonUtilities.DefaultOptions);
142+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
143+
144+
// Assert
145+
Assert.NotNull(deserialized);
146+
var result = Assert.IsType<ElicitRequestParams.TitledMultiSelectEnumSchema>(deserialized);
147+
Assert.Equal("array", result.Type);
148+
Assert.Equal("Features", result.Title);
149+
Assert.Equal("Select desired features", result.Description);
150+
Assert.Equal(2, result.MinItems);
151+
Assert.NotNull(result.Items);
152+
Assert.NotNull(result.Items.AnyOf);
153+
Assert.Equal(3, result.Items.AnyOf.Count);
154+
Assert.Equal("auth", result.Items.AnyOf[0].Const);
155+
Assert.Equal("Authentication", result.Items.AnyOf[0].Title);
156+
Assert.Equal(["auth", "api"], result.Default);
157+
Assert.Contains("\"type\":\"array\"", json);
158+
Assert.Contains("\"anyOf\":", json);
159+
Assert.Contains("\"const\":\"auth\"", json);
160+
Assert.Contains("\"title\":\"Authentication\"", json);
161+
}
162+
163+
[Fact]
164+
public void LegacyTitledEnumSchema_WithEnumNames_Deserializes_As_EnumSchema()
165+
{
166+
// Arrange - JSON with enumNames should deserialize as EnumSchema for backward compatibility
167+
string json = """
168+
{
169+
"type": "string",
170+
"title": "Status",
171+
"enum": ["active", "inactive", "pending"],
172+
"enumNames": ["Active", "Inactive", "Pending"],
173+
"default": "active"
174+
}
175+
""";
176+
177+
// Act
178+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
179+
180+
// Assert
181+
Assert.NotNull(deserialized);
182+
var result = Assert.IsType<ElicitRequestParams.EnumSchema>(deserialized);
183+
Assert.Equal("string", result.Type);
184+
Assert.Equal("Status", result.Title);
185+
Assert.Equal(["active", "inactive", "pending"], result.Enum);
186+
Assert.Equal(["Active", "Inactive", "Pending"], result.EnumNames);
187+
Assert.Equal("active", result.Default);
188+
}
189+
190+
[Fact]
191+
public void EnumSchema_WithoutEnumNames_Deserializes_As_UntitledSingleSelect()
192+
{
193+
// Arrange - JSON without enumNames should deserialize as UntitledSingleSelectEnumSchema
194+
string json = """
195+
{
196+
"type": "string",
197+
"title": "Status",
198+
"enum": ["draft", "published", "archived"],
199+
"default": "draft"
200+
}
201+
""";
202+
203+
// Act
204+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
205+
206+
// Assert
207+
Assert.NotNull(deserialized);
208+
var result = Assert.IsType<ElicitRequestParams.UntitledSingleSelectEnumSchema>(deserialized);
209+
Assert.Equal("string", result.Type);
210+
Assert.Equal("Status", result.Title);
211+
Assert.Equal(["draft", "published", "archived"], result.Enum);
212+
Assert.Equal("draft", result.Default);
213+
}
214+
215+
[Fact]
216+
public void EnumSchema_WithOneOf_Deserializes_As_TitledSingleSelect()
217+
{
218+
// Arrange - JSON with oneOf should deserialize as TitledSingleSelectEnumSchema
219+
string json = """
220+
{
221+
"type": "string",
222+
"title": "Priority",
223+
"oneOf": [
224+
{ "const": "p0", "title": "P0 - Critical" },
225+
{ "const": "p1", "title": "P1 - High" },
226+
{ "const": "p2", "title": "P2 - Medium" }
227+
],
228+
"default": "p1"
229+
}
230+
""";
231+
232+
// Act
233+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
234+
235+
// Assert
236+
Assert.NotNull(deserialized);
237+
var result = Assert.IsType<ElicitRequestParams.TitledSingleSelectEnumSchema>(deserialized);
238+
Assert.Equal("string", result.Type);
239+
Assert.Equal("Priority", result.Title);
240+
Assert.Equal(3, result.OneOf.Count);
241+
Assert.Equal("p0", result.OneOf[0].Const);
242+
Assert.Equal("P0 - Critical", result.OneOf[0].Title);
243+
Assert.Equal("p1", result.Default);
244+
}
245+
246+
[Fact]
247+
public void MultiSelectEnum_WithEnum_Deserializes_As_UntitledMultiSelect()
248+
{
249+
// Arrange
250+
string json = """
251+
{
252+
"type": "array",
253+
"title": "Categories",
254+
"items": {
255+
"type": "string",
256+
"enum": ["tech", "business", "lifestyle"]
257+
},
258+
"default": ["tech"]
259+
}
260+
""";
261+
262+
// Act
263+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
264+
265+
// Assert
266+
Assert.NotNull(deserialized);
267+
var result = Assert.IsType<ElicitRequestParams.UntitledMultiSelectEnumSchema>(deserialized);
268+
Assert.Equal("array", result.Type);
269+
Assert.Equal("Categories", result.Title);
270+
Assert.NotNull(result.Items);
271+
Assert.Equal(["tech", "business", "lifestyle"], result.Items.Enum);
272+
Assert.Equal(["tech"], result.Default);
273+
}
274+
275+
[Fact]
276+
public void MultiSelectEnum_WithAnyOf_Deserializes_As_TitledMultiSelect()
277+
{
278+
// Arrange
279+
string json = """
280+
{
281+
"type": "array",
282+
"title": "Roles",
283+
"items": {
284+
"anyOf": [
285+
{ "const": "admin", "title": "Administrator" },
286+
{ "const": "user", "title": "User" },
287+
{ "const": "guest", "title": "Guest" }
288+
]
289+
},
290+
"default": ["user"]
291+
}
292+
""";
293+
294+
// Act
295+
var deserialized = JsonSerializer.Deserialize<ElicitRequestParams.PrimitiveSchemaDefinition>(json, McpJsonUtilities.DefaultOptions);
296+
297+
// Assert
298+
Assert.NotNull(deserialized);
299+
var result = Assert.IsType<ElicitRequestParams.TitledMultiSelectEnumSchema>(deserialized);
300+
Assert.Equal("array", result.Type);
301+
Assert.Equal("Roles", result.Title);
302+
Assert.NotNull(result.Items);
303+
Assert.NotNull(result.Items.AnyOf);
304+
Assert.Equal(3, result.Items.AnyOf.Count);
305+
Assert.Equal("admin", result.Items.AnyOf[0].Const);
306+
Assert.Equal("Administrator", result.Items.AnyOf[0].Title);
307+
Assert.Equal(["user"], result.Default);
308+
}
309+
}

0 commit comments

Comments
 (0)