* [Schema] Omit the id of an error response that never had one
An error response whose id could not be read sent `"id": ""`, which claims
the peer issued a request with an empty-string id - a different statement
from "the id could not be read". JSON-RPC spells the latter as a null id,
and a receiver that cannot read the id is exactly the case the code covers.
[BC Break] Error::$id accepts null and getId() may return it. All the for*()
factories default to null, fromArray() accepts a missing or explicitly-null
id, and the member is omitted from the serialized form when absent.
* Guard null response id in Client/Server Protocol handlers
Also fix Error::jsonSerialize()'s phpdoc: data belongs inside error, not beside it.
* Never let a throwable escape Protocol::processInput()
* Validate payload types in the schema hydration methods
* Stop leaking handler exception messages to the client
* [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>
* fix: harden JSON-RPC input parsing against malformed input and DoS
MessageFactory::create() detected batches via $input[0] === '{', which
mishandled valid-but-non-object JSON: a scalar ("5") or leading-whitespace
object silently returned [] (with a PHP warning) or could reach a foreach
over a non-array. It also had no bound on batch size, letting one small
request expand into arbitrarily many operations (amplification).
- Detect single-vs-batch from the decoded JSON type (object vs list array)
instead of the raw first byte; reject scalars and empty payloads as
InvalidInputMessageException rather than crashing or no-oping.
- Cap batch size (maxBatchSize, default 100) and reject oversized batches.
- Reject non-object batch elements as InvalidInputMessageException instead
of letting them hit a TypeError.
- Bound the StreamableHttpTransport POST body (maxBodyBytes, default 4 MiB),
returning 413, with an incremental read so unknown-size/chunked bodies
cannot exhaust memory.
* docs: document input-hardening limits in CHANGELOG and transports guide
Add CHANGELOG entries and docs/transports.md sections for the JSON-RPC
batch size cap (maxBatchSize) and the StreamableHttpTransport request
body size cap (maxBodyBytes), plus the type-based single-vs-batch
message detection.
* feat(server): add bidirectional client communication support
* fix(server): use correct session ID string conversion method
* fix: clean up docblocks for request handler methods in Builder
* fix: merge artifacts
* refactor: reorganize server handlers and transport structure
* feat: move the test files to corresponding directories
* fix: linter issues
* fix: correct PHPDoc formatting and streamline handler imports
* refactor: update import statement for ServerBuilder to Builder