PrefectHQ/fastmcp · error · ValueError

Unexpected elicitation action: {result.action}

Error message

Unexpected elicitation action: {result.action}

What it means

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.

Source

Thrown at fastmcp_slim/fastmcp/server/context.py:1104

        # error before hitting the wire instead of the SDK's opaque "Method not
        # found". Handshake-era behavior is unchanged.
        if self._is_modern_protocol():
            raise ToolError(_ELICIT_MODERN_ERROR)
        # Standard request mode: use session.elicit directly
        result = await self.session.elicit(
            message=message,
            requested_schema=config.schema,
            related_request_id=self.request_id,
        )

        if result.action == "accept":
            return handle_elicit_accept(config, result.content)
        elif result.action == "decline":
            return DeclinedElicitation()
        elif result.action == "cancel":
            return CancelledElicitation()
        else:
            raise ValueError(f"Unexpected elicitation action: {result.action}")

    def _make_state_key(self, key: str) -> str:
        """Create session-prefixed key for state storage."""
        return f"{self.session_id}:{key}"

    async def set_state(
        self, key: str, value: Any, *, serializable: bool = True
    ) -> None:
        """Set a value in the state store.

        By default, values are stored in the session-scoped state store and
        persist across requests within the same MCP session. Values must be
        JSON-serializable (dicts, lists, strings, numbers, etc.).

        For non-serializable values (e.g., HTTP clients, database connections),
        pass ``serializable=False``. These values are stored in a request-scoped
        dict and only live for the current MCP request (tool call, resource
        read, or prompt render). They will not be available in subsequent

View on GitHub (pinned to 1f02114297)

Solutions

  1. Check the client implementation that produced the ElicitResult and ensure it only returns 'accept', 'decline', or 'cancel'.
  2. Update fastmcp and the mcp SDK on both sides so client and server agree on the supported elicitation actions.
  3. 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().

Example fix

// before (custom client)
return ElicitResult(action="dismiss")
// after
return ElicitResult(action="decline")
Defensive patterns

Strategy: try-catch

Validate before calling

result = await client_session.call_tool(...)
if getattr(result, "action", None) not in {"accept", "decline", "cancel"}:
    raise ValueError(f"client returned unknown action {result.action!r}")

Type guard

def is_known_action(result) -> bool:
    return getattr(result, "action", None) in {"accept", "decline", "cancel"}

Try / catch

try:
    out = await ctx.elicit(schema)
except ValueError as e:
    if "Unexpected elicitation action" in str(e):
        out = DeclinedElicitation()  # or log + re-raise
    else:
        raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of PrefectHQ/fastmcp@1f02114297 (2026-08-29). Data as JSON: /api/errors/c4cd349e9daceb0b. Report an issue: GitHub.