Skip to content

Latest commit

 

History

History
153 lines (109 loc) · 7.14 KB

client-communication.md

File metadata and controls

153 lines (109 loc) · 7.14 KB

Talking back to the client

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

Protocol revision 2026-07-28. Logging, progress and notifications work as described below on every revision; under the modern lifecycle they travel on the request's own response stream and the client opts into each. Sampling is the exception — see its section. Asking the user something has a page of its own: Asking for input.

ClientGateway

Every communication back to client is handled using the Mcp\Server\ClientGateway and its dedicated methods per operation. Reach it through method argument injection for RequestContext. You can also type-hint a ClientGateway parameter directly. The SDK injects both types and leaves them out of the generated input schema.

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()->progress(...);

Request metadata

RequestContext also carries what the current request said about itself, 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
}

Two more accessors read what a 2026-07-28 request declared in its _meta. On a handshake-era request, they return null and an empty array:

$context->getClientCapabilities();  // the capabilities this request declared
$context->getTraceContext();        // traceparent / tracestate / baggage, verbatim

To check a capability in both protocol eras, use the probes of ClientGateway: supportsElicitation(), supportsSampling(), supportsRoots(), the sub-capability variants and supportsExtension(). They read what the client declared during the handshake, or what the current 2026-07-28 request declared.

On 2026-07-28, the SDK copies the W3C trace context onto every notification the request sends on its response stream. The values are plain strings, so reading them needs no OpenTelemetry package.

RequestContext also gives you the current request and session with getRequest() and getSession(), and the validated OAuth token with getAccessToken() (see Reading the token in handlers).

Sampling

Deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. It keeps working on a handshake-era connection until then. Revision 2026-07-28 has no server-initiated requests, so on that revision sample() and listRoots() fail the call with an internal error (-32603) that your handler cannot catch. New integrations should call an LLM provider's API directly instead.

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 Mcp\Schema\Content\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

Both tools/toolChoice and includeContext are gated on what the client advertised, so check before sending:

if ($clientGateway->supportsSamplingTools()) {
    $result = $clientGateway->sample($messages, options: ['tools' => $tools]);
}

A server must not send tools or toolChoice to a client that did not advertise sampling.tools. The includeContext values other than none are soft-deprecated and should only be sent when the client advertises sampling.context — supportsSamplingContext() reports that one.

Tool loops

When the model wants to call a tool, the result comes back with stopReason: 'toolUse' and one or more ToolUseContent blocks. Execute them, then send a follow-up request with the assistant's message and a user message carrying a matching ToolResultContent for every ToolUseContent:

use Mcp\Schema\Content\SamplingMessage;
use Mcp\Schema\Content\TextContent;
use Mcp\Schema\Content\ToolResultContent;
use Mcp\Schema\Enum\Role;

$messages[] = new SamplingMessage(Role::Assistant, $result->content);
$messages[] = new SamplingMessage(Role::User, [new ToolResultContent($toolUse->id, [new TextContent($output)])]);

The specification is strict about the shape of that exchange: tool results may not be mixed with other content in a message, and every tool use must be answered before the conversation continues. sample() checks these rules before sending and throws an InvalidArgumentException rather than letting the client reject the request with -32602. Use $result->getContentBlocks() to iterate the response regardless of whether it holds one block or a list.

Find more details to sampling payload in the specification.

Logging

Deprecated since protocol revision 2026-07-28 (SEP-2577), earliest removal 2027-07-28. Logging keeps working until then; new integrations should log to stderr (stdio) or use OpenTelemetry instead.

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.');

Messages below the client's log level are dropped, the same way as with the client logger. Until the client sets a level with logging/setLevel, the level is warning.

Progress

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

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

Progress is opt-in by the client in both eras: it sends a progressToken with the request, and without one this call sends nothing.

Notification

Lastly, the server can push all kind of notifications, that extend the abstract Mcp\Schema\JsonRpc\Notification class to the client to:

$clientGateway->notify($yourNotification);