@@ -214,6 +214,26 @@ async def elicitation_callback(context: RequestContext[ClientSession, None], par
214
214
text_contains = ["Validation failed:" , "optional_list" ],
215
215
)
216
216
217
+ # Test valid list[str] for multi-select enum
218
+ class ValidMultiSelectSchema (BaseModel ):
219
+ name : str = Field (description = "Name" )
220
+ tags : list [str ] = Field (description = "Tags" )
221
+
222
+ @mcp .tool (description = "Tool with valid list[str] field" )
223
+ async def valid_multiselect_tool (ctx : Context [ServerSession , None ]) -> str :
224
+ result = await ctx .elicit (message = "Please provide tags" , schema = ValidMultiSelectSchema )
225
+ if result .action == "accept" and result .data :
226
+ return f"Name: { result .data .name } , Tags: { ', ' .join (result .data .tags )} "
227
+ return f"User { result .action } "
228
+
229
+ async def multiselect_callback (context : RequestContext [ClientSession , Any ], params : ElicitRequestParams ):
230
+ if "Please provide tags" in params .message :
231
+ return ElicitResult (action = "accept" , content = {"name" : "Test" , "tags" : ["tag1" , "tag2" ]})
232
+ return ElicitResult (action = "decline" )
233
+
234
+ await call_tool_and_assert (mcp , multiselect_callback , "valid_multiselect_tool" , {}, "Name: Test, Tags: tag1, tag2" )
235
+
236
+
217
237
@pytest .mark .anyio
218
238
async def test_elicitation_with_default_values ():
219
239
"""Test that default values work correctly in elicitation schemas and are included in JSON."""
@@ -267,3 +287,89 @@ async def callback_override(context: RequestContext[ClientSession, None], params
267
287
await call_tool_and_assert (
268
288
mcp ,
callback_override ,
"defaults_tool" , {},
"Name: John, Age: 25, Subscribe: False, Email: [email protected] "
269
289
)
290
+
291
+
292
+ @pytest .mark .anyio
293
+ async def test_elicitation_with_enum_titles ():
294
+ """Test elicitation with enum schemas using oneOf/anyOf for titles."""
295
+ mcp = FastMCP (name = "ColorPreferencesApp" )
296
+
297
+ # Test single-select with titles using oneOf
298
+ class FavoriteColorSchema (BaseModel ):
299
+ user_name : str = Field (description = "Your name" )
300
+ favorite_color : str = Field (
301
+ description = "Select your favorite color" ,
302
+ json_schema_extra = {
303
+ "oneOf" : [
304
+ {"const" : "red" , "title" : "Red" },
305
+ {"const" : "green" , "title" : "Green" },
306
+ {"const" : "blue" , "title" : "Blue" },
307
+ {"const" : "yellow" , "title" : "Yellow" },
308
+ ]
309
+ },
310
+ )
311
+
312
+ @mcp .tool (description = "Single color selection" )
313
+ async def select_favorite_color (ctx : Context [ServerSession , None ]) -> str :
314
+ result = await ctx .elicit (message = "Select your favorite color" , schema = FavoriteColorSchema )
315
+ if result .action == "accept" and result .data :
316
+ return f"User: { result .data .user_name } , Favorite: { result .data .favorite_color } "
317
+ return f"User { result .action } "
318
+
319
+ # Test multi-select with titles using anyOf
320
+ class FavoriteColorsSchema (BaseModel ):
321
+ user_name : str = Field (description = "Your name" )
322
+ favorite_colors : list [str ] = Field (
323
+ description = "Select your favorite colors" ,
324
+ json_schema_extra = {
325
+ "items" : {
326
+ "anyOf" : [
327
+ {"const" : "red" , "title" : "Red" },
328
+ {"const" : "green" , "title" : "Green" },
329
+ {"const" : "blue" , "title" : "Blue" },
330
+ {"const" : "yellow" , "title" : "Yellow" },
331
+ ]
332
+ }
333
+ },
334
+ )
335
+
336
+ @mcp .tool (description = "Multiple color selection" )
337
+ async def select_favorite_colors (ctx : Context [ServerSession , None ]) -> str :
338
+ result = await ctx .elicit (message = "Select your favorite colors" , schema = FavoriteColorsSchema )
339
+ if result .action == "accept" and result .data :
340
+ return f"User: { result .data .user_name } , Colors: { ', ' .join (result .data .favorite_colors )} "
341
+ return f"User { result .action } "
342
+
343
+ # Test deprecated enumNames format
344
+ class DeprecatedColorSchema (BaseModel ):
345
+ user_name : str = Field (description = "Your name" )
346
+ color : str = Field (
347
+ description = "Select a color" ,
348
+ json_schema_extra = {"enum" : ["red" , "green" , "blue" ], "enumNames" : ["Red" , "Green" , "Blue" ]},
349
+ )
350
+
351
+ @mcp .tool (description = "Deprecated enum format" )
352
+ async def select_color_deprecated (ctx : Context [ServerSession , None ]) -> str :
353
+ result = await ctx .elicit (message = "Select a color (deprecated format)" , schema = DeprecatedColorSchema )
354
+ if result .action == "accept" and result .data :
355
+ return f"User: { result .data .user_name } , Color: { result .data .color } "
356
+ return f"User { result .action } "
357
+
358
+ async def enum_callback (context : RequestContext [ClientSession , Any ], params : ElicitRequestParams ):
359
+ if "colors" in params .message and "deprecated" not in params .message :
360
+ return ElicitResult (action = "accept" , content = {"user_name" : "Bob" , "favorite_colors" : ["red" , "green" ]})
361
+ elif "color" in params .message :
362
+ if "deprecated" in params .message :
363
+ return ElicitResult (action = "accept" , content = {"user_name" : "Charlie" , "color" : "green" })
364
+ else :
365
+ return ElicitResult (action = "accept" , content = {"user_name" : "Alice" , "favorite_color" : "blue" })
366
+ return ElicitResult (action = "decline" )
367
+
368
+ # Test single-select with titles
369
+ await call_tool_and_assert (mcp , enum_callback , "select_favorite_color" , {}, "User: Alice, Favorite: blue" )
370
+
371
+ # Test multi-select with titles
372
+ await call_tool_and_assert (mcp , enum_callback , "select_favorite_colors" , {}, "User: Bob, Colors: red, green" )
373
+
374
+ # Test deprecated enumNames format
375
+ await call_tool_and_assert (mcp , enum_callback , "select_color_deprecated" , {}, "User: Charlie, Color: green" )
0 commit comments