{"record":{"id":"385ec73e817d3e37","repo":"PrefectHQ/fastmcp","slug":"ctx-elicit-requires-a-response-type-the-empty-s","errorCode":null,"errorMessage":"ctx.elicit() requires a response_type. The empty-schema form-mode request produced by response_type=None was ambiguous under the MCP spec and caused some clients to render an empty, non-functional form. Pass a type describing the data you expect back — use `bool` for a confirmation.","messagePattern":"ctx\\.elicit\\(\\) requires a response_type\\. The empty-schema form-mode request produced by response_type=None was ambiguous under the MCP spec and caused some clients to render an empty, non-functional form\\. Pass a type describing the data you expect back — use `bool` for a confirmation\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/elicitation.py","lineNumber":173,"sourceCode":"        - `[{\"low\": {...}}]` -> multi-select titled\n        - `[\"a\", \"b\"]` -> single-select untitled\n    - `list[X]` type annotation: multi-select with type\n    - Scalar types (bool, int, float, str, Literal, Enum): single value\n    - Other types (dataclass, BaseModel): use directly\n\n    The ``response_title`` and ``response_description`` arguments customize the\n    label and description of the wrapped ``value`` property for the scalar/dict/list\n    shorthand forms. They are only valid when FastMCP is wrapping the response\n    type; passing them with a full BaseModel/dataclass raises ``TypeError``,\n    because in those cases the user already controls field metadata via\n    ``Field(title=..., description=...)``.\n    \"\"\"\n    has_response_metadata = (\n        response_title is not None or response_description is not None\n    )\n\n    if response_type is None:\n        raise TypeError(_NONE_RESPONSE_TYPE_ERROR)\n\n    if isinstance(response_type, dict):\n        config = _parse_dict_syntax(response_type)\n    elif isinstance(response_type, list):\n        config = _parse_list_syntax(response_type)\n    elif get_origin(response_type) is list:\n        config = _parse_generic_list(response_type)\n    elif _is_scalar_type(response_type):\n        config = _parse_scalar_type(response_type)\n    else:\n        # Other types (dataclass, BaseModel, etc.) - use directly\n        if has_response_metadata:\n            raise TypeError(\n                \"response_title and response_description are only supported when \"\n                \"response_type is a scalar, Literal, Enum, or the dict/list \"\n                \"shorthand forms. For BaseModel or dataclass response types, use \"\n                \"Field(title=..., description=...) on the individual fields.\"\n            )","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/elicitation.py#L155-L191","documentation":"FastMCP's elicit() previously allowed response_type=None to request an empty-schema 'form mode' elicitation. Under the MCP spec that empty form was ambiguous and some clients rendered a non-functional empty form, so parse_elicit_response_type now raises TypeError to force callers to declare what data type they expect back. Passing a type like bool gives the client a concrete schema to render.","triggerScenarios":"Calling `await ctx.elicit(\"Continue?\")` (response_type omitted/None) or explicitly `ctx.elicit(\"message\", response_type=None)` from a tool handler.","commonSituations":"Migrating older FastMCP code written before the None form was removed; following outdated tutorials/docs; asking a yes/no confirmation question and assuming a bare message elicits a boolean.","solutions":["Pass an explicit response_type: use `bool` for a confirmation prompt, or a BaseModel/dataclass/dict shorthand for structured data","For yes/no questions use response_type=bool and check response.data","If you truly need no data back, use ctx.info/report or a plain tool return instead of elicitation"],"exampleFix":"// before\nresult = await ctx.elicit(\"Proceed with deletion?\")\n// after\nresult = await ctx.elicit(\"Proceed with deletion?\", response_type=bool)\nif result.action == \"accept\" and result.data:\n    await do_delete()","handlingStrategy":"validation","validationCode":"if response_type is None:\n    response_type = bool  # confirmations: pass an explicit type\nresult = await ctx.elicit(message, response_type=response_type)","typeGuard":"def elicit_response_type_is_valid(rt) -> bool:\n    return rt is not None and (\n        rt is bool or isinstance(rt, (type, dict, list))\n    )","tryCatchPattern":"try:\n    result = await ctx.elicit('Proceed?', response_type=response_type)\nexcept TypeError as e:\n    logger.error(f'bad elicit call: {e}')\n    raise","preventionTips":["Always pass response_type to ctx.elicit — never rely on the removed None form","Use bool for yes/no prompts; BaseModel/dataclass/dict shorthand for structured data","Update call sites during FastMCP upgrades; the None form raises deliberately","Prefer plain returns/ctx.info when no client data is actually needed"],"tags":["elicitation","mcp","api-change","fastmcp"],"backgroundTag":"missing-response-type","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}