<?php
* This file is part of the official PHP MCP SDK.
*
* A collaboration between Symfony and the PHP Foundation.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Mcp\Server\Stateless;
use Mcp\Schema\Enum\ElicitationMode;
use Mcp\Schema\JsonRpc\ResultInterface;
use Mcp\Schema\Result\CreateSamplingMessageResult;
use Mcp\Schema\Result\ElicitResult;
use Mcp\Schema\Result\ListRootsResult;
* What a retry brought back: the client's answers to a previous
* {@see \Mcp\Schema\Result\InputRequiredResult}, keyed as its `inputRequests`
* were, plus the verified state that result carried.
*
* Absent on a first call, which is how a handler tells the rounds apart.
*
* @author Christopher Hertel <mail@christopher-hertel.de>
*/
final class InputContext
{
* @param array<string, mixed> $responses client answers, keyed as the inputRequests were
* @param array<string, mixed> $requestState the verified payload the server sealed last round
*/
public function __construct(
private readonly array $responses = [],
private readonly array $requestState = [],
) {
}
* The client's answer for `$key`, or null when it did not provide one.
*/
public function response(string $key): mixed
{
return $this->responses[$key] ?? null;
}
* The client's answer to an `elicitation/create` ask, typed.
*
* Null when the key is absent or the answer does not parse — which the
* caller should read as "not answered" and ask again, rather than as an
* error: the specification says a server SHOULD re-ask for information it
* still needs instead of failing the request.
*
* @param ElicitationMode $mode the mode of the ask this answers; url-mode
* answers carry no content, so an accepted one
* is complete without it
*/
public function elicitResult(string $key, ElicitationMode $mode = ElicitationMode::Form): ?ElicitResult
{
return $this->parse($key, static fn (array $data): ElicitResult => ElicitResult::fromArray($data, $mode));
}
* The client's answer to a `sampling/createMessage` ask, typed.
*/
public function samplingResult(string $key): ?CreateSamplingMessageResult
{
return $this->parse($key, CreateSamplingMessageResult::fromArray(...));
}
* The client's answer to a `roots/list` ask, typed.
*/
public function rootsResult(string $key): ?ListRootsResult
{
return $this->parse($key, ListRootsResult::fromArray(...));
}
* @template T of ResultInterface
*
* @param callable(array<string, mixed>): T $factory
*
* @return T|null
*/
private function parse(string $key, callable $factory): ?ResultInterface
{
$response = $this->responses[$key] ?? null;
if (!\is_array($response)) {
return null;
}
try {
return $factory($response);
} catch (\Throwable) {
return null;
}
}
public function has(string $key): bool
{
return \array_key_exists($key, $this->responses);
}
* @return array<string, mixed>
*/
public function all(): array
{
return $this->responses;
}
* @return array<string, mixed>
*/
public function requestState(): array
{
return $this->requestState;
}
}