MCP PHP SDK

InboundClassifier

FinalYes

Decides which protocol era one inbound HTTP request belongs to.

Evaluated exactly once, at the entry boundary, so a single endpoint can serve both eras. The decision is body-primary:

  • A request whose params._meta carries the reserved protocol-version key claims the per-request envelope mechanism and belongs to the era that revision starts. A malformed claim is a validation error, never a silent fall back to the handshake era.
  • A request without that claim is handshake-era traffic — initialize included, since the modern era has no handshake to route.
  • The MCP-Protocol-Version header is a cross-check only. It never upgrades or downgrades a claim, and disagreeing with one is an error in its own right; the check has to live here because a body claiming a handshake revision routes to a leg that has no cross-check of its own.
  • Notifications carry no claim of their own, so for those the header decides.
  • GET and DELETE are handshake-era session operations: the modern era is POST-only.
  • A batch is classified element-wise. The modern era removed batching, so an array holding a modern claim is refused rather than split.

The classifier returns plain values and never throws: an era to route to, or a rejection carrying the error to answer with.

Tags
author

Christopher Hertel mail@christopher-hertel.de

Table of Contents

Constants

PROTOCOL_VERSION_HEADER  : mixed = 'MCP-Protocol-Version'

Methods

classify()  : EraClassification
crossCheckVersion()  : string|null
The header-against-body check both eras' entries share.
header()  : string|null
Case-insensitive header lookup, since PSR-7 preserves the sender's casing.
classifyBatch()  : EraClassification
classifyMessage()  : EraClassification
eraOf()  : EraClassification
The era a claimed revision belongs to.
namesModern()  : bool
Only a *known* modern revision counts here. An unrecognised header with nothing in the body to back it up is not evidence of an era — it is a version this endpoint does not serve, and the handshake leg's version middleware is what says so, naming everything the endpoint does serve.

Constants

PROTOCOL_VERSION_HEADER

public mixed PROTOCOL_VERSION_HEADER = 'MCP-Protocol-Version'

Methods

classify()

public classify(string $httpMethod, string|null $body[, array<string, string> $headers = [] ]) : EraClassification
Parameters
$httpMethod : string

the request's HTTP method

$body : string|null

the request body, already read

$headers : array<string, string> = []

request headers, case-insensitively matched

Return values
EraClassification

crossCheckVersion()

The header-against-body check both eras' entries share.

public static crossCheckVersion(string|null $headerVersion, string $claimedVersion) : string|null

Kept here rather than in the dispatcher so the edge and the leg it routes to cannot disagree about what a request claims.

Parameters
$headerVersion : string|null
$claimedVersion : string
Return values
string|null

the disagreement, or null when the two agree

header()

Case-insensitive header lookup, since PSR-7 preserves the sender's casing.

public static header(array<string, string> $headers, string $name) : string|null
Parameters
$headers : array<string, string>
$name : string
Return values
string|null

eraOf()

The era a claimed revision belongs to.

private static eraOf(string $version) : EraClassification

An unknown revision counts as modern: it cannot be negotiated through a handshake, and the modern leg is the one that can name what it does serve.

Parameters
$version : string
Return values
EraClassification

namesModern()

Only a *known* modern revision counts here. An unrecognised header with nothing in the body to back it up is not evidence of an era — it is a version this endpoint does not serve, and the handshake leg's version middleware is what says so, naming everything the endpoint does serve.

private static namesModern(string|null $version) : bool
Parameters
$version : string|null
Return values
bool
On this page

Search results