{"record":{"id":"ababb475a2b30da7","repo":"PrefectHQ/fastmcp","slug":"imperative-ctx-elicit-is-not-supported-inside-a","errorCode":null,"errorMessage":"Imperative ctx.elicit() is not supported inside a background task. Gather input with the guard pattern instead: return an InputRequiredResult from the tool (with input_requests), and read ctx.input_responses / ctx.request_state when the task re-runs after the client answers.","messagePattern":"Imperative ctx\\.elicit\\(\\) is not supported inside a background task\\. Gather input with the guard pattern instead: return an InputRequiredResult from the tool \\(with input_requests\\), and read ctx\\.input_responses / ctx\\.request_state when the task re-runs after the client answers\\.","errorType":"exception","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"fastmcp_slim/fastmcp/server/context.py","lineNumber":1083,"sourceCode":"                ``value`` field. Same scope rules as ``response_title``.\n\n        Note:\n            Imperative elicitation is not available inside a background task\n            (calling it there raises a ``ToolError``). A task gathers input with\n            the guard pattern: return an ``InputRequiredResult`` and read\n            ``ctx.input_responses`` / ``ctx.request_state`` when the task re-runs.\n        \"\"\"\n        config = parse_elicit_response_type(\n            response_type,\n            response_title=response_title,\n            response_description=response_description,\n        )\n\n        if self.is_background_task:\n            # Background tasks gather input with the guard/return pattern, not\n            # imperative elicitation — the worker never blocks on a client\n            # round-trip. Fail fast with the guidance to use InputRequiredResult.\n            raise ToolError(_TASK_ELICIT_ERROR)\n        # Foreground push path: server-initiated elicitation needs a back-channel,\n        # which the 2026-07-28 era removed (SEP-2577). Raise a clear era-aware\n        # 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\":","sourceCodeStart":1065,"sourceCodeEnd":1101,"githubUrl":"https://github.com/PrefectHQ/fastmcp/blob/1f021142978e0861cd910c8df4e8074bc7cf3978/fastmcp_slim/fastmcp/server/context.py#L1065-L1101","documentation":"ctx.elicit() performs an imperative, blocking server-to-client input request. Inside a background task the worker cannot block on a client round-trip, so FastMCP raises ToolError directing you to the guard pattern: return an InputRequiredResult (with input_requests) from the tool, let the client answer, then read ctx.input_responses / ctx.request_state when the task re-runs.","triggerScenarios":"Calling await ctx.elicit(...) inside a tool running as a background task (self.is_background_task is True) — e.g. a submitted long-running tool that asks for confirmation or extra input mid-run.","commonSituations":"Converting an existing interactive tool into a task-based (background) tool without changing its elicitation calls; worker-queue deployments where tools run detached from the client connection.","solutions":["Refactor to the guard pattern: return InputRequiredResult(input_requests=[...]) instead of calling ctx.elicit().","When the task re-runs after the client answers, read the supplied values via ctx.input_responses / ctx.request_state.","If imperative elicitation is essential, run the tool in the foreground instead of as a background task.","Split the tool: do the pre-input work in one call, gather input, then run the remainder in a second call."],"exampleFix":"// before\nasync def deploy(ctx: Context):\n    r = await ctx.elicit(\"Proceed?\", schema)\n    ...\n// after\nasync def deploy(ctx: Context):\n    if \"confirm\" not in ctx.request_state:\n        return InputRequiredResult(input_requests=[ElicitRequest(message=\"Proceed?\", schema=schema)])\n    confirmed = ctx.input_responses[\"confirm\"]\n    ...","handlingStrategy":"try-catch","validationCode":"if getattr(ctx, \"is_background_task\", False):\n    raise RuntimeError(\"use InputRequiredResult guard pattern instead of ctx.elicit()\")","typeGuard":null,"tryCatchPattern":"try:\n    res = await ctx.elicit(\"Proceed?\", schema)\nexcept ToolError as e:\n    if \"background task\" in str(e):\n        return InputRequiredResult(input_requests=[build_request(schema)])\n    raise","preventionTips":["When converting a tool to a background task, audit it for ctx.elicit() calls first.","Prefer the guard pattern (InputRequiredResult + ctx.input_responses) for all task-based tools.","Keep interactive prompts at tool boundaries, never mid-task in workers."],"tags":["python","context","elicitation","background-tasks","toolerror"],"backgroundTag":"elicitation-in-background-task","analyzedSha":"1f021142978e0861cd910c8df4e8074bc7cf3978","analyzedAt":"2026-08-29T14:31:16.082Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}