Protocol
Client protocol handler for MCP communication.
Handles message routing, request/response correlation, and the initialization handshake. All blocking operations are delegated to the transport.
Tags
Table of Contents
Constants
- MAX_ROUND_TRIPS : mixed = 10
- How many times a request may be re-sent before the client gives up.
Properties
- $envelope : RequestEnvelope|null
- Set only when the configured revision has no handshake.
- $headers : HeaderFactory|null
- $inputRequests : InputRequestResolver
- $logger : LoggerInterface
- $messageFactory : MessageFactory
- $notificationHandlers : array<string|int, NotificationHandlerInterface>
- $progressTokens : int
- Progress tokens are only required to be unique within a connection, and a retry keeps the caller's one — the work being reported on is the same.
- $requestHandlers : array<string|int, mixed>
- $state : ClientStateInterface
- $tools : ToolCatalog
- $transport : TransportInterface|null
Methods
- __construct() : mixed
- connect() : void
- Connect this protocol to a transport.
- getState() : ClientStateInterface
- getToolCatalog() : ToolCatalog
- What the client knows about the server's tools, from `tools/list`.
- initialize() : Response<string|int, array<string, mixed>>|Error
- Ready the connection for use.
- processMessage() : void
- Process an incoming message from the server.
- request() : Response<string|int, array<string, mixed>>|Error
- Send a request to the server and wait for response.
- sendNotification() : void
- Send a notification to the server (fire and forget).
- discover() : Response<string|int, array<string, mixed>>
- Stand in for the handshake in the modern era.
- exchange() : Response<string|int, array<string, mixed>>|Error
- One request on the wire: assign an id, send, and wait for its answer.
- handleNotification() : void
- Handle a notification from the server.
- handleRequest() : void
- Handle a request from the server (e.g., sampling request).
- handleResponse() : void
- Handle a response from the server.
- headersFor() : array<string, string>
- The headers belonging to an encoded message, for a transport that has any.
- readDiscovery() : void
- Read defensively: `server/discover` is optional, so a server may answer with something that is not a DiscoverResult at all, and none of it is load-bearing for the requests that follow.
- reconcileVersion() : void
- Move to a revision the server actually speaks, if it said which.
- send() : void
- Encode and hand a message to the transport, stamping the per-request envelope on the way out when the revision calls for one.
- sendResponse() : void
- Send a response back to the server (for server-initiated requests).
- withAcceptedVersion() : ProtocolVersion|null
- Switches the offered revision when the server refuses the current one, or null when there is nothing to retry with.
- withMeta() : array<string, mixed>
Constants
MAX_ROUND_TRIPS
How many times a request may be re-sent before the client gives up.
private
mixed
MAX_ROUND_TRIPS
= 10
Both loops that re-send are bounded by it: a server that keeps asking for input, and one that keeps rejecting the offered revision. Neither is expected to run more than a round or two, so the cap is only there to stop a broken or hostile server from spinning the client forever.
Properties
$envelope
Set only when the configured revision has no handshake.
private
RequestEnvelope|null
$envelope
= null
$headers
private
HeaderFactory|null
$headers
= null
$inputRequests read-only
private
InputRequestResolver
$inputRequests
$logger
private
LoggerInterface
$logger
$messageFactory
private
MessageFactory
$messageFactory
$notificationHandlers
private
array<string|int, NotificationHandlerInterface>
$notificationHandlers
$progressTokens
Progress tokens are only required to be unique within a connection, and a retry keeps the caller's one — the work being reported on is the same.
private
int
$progressTokens
= 0
$requestHandlers read-only
private
array<string|int, mixed>
$requestHandlers
= []
$state
private
ClientStateInterface
$state
$tools
private
ToolCatalog
$tools
$transport
private
TransportInterface|null
$transport
= null
Methods
__construct()
public
__construct([array<string|int, RequestHandlerInterface<string|int, mixed>> $requestHandlers = [] ][, array<string|int, NotificationHandlerInterface> $notificationHandlers = [] ][, MessageFactory|null $messageFactory = null ][, LoggerInterface|null $logger = null ]) : mixed
Parameters
- $requestHandlers : array<string|int, RequestHandlerInterface<string|int, mixed>> = []
- $notificationHandlers : array<string|int, NotificationHandlerInterface> = []
- $messageFactory : MessageFactory|null = null
- $logger : LoggerInterface|null = null
connect()
Connect this protocol to a transport.
public
connect(TransportInterface $transport, Configuration $config) : void
Sets up message handling callbacks.
Parameters
- $transport : TransportInterface
-
The transport to connect
- $config : Configuration
-
The client configuration for initialization
getState()
public
getState() : ClientStateInterface
Return values
ClientStateInterfacegetToolCatalog()
What the client knows about the server's tools, from `tools/list`.
public
getToolCatalog() : ToolCatalog
Kept on the protocol rather than the facade because it is what makes the SEP-2243 headers derivable at send time.
Return values
ToolCataloginitialize()
Ready the connection for use.
public
initialize(Configuration $config) : Response<string|int, array<string, mixed>>|Error
Up to 2025-11-25 that means the initialize handshake: offer a revision,
take the server's answer, confirm with notifications/initialized. From
2026-07-28 there is no handshake at all — see self::discover().
Parameters
- $config : Configuration
-
The client configuration
Return values
Response<string|int, array<string, mixed>>|ErrorprocessMessage()
Process an incoming message from the server.
public
processMessage(string $input) : void
Routes to appropriate handler based on message type.
Parameters
- $input : string
request()
Send a request to the server and wait for response.
public
request(Request $request, int $timeout[, bool $withProgress = false ]) : Response<string|int, array<string, mixed>>|Error
If a response is immediately available (sync HTTP), returns it. Otherwise, suspends the Fiber and waits for the transport to resume it.
In the modern era this is also where the two loops that re-send live: answering a server's request for input (SEP-2322), and retrying under a revision the server accepts (SEP-2575). Both re-send the same call, so they belong together and above the single exchange.
Parameters
- $request : Request
-
The request to send
- $timeout : int
-
The timeout in seconds
- $withProgress : bool = false
-
Whether to attach a progress token to the request
Return values
Response<string|int, array<string, mixed>>|ErrorsendNotification()
Send a notification to the server (fire and forget).
public
sendNotification(Notification $notification) : void
Parameters
- $notification : Notification
discover()
Stand in for the handshake in the modern era.
private
discover(Configuration $config) : Response<string|int, array<string, mixed>>
There is nothing to negotiate: the revision travels on every request, so
the connection is usable the moment the transport is. server/discover
is only asked because the facade exposes getServerInfo(), and a server
that will not answer it still serves every other method — so a failure
here is logged and the connection proceeds.
Parameters
- $config : Configuration
Return values
Response<string|int, array<string, mixed>>exchange()
One request on the wire: assign an id, send, and wait for its answer.
private
exchange(array<string, mixed> $payload, int $timeout) : Response<string|int, array<string, mixed>>|Error
A retry gets a new id, because the previous one is spent — the server has already answered it, and reusing it would make the two indistinguishable.
Parameters
- $payload : array<string, mixed>
- $timeout : int
Return values
Response<string|int, array<string, mixed>>|ErrorhandleNotification()
Handle a notification from the server.
private
handleNotification(Notification $notification) : void
Parameters
- $notification : Notification
handleRequest()
Handle a request from the server (e.g., sampling request).
private
handleRequest(Request $request) : void
Parameters
- $request : Request
handleResponse()
Handle a response from the server.
private
handleResponse(Response<string|int, mixed>|Error $response) : void
This stores it in session. The transport will pick it up and resume the Fiber.
Parameters
headersFor()
The headers belonging to an encoded message, for a transport that has any.
private
headersFor(string $payload) : array<string, string>
Parameters
- $payload : string
Return values
array<string, string>readDiscovery()
Read defensively: `server/discover` is optional, so a server may answer with something that is not a DiscoverResult at all, and none of it is load-bearing for the requests that follow.
private
readDiscovery(array<string, mixed> $result) : void
Parameters
- $result : array<string, mixed>
reconcileVersion()
Move to a revision the server actually speaks, if it said which.
private
reconcileVersion(mixed $supportedVersions) : void
server/discover reports rather than negotiates, so a client that asked
for something the server does not list learns it here — and learning it
now is far better than a stream of refusals later. A server that stays
silent about its versions is left alone; the method is optional and
saying nothing is not the same as saying no.
Parameters
- $supportedVersions : mixed
send()
Encode and hand a message to the transport, stamping the per-request envelope on the way out when the revision calls for one.
private
send(array<string, mixed> $payload, string $kind) : void
Parameters
- $payload : array<string, mixed>
- $kind : string
sendResponse()
Send a response back to the server (for server-initiated requests).
private
sendResponse(Response<string|int, mixed>|Error $response) : void
Parameters
withAcceptedVersion()
Switches the offered revision when the server refuses the current one, or null when there is nothing to retry with.
private
withAcceptedVersion(Error $error) : ProtocolVersion|null
Parameters
- $error : Error
-
the server's refusal
Return values
ProtocolVersion|nullwithMeta()
private
static withMeta(array<string, mixed> $payload, array<string, mixed> $meta) : array<string, mixed>
Parameters
- $payload : array<string, mixed>
- $meta : array<string, mixed>