Latest articles
All articleslaravel/ai v1.2.1: Stop middleware, safer streams, and provider polish
A concise summary of the laravel/ai v1.2.1 patch release covering the new Stop middleware, safer stream handling, provider fixes, and upgrade notes.
Revert of stateless MCP elicitation support
## What changed Pull request #356 reverts #328, "Add stateless MCP elicitation support," removing the entire stateless elicitation subsystem from the package. The stateless elicitation feature let a tool, prompt, or resource handler pause mid-request and ask the connected MCP client for more input via `$request->ask()`, carrying answers and arbitrary state across separate JSON-RPC requests by encrypting a `requestState` blob into the response (with a one-hour TTL) instead of relying on a server-side session. This revert deletes all of it: - `Request::ask()`, `remember()`, `canAsk()`, `clientSupports()`, `inputResponses()`, `state()`, `shareStateWith()`, and `setInputResponses()` - `InputRequiredException`, the `input_required` result type, and the `@throws` declaration on the `Method` contract - The `ElicitResponse` class (including `accepted()`, `declined()`, `get()`, schema assertions, and validation rules) and the `ElicitationAction` enum - The `inputResponses` and `requestState` params on `JsonRpcRequest`, including `encodeRequestState()` and its decryption and scope-integrity checks - `ToolSearch`'s special handling for tools that request user input - Testing helpers: `withClientCapabilities()`, `assertInputRequired()`, `assertElicits()`, `respond()`, `respondWith()`, `decline()`, `cancel()`, and `assertErrorCode()` As housekeeping, `phpunit.xml.dist` no longer injects an `APP_KEY`, since nothing in the test suite encrypts request state anymore. ## Why it was pulled back Stateless elicitation was an ambitious piece of machinery. It involved client capability negotiation (`elicitation.form`, `sampling`, `roots`, and friends), deterministically hashed elicitation keys, encrypted scope-bound state passed back and forth with the client, and exception-driven control flow bubbling through `runMethodHandle()` and the response concerns. In short, it touched nearly every layer of the server request lifecycle. The revert message doesn't include a detailed rationale, but the breadth of the removal signals that the team wants this feature redesigned before it stabilizes. Practically, the package returns to a simpler, synchronous request/response model: a tool's inputs come entirely from its declared schema and arguments, and every response is reported with `resultType: 'complete'`. ## Upgrade impact How this affects you depends on your path from v1.0.1: - **Upgrading directly from v1.0.1:** there is nothing to migrate. None of these APIs existed in v1.0.1, so the package surface you knew is restored. - **Coming from the release that shipped #328:** this is a breaking change. Calls to `$request->ask()` will fail with "Call to undefined method", `use` statements for the removed classes will be fatal errors, and tests using the removed assertions will error out. If you implemented the `Method` contract yourself or constructed `TestResponse` manually, note that its constructor no longer accepts the `$server` and `$request` arguments. ## Updating your code A tool written against the stateless elicitation API looked like this: ```php class BookingTool extends Tool { public function handle(Request $request): Response { $answer = $request->ask( 'Which email should receive the booking confirmation?', fn (JsonSchema $schema) => $schema->string('email')->required(), ); if (! $answer->accepted()) { return Response::text('Booking cancelled.'); } return Response::text("Confirmation sent to {$answer->get('email')}."); } } ``` After the revert, collect everything up front through the tool's schema instead: ```php use Laravel\Mcp\Support\JsonSchema; class BookingTool extends Tool { public function schema(): JsonSchema { return JsonSchema::object() ->string('email', 'Email address for the confirmation.') ->required(); } public function handle(Request $request): Response { $email = $request->string('email'); return Response::text("Confirmation sent to {$email}."); } } ``` ## Updating your tests The multi-step elicit-then-respond test flow is gone. Tests that previously chained `assertElicits()` and `respond()` should simply invoke the tool with complete arguments: ```php it('sends the confirmation', function () { BookingServer::tool(BookingTool::class, ['email' => '[email protected]']) ->assertOk() ->assertSee('Confirmation sent to [email protected].'); }); ``` If you used `assertErrorCode()` for general error assertions, replace it with direct assertions on the JSON-RPC error payload. ## Takeaways - #356 fully reverts #328; all stateless elicitation APIs (`ask()`, `remember()`, `InputRequiredException`, `ElicitResponse`, `ElicitationAction`) are removed. - Upgrading straight from v1.0.1 is a no-op for this change; upgraders who adopted #328's APIs must remove that code. - The encrypted `requestState` / `inputResponses` transport params no longer exist, so clients must not send them. - Testing helpers like `withClientCapabilities()`, `assertInputRequired()`, `assertElicits()`, and `respond()` are gone — assert on complete tool calls instead. - Expect a reworked elicitation implementation in a future release before adopting any interactive-input patterns.
Browse by topic
Most read
- 01 laravel/ai v1.2.0: OpenAI Decisions classification, sturdier structured output, and new Anthropic defaults 78 views
- 02 laravel/ai: structured output format no longer dropped when provider options set output_config 78 views
- 03 Add OpenAI Decisions classification with image attachments 63 views
- 04 laravel/framework: Opt-In defaults() Trait for Eloquent Models Lands in 13.35.0 62 views
Get new articles by email
One email each time a new article is published.