[Server][Tests] Serve the modern (SEP-2575) lifecycle (#444)
* [Server][Tests] Serve the modern (SEP-2575) lifecycle The revision removed the initialize handshake: every request carries its own _meta - protocol version, client capabilities, client info - and answers server/discover for what initialize used to return. There is no session to keep, so there is nothing for the handshake-era dispatcher to do. Builder::buildStateless() produces StatelessProtocol, and StatelessHttpTransport serves it over one POST-only endpoint - GET and DELETE are 405, and no Mcp-Session-Id exists to hand out. Results go through a per-era wire codec rather than the handlers, so resultType and the caching hints are stamped for 2026-07-28 and left off the revisions that do not define them. Covered by the modern conformance suite (make conformance-draft-server) against its own baseline; unit coverage arrives with the fixes stacked on this. * Apply suggestions from code review Co-authored-by: Christopher Hertel <mail@christopher-hertel.de> * Fix critical stateless-lifecycle bugs from review - Don't leak internal exception messages to clients on uncaught \Throwable; use a generic message like the handshake-era protocol and every built-in handler already do. - Default a missing/invalid request id to null instead of "", and stop answering notifications and malformed messages as if they were "method not found" 404s. - Read the POST body in a loop instead of a single read() call, which PSR-7 allows to return short.
This commit is contained in:
parent
60711951d4
commit
3d596ca75b
1 changed files with 1735 additions and 16 deletions
75
src/Schema/Result/DiscoverResult.php
Normal file
75
src/Schema/Result/DiscoverResult.php
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the official PHP MCP SDK.
|
||||
*
|
||||
* A collaboration between Symfony and the PHP Foundation.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Mcp\Schema\Result;
|
||||
|
||||
use Mcp\Exception\InvalidArgumentException;
|
||||
use Mcp\Schema\Enum\ProtocolVersion;
|
||||
use Mcp\Schema\Enum\ResultType;
|
||||
use Mcp\Schema\JsonRpc\ResultInterface;
|
||||
use Mcp\Schema\ServerCapabilities;
|
||||
|
||||
/**
|
||||
* The server's response to a `server/discover` request.
|
||||
*
|
||||
* This is the modern era's replacement for `InitializeResult`. The difference
|
||||
* is not just the method name: `initialize` *negotiates* a single version and
|
||||
* remembers it for the connection, whereas `server/discover` merely *reports*
|
||||
* every version this server speaks and lets each subsequent request pick one.
|
||||
*
|
||||
* Server identity travels in the result `_meta` rather than in a body
|
||||
* `serverInfo` member (spec PR #3002 removed the latter).
|
||||
*
|
||||
* @author Christopher Hertel <mail@christopher-hertel.de>
|
||||
*/
|
||||
class DiscoverResult implements ResultInterface
|
||||
{
|
||||
/**
|
||||
* @param list<ProtocolVersion> $supportedVersions every revision this server can answer
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly array $supportedVersions,
|
||||
public readonly ServerCapabilities $capabilities,
|
||||
public readonly ?string $instructions = null,
|
||||
) {
|
||||
if ([] === $this->supportedVersions) {
|
||||
throw new InvalidArgumentException('A DiscoverResult must advertise at least one supported version.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Carries no `resultType`, caching hints or serverInfo identity: those are
|
||||
* wire vocabulary, stamped by {@see \Mcp\Server\Wire\Rev2026Codec} on the
|
||||
* way out. What is modelled here is only what discovery actually answers.
|
||||
*
|
||||
* @return array{
|
||||
* supportedVersions: list<string>,
|
||||
* capabilities: ServerCapabilities,
|
||||
* instructions?: string,
|
||||
* }
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$data = [
|
||||
'supportedVersions' => array_values(array_map(
|
||||
static fn (ProtocolVersion $version): string => $version->value,
|
||||
$this->supportedVersions,
|
||||
)),
|
||||
'capabilities' => $this->capabilities,
|
||||
];
|
||||
|
||||
if (null !== $this->instructions) {
|
||||
$data['instructions'] = $this->instructions;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue