@@ -214,6 +214,26 @@ async def elicitation_callback(context: RequestContext[ClientSession, None], par
214214 text_contains = ["Validation failed:" , "optional_list" ],
215215 )
216216
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+
217237@pytest .mark .anyio
218238async def test_elicitation_with_default_values ():
219239 """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
267287 await call_tool_and_assert (
268288 mcp ,
callback_override ,
"defaults_tool" , {},
"Name: John, Age: 25, Subscribe: False, Email: [email protected] " 269289 )
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