{"record":{"id":"c4cd349e9daceb0b","repo":"PrefectHQ/fastmcp","slug":"unexpected-elicitation-action-result-action","errorCode":null,"errorMessage":"Unexpected elicitation action: {result.action}","messagePattern":"Unexpected elicitation action: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/context.py","lineNumber":1104,"sourceCode":"        # error before hitting the wire instead of the SDK's opaque \"Method not\n        # found\". Handshake-era behavior is unchanged.\n        if self._is_modern_protocol():\n            raise ToolError(_ELICIT_MODERN_ERROR)\n        # Standard request mode: use session.elicit directly\n        result = await self.session.elicit(\n            message=message,\n            requested_schema=config.schema,\n            related_request_id=self.request_id,\n        )\n\n        if result.action == \"accept\":\n            return handle_elicit_accept(config, result.content)\n        elif result.action == \"decline\":\n            return DeclinedElicitation()\n        elif result.action == \"cancel\":\n            return CancelledElicitation()\n        else:\n            raise ValueError(f\"Unexpected elicitation action: {result.action}\")\n\n    def _make_state_key(self, key: str) -> str:\n        \"\"\"Create session-prefixed key for state storage.\"\"\"\n        return f\"{self.session_id}:{key}\"\n\n    async def set_state(\n        self, key: str, value: Any, *, serializable: bool = True\n    ) -> None:\n        \"\"\"Set a value in the state store.\n\n        By default, values are stored in the session-scoped state store and\n        persist across requests within the same MCP session. Values must be\n        JSON-serializable (dicts, lists, strings, numbers, etc.).\n\n        For non-serializable values (e.g., HTTP clients, database connections),\n        pass ``serializable=False``. These values are stored in a request-scoped\n        dict and only live for the current MCP request (tool call, resource\n        read, or prompt render). They will not be available in subsequent","sourceCodeStart":1086,"sourceCodeEnd":1122,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/context.py#L1086-L1122","documentation":"Context.elicit() maps the client's elicitation response action (accept/decline/cancel) onto typed result objects. This ValueError is thrown when the client returns an action string outside that set, meaning the server received a response it cannot interpret. FastMCP throws it deliberately to surface protocol mismatches instead of silently guessing at the client's intent.","triggerScenarios":"Calling context.elicit(...) where the client's ElicitResult.action is not 'accept', 'decline', or 'cancel' — e.g. a client bug, a new action added in a newer MCP spec that the server does not recognize, or a custom/patched client sending arbitrary action values.","commonSituations":"Custom MCP client implementations that return a nonstandard action string; clients and servers built against different MCP spec versions; middleware or proxies that mutate elicitation results in transit.","solutions":["Check the client implementation that produced the ElicitResult and ensure it only returns 'accept', 'decline', or 'cancel'.","Update fastmcp and the mcp SDK on both sides so client and server agree on the supported elicitation actions.","If a new action was added in a newer MCP spec, upgrade fastmcp to a version that maps it, or handle it manually before calling elicit()."],"exampleFix":"// before (custom client)\nreturn ElicitResult(action=\"dismiss\")\n// after\nreturn ElicitResult(action=\"decline\")","handlingStrategy":"try-catch","validationCode":"result = await client_session.call_tool(...)\nif getattr(result, \"action\", None) not in {\"accept\", \"decline\", \"cancel\"}:\n    raise ValueError(f\"client returned unknown action {result.action!r}\")","typeGuard":"def is_known_action(result) -> bool:\n    return getattr(result, \"action\", None) in {\"accept\", \"decline\", \"cancel\"}","tryCatchPattern":"try:\n    out = await ctx.elicit(schema)\nexcept ValueError as e:\n    if \"Unexpected elicitation action\" in str(e):\n        out = DeclinedElicitation()  # or log + re-raise\n    else:\n        raise","preventionTips":["Keep fastmcp and the mcp SDK versions aligned on client and server","Only return the three spec-defined actions from custom clients","Integration-test elicitation flows against your actual client"],"tags":["elicitation","protocol","mcp"],"backgroundTag":"elicitation-unknown-action","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}