[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:
Johannes Wachter 2026-07-14 23:55:06 +02:00 • committed by GitHub
commit e4a8a224ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -18,6 +18,7 @@ use Mcp\Schema\JsonRpc\Error;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Schema\Notification\CancelledNotification;
use Mcp\Schema\Notification\InitializedNotification;
use Mcp\Schema\Request\ElicitRequest;
use Mcp\Schema\Request\GetPromptRequest;
use Mcp\Schema\Request\PingRequest;
use PHPUnit\Framework\TestCase;
@ -336,6 +337,22 @@ final class MessageFactoryTest extends TestCase
$this->assertInstanceOf(PingRequest::class, $results[0]);
}
public function testMakeFactoryParsesElicitationCreate(): void
{
$factory = MessageFactory::make();
$json = '{"jsonrpc": "2.0", "method": "elicitation/create", "id": 1, "params": {"message": "Your name?", "requestedSchema": {"type": "object", "properties": {"name": {"type": "string", "title": "Name"}}, "required": ["name"]}}}';
$results = $factory->create($json);
$this->assertCount(1, $results);
/** @var ElicitRequest $result */
$result = $results[0];
$this->assertInstanceOf(ElicitRequest::class, $result);
$this->assertSame('elicitation/create', $result::getMethod());
$this->assertSame('Your name?', $result->message);
$this->assertSame(1, $result->getId());
}
public function testResponseWithInvalidIdType(): void
{
$json = '{"jsonrpc": "2.0", "id": true, "result": {"status": "ok"}}';