[Server] Serve both protocol eras from one endpoint (#455)

* [Server] Serve both protocol eras from one endpoint

The two lifecycles share a transport, not a dispatcher. StreamableHttpTransport
classifies each request - a 2026-07-28 envelope, an initialize handshake, or a
session-bound follow-up - through InboundClassifier and routes it to the
dispatcher that owns it, so one URL answers a modern client and a handshake-era
one alike. Builder::build() carries both; withoutModernEra() opts out and
setModernVersions() narrows what the modern leg answers for.

InputRequiredShim lets a handler written for multi round-trip requests also
serve a handshake-era client, by turning each ask into the request/response
exchange that era has - so a handler is written once rather than twice.

The conformance fixture collapses into one server for the same reason: both
legs now hit the same URL.

* Trim redundant comments, fix stale in-memory claim in conformance fixture

* Drop stale json-schema-2020-12 baseline entries

The new fixture tool makes this scenario pass in both eras now.

* Reject handshake revisions in setModernVersions(), warn on unsafe custom middleware

Copilot review: setModernVersions() accepted revisions InboundClassifier
would never route to the modern leg. StreamableHttpTransport now warns
when a custom middleware list carries ProtocolVersionMiddleware, since
it runs before era classification and rejects modern-era traffic by
default. Also fixed a misleading docblock claim on the same middleware.
This commit is contained in:
Christopher Hertel 2026-08-19 01:36:56 +02:00 • committed by GitHub
commit cbceef06eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -147,6 +147,23 @@ final class StreamableHttpTransportTest extends TestCase
$this->assertFalse($response->hasHeader('Access-Control-Allow-Methods'));
}
#[TestDox('a custom middleware list carrying ProtocolVersionMiddleware warns that it will reject the modern era')]
public function testCustomMiddlewareWithProtocolVersionMiddlewareWarns(): void
{
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->once())
->method('warning')
->with($this->stringContains('ProtocolVersionMiddleware'));
new StreamableHttpTransport(
$this->factory->createServerRequest('POST', 'http://localhost/')->withHeader('Host', 'localhost'),
$this->factory,
$this->factory,
$logger,
[new CorsMiddleware(), new DnsRebindingProtectionMiddleware(), new ProtocolVersionMiddleware()],
);
}
#[TestDox('null middleware does not trigger the empty-list warning')]
public function testNullMiddlewareDoesNotWarn(): void
{