@@ -199,8 +199,23 @@ async def run_scenario_setup(self, scenario_name: str, args: dict[str, Any]) ->
199199 except Exception :
200200 available = "(could not fetch available scenarios)"
201201
202+ # Check if the prompt exists - if so, the error is something else
203+ original_error = str (e )
204+ if prompt_id in scenario_prompts :
205+ # Prompt exists but get_prompt failed for another reason
206+ raise ValueError (
207+ f"⚠️ ERROR: Scenario '{ prompt_id } ' exists but failed to execute.\n \n "
208+ f"The scenario was found but encountered an error during setup:\n "
209+ f" { original_error } \n \n "
210+ f"This could be caused by:\n "
211+ f" - Missing or invalid scenario arguments\n "
212+ f" - An error in the scenario's setup function\n "
213+ f" - Connection or serialization issues\n \n "
214+ f"Check the scenario definition and required arguments."
215+ ) from e
216+
202217 raise ValueError (
203- f"Scenario not found.\n \n "
218+ f"⚠️ ERROR: Scenario not found.\n \n "
204219 f"Scenario IDs have the format 'environment_name:scenario_name'.\n "
205220 f"If you only specify 'scenario_name', the SDK uses your task's env name "
206221 f"as the prefix.\n "
@@ -362,7 +377,7 @@ def decorator(
362377 # Only include JSON-serializable defaults
363378 default_val = p .default
364379 if default_val is None or isinstance (
365- default_val , (str , int , float , bool , list , dict )
380+ default_val , (str | int | float | bool | list | dict )
366381 ):
367382 arg_info ["default" ] = default_val
368383
@@ -413,26 +428,51 @@ async def prompt_handler(**handler_args: Any) -> list[str]:
413428
414429 # Deserialize JSON-encoded arguments using Pydantic TypeAdapter
415430 # This properly handles: Pydantic models, enums, datetime, lists, dicts
431+ # MCP prompts only support string arguments, so we JSON-serialize complex
432+ # types on the sending side and deserialize them here
416433 deserialized_args : dict [str , Any ] = {}
417434 for arg_name , arg_value in handler_args .items ():
418435 annotation = param_annotations .get (arg_name )
419- if (
420- annotation is not None
421- and annotation is not str
422- and isinstance (arg_value , str )
423- ):
424- # Try TypeAdapter.validate_json for proper type coercion
436+
437+ # Only attempt deserialization on string values
438+ if not isinstance (arg_value , str ):
439+ deserialized_args [arg_name ] = arg_value
440+ continue
441+
442+ # If annotation is explicitly str, keep as string (no deserialization)
443+ if annotation is str :
444+ deserialized_args [arg_name ] = arg_value
445+ continue
446+
447+ # If we have a non-str type annotation, use TypeAdapter
448+ if annotation is not None :
425449 try :
426450 adapter = TypeAdapter (annotation )
427451 deserialized_args [arg_name ] = adapter .validate_json (arg_value )
428- except Exception :
429- # Fall back to plain json.loads if TypeAdapter fails
430- try :
431- deserialized_args [arg_name ] = json .loads (arg_value )
432- except json .JSONDecodeError :
433- deserialized_args [arg_name ] = arg_value
434- else :
435- deserialized_args [arg_name ] = arg_value
452+ continue
453+ except Exception : # noqa: S110
454+ pass # Fall through to generic JSON decode
455+
456+ # No type annotation - try JSON decode for strings that look like JSON
457+ # (arrays, objects, numbers, booleans, null)
458+ stripped = arg_value .strip ()
459+ if (stripped and stripped [0 ] in "[{" ) or stripped in ("true" , "false" , "null" ):
460+ try :
461+ deserialized_args [arg_name ] = json .loads (arg_value )
462+ continue
463+ except json .JSONDecodeError :
464+ pass # Keep as string
465+
466+ # Also try to decode if it looks like a number
467+ if stripped .lstrip ("-" ).replace ("." , "" , 1 ).isdigit ():
468+ try :
469+ deserialized_args [arg_name ] = json .loads (arg_value )
470+ continue
471+ except json .JSONDecodeError :
472+ pass
473+
474+ # Keep as string
475+ deserialized_args [arg_name ] = arg_value
436476
437477 # Create generator instance with deserialized args
438478 gen = scenario_fn (** deserialized_args )
0 commit comments