[Client] Add client-side elicitation support (#371)
* [Client] Add client-side elicitation support The SDK could send elicitation/create requests from a server tool via ClientGateway::elicit(), but the client could not receive them: there was no request handler, and ElicitRequest was not registered in MessageFactory, so incoming elicitation/create messages failed to parse. This adds the client half, mirroring the existing Sampling pattern: - Register ElicitRequest in MessageFactory so elicitation/create parses. - ElicitationRequestHandler wraps a user callback and returns an ElicitResult (accept/decline/cancel) or an Error. - ElicitationCallbackInterface defines the callback contract. - ElicitationException forwards a specific message to the server; any other throwable returns a generic error. Includes unit tests, a runnable client example against the elicitation demo server, and client docs/README updates. * [Client] Simplify elicitation example labelFor() per review Type the parameter as AbstractSchemaDefinition and read ->title directly instead of matching on each concrete definition type. title is a required non-null string on the base class, so the match and the $name fallback were redundant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
206aa9da52
commit
e4a8a224ff
4 changed files with 495 additions and 1 deletions
26
src/Client/Handler/Request/ElicitationCallbackInterface.php
Normal file
26
src/Client/Handler/Request/ElicitationCallbackInterface.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?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\Client\Handler\Request;
|
||||
|
||||
use Mcp\Schema\Request\ElicitRequest;
|
||||
use Mcp\Schema\Result\ElicitResult;
|
||||
|
||||
/**
|
||||
* Contract for callbacks used by ElicitationRequestHandler.
|
||||
*
|
||||
* Implementations present the requested schema to the user and collect their
|
||||
* response when the server sends an elicitation/create request.
|
||||
*/
|
||||
interface ElicitationCallbackInterface
|
||||
{
|
||||
public function __invoke(ElicitRequest $request): ElicitResult;
|
||||
}
|
||||
68
src/Client/Handler/Request/ElicitationRequestHandler.php
Normal file
68
src/Client/Handler/Request/ElicitationRequestHandler.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?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\Client\Handler\Request;
|
||||
|
||||
use Mcp\Exception\ElicitationException;
|
||||
use Mcp\Schema\JsonRpc\Error;
|
||||
use Mcp\Schema\JsonRpc\Request;
|
||||
use Mcp\Schema\JsonRpc\Response;
|
||||
use Mcp\Schema\Request\ElicitRequest;
|
||||
use Mcp\Schema\Result\ElicitResult;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
/**
|
||||
* Handler for elicitation requests from the server.
|
||||
*
|
||||
* The MCP server may request additional information from the user during tool
|
||||
* execution. This handler wraps a user-provided callback that presents the
|
||||
* requested schema to the user and returns their response.
|
||||
*
|
||||
* @implements RequestHandlerInterface<ElicitResult>
|
||||
*
|
||||
* @author Johannes Wachter <johannes@sulu.io>
|
||||
*/
|
||||
class ElicitationRequestHandler implements RequestHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ElicitationCallbackInterface $callback,
|
||||
private readonly LoggerInterface $logger = new NullLogger(),
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(Request $request): bool
|
||||
{
|
||||
return $request instanceof ElicitRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Response<ElicitResult>|Error
|
||||
*/
|
||||
public function handle(Request $request): Response|Error
|
||||
{
|
||||
\assert($request instanceof ElicitRequest);
|
||||
|
||||
try {
|
||||
$result = $this->callback->__invoke($request);
|
||||
|
||||
return new Response($request->getId(), $result);
|
||||
} catch (ElicitationException $e) {
|
||||
$this->logger->error('Elicitation failed: '.$e->getMessage(), ['exception' => $e]);
|
||||
|
||||
return Error::forInternalError($e->getMessage(), $request->getId());
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->error('Unexpected error during elicitation', ['exception' => $e]);
|
||||
|
||||
return Error::forInternalError('Error while processing elicitation', $request->getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
24
src/Exception/ElicitationException.php
Normal file
24
src/Exception/ElicitationException.php
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?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\Exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when an elicitation request fails.
|
||||
*
|
||||
* When thrown from an elicitation callback, this exception's message will be
|
||||
* included in the error response sent back to the server.
|
||||
*
|
||||
* @author Johannes Wachter <johannes@sulu.io>
|
||||
*/
|
||||
final class ElicitationException extends \RuntimeException implements ExceptionInterface
|
||||
{
|
||||
}
|
||||
|
|
@ -53,6 +53,7 @@ final class MessageFactory
|
|||
Schema\Request\CallToolRequest::class,
|
||||
Schema\Request\CompletionCompleteRequest::class,
|
||||
Schema\Request\CreateSamplingMessageRequest::class,
|
||||
Schema\Request\ElicitRequest::class,
|
||||
Schema\Request\GetPromptRequest::class,
|
||||
Schema\Request\InitializeRequest::class,
|
||||
Schema\Request\ListPromptsRequest::class,
|
||||
|
|
|
|||
Loading…
Reference in a new issue