[Schema][Server] Add the extensions framework SEP-2133 defines (#443)

* [Schema][Server] Add the extensions framework SEP-2133 defines

enableExtension() took any string and advertised it, with no way for an
extension to add a method: MessageFactory could not decode one, so nothing
downstream ever saw it.

Identifiers are now checked against the _meta key naming rules through
ExtensionIdentifier - a prefix is mandatory, since an unprefixed name has no
owner, and the modelcontextprotocol/mcp second labels the specification
reserves are recognised. An extension implementing
MethodProvidingExtensionInterface contributes both its message classes and
the handlers serving them.

MessageFactory::make() takes an $additional list of message classes, and
RequestHandlerInterface's result template is covariant so a handler
declaring a concrete result satisfies a collection typed by the interface.

* Fix stale @see reference in MethodProvidingExtensionInterface docblock

* [Schema][Server][Client] Make ExtensionIdentifier a value object

ExtensionInterface::getId() now returns ExtensionIdentifier, which
validates the SEP-2133 naming rules at construction instead of the
callers checking it. getMessages()/getRequestHandlers() move into
ExtensionInterface itself; AbstractExtension gives them empty
defaults for extensions that only announce a capability.
This commit is contained in:
Christopher Hertel 2026-08-18 20:47:27 +02:00 • committed by GitHub
commit 60711951d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 525 additions and 17 deletions

View file

@ -0,0 +1,47 @@
<?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\Tests\Unit\Server\Extension;
use Mcp\Schema\Extension\ExtensionIdentifier;
use Mcp\Schema\Extension\ExtensionInterface;
/**
* A minimal extension: an identifier, a capability payload, and one method it
* defines and serves.
*/
final class ThingExtension implements ExtensionInterface
{
public function __construct(
private readonly string $id = 'com.example/things',
) {
}
public function getId(): ExtensionIdentifier
{
return new ExtensionIdentifier($this->id);
}
public function getCapabilities(): array
{
return ['flavour' => 'vanilla'];
}
public function getMessages(): array
{
return [ThingListRequest::class];
}
public function getRequestHandlers(): iterable
{
yield new ThingListHandler();
}
}

View file

@ -0,0 +1,33 @@
<?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\Tests\Unit\Server\Extension;
use Mcp\Schema\JsonRpc\Request;
use Mcp\Schema\JsonRpc\Response;
use Mcp\Server\Handler\Request\RequestHandlerInterface;
use Mcp\Server\Session\SessionInterface;
/**
* @implements RequestHandlerInterface<ThingListResult>
*/
final class ThingListHandler implements RequestHandlerInterface
{
public function supports(Request $request): bool
{
return $request instanceof ThingListRequest;
}
public function handle(Request $request, SessionInterface $session): Response
{
return new Response($request->getId(), new ThingListResult(['a', 'b']));
}
}

View file

@ -0,0 +1,32 @@
<?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\Tests\Unit\Server\Extension;
use Mcp\Schema\JsonRpc\Request;
final class ThingListRequest extends Request
{
public static function getMethod(): string
{
return 'com.example/things.list';
}
protected static function fromParams(?array $params): static
{
return new self();
}
protected function getParams(): ?array
{
return null;
}
}

View file

@ -0,0 +1,33 @@
<?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\Tests\Unit\Server\Extension;
use Mcp\Schema\JsonRpc\ResultInterface;
final class ThingListResult implements ResultInterface
{
/**
* @param list<string> $things
*/
public function __construct(
public readonly array $things,
) {
}
/**
* @return array{things: list<string>}
*/
public function jsonSerialize(): array
{
return ['things' => $this->things];
}
}