ElicitationReplay
Answers a handler's {@see \Mcp\Server\ClientGateway::elicit()} on a lifecycle that has no server-initiated requests.
The 2026-07-28 revision carries an ask in the result and lets the client re-send the whole call with the answer, so the blocking call a handler makes cannot block: there is nobody to ask while the request is open. What there is instead is a way to end the request by asking, and to be entered again once the answer exists — which is the same call, resolved one round later.
So an ask is served twice. The first time nothing has been answered, and the gateway's suspension becomes an InputRequiredResult that ends the request. The second time the handler runs from the top again, reaches the same ask, and this time the answer is here, so the call returns it and the handler continues past it as if it had never stopped. A handler written against the handshake era therefore serves this one unchanged — at the price of running once per ask, which is what makes anything it does before its last ask happen once per round.
Answers are kept in the requestState because the client only ever echoes
the round it just answered (Protocol), so round three
would otherwise no longer know what round one said. That state is signed and
not encrypted, and it is the client's own answers travelling back to the
client that gave them — but a handler that elicits something it would not
hand back should ask for it in one round and use it in that same round.
Tags
Table of Contents
Constants
- CARRIED_ANSWERS : mixed = '_mcp.answers'
- The `requestState` member carrying answers from earlier rounds. Reserved, like every `_mcp.` key: a handler's own payload travels beside it.
Properties
- $answers : array<string, array<string, mixed>>
- $asked : int
- $codec : RequestStateCodec|null
- $payload : array<string, mixed>
- $rejected : array<string, true>
Methods
- __construct() : mixed
- answer() : array<string, mixed>|null
- The answer to `$key`, or null when there is none to give the handler.
- ask() : InputRequiredResult
- The result that ends this request by asking, carrying everything already answered so the next round does not have to ask for it again.
- key() : string
- The key an ask is filed under: the one the handler named, or its position among this run's asks.
- state() : string|null
Constants
CARRIED_ANSWERS
The `requestState` member carrying answers from earlier rounds. Reserved, like every `_mcp.` key: a handler's own payload travels beside it.
public
mixed
CARRIED_ANSWERS
= '_mcp.answers'
Properties
$answers
private
array<string, array<string, mixed>>
$answers
= []
raw answers, keyed as they were asked
$asked
private
int
$asked
= 0
$codec read-only
private
RequestStateCodec|null
$codec
= null
$payload
private
array<string, mixed>
$payload
the verified state this round arrived with
$rejected
private
array<string, true>
$rejected
= []
keys whose answer this run could not read
Methods
__construct()
public
__construct(InputContext|null $input[, RequestStateCodec|null $codec = null ]) : mixed
Parameters
- $input : InputContext|null
- $codec : RequestStateCodec|null = null
answer()
The answer to `$key`, or null when there is none to give the handler.
public
answer(string $key[, ElicitationMode $mode = ElicitationMode::Form ]) : array<string, mixed>|null
Null covers an answer that does not parse as well as one that never arrived: the specification says a server SHOULD ask again for what it still needs, and a malformed answer left the server still needing it.
Parameters
- $key : string
- $mode : ElicitationMode = ElicitationMode::Form
Return values
array<string, mixed>|nullask()
The result that ends this request by asking, carrying everything already answered so the next round does not have to ask for it again.
public
ask(string $key, ElicitRequest $request) : InputRequiredResult
Parameters
- $key : string
- $request : ElicitRequest
Tags
Return values
InputRequiredResultkey()
The key an ask is filed under: the one the handler named, or its position among this run's asks.
public
key(string|null $key) : string
Positional keys hold across rounds only because the handler reaches its asks in the same order every time — which it does whenever it is re-enterable at all. A handler whose asks depend on a coin flip should name them.
Parameters
- $key : string|null
Return values
stringstate()
private
state() : string|null