php-sdk/docs/server-client-communication.md
Christopher Hertel becce29c41
[Capability] Harden structuredContent extraction (#417)
* Address review feedback on resource_link

Three follow-ups from review of the resource_link work:

* PromptResultFormatter: add a regression test proving the optional
  fields of a typed resource_link block (title, description, mimeType,
  size, annotations, _meta) survive formatting. The delegation to
  ResourceLink::fromArray() already landed while rebasing onto the
  PromptResultFormatter refactor, but the existing test only supplied
  uri/name and so could not catch a reintroduced field drop.

* ResourceLink::fromArray(): validate optional fields consistently, the
  way the equivalent ResourceDefinition::fromArray() already does.
  description, mimeType and size now raise InvalidArgumentException
  instead of surfacing a TypeError (or silently coercing, for size's
  (int) cast); annotations goes through Annotations::tryFromArray(),
  which carries the is_array() guard that icons already had; icons uses
  Icon::listFromArray() so a non-array entry is reported in context.

* ToolReference::extractStructuredContent(): never emit a list as
  structuredContent. The Content guard added earlier fixed only one
  instance of the real invariant - structuredContent must be a JSON
  object, and a PHP list can never be one. Tool::fromArray() already
  enforces the matching rule by rejecting an outputSchema whose type is
  not "object", so this aligns the runtime path with it. The test
  asserting the opposite for an array-typed outputSchema contradicted
  the phpstan type and the fromArray() check introduced alongside it in
  the same commit, so it is replaced by tests for the two list shapes.

* Apply the structuredContent object rule to object results too

`extractStructuredContent()` guards raw array results against being emitted
as a JSON array, but the object branch handed back whatever `json_decode()`
produced. A `JsonSerializable` returning a list or a scalar slipped straight
through, producing exactly the `structuredContent` the array guard exists to
prevent — and a return value that contradicts the method's own
`array<string, mixed>|null` signature.

Check the decoded value before returning it, and cover the object branch,
which had no tests at all.

* Document structured tool output

`outputSchema` and `structuredContent` were undocumented: the tool return
value docs covered only the `content` side, and the schema generation
section is about tool parameters.

Add a "Structured Output" subsection covering how to declare the schema,
which return values populate `structuredContent`, and why a list has to be
wrapped in a key to get structured output at all.

* Gate structuredContent on the negotiated protocol revision

SEP-2106 (revision 2026-07-28) widens structuredContent to any JSON value;
earlier revisions require an object. Resolve the revision per request and
apply the matching rule, and warn when a declared outputSchema yields none.

* Resolve the protocol revision on RequestContext

Keeps the `_meta`-then-session lookup in one place instead of per handler,
and exposes the negotiated revision to tool handlers.

* Document reading the negotiated revision from RequestContext

* Warn when a self-built CallToolResult carries an invalid structuredContent

Returning a CallToolResult opts out of the extraction rules, so the value is
still sent unchanged — but a JSON array is not valid before SEP-2106.
2026-08-15 04:03:20 +02:00

3.3 KiB

Client Communication

MCP supports various ways a server can communicate back to a server on top of the main request-response flow.

Table of Contents

ClientGateway

Every communication back to client is handled using the Mcp\Server\ClientGateway and its dedicated methods per operation. To use the ClientGateway in your code, you need to use method argument injection for RequestContext.

Every reference of a MCP element, that translates to an actual method call, can just add an type-hinted argument for the RequestContext and the SDK will take care to include the gateway in the arguments of the method call:

use Mcp\Capability\Attribute\McpTool;
use Mcp\Server\RequestContext;

class MyService
{
    #[McpTool(name: 'my_tool', description: 'My Tool Description')]
    public function myTool(RequestContext $context): string
    {
        $context->getClientGateway()->log(...);

The same object also carries the protocol revision negotiated for the current request, which is useful when a feature is only available from a certain revision on:

use Mcp\Schema\Enum\ProtocolVersion;

if ($context->getProtocolVersion()->isAtLeast(ProtocolVersion::V2026_07_28)) {
    // e.g. a bare list is only valid as `structuredContent` from this revision on
}

Sampling

With sampling servers can request clients to execute "completions" or "generations" with a language model for them:

$result = $clientGateway->sample('Roses are red, violets are', 350, 90, ['temperature' => 0.5]);

The sample method accepts four arguments:

  1. message, which is required and accepts a string, an instance of Content or an array of SamplingMessage instances.
  2. maxTokens, which defaults to 1000
  3. timeout in seconds, which defaults to 120
  4. options which might include systemPrompt, preferences for model choice, includeContext, temperature, stopSequences, metadata, tools, and toolChoice

Only send includeContext when the client advertises sampling.context, and only send tools or toolChoice when it advertises sampling.tools. The context modes other than none are soft-deprecated by the current specification.

Find more details to sampling payload in the specification.

Logging

The Logging utility enables servers to send structured log messages as notification to clients:

use Mcp\Schema\Enum\LoggingLevel;

$clientGateway->log(LoggingLevel::Warning, 'The end is near.');

Progress

With a Progress notification a server can update a client while an operation is ongoing:

$clientGateway->progress(4.2, 10, 'Downloading needed images.');

Notification

Lastly, the server can push all kind of notifications, that implement the Mcp\Schema\JsonRpc\Notification interface to the client to:

$clientGateway->notify($yourNotification);